最近遇到一個基於輸入文字產生摩斯程式碼音訊檔案的需求。幾番搜尋無果之後,我決定自己寫一個生成器。本文主要介紹了PHP實作基於文字的摩斯電碼產生器的相關資料,需要的朋友可以參考下。希望對大家有幫助。
因為我希望透過web的方式存取我的摩斯代碼音訊文件,所以我決定採用PHP作為我主要的編程語言。上面的截圖顯示了一個開始產生莫斯程式碼的網頁。在下載的zip檔案中,包含了用於提交文字的網頁以及用於產生和展現音訊檔案的PHP來源檔案。如果你想測試PHP程式碼,你需要將網頁和相關的PHP檔案複製到啟用了PHP的伺服器上。
對許多人來說,莫斯程式碼就像一些老電影中表現的那樣,就是一些「點」和「橫線」的序列,或者一連串的嗶嗶聲。顯然,如果你想用電腦程式碼來產生莫斯程式碼,這樣的了解是遠遠不夠的。這篇文章將會介紹產生莫斯程式碼的要素,如何產生WAVE 格式的音訊文件,以及如何用PHP將莫斯程式碼轉換成音訊檔案。
莫斯程式碼
莫斯程式碼是一種文字編碼方式。它的優點是編碼方便,用人耳就能夠方便的解碼。本質上,是透過音訊(或無線電頻)的開和關,從而形成或短或長的音頻脈衝,一般稱作點(dot)和線(dash),或者用無線電術語稱作“嘀”和“嗒」。以現代數位通訊術語,莫斯程式碼是一種振幅鍵控(amplitude shift keying ,ASK)。
在莫斯程式碼中,字元(字母,數字,標點符號和特殊符號)被編碼成一個「嘀」和「嗒」的序列。所以為了把文字轉換成莫斯程式碼,我們首先要確定如何來表示「嘀」和「嗒」。一個很顯然的選擇是,用0表示“嘀”,用1表示“嗒”,或者反過來。不幸的是,莫斯程式碼採用的是可變長編碼方案。所以我們也必須要使用一種可變長序列,或者採取一種方式,把資料打包成一種電腦記憶體通用的固定位寬(fixed bit-size)的格式。另外,需要特別注意的是,莫斯代碼並沒有區分字母大小寫,而且對某些特殊符號無法編碼。在我們這個實作中,未定義的字元和符號將會被忽略。
在這個專案中,記憶體佔用並不是一個需要特別考慮的問題。所以,我們提出一個簡單的編碼方案,即用“0”來表示每個“嘀”,用“1”來表示每個“嗒”,並且把他們放在一個字串關聯數組中。定義莫斯程式碼編碼表的PHP程式碼就像下面這樣:
$CWCODE = array ('A'=>'01','B'=>'1000','C'=>'1010','D'=>'100','E'=>'0', 'F'=>'0010','G'=>'110','H'=>'0000','I'=>'00','J'=>'0111', 'K'=>'101','L'=>'0100','M'=>'11','N'=>'10', 'O'=>'111', 'P'=>'0110','Q'=>'1101','R'=>'010','S'=>'000','T'=>'1', 'U'=>'001','V'=>'0001','W'=>'011','X'=>'1001','Y'=>'1011', 'Z'=>'1100', '0'=>'11111','1'=>'01111','2'=>'00111', '3'=>'00011','4'=>'00001','5'=>'00000','6'=>'10000', '7'=>'11000','8'=>'11100','9'=>'11110','.'=>'010101', ','=>'110011','/'=>'10010','-'=>'10001','~'=>'01010', '?'=>'001100','@'=>'00101');
#要注意的是,如果你特別在意記憶體佔用的話,上面的程式碼可以解釋為位元(bit)。給每個程式碼增加一個開始位,就可以形成一個位元的模式,每個字元就可以用一個位元組來儲存。同時,當解析最終編碼的時候,要刪除開始位左邊的位元(bit),從而得到真正的變長編碼。
儘管許多人沒有意識到,事實上「時間間隔」是定義莫斯程式碼的主要因素,所以理解這一點是產生莫斯程式碼的關鍵。所以,我們要做的第一件事,就是定義莫斯程式碼的內部碼(即「嘀」和「嗒」)的時間間隔。為了方便起見,我們定義一個「嘀」的聲音長度為一個時間單位dt,「嘀」和「嗒」之間的間隔也是一個時間單位dt;定義一個「嗒」的長度為3個dt,字元( letters)之間的間隔也是3個dt;定義單字(words)之間的間隔是7個dt。所以,總結起來,我們的時間間隔表就像下面這樣:
在莫斯程式碼中,編碼聲音的「播放速度」通常用單字數/分鐘( WPM) 來表示。由於英文單字有不同的長度,而且字元也有不同數量的“嘀”和“嗒”,所以,從WPM轉換成(音訊)數字採樣並不是看上去那樣簡單。在一份被國際組織採用的方案中,採用5個字元作為單字的平均長度,同時,一個數字或標點符號被當作2個字元。這樣,平均一個單字就是50個時間單位dt。這樣,如果你指定了WPM,那麼我們總的播放時間就是 50 * WPM的時間單位/分鐘,每個「嘀」(即一個時間單位dt)的長度等於1.2/WPM秒。這樣,給一個「嘀」的時間長度,其他元素的時間長度很容易就能夠計算出來。
你可能已经注意到,在上面显示的网页中,对于低于15WPM的选项,我们使用了“Farnsworth spacing”。那么这个“Farnsworth spacing”又是个什么鬼?
当报务员学习用耳朵来解码莫斯代码的时候,他就会意识到,当播放速度变化的时候,字符出现的节奏也会跟着变化。当播放速度低于10WPM的时候,他能够从容的识别“嘀”和“嗒”,并且知道发送的哪个字符。但是当播放速度超过10WPM的时候,报务员的识别就会出错,他识别出来的字符会多于实际的“嘀”和“嗒”。当一个学习的时候习惯低速莫斯代码的人,在处理高速播放代码的时候,就会出现问题。因为节奏变了,他潜意识的识别就会出错。
为了解决这个问题,“Farnsworth spacing”就被发明出来了。本质上来讲,字母和符号的播放速度依然采取高于15WPM的速度,同时,通过在字符之间插入更多的空格,来使整体的播放速度降低。这样,报务员就能够以一个合理的速度和节奏来识别每个字符,一旦所有的字符都学习完毕,就可以增加速度,而接收员只需要加快识别字符的速度就可以了。本质上来说,“Farnsworth spacing”这个技巧解决了节奏变化这个问题,使接收员能够快速学习。
所以,在整个系统中,对于更低的播放速度,都统一成15WPM。相对应的,一个“嘀”的长度是0.08秒,但是字符之间和单词之间的间隔就不再是3个dit或者7个dit,而是进行的调整以适应整体速度。
生成声音
在PHP代码中,一个字符(即前面数组的索引)代表一组由“嘀”、“嗒”和空白间隔组成的莫斯声音。我们用数字采样来组成音频序列,并且将其写入到文件中,同时加上适当的头信息来将其定义成WAVE格式。
生成声音的代码其实相当简单,你可以在项目中PHP文件中找到它们。我发现定义一个“数字振荡器”相当方便。每调用一次osc(),它就会返回一个从正玄波产生的定时采样。运用声音采样和声频规范,生成WAVE格式的音频已经足够了。在产生的正玄波中的-1到+1之间是被移动和调整过的,这样声音的字节数据可以用0到255来表示,同时128表示零振幅。
同时,在生成声音方面我们还要考虑另外一个问题。一般来讲,我们是通过正玄波的开关来生成莫斯代码。但是你直接这样来做的话,就会发现你生成的信号会占用非常大的带宽。所以,通常无线电设备会对其加以修正,以减少带宽占用。
在我们的项目中,也会做这样的修正,只不过是用数字的方式。既然我们已经知道了一个最小声音样本“嘀”的时间长度,那么,可以证明,最小带宽的声幅发生在长度等于“嘀”的正玄波半周期。事实上,我们使用低通滤波器(low pass filter)来过滤音频信号也能达到同样的效果。不过,既然我们已经知道所有的信号字符,我们直接简单的过滤一下每一个字符信号就可以了。
生成“嘀”、“嗒”和空白信号的PHP代码就像下面这样:
while ($dt < $DitTime) { $x = Osc(); if ($dt < (0.5*$DitTime)) { // Generate the rising part of a dit and dah up to half the dit-time $x = $x*sin((M_PI/2.0)*$dt/(0.5*$DitTime)); $ditstr .= chr(floor(120*$x+128)); $dahstr .= chr(floor(120*$x+128)); } else if ($dt > (0.5*$DitTime)) { // For a dah, the second part of the dit-time is constant amplitude $dahstr .= chr(floor(120*$x+128)); // For a dit, the second half decays with a sine shape $x = $x*sin((M_PI/2.0)*($DitTime-$dt)/(0.5*$DitTime)); $ditstr .= chr(floor(120*$x+128)); } else { $ditstr .= chr(floor(120*$x+128)); $dahstr .= chr(floor(120*$x+128)); } // a space has an amplitude of 0 shifted to 128 $spcstr .= chr(128); $dt += $sampleDT; } // At this point the dit sound has been generated // For another dit-time unit the dah sound has a constant amplitude $dt = 0; while ($dt < $DitTime) { $x = Osc(); $dahstr .= chr(floor(120*$x+128)); $dt += $sampleDT; } // Finally during the 3rd dit-time, the dah sound must be completed // and decay during the final half dit-time $dt = 0; while ($dt < $DitTime) { $x = Osc(); if ($dt > (0.5*$DitTime)) { $x = $x*sin((M_PI/2.0)*($DitTime-$dt)/(0.5*$DitTime)); $dahstr .= chr(floor(120*$x+128)); } else { $dahstr .= chr(floor(120*$x+128)); } $dt += $sampleDT; }
WAVE格式的文件
WAVE是一种通用的音频格式。从最简单的形式来看,WAVE文件通过在头部包含一个整数序列来表示指定采样率的音频振幅。关于WAVE文件的详细信息请查看这里Audio File Format Specifications website。对于产生莫斯代码,我们并不需要用到WAVE格式的所有参数选项,仅仅需要一个8位的单声道就可以了,所以,so easy。需要注意的是,多字节数据需要采用低位优先(little-endian)的字节顺序。WAVE文件使用一种由叫做“块(chunks)”的记录组成的RIFF格式。
WAVE文件由一个ASCII标识符RIFF开始,紧跟着一个4字节的“块”,然后是一个包含ASCII字符WAVE的头信息,最后是定义格式的数据和声音数据。
在我们的程序中,第一个“块”包含了一个格式说明符,它由ASCII字符fmt和一个4倍字节的“块”。在这里,由于我使用的是普通脉冲编码调制(plain vanilla PCM)格式,所以每个“块”都是16字节。然后,我们还需要这些数据:声道数、声音采样/秒、平均字节/秒、一个区块(block)对齐指示器、位(bit)/声音采样。另外,由于我们不需要高质量立体声,我们只采用单声道,我们使用 11050采样/秒(标准的CD质量音频的采样率是 44200采样/秒)的采样率来生成声音,并且用8位(bit)保存。
最后,真实的音频数据储存在接下来的“块”中。其中包含ASCII字符data,一个4字节的“块”,最后是由字节序列(因为我们采用的是8位(bit)/采样)组成的真实音频数据。
在程序中,由8位音频振幅序列组成的声音保存在变量$soundstr中。一旦音频数据生成完毕,就可以计算出所有的“块”大小,然后就可以把它们合并在一起写入磁盘文件中。下面的代码展示了如何生成头信息和音频“块”。需要注意的是,$riffstr表示RIFF头,$fmtstr表示“块”格式,$soundstr表示音频数据“块”。
$riffstr = 'RIFF'.$NSizeStr.'WAVE'; $x = SAMPLERATE; $SampRateStr = ''; for ($i=0; $i<4; $i++) { $SampRateStr .= chr($x % 256); $x = floor($x/256); } $fmtstr = 'fmt '.chr(16).chr(0).chr(0).chr(0).chr(1).chr(0).chr(1).chr(0) .$SampRateStr.$SampRateStr.chr(1).chr(0).chr(8).chr(0); $x = $n; $NSampStr = ''; for ($i=0; $i<4; $i++) { $NSampStr .= chr($x % 256); $x = floor($x/256); } $soundstr = 'data'.$NSampStr.$soundstr;
总结和评论
我们的文本莫斯代码生成器目前看起来还不错。当然,我们还可以对它做很多的修改和完善,比如使用其他字符集、直接从文件中读取文本、生成压缩音频等等。因为我们这个项目的目的是使其能够在网络上方便的使用,所以我们这个简单的方案,已经达到我们的目的了。
相关推荐:
PHP实现迪菲赫尔曼密钥交换(Diffie–Hellman)算法
以上是PHP產生以文字為主的摩斯電碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!