You are Here:
Linux Lite 3.8 Final has been released. See the Release Announcements Section.




Using Linux has inspired me to learn C and here's my MORSE program

Author (Read 375 times)

0 Members and 1 Guest are viewing this topic.

 

alowe

  • New to Forums
  • *
  • 10
    Posts
  • Country: gb
  • Reputation: 2
  • Linux Lite Member

  • Linux Lite: 3.6 32bit

  • CPU: Core 2-duo 1.73MHz

  • MEMORY: 1Gb
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:

Code: [Select]
// 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: [Select]
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"
Last Edit: April 02, 2018, 02:06:00 PM by alowe
 


Re: Using Linux has inspired me to learn C and here's my MORSE program
« Reply #1 on: April 03, 2018, 06:20:46 AM »
 

bitsnpcs

  • Platinum Level Poster
  • **********
  • 2169
    Posts
  • Country: 00
  • Reputation: 230

  • Linux Lite: 3.2 64bit

  • CPU: i7-6700 3.40Ghz (4Ghz Turbo) x8 Cores

  • MEMORY: 16Gb

  • VIDEO CARD: Intel Skylake 530 HD
@bluzeo  was looking for Morse software recently.
LIYF ~ live it
 

Re: Using Linux has inspired me to learn C and here's my MORSE program
« Reply #2 on: April 03, 2018, 06:53:32 PM »
 

Wirezfree

  • PayPal Supporter
  • Platinum Level Poster
  • *****
  • 1479
    Posts
  • Country: gb
  • Reputation: 404
  • Linux Lite "Advocate"

  • Linux Lite: 2.8 64bit

  • CPU: i7-4790S

  • MEMORY: 16Gb

  • VIDEO CARD: Intel HD4600 (Integrated)
Maybe us Radio Amatuers should start a "LL NET" :)

73 - G8LIY
Upgrades WIP 2.6 to 2.8 - (6 X 2.6 to 2.8 completed on: 20/02/16 All O.K )
Linux Lite 3.0 Humming on a ASRock N3070 Mobo ~ btrfs RAID 10 Install on 4 Disks :)

Computers Early days:
ZX Spectrum(1982) , HP-150 MS-DOS(1983) , Amstrad CPC464(1984) ,  BBC Micro B+64(1985) , My First PC HP-Vectra(1987)
 

Re: Using Linux has inspired me to learn C and here's my MORSE program
« Reply #3 on: April 04, 2018, 11:09:24 AM »
 

alowe

  • New to Forums
  • *
  • 10
    Posts
  • Country: gb
  • Reputation: 2
  • Linux Lite Member

  • Linux Lite: 3.6 32bit

  • CPU: Core 2-duo 1.73MHz

  • MEMORY: 1Gb
I've just noticed some more "bugs" in the program. There should be a constant delay between dots and dashes in the audio output, but looking at the sample this is not the case.

I think this is because the sample sent to libao does not pause execution inside the C program.

There are two solutions to this:
1) Calculate an additional time to wait and add it to the delay sent to usleep(); e.g. usleep(delay + (<dot or dash> * 1000));
2) Have a loop that polls libao for some finished state.

Personally I like solution 1) because, it's easier to implement, requires less reading about libao and relies less on the behaviour of software outside of the morse program (e.g. if libao never returns finish, what to do? complicated). Then again solution 1) is also complicated because the time it takes to play a dot or dash is based on other things like the sample rate.

There's also something not right about the sound recorded by Audacity. This shows that at the end of the sample the output is left high when it should be left low, which looks ugly and I think makes "ticking" more likely if/when the sample is interrupted. Since the human ear (and computer speaker) function by changes in movement from down to up and up to down, this makes no difference to the sound you hear. It just looks ugly.

There's also a slight issue if the C program is forcefully halted by something like a Ctrl-C. Even though I've put code to cleanly close and shutdown the ao device, this will not get executed if the program is halted externally. The OS might be clever and do this upon detecting the executable that opened the device has been forcefully halted. It may not. I'm not sure there is a C solution to this (i.e. a function that gets executed upon a forceful halt). Maybe I could fork off a TSR process in memory that monitors the existence of the morse executable process and cleanly shuts down the ao device if it stops running. That's complicated and may introduce further problems. I've tested forcing the program to halt without closing the ao device and this doesn't effect sound output from other programs or future executions of morse. So this might just be me worrying about nothing.

If I ever go back to improve this morse program I will fix all of these things (including the tweaking).
Last Edit: April 04, 2018, 11:21:45 AM by alowe
 


Tags:
 


Linux Lite 3.8 Final has been released. See the Release Announcements Section.