/*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 #include #include #include #include #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(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