9

Coding4Fun - 使用 C# 製作聲音檔,以摩斯電碼為例

 2 years ago
source link: https://blog.darkthread.net/blog/text-to-morse-code-w-csharp/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
使用 C# 製作聲音檔,以摩斯電碼為例-黑暗執行緒

很久沒有 Coding4Fun,想玩個有趣的題目 - 用 C# 無中生有產生聲音檔。

做了研究,.wav 檔用未壓縮的 byte[] 記錄聲音波形,只要依規範在檔案開頭填入聲道數、取樣頻率、解析度位元數... 等資訊,輕鬆就能在電腦上生出一段正弦波。(謎:要多無聊才會幹這種事?到底)

不過,大費周章讓電腦播放正弦波聲音太無趣,我想到一個讓它更好玩的點子 - 寫一個 C# 小工具將文字轉成摩斯電碼! (謎:好像並沒有比較好)

我從小就覺得摩斯電碼很酷,能靠一條電線跟簡單裝置傳送複雜訊息,以現在的眼光看當然沒什麼,隨便一支智慧型手機都能用 1/100 時間傳 100 倍資訊到千里之外,但在那個網路還不發達的年代,可以自己動手實現電子傳輸是很神奇的事。當年我有一套類似動動腦的電子套件玩具,其中有個按鍵點亮燈泡的模組,模組上還印了摩斯電碼表,記得我還抄了一份放在書包立志要背起來,但如大家所猜,沒多久它便與其他一百條志願一起消逝在風中。

此生第二次與摩斯密碼近距離接觸是在當兵,當時隊上有搞通訊的海軍弟兄,聽電報是本職學能,偶爾會看到他們聽著錄音機「嘟嘟嘟...嘟嘟...」,緊張兮兮地練習抄報,擔心沒考好被禁假,是我第一次聽到真實的電報聲。(當年沒有 Google 大神,長見識全靠機緣)

第三次與它相遇,這回我要寫程式把文字轉成摩斯電碼播放出來。

文字轉摩斯電碼不是什麼深奧技術,在 Github 就有 Python 範例,甚至有網站可以線上轉換 - Play! Morse Code

那... 還需要自己寫嗎?Why Not?哈!

先看成果:

展示影片

影片右下角的手機上跑的是一個可以聽摩斯電碼轉成文字的 App - Morse Code Reader,用來驗證程式產生的摩斯電碼是否標準。

核心程式如下,包含字元與摩斯電碼映表 Dictionary,一個將字串轉成 000111010101000... 格式轉換函式,每個 0, 1 為固定時間長度,0 代表無聲、1 代表發聲,111 是長音,1 是短音,以此類推。對大家較有參考價值的是 CreateWav(),示範用 byte[] 從無到有建立 .wav 檔。

public class MorseCodeModule
{
    //REF: https://github.com/cduck/morse
    static Dictionary<char, string> MorseCodeTable = new Dictionary<char, string>
    {
        ['A'] = ".-",
        ['B'] = "-...",
        ['C'] = "-.-.",
        ['D'] = "-..",
        ['E'] = ".",
        ['F'] = "..-.",
        ['G'] = "--.",
        ['H'] = "....",
        ['I'] = "..",
        ['J'] = ".---",
        ['K'] = "-.-",
        ['L'] = ".-..",
        ['M'] = "--",
        ['N'] = "-.",
        ['O'] = "---",
        ['P'] = ".--.",
        ['Q'] = "--.-",
        ['R'] = ".-.",
        ['S'] = "...",
        ['T'] = "-",
        ['U'] = "..-",
        ['V'] = "...-",
        ['W'] = ".--",
        ['X'] = "-..-",
        ['Y'] = "-.--",
        ['Z'] = "--..",
        ['0'] = "-----",
        ['1'] = ".----",
        ['2'] = "..---",
        ['3'] = "...--",
        ['4'] = "....-",
        ['5'] = ".....",
        ['6'] = "-....",
        ['7'] = "--...",
        ['8'] = "---..",
        ['9'] = "----.",
        ['.'] = ".-.-.-",
        [','] = "--..--",
        ['?'] = "..--..",
        ['\\'] = ".----.",
        ['!'] = "-.-.--",
        ['/'] = "-..-.",
        ['('] = "-.--.",
        [')'] = "-.--.-",
        ['&'] = ".-...",
        [':'] = "---...",
        [';'] = "-.-.-.",
        ['='] = "-...-",
        ['+'] = ".-.-.",
        ['-'] = "-....-",
        ['_'] = "..--.-",
        ['\"'] = ".-..-.",
        ['$'] = "...-..-",
        ['@'] = ".--.-.",
        ['\x02'] = "-.-.-", //Start
        ['\x03'] = "...-." //End
    };


    static string TextToMorseCode(string message)
    {
        return string.Join("000", $"\x2 {message} \x3".ToUpper().ToArray()
            .Select(ch =>
            {
                if (ch == ' ') return "0000"; //Word Spacing 3+4
                return string.Join("0",
                    MorseCodeTable[ch].ToArray()
                    .Select(o => o == '.' ? "1" : "111")
                    .ToArray());
            }).ToArray());
    }

    public static void CreateWavFile(string wavPath, string message)
    {
        var f = new FileStream(wavPath, FileMode.Create);
        CreateWav(f, " " + message);
        f.Dispose();
    }

