crb3

morswave

Jul 19th, 2015
320
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Perl 19.93 KB | None | 0 0
  1. #!/usr/bin/perl -w
  2. #
  3. # morswave  --crb3 30Sep05/08Oct05/26apr06/02Aug06/02mar14
  4. #  a recovery of gen_morsewavs      --crb3 26jun03
  5. #
  6. # program copyright (C) 2005/2014 Carroll R. Bryan III, WB1HKU.
  7. # Released under the GNU General Public License, version 2, as
  8. # posted at http://www.gnu.org.
  9. #
  10. # produces WAV files of International Morse signals by sinewave
  11. # generation.
  12. #
  13. # Unless the -C switch is used, the sinewaves are abrupt coming
  14. # and going. sinewave generation starts at 0-degree, so no
  15. # unexpected harmonics there, but the abrupt ending sounds like
  16. # keyclicks on trailing edges at high ditrate and low frequecy.
  17. # The -C switch applies linear ramp envelope-shaping to both
  18. # edges of each sinewave burst. It sounds a little more like
  19. # crystal-filtered-IF radio, a little harder to copy, but it
  20. # eliminates the tail-clicks. The -H switch ramps the leading
  21. # edge as well; that sounds a bit like a DC receiver with active
  22. # filtering.
  23. #
  24. # The program has three possible functions:
  25. # -A        generate a complete letterset
  26. # -b <n>    generate an n-char random burst (one burst per run)
  27. # -m <text> generate a text message: inline 'string', or '@file'.
  28. #
  29. # Morse params:
  30. # -f <n>    sinewave frequency in Hz
  31. # -F        toggle Farnsworth spacing on/off (default: OFF, or FCC spacing)
  32. # -w <n>    wpmx: words-per-minute-times-ten
  33. # -t <file> use specified tablefile instead of internal DATAset
  34. #
  35. # WAV params:
  36. # -r <rate> samples-per-second sample-rate
  37. # -c <n>    audio channels. 1,2 is mono,stereo
  38. # -B <n>    bits per sample: 8 or 16.
  39. # -I        toggle whether right chan is inverted/uninverted copy of left
  40. # -C        clickfree, as described above.
  41. # -e        toggle: echo created filenames for caller to use
  42. #
  43. # /home/crb3/crb3/perl/m/morstrain/
  44. #
  45. # C port will be to /home/crb3/crb3/c/music/wave/morse/
  46. #
  47. # Version History:
  48. # v0.1  --crb3 08Oct05  initial working version. -C unimplemented
  49. # v0.2  --crb3 02Aug06  -C implemented with linear shaping ramp
  50. # v0.3  --crb3 02Mar14  strictify it, clean it up a little
  51. #
  52. use strict;
  53.  
  54. my $debug=0;
  55. my $chatty=0;
  56. my $shutup=0;
  57.  
  58. $|=1 if $debug;
  59.  
  60. my $declicktime=2E-3;       # 2 ms linear envelope-shaping ramp
  61.  
  62. (my $me = $0)  =~ s/^.*\///; # whack off any pathing
  63. my %params=(
  64.   'pgm' => $me,
  65. #
  66. # default params
  67. #
  68.   'wpmx' => 165,        # 16.5 wpm default, fast but copyable
  69.   'freq' => 770,        # 770 Hz default, a common sidetone pitch
  70.   'farns' => 0,         # farnworth OFF by default
  71.  
  72.   'samrate' => 11025,       # samples-per-second in Hz
  73.   'sambits' => 16,      # 8-bit/16-bit WAVs emitted
  74.   'samchans' => 1,      # 1,2 == mono, stereo
  75.   'flipside' => 0,      # stereo chans are inverse of each other?
  76.   'doloud' => 0,        # (override) full-excursion, not half?
  77.   'declick' => 0,       # apply anti-keyclick linear ramp?
  78.   'headclik' => 1,      # apply declick to start of burst for
  79.                 # symmetry? or only to tail where needed?
  80.   'echoit' => 0,        # echo created filenames on stdout?
  81.   'unclickramp' => 22,      # 2 ms at default samrate
  82. #
  83. # WAV header constants...
  84. #
  85.   'riff' => 'RIFF',
  86.   'wave' => 'WAVE',
  87.   'data' => 'data',
  88.   'fmt_' => 'fmt ',
  89.   'storage' => 1,                     # compression method. 1 = no compression
  90.  
  91. );
  92.  
  93. my($use_table,$do_all,$do_burst,$do_message)=(0,0,0,0);
  94. my($key,$arg,$tablefile,$burst,$message);
  95. while(defined($ARGV[0]) and index($ARGV[0],'-')==0){
  96.   $arg=shift(@ARGV);                    # get any switches
  97.   $key=substr($arg,1,1);        # get no-arg switches first
  98.   substr($arg,0,2)="";
  99.   if($key eq "v"){                      # verbose?
  100.     $chatty ^= 1;
  101.     next;
  102.   }elsif($key eq "q"){                  # silent running?
  103.     $shutup^=1;
  104.     next;
  105.   }elsif($key eq "F"){                  # Farnsworth spacing
  106.     $params{farns}^=1;
  107.     next;
  108.   }elsif($key eq "I"){                  # inverse: right channel is inverted
  109.     $params{flipside}^=1;       #  copy of left chan
  110.     next;
  111.   }elsif($key eq "C"){                  # cleanup. sinewaves end at zero-
  112.     $params{declick}^=1;        #  -crossing for click-free keying.
  113.     next;
  114.   }elsif($key eq "A"){                  # generate a complete tableset array
  115.     $do_all^=1;             #  of WAVs with these params
  116.     next;
  117.   }elsif($key eq "e"){                  # generate a complete tableset array
  118.     $params{echoit}^=1;             #  of WAVs with these params
  119.     next;
  120.   }elsif($key eq 'H'){          # ramp leading edge of burst too?
  121.     $params{headclik} ^= 1;
  122.     next;
  123.   }elsif($key eq 'L'){          # full-excursion sines. not half?
  124.     $params{doloud} ^= 1;
  125.     next;
  126.   }
  127.   $arg =~ s/^\=//;                      # handles switch=arg
  128.   $arg=shift(@ARGV) if($arg eq "" and index($ARGV[0],'-') != 0);
  129.                                         # handles space-separated switch/arg
  130.   if($key eq "w"){          # wpm x 10
  131.     $params{wpmx}=$arg;
  132.   }elsif($key eq "c"){          # 1 or 2, for mono or stereo
  133.     $params{samchans}=$arg;
  134.   }elsif($key eq "f"){          # sidetone frequency
  135.     $params{freq}=$arg;
  136.   }elsif($key eq "r"){          # samples per second samplerate
  137.     $params{samrate}=$arg;
  138.   }elsif($key eq "B"){          # bits per sample, 8 or 16
  139.     $params{sambits}=$arg;
  140.   }elsif($key eq "t"){          # code-structure table supplants <DATA>
  141.     $tablefile=$arg;
  142.     $use_table=1;
  143.   }elsif($key eq "b"){          # random-char burst: how many?
  144.     $burst=$arg;
  145.     $do_burst=1;
  146.   }elsif($key eq 'm'){
  147.     $message=$arg;
  148.     $do_message=1;
  149.   }else{
  150.     warn "$me: unrecognized option -$key $arg\n";
  151.   }
  152. }
  153.  
  154. #
  155. # constants...
  156. #
  157. my $TWOPI=(atan2(1,1)*8);   # from the camel book, with tweak
  158. my $paris=50;           # dits (bauds) in 60 secs for 1wpm
  159.  
  160. #
  161. # facts are in, so run basic calculations once
  162. #
  163.  
  164. $params{wpm}=$params{wpmx}/10;  # in perl, we have easy decimals.
  165. my $ditperiod = 60 /($paris * $params{wpm});
  166. $params{ditsams} = $params{samrate} * $ditperiod;
  167.  
  168. $params{samalign}=$params{samchans} * ($params{sambits}/8);
  169. $params{bytrate}=$params{samrate} * $params{samalign};
  170.  
  171. #
  172. # WAV sine amplitude is set at half of available headroom.
  173. # this is deliberate, so the loudness is more like that of
  174. # normal PC soundcard files.
  175. # Use the -L switch to override this and produce WAVfiles
  176. # with full excursions.
  177. #
  178. my $maxwav=(2**($params{sambits})); # unsigned-bits excursion
  179. $maxwav /= 2 unless $params{doloud};    # half of that (save your hearing)
  180.  
  181. $params{maxvol}=$maxwav/2;  # half of that -- zerocrossing-to-halfmax
  182. $params{cyclesams}=$params{samrate} / $params{freq};
  183. $params{unclickramp} = int($declicktime / (1/ $params{samrate}));
  184. #
  185. # fetch the morse table in
  186. #
  187. my $t={};
  188. my $fH;
  189. if($use_table){
  190.   if(open(FH,"<$tablefile")){
  191.     $fH = \*FH;
  192.   }else{
  193.     warn "$me: no file $tablefile to open; using internal tables\n";
  194.     $use_table=0;
  195.   }
  196. }
  197. unless($use_table){
  198.   $fH = \*DATA;
  199. }
  200. build_table_struct($t,$fH);
  201. close($fH); # if $use_table;
  202. #
  203. # if there's a message to send, it might be a file (using the
  204. # old CP/M-hacker signal for that, a leading '@'). Doing it this
  205. # way, rather than with shell-level redirection, keeps an
  206. # unknown number and sequence of tabled characters from being
  207. # interpreted as shell metacharacters. file newlines are
  208. # converted to spaces.
  209. #
  210. if($do_message){
  211.   my($ms,@msg);             # local to this block
  212.   if(substr($message,0,1) eq '@'){
  213.     substr($message,0,1)="";
  214.     $params{messagefile}=$message;  # save that for naming
  215.     open(MSG,"<$message") or die "$me: message file $message not found\n";
  216.     (@msg)=(<MSG>);
  217.     close(MSG);
  218.     foreach $ms (@msg){
  219.       $ms =~ s/\r?\n$//;        # chomp DOS or UNIX
  220.     }
  221.     $message = join(' ',(@msg));
  222.   }
  223. }
  224. my $pt=\%params;    # there, now both are used via references.
  225.  
  226. if($debug){
  227.   use Data::Dumper;
  228.   my $dumped=Dumper($pt);
  229.   print $dumped;
  230.   $dumped=Dumper($t);
  231.   print $dumped;
  232. }
  233. my(@tsorted)=sort (keys %$t);
  234.  
  235. #
  236. # Now the commandline tasks.
  237. #
  238. # do_all means to generate a WAV file for each letter in the  
  239. # table, at current Morse and WAV settings. the names are
  240. # explicit: they're meant to be program-called.
  241. #
  242. if($do_all){        # might as well emit in sorted order
  243.   foreach my $letter (@tsorted){
  244.     gen_morsfile($t,$pt,$letter);
  245.   }
  246. }
  247.  
  248. #
  249. # do_burst takes a numeric argument, and sends that many random
  250. # characters as a one-'word' WAV file. if you're using a
  251. # success-history wrapper program, though, you're better off
  252. # having that wrapper generate '-m' messages based on the user's
  253. # worst scores, rather than pure random.
  254. #
  255. if($do_burst){
  256.   srand( time() ^ ($$ + ($$<< 15)) );   # from the camel book p223
  257.   my $text="";
  258.   for (1..$burst){
  259.     $text .= $tsorted[rand($#tsorted)];
  260.   }
  261.   gen_morsfile($t,$pt,$text);  
  262. }
  263. #
  264. # do_message takes a text string (of any length) and produces
  265. # one WAV file, with proper interword spacing. That file can
  266. # be one letter or a whole qso. If the first char of the string
  267. # is '@', the rest of the string is regarded as a filename, the
  268. # contents of which are read in as the message.
  269. #
  270. if($do_message){
  271.   gen_morsfile($t,$pt,$message);
  272. }
  273.  
  274. print "-x-\n" if $debug;
  275.  
  276.  
  277. #----------------------
  278.  
  279. #
  280. # gen_morsfile.
  281. #
  282. # generate a WAV file using the provided text and structed params.
  283. #
  284. sub gen_morsfile {
  285.   my($t,$pt,$txt)=(@_);
  286.   my ($fn);
  287.  
  288.   my $bs=gen_baudstring($t,$pt,$txt);
  289.  
  290.   ($fn=$txt) =~ s/ /\-/g;   # underwire the spaces for a filename
  291.   $fn=gen_wavfname($pt,$fn);    # standardize it
  292.  
  293.   my $samlen = length($bs) * $pt->{ditsams};
  294.  
  295.   open(WAV,">$fn") or die "$me: can't make WAVfile $fn\n";
  296.   $fH = \*WAV;
  297.   print "$fn\n" if $pt->{echoit};   # echo name to stdout for
  298.                     #  caller to use
  299.  
  300.   gen_wavheaders($pt,$fH,$samlen);
  301.  
  302.   emit_bauds($pt,$bs,$fH);
  303.  
  304.   close(WAV);
  305. }
  306.  
  307. #
  308. # gen_wavheaders.
  309. #
  310. # emit WAV file headers to an open filehandle, using current
  311. # structed params and provided samplecount.
  312. #
  313. sub gen_wavheaders {
  314.   my($pt,$fH,$samct)=(@_);  # \% \*FH $
  315.  
  316.   my $databyct=$samct * $pt->{samchans} * ($pt->{sambits}/8);
  317.   my $riffsize=$databyct + 8    # data header/byct
  318.                   + 16 + 4 ;    # fmt header
  319.  
  320.   my $headers=pack("a4Ia4"."a4IvvIIvv"."a4I",
  321.         $pt->{riff},
  322.         $riffsize,      # hoping the native endian is vax/intel
  323.         $pt->{wave},
  324.  
  325.         $pt->{fmt_},
  326.         16,                     # fmtcnt, size of 'fmt ' args
  327.         $pt->{storage},
  328.         $pt->{samchans},
  329.         $pt->{samrate},
  330.         $pt->{bytrate},
  331.         $pt->{samalign},
  332.         $pt->{sambits},
  333.  
  334.         $pt->{data},
  335.         $databyct  );
  336.  
  337.   return(print $fH $headers);
  338. }
  339.  
  340. #
  341. # gen_wavfname.
  342. #
  343. # generate a specifying WAVfile filename, specifying the params
  344. # used to generate the Morse, plus the text itself.
  345. # usually, that's the characters themselves. if the message text
  346. # came out of a file, though, use the filename instead, otherwise
  347. # the resultant filename will be lo-o-o-ong.
  348. # factored out for clarity. oh, yeah, gotta fudge out metachars...
  349. # '/' becomes '`' etc
  350. #
  351. sub gen_wavfname {
  352.   my($pt,$didah,$let)=(@_);
  353.   my($unklik,$chans);
  354.  
  355.   $chans = ($pt->{samchans} == 2 ? "stereo" : "mono");
  356.  
  357.   if($pt->{declick}){
  358.     $unklik = 'C';
  359.     $unklik .= 'H' if $pt->{headclik};
  360.   }else{
  361.     $unklik="";
  362.   }
  363.   $unklik .= 'F' if $pt->{farns};
  364.  
  365.   if($pt->{messagefile}){
  366.     ($didah=$pt->{messagefile}) =~ s/^.*\///;   # whack path, leave fn.t
  367.   }else{
  368.     $didah = lc($didah);
  369.   }
  370.   $didah = "$let.$didah" if defined $let;
  371.  
  372.   $didah =~ tr/\\\?\//HQ|/;
  373.  
  374.   my $wfn = join('.',
  375.     "$pt->{pgm}\-$didah",
  376.     "$pt->{wpmx}$unklik",
  377.     "$pt->{freq}",
  378.     "$chans\-$pt->{sambits}",
  379.     "wav");
  380.   return($wfn);
  381. }
  382.  
  383. #
  384. # gen_baudstring.
  385. #
  386. # generate a keydown/keyup Morse version of the provided text
  387. # string, doing lookups in the current tablefile hash and using
  388. # structed params.
  389. #
  390. sub gen_baudstring {
  391.   my($t,$pt,$txt)=(@_);     # \%table, \%params, $text
  392.   my $bs="";
  393.   my $let;
  394.  
  395.   my $spd = '-' x ($pt->{farns} ? 14 : 4);  # CHECK THESE AGAINST MORBEEP
  396.                         # padding for interword spacing
  397.  
  398.   foreach my $letter (split(//,$txt)){
  399.     if($letter eq ' '){     # presume this comes after-word, so
  400.       $bs .= $spd;      #  after-letter unkey is already there.
  401.     }else{
  402.       $let = $t->{$letter};
  403.       $bs .= string_bauds($pt,$let);
  404.     }
  405.   }
  406.   return($bs);      # return something like 'xxx-x-xxx-x---x-xxx-x------'
  407. }
  408.  
  409. #
  410. # string_bauds.
  411. #
  412. # takes in a letterform in one of two symbol-sets, looked up
  413. # from a tablefile hash, and returns a single keydown/keyup
  414. # sequence string. this is where the tablefile gets interpreted,
  415. # functionally for a single letter at a time.
  416. #
  417. sub string_bauds {
  418.   my($pt,$inline)=(@_);     # \%params $string
  419.   my $seq="";
  420.  
  421.   foreach my $baud (split(//,$inline)){
  422.     if($baud eq '#'){       # key for a ditlength.
  423.       $seq .= 'x';
  424.     }elsif($baud eq '_'){   # unkey for a ditlength
  425.       $seq .= '-';
  426.     }elsif($baud eq '-'){   # dah
  427.       $seq .= 'xxx-';
  428.     }elsif($baud eq '.'){   # dit
  429.       $seq .= 'x-';
  430.     }else{
  431.       warn "$me: unknown symbol $baud in $inline\n";
  432.       return(0);    
  433.     }
  434.   }
  435. #
  436. # now put the char-tail unkeys on. farns? 7. else, 3,
  437. # already got one dit of unkey, unless the tables are botched.
  438. # this is inter-char spacing.
  439. #
  440.   $seq .= ($pt->{farns} ? '------' : '--');
  441.   return($seq);
  442. }
  443.  
  444. #
  445. # emit_bauds.
  446. #
  447. # take in a string of 'x-xxx-', a key/unkey sequence, and emit
  448. # sinewave samples to an already-open WAV or RAW file or stream.
  449. # 'x' is keydown, at freq $freq; '-' is is keyup, at freq 0.
  450. # dits and dahs start at zero-crossing but don't often end that
  451. # way, so effectively there's a little bit of keyclick-on-break.
  452. # params:
  453. #   sambits = 8 or 16 (bits per sample)
  454. #   samchans = 1 or 2 (channels of audio)
  455. #   wpmx = words-per-minute * 10
  456. #   freq = sinewave frequency (Hz)
  457. #   cyclesams = samples-per-cycle, derived from freq and samplerate
  458. #   peak sample amplitude for sample size (set to half-power)
  459. #
  460. sub emit_bauds {
  461.   my($pt,$seq,$fH)=(@_);
  462.   my($bauds,$samfreq,$samcount,$c);
  463.   my $i=0;  # i don't often use an 'i' var, but this is an index.
  464.  
  465.   while(defined($c=substr($seq,$i)) and $c ne ""){
  466.     if($c =~ /^x+/){        # keydown
  467.       $samfreq=$pt->{freq};
  468.     }elsif($c =~ /^\-+/){   # keyup
  469.       $samfreq=0;
  470.     }else{
  471.       warn "$me: bad baud char \'$c\' at index $i in emit_bauds string \'$seq\'\n";
  472.       return(0);
  473.     }
  474.  
  475.     $i += ($bauds=length($&));      # advance index past current group.
  476.                     # grouping keeps keydown dahs from
  477.                     # clicking at dit intervals.
  478.  
  479.     $samcount = $bauds * $pt->{ditsams};
  480.                    
  481.     unless(emit_samples($pt,$samcount,$samfreq,$fH) ){
  482.       return(0);           
  483.     }
  484.   }
  485.   return(1);
  486. }
  487.  
  488. #
  489. # emit_samples.
  490. #
  491. # low-level tone/silence-emitter function. sends a specified count
  492. # of sinewave-or-silence (freq=0) sample-blocks out to an already-
  493. # opened filehandle. returns: 1, success. 0, error (write error).
  494. # 'freq' param is used only as a keydown/keyup flag here.
  495. #
  496. # params are pulled out of struct for faster access in the core loop:
  497. #  write-once, read-manymany. On a slow machine (like my 400 MHz K6),
  498. # this program still takes almost as much time to generate the Morse
  499. # as it does to send it.
  500. #
  501. sub emit_samples {
  502.   my($pt,$samcount,$samfreq,$fH)=(@_);  # \%params, $ct, \*WAV
  503.  
  504.   my $flipside = $pt->{flipside};
  505.   my $samchans = $pt->{samchans};
  506.   my $sambits = $pt->{sambits};
  507.   my $maxvol = $pt->{maxvol};
  508.   my $cyclesams = $pt->{cyclesams};
  509.   my $declik = $pt->{declick};
  510.   my($sam,$isam,$lsam,$rsam);
  511.   my $samstep=0;
  512.   my $oldsam=0;
  513.   my $hdclik = $pt->{headclik};
  514.   my $cnt;
  515.   my $clikmax = ($pt->{declick} ? $pt->{unclickramp} : 0);
  516.   my $sammax=$samcount;
  517.   my($kmul,$out);
  518.  
  519.   while($samcount > 0){
  520.     if($samfreq and $samstep){
  521.       $sam =$maxvol * ( d_sin(360 * ($samstep / $cyclesams)));
  522.     }else{
  523.       $sam=0;
  524.     }
  525.  
  526.     if($declik){
  527.       if($hdclik and ( ($cnt=$sammax-$samcount) < $clikmax)){
  528.         $kmul = $cnt / $clikmax;
  529.         $sam *= $kmul;
  530.       }elsif( ($cnt=$samcount) < $clikmax){
  531.         $kmul = $cnt / $clikmax;
  532.         $sam *= $kmul;
  533.       }
  534.     }
  535.  
  536.     $isam=$sam;
  537.     if($sambits==8){
  538.       $sam ^= 0x80;               # 8bit WAV samples are unsigned
  539.     }
  540.     $lsam=$rsam=$sam;
  541.     if($flipside){
  542.       if($sambits==8){
  543.         $rsam= $sam ^ 0xFF;
  544.       }else{
  545.         $rsam = 0-$sam;
  546.       }
  547.     }
  548.  
  549.     if($samchans==2){
  550.       if($sambits==16){
  551.         $out = pack("v2",$lsam,$rsam);
  552.       }else{    # 8-bit
  553.         $out = pack("c2",$lsam,$rsam);
  554.       }
  555.     }else{        # one channel
  556.       if($sambits==16){
  557.         $out = pack("v",$lsam);
  558.       }else{    # 8-bit
  559.         $out = pack("c",$lsam);
  560.       }
  561.     }
  562.     unless(print $fH $out){ # print to open (\*TYPEGLOB) fileHandle
  563.       warn "write-to-filehandle error\n";
  564.       return(0);
  565.     }
  566.  
  567.     $oldsam=$isam;
  568.     $samcount-- if $samcount;   # don't underflow
  569.     $samstep++;
  570.   }
  571.   return(1);
  572. }
  573.  
  574. #
  575. # d_sin.
  576. #
  577. # sine in degrees. uses $TWOPI global.
  578. #
  579. sub d_sin {
  580.   my $d = shift(@_);
  581.  
  582.   return( sin(($d/360)*$TWOPI) );  
  583. }                                                                                
  584.  
  585. #
  586. # build_table_struct.
  587. #
  588. # suck in a morse table, on an open filehandle from either a
  589. # file or from DATA, and build an encoding table with it.
  590. # there's no return value, instead the provided hashref is
  591. # festooned like a Yule tree.
  592. #
  593. sub build_table_struct {
  594.   my($t,$fH)=(@_);      # \%table \*HANDLE
  595.  
  596.   my($junk,$ln,$val,$key);
  597.  
  598.   while(defined($ln=<$fH>) and index($ln,'__END__')<0){
  599.     chomp $ln;
  600.     next if $ln =~ /^\s*$/;
  601.     ($key,$val,$junk)=split(/\s+/,$ln);
  602.     $t->{$key} = $val;
  603.   }
  604. }
  605.  
  606.  
  607. __DATA__
  608.  
  609. a   .-
  610. b   -...
  611. c   -.-.
  612. d   -..
  613. e   .
  614. f   ..-.
  615. g   --.
  616. h   ....
  617. i   ..
  618. j   .---
  619. k   -.-
  620. l   .-..
  621. m   --
  622. n   -.
  623. o   ---
  624. p   .--.
  625. q   --.-
  626. r   .-.
  627. s   ...
  628. t   -
  629. u   ..-
  630. v   ...-
  631. w   .--
  632. x   -..-
  633. y   -.--
  634. z   --..
  635. 1   .----
  636. 2   ..---
  637. 3   ...--
  638. 4   ....-
  639. 5   .....
  640. 6   -....
  641. 7   --...
  642. 8   ---..
  643. 9   ----.
  644. 0   -----
  645. ?   ..--..      imi
  646. .   .-.-.-
  647. ,   --..--
  648. =   -...-
  649. -   -....-
  650. /   -..-.
  651. +   .-.-.       ar
  652. \   ........    hh
  653. &   #__#_#_#_
  654. @   .--.-.
  655. __END__
  656.  
  657. =pod
  658.  
  659. Clean Enough?
  660.  
  661. morswave generates calculated-sinewave Morse code signals as
  662. WAV files. It's designed to be used in a Morse training system,
  663. generating the required WAV files at the start of a training run
  664. so the session itself proceeds without delay.
  665.  
  666. The keydown periods start at the zero-crossing, which is clean,
  667. and stop at the required samplecount, no matter what the
  668. sinewave is doing. This makes for noticeably clicky endings.
  669.  
  670. The -C option puts a stop to that, imposing a linear-ramp filter
  671. on the first and last couple of milliseconds of samples. Normal
  672. on-the-air Morse has an ASR-style key-envelope with defined
  673. attack and release times, to prevent make- and break-time
  674. keyclicks and their harmonic content. The result of the -C
  675. option sounds like such signals. At some tone frequencies, in
  676. fact, it sounds a little hooty, like a signal from a DC receiver
  677. as passed through an audio filter.
  678.  
  679. The simple linear ramp does impose its own, lesser, distortion,
  680. sounding like mild speaker thumps. In a Morsecode trainer setup,
  681. this doesn't matter. If you're driving a transmitter with these
  682. WAV signals, though, you'll need to clean them up to keep your
  683. transmitter out of trouble.
  684.  
  685. 8-bit sampled sinewaves, which is what you'll probably use if an
  686. embedded MCU is driving a transmitter with tabled values, need
  687. cleanup to drive a transmitter, period; the sampling is too
  688. granular for a clean signal. You can clean it up slightly by
  689. doubling the 'maxvol' parameter to use the full 8-bit waveform
  690. range with the -L option, but you'll still need a low-pass
  691. filter to get rid of harmonics.
  692.  
  693. Figuring It Out
  694.  
  695. Sending-rate is measured in words-per-minute. The standard test
  696. is the word PARIS sent continuously for a minute. Including
  697. standard spacing, that word has 46 dits of time within, plus an
  698. additional 4 dits interword spacing on the trailing silence, so
  699. for each wpm we get 50 dits in 60 seconds. We take in a dpm*10
  700. value as arg so we can get fractional dpm at the low end; let's
  701. call that 'wpmx'.
  702.  
  703. Working with WAV files, the natural time-granularity is the
  704. samplerate, so structed params are calculated down to a convenience
  705. constant, ditsams, samples-per-dit.
  706.  
  707. =cut
  708.  
  709.  
  710. __END__
Advertisement
Add Comment
Please, Sign In to add comment