
/*Morse.c
  A crude hack to generate morse code in what should be a fairly OS 
  independent way, but which may in fact be completely non-portable.  

  The only system under which this code has been tested has been FreeBSD.  I
  cannot guarantee it will even compile under any other system.  It should,
  however, be fairly portable to any ANSI C compiler.  Other than that 
  statement I make no claims about it.

  Author: Tom Russo
          russo@swcp.com

   Copyright 1999, Thomas V. Russo, KM5VY, All rights reserved.

  You may freely distribute this software so long as you retain this notice 
  and provide a detailed description of any changes you might have made.

 This software provided with absolutely no warranty whatsoever.  Use it if it
  is useful, file it in your circular hacks file if not.

Compilation:  cc -o morse morse.c -lm  (must include the math library!)

Usage: morse fwpm wpm tone

fwpm = character speed in WPM
wpm = word speed in WPM
tone = tone in Hz  

.au file is sent to the standard output --- you'll want to redirect that.

All text on standard in is converted to morse code.   Characters not in the 
alphabet normally used by hams are represented as a question mark (?).  
You can include prosigns, but they're coded as single characters:

Prosigns: BT is represented by =, AR by +, KN by # and SK by $

  */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <ctype.h>
#include <unistd.h>

#define PI 3.14159265358979
#define SRATE 8000.0     /* samples per second */
#define  PARIS 50.0       /* fifty "ticks" in one instance of PARIS at*/
                          /* normal character spacing */
#define  SECPM 60.0       /* 60 seconds per minute */
#define hdr 01
#define NCHANS 1          /* default to single channel instead of stereo */
int nchans=NCHANS;

/* Define dahs and dits for the International Morse Code for letters,
   numbers and punctuation/prosigns.  */
/* code is the letters [a-z], num is the numbers [0-9], punct is the rest */
char  *code[]={".-","-...","-.-.","-..",".","..-.","--.","....","..",".---",
	       "-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-",
	       "..-","...-",".--","-..-","-.--","--.."};
char *num[]={"-----",".----","..---","...--","....-",".....","-....","--...",
	       "---..","----."};
char *punct[]={"-..-.", /* slash */ 
	       "--..--", /* comma */
	       ".-.-.-", /* period */
	       "..--..", /* question */
               "-...-",/*BT  = */
	       ".-.-.",/*AR  +*/
	       "-.--.",/* KN #*/ 
	     "...-.-"};/*  SK $*/

double ticks_per_minute,secs_per_tick,intrachar_space,interchar_space,interword_space;

/* This routine generates code for one character and outputs it */
/* There is a better way to do this.  I don't care, this works. */
/* isalpha, isdigit, islower, isupper and ispunct really ought to exist on
   your system.  You might have to write them if not.  Each returns
   nonzero if the argument is the thing it is looking for, 0 otherwise */
putcode(char c,FILE *outfil,double freq)
{

  int off;
  char *dida;

  dida="";
  if (isalpha(c))
    {
       if (islower(c))
          {
              off = c-'a';
              dida = code[off];
           }
       if (isupper(c))
	  {
              off = c-'A';
              dida=code[off];
          }
    }

  if (isdigit(c))
    {
      off = c-'0';
      dida = num[off];
    }
  off=-1;
  if (ispunct(c))
    {
      if (c == '/') off=0;
      if (c == ',') off = 1;
      if (c == '.') off = 2;
      if (c == '?') off = 3;
      if (c == '=') off = 4;
      if (c == '+') off = 5;
      if (c == '#') off = 6;
      if (c == '$') off = 7;
      /* if we don't recognize the character, make it a ? */
      if (off < 0) off=3;
      dida = punct[off];
    }

  /* take care of stray unknowns */
  if (dida[0]=='\0')
    dida=punct[3];
 
  while (*dida != '\0')
    {
      if (*dida == '.')
	out_tone(secs_per_tick,freq,SRATE,outfil);
      else
	out_tone(3*secs_per_tick,freq,SRATE,outfil);
      out_space(intrachar_space,freq,SRATE,outfil);
      dida++;
    }
  out_space(interchar_space-intrachar_space,freq,SRATE,outfil);
}

