Morse sound file generator

The GNU Octave program below generates a sound file (.wav) with the morse code representing a given text.

Three modes are implemented to "smooth the envelope": none, sine and gaussian.

Related information:

Here are the resulting graphs, bandwidth analysis and sound.




NO SMOOTHINGSINEGAUSSIAN
no smoothing
Click to enlarge
Click to enlarge
Click to enlarge
Click to enlarge
Click to enlarge
Click to enlarge
Click to enlarge
Click to enlarge
Corresponding sound file

Lots of "clicks"...

Corresponding sound file

Smoother...

Corresponding sound file

Smoother...

##############################################################################
#
# Morse sound file generator
#
# Generate a .wav file playing user text in Morse code
#
# Copyright (C) 2012-2025 Christophe DAVID ( ON6ZQ | AC6ZQ )
#
##############################################################################

clear;
clc;
close all;

# User-configurable parameters
text = "73 de ON6ZQ";
WordsPerMinute = 20;
OutputDirectory = '';
ToneFrequency = 600;       # in Hertz
SamplingRate = 22500;      # samples per second
BitDepth = 16;             # 8, 16, 24, 32 bits
EnvelopeSmoothingMethod = 2; # 0: none, 1: cosine, 2: Gaussian
RiseTime = 0.010;          # seconds
FallTime = 0.010;          # seconds

##############################################################################
# Convert input text to Morse code
function OutputMatrix = TextToMorse(InputString)
    morse = cell(1, 128);

    morse{48+1} = '-----'; morse{49+1} = '.----'; morse{50+1} = '..---';
    morse{51+1} = '...--'; morse{52+1} = '....-'; morse{53+1} = '.....';
    morse{54+1} = '-....'; morse{55+1} = '--...'; morse{56+1} = '---..';
    morse{57+1} = '----.';

    letters = {
        '.-', '-...', '-.-.', '-..', '.', '..-.', '--.', '....', '..', '.---', ...
        '-.-', '.-..', '--', '-.', '---', '.--.', '--.-', '.-.', '...', '-', ...
        '..-', '...-', '.--', '-..-', '-.--', '--..'
    };

    for k = 65:90
        morse{k+1} = letters{k-64};
        morse{k+33} = letters{k-64};  # lower-case
    end

    InputString = char(InputString);
    OutputMatrix = cellstr("");
    M = double(InputString);

    for i = 1:length(M)
        if M(i) <= length(morse) && ~isempty(morse{M(i)+1})
            OutputMatrix{i} = morse{M(i)+1};
        else
            OutputMatrix{i} = "";
        end
    end
endfunction

##############################################################################
# Gaussian envelope function
function f = GaussDistribution(x, mu, s)
    f = exp(-0.5 * ((x - mu) ./ s).^2) ./ (s * sqrt(2*pi));
endfunction

##############################################################################
# Morse timing units
UnitsPerDit = 1;
UnitsPerDah = 3;
UnitsBetweenElements = 1;
UnitsBetweenCharacters = 3;
UnitsBetweenWords = 7;
UnitDuration = 1.2 / WordsPerMinute;

InterElements = zeros(1, round(UnitsBetweenElements * UnitDuration * SamplingRate));
InterCharacters = zeros(1, round(UnitsBetweenCharacters * UnitDuration * SamplingRate));
InterWords = zeros(1, round(UnitsBetweenWords * UnitDuration * SamplingRate));

Dit = 0:1:(UnitsPerDit * UnitDuration * SamplingRate);
Dah = 0:1:(UnitsPerDah * UnitDuration * SamplingRate);
Dit = sin((Dit / SamplingRate) * 2 * pi * ToneFrequency);
Dah = sin((Dah / SamplingRate) * 2 * pi * ToneFrequency);

# Apply envelope smoothing
if EnvelopeSmoothingMethod > 0
    SmoothRise = 1:(RiseTime * SamplingRate);
    SmoothFall = (FallTime * SamplingRate):-1:1;

    switch EnvelopeSmoothingMethod
        case 1  # Cosine
            SmoothRise = sin((SmoothRise / SamplingRate) * (pi/(2 * RiseTime)));
            SmoothFall = sin((SmoothFall / SamplingRate) * (pi/(2 * FallTime)));
        case 2  # Gaussian
            mu = RiseTime * SamplingRate;
            s = mu / 2;
            SmoothRise = GaussDistribution(SmoothRise, mu, s);
            SmoothFall = GaussDistribution(SmoothFall, mu, s);
            SmoothRise = SmoothRise / max(SmoothRise);
            SmoothFall = SmoothFall / max(SmoothFall);
        otherwise
            error("Unsupported smoothing method.");
    end

    figure; plot(SmoothRise, 'g'); hold on; plot(SmoothFall, 'r'); legend("SmoothRise", "SmoothFall");
    FileName = sprintf("%sMorseSoundFileGenerator_Envelope_%da.jpg", OutputDirectory, EnvelopeSmoothingMethod);
    print(FileName, "-djpg");

    printf("Envelope smoothing plot saved to %s\n", FileName);
    input("Press Enter to continue...\n", "s");
    clf;

    Dit(1:length(SmoothRise)) = Dit(1:length(SmoothRise)) .* SmoothRise;
    Dah(1:length(SmoothRise)) = Dah(1:length(SmoothRise)) .* SmoothRise;

    Dit(end-length(SmoothFall)+1:end) = Dit(end-length(SmoothFall)+1:end) .* SmoothFall;
    Dah(end-length(SmoothFall)+1:end) = Dah(end-length(SmoothFall)+1:end) .* SmoothFall;
end

figure; plot(Dit);
FileName = sprintf("%sMorseSoundFileGenerator_Dit_%db.jpg", OutputDirectory, EnvelopeSmoothingMethod);
print(FileName, "-djpg");

printf("Modified 'Dit' waveform plot saved to %s\n", FileName);
input("Press Enter to continue...\n", "s");
clf;

##############################################################################
# Generate the Morse signal
MorseText = TextToMorse(text);
SoundData = [];

for i = 1:length(MorseText)
    symbol = MorseText{i};
    if isempty(symbol)
        SoundData = [SoundData, InterWords];
    else
        for j = 1:length(symbol)
            switch symbol(j)
                case '-'
                    SoundData = [SoundData, Dah];
                case '.'
                    SoundData = [SoundData, Dit];
                otherwise
                    printf("Cannot send: %s\n", symbol(j));
            end
            SoundData = [SoundData, InterElements];
        end
        SoundData = [SoundData, InterCharacters];
    end
end

##############################################################################
# Save to WAV file
FileName = sprintf("%sMorseSoundFileGenerator_%d.wav", OutputDirectory, EnvelopeSmoothingMethod);
audiowrite(FileName, SoundData', SamplingRate, 'BitsPerSample', BitDepth);

printf("\nMorse code audio saved to: %s\n", FileName);