Something about using Linux encourages me to learn C. So I wrote a simple program to output morse code.
I wanted it to play the morse a bit faster but if I reduce the sample lengths any further they fail to play at all. So this looks like it's as far as it will go. Never intended for it to be practical. Just wanted to try something.
I'm not sure if attaching Linux executables is safe or advisable (it isn't on Windows systems). Incase it doesn't work or you want to compile it yourself, here is the code:
I can see things that could be improved but resisting the urge to tinker further. E.g. reveal should still work even if the audio driver fails to load and the final return doesn't need a ternary operator. Must resist urge to tinker...
Expected output:
Any number of options can be given in any order. If using audio, the morse is played slowly enough that you can learn how to recognise the sounds. If you wanted to use it to play fast morse I guess you could sample the raw output into something like Audacity and then speed it up without raising the pitch. If your device doesn't support direct sampling internally then just use a male to male audio jack between the microphone and headphone inputs. Does the job.
Edit:
Have attached an example.mp3 file to show what that the following message sounds like when sped up in Audacity:
"Example of output from the morse program"
I wanted it to play the morse a bit faster but if I reduce the sample lengths any further they fail to play at all. So this looks like it's as far as it will go. Never intended for it to be practical. Just wanted to try something.
I'm not sure if attaching Linux executables is safe or advisable (it isn't on Windows systems). Incase it doesn't work or you want to compile it yourself, here is the code:
Code:
// Compilation:
// gcc -o morse morse.c -lao -ldl -lm
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <ao/ao.h>
#include <math.h>
#define bool char
#define true 1
#define false 0
#define dot 25200 // length of dot sound
#define dash 44100 // length of dash sound
#define delay 200000 // delay in microseconds between dots and dashes in morse code
const char* morse[] = {
".-", // A
"-...", // B
"-.-.", // C
"-..", // D
".", // E
"..-.", // F
"--.", // G
"....", // H
"..", // I
".---", // J
"-.-", // K
".-..", // L
"--", // M
"-.", // N
"---", // O
".--.", // P
"--.-", // Q
".-.", // R
"...", // S
"-", // T
"..-", // U
"...-", // V
".--", // W
"-..-", // X
"-.--", // Y
"--..", // Z
"-----", // 0
".----", // 1
"..---", // 2
"...--", // 3
"....-", // 4
".....", // 5
"-....", // 6
"--...", // 7
"---..", // 8
"----." // 9
};
void dotdash(char* code, ao_device* device, char* buffer, char audio, char reveal, char suppress)
{
// exit early if there's nothing to do
if (!audio && suppress) return;
// play dot and dash sounds for each dot and dash in code
for (int i=0; i < strlen(code); i++) {
if (audio) {
switch (code[i]) {
case '.': ao_play(device, buffer, dot); break;
case '-': ao_play(device, buffer, dash); break;
// ignore other characters
}
}
if (reveal && !suppress) {
printf("%c", code[i]);
fflush(stdout);
}
usleep(delay);
}
if (reveal && !suppress) printf(" ");
usleep(delay);
}
int main(int argc, char** argv)
{
ao_device* device;
ao_sample_format format;
char* buffer;
float freq = 1000.0; // the pitch of the sound
int default_driver, buf_size, sample, i;
char ascii, opt, optcount=0;
bool audio=false, reveal=false, suppress=false;
// read options
for (i=1; i<argc; i++) {
if (argv[i][0] == '-' && strlen(argv[i]) > 1) {
opt = (argv[i][1] >= 65 && argv[i][1] <= 90) ? argv[i][1] + 32 : argv[i][1]; // convert to lower case
switch (opt) {
case 'a': audio = true; optcount++; break;
case 'r': reveal = true; optcount++; break;
case 's': suppress=true; optcount++; break;
// ignore all unrecognised options
}
}
}
// the final argument is assumed to be the text to be translated
// initialise default audio driver
if (audio) {
ao_initialize();
default_driver = ao_default_driver_id();
memset(&format, 0, sizeof(format));
format.bits = 16;
format.channels = 2;
format.rate = 44100;
format.byte_format = AO_FMT_LITTLE;
// open audio driver
device = ao_open_live(default_driver, &format, NULL);
if (device == NULL) {
if (!suppress) printf("Error opening audio device.\n");
fprintf(stderr, "Error opening audio device.\n");
} else {
// create morse sound
buf_size = format.bits/8 * format.channels * format.rate;
buffer = calloc(buf_size, sizeof(char));
for (i = 0; i < format.rate; i++) {
sample = (int)(0.75 * 32768.0 * sin(2 * M_PI * freq * ((float) i/format.rate)));
// left and right channel
buffer[4*i] = buffer[4*i+2] = sample & 0xff;
buffer[4*i+1] = buffer[4*i+3] = (sample >> 8) & 0xff;
}
}
}
// disable audio and reveal options if audio failed to initialise
if (device == NULL) {
audio = false;
reveal = false;
}
// usage
if (argc == 1) {
printf("Usage: morse [opt] [text]\n\n");
printf(" -a output audio (requires libao): sudo apt-get install libao-dev\n");
printf(" -r reveal morse as audio is played\n");
printf(" -s suppress text output\n\n");
printf("Translates to international morse code. Put text in quotes if including spaces.\n");
printf("Only alphanumeric characters translated. All other characters are ignored.\n");
// output text translation
} else if (argc > optcount + 1) {
// regardless of audio, if there is no reveal just print the morse quickly
if (!reveal && !suppress) {
for (i=0; i < strlen(argv[argc-1]); i++) {
ascii = argv[argc-1][i];
if (ascii == 32) {
// spaces
printf(" ");
} else if (ascii >= 48 && ascii <= 57) {
// numbers 0 to 9...
printf("%s", morse[ascii-48+26]);
} else if ( (ascii >= 65 && ascii <= 90) || (ascii >=97 && ascii <= 122) ) {
// letters a to z...
if ( ascii >= 97 ) ascii -= 32;
printf("%s", morse[ascii-65]);
}
if (ascii != 32 ) printf(" ");
}
fflush(stdout);
}
// but if there is audio or the morse is being revealed in morse timing then step though it
if (audio || reveal) {
for (i=0; i < strlen(argv[argc-1]); i++) {
ascii = argv[argc-1][i];
if (ascii == 32) {
// spaces
if (!suppress) printf(" ");
usleep(delay*2);
} else if (ascii >= 48 && ascii <= 57) {
// numbers 0 to 9...
dotdash((char*) morse[ascii-48+26], device, buffer, audio, reveal, suppress);
} else if ( (ascii >= 65 && ascii <= 90) || (ascii >=97 && ascii <= 122) ) {
// letters a to z...
if ( ascii >= 97 ) ascii -= 32;
dotdash((char*) morse[ascii-65], device, buffer, audio, reveal, suppress);
}
}
}
if (!suppress) printf("\n");
}
// close and shutdown
if (audio) {
ao_close(device);
ao_shutdown();
}
return (device == NULL) ? 0 : 1;
}
I can see things that could be improved but resisting the urge to tinker further. E.g. reveal should still work even if the audio driver fails to load and the final return doesn't need a ternary operator. Must resist urge to tinker...
Expected output:
Code:
Usage: morse [opt] [text]
-a output audio (requires libao): sudo apt-get install libao-dev
-r reveal morse as audio is played
-s suppress text output
Translates to international morse code. Put text in quotes if including spaces.
Only alphanumeric characters translated. All other characters are ignored.
Any number of options can be given in any order. If using audio, the morse is played slowly enough that you can learn how to recognise the sounds. If you wanted to use it to play fast morse I guess you could sample the raw output into something like Audacity and then speed it up without raising the pitch. If your device doesn't support direct sampling internally then just use a male to male audio jack between the microphone and headphone inputs. Does the job.
Edit:
Have attached an example.mp3 file to show what that the following message sounds like when sped up in Audacity:
"Example of output from the morse program"