    public static void CreateWav(Stream f, string message)
    {
        ushort channelCount = 1;
        ushort sampleBytes = 1; // in bytes
        uint sampleRate = 8000;
        int freq = 641; // US Army 641
        int dotPerSec = 20;

        var bitData = TextToMorseCode(message);

        // 641Hz tone sample
        byte[] toneSample = new byte[sampleRate / freq];
        for (var i = 0; i < toneSample.Length; i++)
        {
            byte v = i > toneSample.Length / 2 ? (byte)255 : (byte)0;
            toneSample[i] = v;
        }

        uint dataLen = (uint)(sampleRate / dotPerSec * bitData.Length);

        var wr = new BinaryWriter(f);
        wr.Write("RIFF".ToArray());
        uint fileLength = 36 + dataLen * channelCount * sampleBytes;
        wr.Write(fileLength); //FileLength
        wr.Write("WAVEfmt ".ToArray());
        wr.Write(16); //ChunkSize
        wr.Write((ushort)1); //FormatTag
        wr.Write(channelCount); //Channels
        wr.Write(sampleRate); //Frequency
        wr.Write(sampleRate * sampleBytes * channelCount); //AverageBytesPerSec
        wr.Write((ushort)(sampleBytes * channelCount)); //BlockAlign
        wr.Write((ushort)(8 * sampleBytes)); //BitsPerSample
        wr.Write("data".ToArray());
        wr.Write(dataLen * sampleBytes); //ChunkSize

        var sampleIdx = 0;
        var bitIndex = 0;
        var mute = false;
        int dotDura = 0;
        for (int i = 0; i < dataLen; i++)
        {
            dotDura--;
            if (dotDura <= 0)
            {
                dotDura = (int)sampleRate / dotPerSec;
                mute = bitData[bitIndex] == '0';
                bitIndex++;
            }
            wr.Write(mute ? (byte)127 : toneSample[sampleIdx]);
            sampleIdx++;
            if (sampleIdx >= toneSample.Length) sampleIdx = 0;
        }
        wr.Dispose();
    }
}

主程式如下,接受文字參數,將 WAVE 內容存成 test.wav,並呼叫 Windows Shell 用預設開啟程式播放:

var wavPath = "test.wav";
MorseCodeModule.CreateWavFile(wavPath, args.Length == 0 ? "TEST" : args[0]);
System.Diagnostics.Process.Start("explorer", wavPath);

為防大家跟我一樣無聊想動手玩玩,程式已推上 Github,有興趣的朋友請自取。

【參考資料】


Recommend

  • 34
    • worktile.com 5 years ago
    • Cache

    RxJS 实现摩斯密码(内附脑图)

    参加 2018 ngChina 开发者大会,特别喜欢 Michael Hladky 奥地利帅哥的 RxJS 分享,现在拿出来好好学习工作坊的内容(工作坊Demo地址),结合这个示例,做了一个改进版本,实现更简洁,逻辑更直观。 一、摩斯密码是什么?

  • 18
    • blog.niclin.tw 3 years ago
    • Cache

    我的 Youtube 影片製作流程

    最近做影片有感,這裡把我自己的流程和心得記錄下來認真剪三支以上影片,就會獲得進步,每次都要有不同的目標,目的是一次比一次的質感還要提升我只花一個禮拜學剪輯和打光,在 30 天內,上傳了 3 支影片,訂閱從 30 一路飙破 650,總觀看時數從...

  • 1

    使用 PowerShell 製作 Arduino/ESP TFT 圖檔資料-黑暗執行緒618 免運優惠失心瘋買了一堆 Arduino/ESP 零件,裡面有兩塊 TFT 彩色螢幕,想玩玩彩色顯示。 花了點時間,總算試出來

  • 6

    徒手製作 AJAX 載入中遮罩-黑暗執行緒今天這篇屬於前端開發的單兵基本教練,練習為耗時的 AJAX 呼叫製作載入中狀態顯示,如以下效果: 針對較耗時的 AJAX 呼叫,在等待結果回傳期間顯示轉圈圈之類的載入動畫,安撫使用者情緒提供明確的執行狀態回饋,同...

  • 4
    • teddy-chen-tw.blogspot.com 2 years ago
    • Cache

    有沒有發出聲音?

    有沒有發出聲音? September 14 10:25~11:16

  • 6

    我昨天在 Kubernetes Summit 2021 舉行了一場實戰工作坊,詳細的介紹 Dapr 這套工具如何部署到 Kubernetes,以及如何透過 Dapr 大幅降低導入微服務架構的複雜度。今天這篇文章我打算...

  • 3
    • www.frank.hk 2 years ago
    • Cache

    如何製作冰山雪頂咖啡

    如何製作冰山雪頂咖啡發佈:2012-05-291,663 Views非技術類

  • 7
    • www.frank.hk 2 years ago
    • Cache

    教你製作完全 On-Chain 的 NFT

    教你製作完全 On-Chain 的 NFT首先看看我做的這個 demo: https://testnets.opensea.io/collection/onchainnft-demo

  • 3

    用情趣用品在西洋棋比賽裡面傳遞摩斯電碼作弊 標題資訊量有點大... 先講一下最近西洋棋界的新聞,九月的時候

  • 3

    Windows 小技巧 - 將電腦播放聲音轉為輸入音源-黑暗執行緒 最近想挑戰將線上直播內容串接語音轉文字識別 API,試試即席翻譯。要實現這步,一個簡單做法是將 Windows 目前播放的影片、音樂、聲音當成麥克風或外界輸入的音源,如...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK