65.9K
CodeProject 正在变化。 阅读更多。
Home

文本生成摩尔斯电码。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (24投票s)

2010年6月2日

CPOL

10分钟阅读

viewsIcon

69284

downloadIcon

1085

一个PHP脚本,用于从文本生成莫尔斯电码音频文件。

codegen_screen.png

引言

我最近需要能够根据输入的文本生成莫尔斯电码音频文件。经过几次快速搜索,我没有找到任何符合我需求的东西,所以我决定自己编写一个生成器。结果就是我在这里呈现的CodeGen。

出于我的目的,我需要通过网络访问莫尔斯电码音频,所以我决定使用PHP作为主要的编程语言。上面的截图显示了一个启动莫尔斯电码生成的网页。zip文件包含用于提交文本的网页和实际生成并呈现音频文件的PHP源代码文件。如果你想测试PHP代码,你需要将网页和相关的PHP文件复制到支持PHP的Web服务器上。

许多人熟悉莫尔斯电码,仅仅是因为它是由点和划组成的序列,或者是一些在老电影中出现的嘟嘟声。不幸的是,如果我们要编写计算机代码来生成莫尔斯电码,这些描述帮助不大。本文将描述定义莫尔斯电码的参数,解释如何生成WAVE格式的声音文件,并提供实现莫尔斯电码翻译和生成WAVE文件的PHP代码。

莫尔斯电码

莫尔斯电码是一种文本编码方法,其优点是易于编码,并且可以用人耳解码。本质上,莫尔斯电码是通过打开和关闭音频(或射频)源并形成短而长的声音脉冲来生成的,俗称(dots)和(dashes),或在无线电通信行话中称为ditdah。在现代数字通信术语中,莫尔斯电码将被描述为一种幅度移键(ASK)的形式。

在莫尔斯电码中,字符(字母、数字、标点符号和特殊符号)被编码为ditdah的序列,因此要将文本转换为莫尔斯电码,我们首先需要确定如何表示这些符号。一个明显的选择是将dit表示为0位,将dah表示为1位,反之亦然。不幸的是,莫尔斯电码使用可变长度编码方案,因此也有必要使用可变长度序列,或者找到一种方法将数据打包到计算机内存中常用的固定位大小中。此外,需要注意的是,莫尔斯电码不区分大写和小写字母,并且缺少对特殊符号和某些字符的编码。在此实现中,所有未定义的字符和符号都将被忽略。

在这个项目中,节省内存不是真正的问题,因此设计了一种简单的编码方案,使用关联数组,用“0”表示dit,用“1”表示dah。定义莫尔斯电码编码表的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');

请注意,如果内存是问题,上述编码可以解释为位。在每个代码前添加一个起始位将产生一个位模式,该模式可以为每个字符存储在一个字节中。在使用生成的编码时,字节将向左移位,直到找到起始位来确定可变长度代码。

虽然大多数人没有意识到,莫尔斯电码主要由时序参数定义,因此正确表示这些参数是生成莫尔斯电码的首要任务。我们需要做的第一件事是定义莫尔斯电码字符固有的时序。按照惯例,单位时间,即dt,被定义为单个dit声音的长度,而dit/dah符号之间的间隔长度与dit的长度相同。dah的长度和字母之间的间隔长度都等于dit长度的3倍。单词之间的间隔通常等于dit长度的7倍,因此可以定义以下时序表:

项目 持续时间
dit dt
符号间间隔 dt
dah 3*dt
字符间间隔 3*dt
词间间隔 7*dt

在莫尔斯电码中,传输速度通常以字/分钟(WPM)表示。由于英语单词长度不同,字符的点划数量也不同,将WPM转换为数字采样时序并不明显。国际惯例采用的定义是平均单词长度为5个字符,数字和标点符号算作2个字符。这导致平均每字50个时间单位。因此,如果以WPM指定速度,则时序为每分钟50*WPM时间单位,单个dit的时间长度为dt = 1.2/WPM秒。给定dit的长度,所有其他时序参数都可以轻松确定。

您可能已经注意到上面显示的网页中,在15 WPM以下使用了Farnsworth间距。什么是“Farnsworth间距”?

当一个人通过耳朵学习识别(抄写)莫尔斯电码时,长期以来人们认识到,随着速度的变化,字符的明显节奏似乎也会发生变化。在10 WPM以下,一个人可以数出ditdah,然后决定发送了哪个字符,但在10 WPM以上,这通常是不可能的,代码更多地是通过字符的节奏而不是实际的点划数量来识别的。那些以慢速学习莫尔斯电码的人,由于他们下意识地数符号,或者因为节奏似乎在改变,常常在进步到更快的速度时遇到困难。