/* This subroutine generates the sound for one line of text */
outcode (char *c,FILE *outfil, double freq)
{

  while (*c != '\0' && *c != '\n')
    {
      if (*c == ' ')
	out_space(interword_space-interchar_space+intrachar_space,freq,SRATE,outfil);
      else 
	putcode(*c,outfil,freq);
      c++;
    }

}
main(int argc, char **argv)
{
  int i;
  unsigned char foo;
  unsigned o32;
  union {
    long l;
    char c[4];
  }u32;
     
  FILE *junk;
  double length_of_silence,length_of_paris;
  char line[128];
  double FWPM,WPM,TONE;

  junk=stdout;

  /* in an earlier version I used "bas" as the default output file, you can
    uncomment this if you like.  I commented it out when I found that "isatty"
    doesn't always exist on a standard unix machine.  isatty is supposed to
    return nonzero if the standard output file is a terminal.  The intention
    was to refuse to send audio output to a terminal.  In typical Unix Nerd 
    fashion I've opted to assume the user knows exactly what he or she is doing
    and not built in all sorts of sanity checks here.
    */

  /*
  if (isatty(1)==0)
    junk=stdout;
  else
    junk=fopen("baz","w"); 
    */

  FWPM=18;
  WPM=13;

  if (argc < 4)
    {
      fprintf(stderr,"Usage: %s fwpm wpm tone\n",argv[0]);
      exit(1);
    }
  if (argc == 4)
    {
      FWPM = atof(argv[1]);
      WPM = atof(argv[2]);
      TONE=atof(argv[3]);
    }

  /* If you want a fully specified Sun audio file (MIME type "audio/basic")
     you need the sound header.  This is on by default, change hdr to 0 above 
     if you don't want it.  It is needed by some players and conversion 
     utilities (e.g. Sox) */

  if (hdr)
    {
      fwrite(".snd",sizeof(char),4,junk);      /* 4 */
      o32=32;  /* hdr size */
      swab(&o32,&(u32.l),4);
      fwrite(&(u32.c[2]),sizeof(char),2,junk);    /* 8 */
      fwrite(&(u32.c[0]),sizeof(char),2,junk);    /* 8 */
      o32 = (unsigned) (~0);          /* unknown data size */
      swab(&o32,&(u32.l),4);
      fwrite(&(u32.c[2]),sizeof(char),2,junk);    /* 8 */
      fwrite(&(u32.c[0]),sizeof(char),2,junk);    /* 8 */
      o32 = 2;    /* 8-bit linear PCM */
      swab(&o32,&(u32.l),4);
      fwrite(&(u32.c[2]),sizeof(char),2,junk);    /* 8 */
      fwrite(&(u32.c[0]),sizeof(char),2,junk);    /* 8 */
      o32 = SRATE;  /* sample rate */
      swab(&o32,&(u32.l),4);
      fwrite(&(u32.c[2]),sizeof(char),2,junk);    /* 8 */
      fwrite(&(u32.c[0]),sizeof(char),2,junk);    /* 8 */
      o32 = nchans;     /* number of channels */
      swab(&o32,&(u32.l),4);
      fwrite(&(u32.c[2]),sizeof(char),2,junk);    /* 8 */
      fwrite(&(u32.c[0]),sizeof(char),2,junk);    /* 8 */
      o32 = 0;    /* filler */
      swab(&o32,&(u32.l),4);
      fwrite(&u32,sizeof(unsigned),1,junk);    /* 28 */
      fwrite(&u32,sizeof(unsigned),1,junk);    /* 32 */
    }
  

/* Let's try to figure out how to generate the code */
  /* This is a bit confusing, so I'll lay it all out so I can remember it!
     The canonical code "word" for determining speeds is PARIS.  If we're
     sending code at 20 WPM, then we can send 20 copies of the word PARIS
     at perfect timing in one minute.

     The normal timing (forget farnsworth) is supposed to be:
      dit = one tick
      dah = three ticks
      space between elements of one character = one tick
      space between characters of a word = three ticks
      space between words = seven ticks.

      P = .__. = 1+1+3+1+3+1+1 ticks = 11 ticks
      A = ._ = 5 ticks
      R = ._. = 7 ticks
      I = .. = 3 ticks
      S = ... = 5 ticks

      Not counting the space between letters, then, PARIS is 31 ticks.
      Counting the space between letters as 3 ticks each, and 7 ticks at the 
      end for the word space, gives 50 ticks per PARIS at normal spacing.
      That's the meaning of the defined constant PARIS.
      */
/* At normal spacing we want to  fit FWPM instances of the word "PARIS" into 
   a minute.
   */

  /* how many ticks per minute at the given farnsworth speed (FWPM) */   
  ticks_per_minute = PARIS*FWPM;
  /* length of a tick in seconds */
  secs_per_tick = SECPM/ticks_per_minute;
  /* time between each element (dah or dit) of a character */
  intrachar_space = secs_per_tick;

  /* Now we get into the discrepency between farnsworth and character spacings.
     If we used spacing 3*intrachar_space between letters and 7*intrachar_space
     between words we'd be sending at FWPM.  But we don't want that.
     We want WPM, which means stretching out the character and word spaces some
     */

  /* How much time a paris should take at the given WPM */
  /* This is how long the total of the characters P A R I and S take,
     plus all the extra time in between to pad them out to WPM. */
  length_of_paris=SECPM/WPM;

  /* Given a character spacing of FWPM, the letters themselves take 31 ticks.
     If FWPM were equal to WPM we'd put 3 ticks between each letter, but if
     FWPM isn't equal to WPM we need to stretch out the intercharacter spacing.
     That's what we're calculating here */

  /* the total time spent in between characters is the difference between the 
   total time spent in the word and the amount of time spent in characters*/
  length_of_silence=length_of_paris-31*secs_per_tick; 

  /* Now we divide up all that time so that the time between words is always
     7/3 of the time between letters. If the space between chars is A,
     and there are four spaces plus and interword space of (7/3)A in
     one instance of PARIS, the total silence is (6+1/3)A.  Solve for A.  */
  interchar_space = length_of_silence/(6.+1./3.);
  interword_space = (7.*interchar_space)/3.0;

  /* If we space characters interchar_space apart and put interword_space
     between each word, we should fit WPM instances of PARIS into one minute

      TAH DAH!
     (note: as I generate characters, I put interchar_space at the end.  So
     when a word break comes, I then generate interword_space-interchar_space
     of additional silence)
     */

  /*
  fprintf(stderr,"A tick would be %lf\n",secs_per_tick);
  fprintf(stderr,"intrachar: %lf  interchar %lf    interword: %lf\n",intrachar_space, interchar_space,interword_space); 
  */
  /* Now we're ready to rock.  Loop around, grab lines, and send them on
     to the sound device */
  while(fgets(line,128,stdin))
    outcode(line,junk,TONE);
  fclose(junk);
}

  
/* Output a tone of given duration at given frequency at given sample rate to 
   the audio file */
out_tone(double duration,double frequency, double srate, FILE *file)
{

  int nsamples = duration*srate;
  double t,fac;
  int i;
  unsigned char foo;

  for (i=0;i<nsamples;i++)
    {
      t=((float)i)/srate;
      fac=1;
      /* make rise and fall linear instead of a step --- we ramp up to
         maximum amplitude in 50 msec, ramp down over same span.
         This helps to reduce clicking in the .au output. */
      if (t < secs_per_tick*.05)
	fac=(20.*t/secs_per_tick);
      if (t>(duration-secs_per_tick*.05))
	fac=20.*(duration-t)/secs_per_tick;
      foo=255*(fac*sin(2.0*PI*frequency*t)+1)/2;
      fwrite(&foo,sizeof(unsigned char),1,file);
      if (nchans==2)
	fwrite(&foo,sizeof(unsigned char),1,file);
    }
}

/* output silence in the same way */
out_space(double duration,double frequency, double srate, FILE *file)
{

  int nsamples = duration*srate;
  double t;
  int i;
  unsigned char foo;

  for (i=0;i<nsamples;i++)
    {
      t=((float)i)/srate;
      foo=127;
      fwrite(&foo,sizeof(unsigned char),1,file);
      if (nchans==2)
	fwrite(&foo,sizeof(unsigned char),1,file);
    }
}