为了缓解从慢速学习莫尔斯电码到快速抄写的过渡,Farnsworth间距被开发出来。本质上,符号和字母以高速度发送,通常在15 WPM左右,但整体较慢的速度是通过在字符之间插入更多间隔来维持的。因此,一个人可以以合理的速度听到字符的声音和节奏,一旦学会了所有的字母,只需加快速度即可。本质上,Farnsworth间距技术消除了节奏的变化,以便(希望)更快地学习。

在这个系统中,对于较低的速度,莫尔斯电码的时序计算使得字符以15 WPM发送,对应于0.08秒的dit长度,但字符间和词间间隔,而不是3或7dit的长度,被调整为获得正确的整体速度。

声音生成

在PHP代码中,预先计算了对应于常见的莫尔斯电码音频元素ditdahspace的字符字符串。然后根据需要将这些音频样本连接起来形成声音序列,最后写入带有必要头信息的文件,以定义WAVE格式。

生成声音的代码相当简单,可以在项目的PHP文件中找到。我发现定义一个“数值振荡器”,Osc(),每次调用时都从正弦波返回有时间间隔的样本,这很方便。使用声音采样和声音频率规格,生成音频波形很容易。生成的正弦波在-1到+1之间变化,被移位并调整,使得声音字节数据在0到255之间变化,而128的值代表零幅度。

然而,声音生成还有另一个考虑因素。通常,莫尔斯电码被描述为由一个开关生成,对应于方波。如果你实际尝试这样做,你会发现生成的信号具有巨大的带宽,并且声音最好被描述为“咔嗒声”。因此,在无线电设备中,波形总是被塑形,以使其声音“更柔和”,并使用更少的带宽。

在我们的例子中,我们也需要以数值方式做同样的事情。由于我们知道最小声音样本(dit)的时间长度,可以证明当声音幅度以半周期等于dit长度的正弦波形状上升时,带宽最小。通过将生成的信号通过低通滤波器可以获得相同效果,但由于我们已经知道所有信号特性,直接生成滤波信号会更简单。

生成ditdahspace的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格式中所有可用的选项。只需要一个8位单声道声音通道,因此格式特别容易生成。请注意,多字节数据以小端字节序表示。WAVE文件使用一种称为RIFF的格式,并由一系列称为“块”(chunks)的记录组成。

WAVE文件本身以ASCII标识符RIFF开头,后面是4个字节表示大小,然后是一个WAVE头部,由ASCII字符WAVE组成,后跟定义格式和声音数据的数据。

第一个块,在我们的例子中,由格式指定符组成,它以ASCII字符fmt开头,后面跟着一个4字节的块大小,该大小等于16、18或40,取决于使用的声音编码格式。在此应用中,我使用纯粹的PCM格式,因此块大小始终为16字节,并且需要的数据是通道数、每秒声音样本数、每秒平均字节数、块对齐指示符以及每声音样本的位数。在此应用中,不需要高质量的立体声音效,因此PHP代码将格式块生成为字符字符串,假定为单声道(mono)、8位声音,以11050样本/秒的速率生成。请注意,标准的CD音质音频是44200样本/秒。

最后,实际的声音数据包含在下一个也是最后一个块中,该块由ASCII字符data、4字节的块大小组成,然后是声音数据本身作为字节序列(因为我们指定了每样本8位)。

该程序将声音生成为变量$soundstr中表示的8位音频幅度数字序列。一旦声音本身生成完毕,就可以确定块大小,然后将整个文件组合起来并写入磁盘。下面显示了生成头部和声音块的PHP代码。请注意,$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;

结论和评论

这里提出的莫尔斯电码生成软件在将文本转换为莫尔斯电码音频方面似乎效果很好。当然,可以进行许多修改和改进,包括使用其他字符集、直接从文件中读取文本、生成压缩音频等。由于这项工作的目的是为了通过网络提供一个转换程序,这个简单的解决方案似乎已经达到了目的。

当然,一如既往,欢迎对改进这个代码的任何建议。我欠许多多年来教导我的人关于莫尔斯电码的背景信息,但我确信任何错误或遗漏都必须由我自己负责。

修订历史

  • 2010年6月2日:初始提交。
© . All rights reserved.