Morse sound file generator
The Scilab 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:
- Calculating Morse Code Speed
- A standard for morse timing using the Farnworth technique
- An Intuitive Explanation of CW Bandwidth
- Spectral Analysis of a CW keying pulse
- Video - Technique Of Hand Sending (1944)
- Video - INTERNATIONAL MORSE CODE, HAND SENDING
Here are the resulting graphs, bandwidth analysis and sound.
NO SMOOTHING | SINE | GAUSSIAN |
no smoothing | ||
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
//
// Copyright (C) 2012 Christophe DAVID (ON6ZQ)
// http://www.on6zq.be/Scilab/MorseSoundFileGenerator
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License version 2 as published
// by the Free Software Foundation (http://www.gnu.org/licenses/gpl.html).
//
//////////////////////////////////////////////////////////////////////////////
//
// This program was last tested with Scilab 5.5
// http://www.scilab.org
//
//////////////////////////////////////////////////////////////////////////////
// A semicolon at the end of an instruction causes the result not to be displayed.
errclear; // clear all errors
clear; // kill variables
clearglobal // kill global variables
clc; // clear command window
tohome; // move the cursor to the upper left corner of the Command Window
clf; // clear or reset the current graphic figure (window) to default values
// stacksize('max'); // increase the size of this stack to the maximum.
stacksize(1e8); //stacksize('max') does not appear to work on my system ;-(
//////////////////////////////////////////////////////////////////////////////
// text = "PARIS PARIS PARIS PARIS PARIS PARIS PARIS PARIS PARIS PARIS PARIS PARIS "; // 1 minute at 12 WPM
// text = "The Quick Brown Fox Jumps Over The Lazy Dog 1234567890"; // all alphanumerical characters
text = "73 de ON6ZQ";
WordsPerMinute = 20;
OutputDirectory = '/home/cda/test/';
ToneFrequency = 600; // hertz
SamplingRate = 22500; // samples per second
BitDepth = 16; // 8, 16, 24, 32 bits. number of bits each sample
EnvelopeSmoothingMethod = 2 // 0: none, 1: cosine, 2: Gaussian
RiseTime = 0.010 // second
FallTime = 0.010 // second
//////////////////////////////////////////////////////////////////////////////
function [OutputMatrix] = TextToMorse(InputString)
morse = [ // ASCII values and meanings
'', // 1
'', // 2
'', // 3
'', // 4
'', // 5
'', // 6
'', // 7
'', // 8
'', // 9
'', // 10
'', // 11
'', // 12
'', // 13
'', // 14
'', // 15
'', // 16
'', // 17
'', // 18
'', // 19
'', // 20
'', // 21
'', // 22
'', // 23
'', // 24
'', // 25
'', // 26
'', // 27
'', // 28
'', // 29
'', // 30
'', // 31
'', // 32
'', // 33 !
'', // 34 "
'', // 35 #
'', // 36 $
'', // 37 %
'', // 38 &
'', // 39 '
'', // 40 (
'', // 41 )
'', // 42 *
'', // 43 +
'', // 44 ,
'', // 45 -
'', // 46 .
'', // 47 /
'-----', // 48 0
'.----', // 49 1
'..---', // 50 2
'...--', // 51 3
'....-', // 52 4
'.....', // 53 5
'-....', // 54 6
'--...', // 55 7
'---..', // 56 8
'----.', // 57 9
'', // 58 :
'', // 59 ;
'', // 60 <
'', // 61 =
'', // 62 >
'', // 63 ?
'', // 64 @
'.-', // 65 A
'-...', // 66 B
'-.-.', // 67 C
'-..', // 68 D
'.', // 69 E
'..-.', // 70 F
'--.', // 71 G
'....', // 72 H
'..', // 73 I
'.---', // 74 J
'-.-', // 75 K
'.-..', // 76 L
'--', // 77 M
'-.', // 78 N
'---', // 79 O
'.--.', // 80 P
'--.-', // 81 Q
'.-.', // 82 R
'...', // 83 S
'-', // 84 T
'..-', // 85 U
'...-', // 86 V
'.--', // 87 W
'-..-', // 88 X
'-.--', // 89 Y
'--..', // 90 Z
'', // 91 [
'', // 92 \
'', // 93 ]
'', // 94 ^
'', // 95 _
'', // 96 `
'.-', // 97 a
'-...', // 98 b
'-.-.', // 99 c
'-..', // 100 d
'.', // 101 e
'..-.', // 102 f
'--.', // 103 g
'....', // 104 h
'..', // 105 i
'.---', // 106 j
'-.-', // 107 k
'.-..', // 108 l
'--', // 109 m
'-.', // 110 n
'---', // 111 o
'.--.', // 112 p
'--.-', // 113 q
'.-.', // 114 r
'...', // 115 s
'-', // 116 t
'..-', // 117 u
'...-', // 118 v
'.--', // 119 w
'-..-', // 120 x
'-.--', // 121 y
'--..', // 122 z
'', // 123 {
'', // 124 |
'', // 125 }
'', // 126 ~
'', // 127
];
OutputMatrix = [];
M = asciimat(InputString);
for i = 1 : length(M)
// printf("\n%03d %s %s", M(i), ascii(M(i)), morse(M(i)));
OutputMatrix(i) = morse(M(i));
end
endfunction
//////////////////////////////////////////////////////////////////////////////
function f = GaussDistribution(x, mu, s)
p1 = -.5 * ((x - mu) / s) .^ 2;
p2 = (s * sqrt(2 * %pi));
f = exp(p1) ./ p2;
endfunction
//////////////////////////////////////////////////////////////////////////////
// http://www.kent-engineers.com/codespeed.htm
// http://www.arrl.org/files/file/Technology/x9004008.pdf
UnitsPerDit = 1;
UnitsPerDah = 3;
UnitsBetweenElements = 1;
UnitsBetweenCharacters = 3;
UnitsBetweenWords = 7;
UnitDuration = 1.2 / WordsPerMinute; // seconds
InterElements = zeros(1, (UnitsBetweenElements * UnitDuration * SamplingRate))
InterCharacters = zeros(1, (UnitsBetweenCharacters * UnitDuration * SamplingRate))
InterWords = zeros(1, (UnitsBetweenWords * UnitDuration * SamplingRate))
Dit = 0 : 1 : (UnitsPerDit * UnitDuration * SamplingRate);
Dah = 0 : 1 : (UnitsPerDah * UnitDuration * SamplingRate);
Dit = sind((Dit / SamplingRate) * ToneFrequency * 360);
Dah = sind((Dah / SamplingRate) * ToneFrequency * 360);
if EnvelopeSmoothingMethod > 0 then
// http://www.w8ji.com/cw_bandwidth_described.htm
// http://fermi.la.asu.edu/w9cf/articles/click/index.html
SmoothRise = 1 : 1 : (RiseTime * SamplingRate);
SmoothFall = (FallTime * SamplingRate) : -1 : 1;
select EnvelopeSmoothingMethod
case 1 then
SmoothRise = sind((SmoothRise / SamplingRate) * (1 / RiseTime) * 90);
SmoothFall = sind((SmoothFall / SamplingRate) * (1 / FallTime) * 90);
case 2 then
mu = RiseTime * SamplingRate;
s = (RiseTime * SamplingRate) / 2;
SmoothRise = GaussDistribution(SmoothRise, mu, s);
SmoothFall = GaussDistribution(SmoothFall, mu, s);
SmoothRise = SmoothRise * (1 / max(SmoothRise));
SmoothFall = SmoothFall * (1 / max(SmoothFall));
else abort;
end
plot(SmoothRise, 'g')
plot(SmoothFall, 'r')
legend(['SmoothRise';'SmoothFall']);
FileName = sprintf("%sMorseSoundFileGenerator_Envelope_%da.jpg", OutputDirectory, EnvelopeSmoothingMethod);
xs2jpg(gcf(), FileName); // Export to a JPG file.
input('Check the graphic window then press enter to continue.')
clf;
[_rows, _columns] = size(SmoothRise);
Dit(1:_columns) = Dit(1:_columns) .* SmoothRise;
Dah(1:_columns) = Dah(1:_columns) .* SmoothRise;
[_rows, _columns] = size(SmoothFall);
Dit($ - _columns + 1 : $) = Dit($ - _columns + 1 : $) .* SmoothFall;
Dah($ - _columns + 1 : $) = Dah($ - _columns + 1 : $) .* SmoothFall;
end
plot(Dit)
FileName = sprintf("%sMorseSoundFileGenerator_Dit_%db.jpg", OutputDirectory, EnvelopeSmoothingMethod);
xs2jpg(gcf(), FileName); // Export to a JPG file.
input('Check the graphic window then press enter to continue.')
clf;
MorseText = TextToMorse(text)
[_rows, _columns] = size(MorseText);
SoundData = [];
for i = 1 : _rows
// printf("\n%s", MorseText(i));
if MorseText(i) == '' then
SoundData = [SoundData, InterWords];
else
for j = 1 : length(MorseText(i))
// printf("\n%03d %03d %s", i, j, part(MorseText(i), j:j));
select part(MorseText(i), j:j),
case '-' then SoundData = [SoundData, Dah],
case '.' then SoundData = [SoundData, Dit],
else printf("\nCannot send %s", part(MorseText(i), j:j));
end
SoundData = [SoundData, InterElements]
end
SoundData = [SoundData, InterCharacters]
end
end
analyze(SoundData, ToneFrequency - 300, ToneFrequency + 300, SamplingRate, 8192);
FileName = sprintf("%sMorseSoundFileGenerator_Analysis_%dc.jpg", OutputDirectory, EnvelopeSmoothingMethod);
xs2jpg(gcf(), FileName); // Export to a JPG file.
// wavwrite expects data arranged with one channel per column
FileName = sprintf("%sMorseSoundFileGenerator_%d.wav", OutputDirectory, EnvelopeSmoothingMethod);
wavwrite(SoundData', SamplingRate, BitDepth, FileName);
printf('Check the %s directory.', OutputDirectory)
//////////////////////////////////////////////////////////////////////////////
//
// Morse sound file generator
//
// Generate a .wav file playing user text in morse
//
// Copyright (C) 2012 Christophe DAVID (ON6ZQ)
// http://www.on6zq.be/Scilab/MorseSoundFileGenerator
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License version 2 as published
// by the Free Software Foundation (http://www.gnu.org/licenses/gpl.html).
//
//////////////////////////////////////////////////////////////////////////////
//
// This program was last tested with Scilab 5.5
// http://www.scilab.org
//
//////////////////////////////////////////////////////////////////////////////
// A semicolon at the end of an instruction causes the result not to be displayed.
errclear; // clear all errors
clear; // kill variables
clearglobal // kill global variables
clc; // clear command window
tohome; // move the cursor to the upper left corner of the Command Window
clf; // clear or reset the current graphic figure (window) to default values
// stacksize('max'); // increase the size of this stack to the maximum.
stacksize(1e8); //stacksize('max') does not appear to work on my system ;-(
//////////////////////////////////////////////////////////////////////////////
// text = "PARIS PARIS PARIS PARIS PARIS PARIS PARIS PARIS PARIS PARIS PARIS PARIS "; // 1 minute at 12 WPM
// text = "The Quick Brown Fox Jumps Over The Lazy Dog 1234567890"; // all alphanumerical characters
text = "73 de ON6ZQ";
WordsPerMinute = 20;
OutputDirectory = '/home/cda/test/';
ToneFrequency = 600; // hertz
SamplingRate = 22500; // samples per second
BitDepth = 16; // 8, 16, 24, 32 bits. number of bits each sample
EnvelopeSmoothingMethod = 2 // 0: none, 1: cosine, 2: Gaussian
RiseTime = 0.010 // second
FallTime = 0.010 // second
//////////////////////////////////////////////////////////////////////////////
function [OutputMatrix] = TextToMorse(InputString)
morse = [ // ASCII values and meanings
'', // 1
'', // 2
'', // 3
'', // 4
'', // 5
'', // 6
'', // 7
'', // 8
'', // 9
'', // 10
'', // 11
'', // 12
'', // 13
'', // 14
'', // 15
'', // 16
'', // 17
'', // 18
'', // 19
'', // 20
'', // 21
'', // 22
'', // 23
'', // 24
'', // 25
'', // 26
'', // 27
'', // 28
'', // 29
'', // 30
'', // 31
'', // 32
'', // 33 !
'', // 34 "
'', // 35 #
'', // 36 $
'', // 37 %
'', // 38 &
'', // 39 '
'', // 40 (
'', // 41 )
'', // 42 *
'', // 43 +
'', // 44 ,
'', // 45 -
'', // 46 .
'', // 47 /
'-----', // 48 0
'.----', // 49 1
'..---', // 50 2
'...--', // 51 3
'....-', // 52 4
'.....', // 53 5
'-....', // 54 6
'--...', // 55 7
'---..', // 56 8
'----.', // 57 9
'', // 58 :
'', // 59 ;
'', // 60 <
'', // 61 =
'', // 62 >
'', // 63 ?
'', // 64 @
'.-', // 65 A
'-...', // 66 B
'-.-.', // 67 C
'-..', // 68 D
'.', // 69 E
'..-.', // 70 F
'--.', // 71 G
'....', // 72 H
'..', // 73 I
'.---', // 74 J
'-.-', // 75 K
'.-..', // 76 L
'--', // 77 M
'-.', // 78 N
'---', // 79 O
'.--.', // 80 P
'--.-', // 81 Q
'.-.', // 82 R
'...', // 83 S
'-', // 84 T
'..-', // 85 U
'...-', // 86 V
'.--', // 87 W
'-..-', // 88 X
'-.--', // 89 Y
'--..', // 90 Z
'', // 91 [
'', // 92 \
'', // 93 ]
'', // 94 ^
'', // 95 _
'', // 96 `
'.-', // 97 a
'-...', // 98 b
'-.-.', // 99 c
'-..', // 100 d
'.', // 101 e
'..-.', // 102 f
'--.', // 103 g
'....', // 104 h
'..', // 105 i
'.---', // 106 j
'-.-', // 107 k
'.-..', // 108 l
'--', // 109 m
'-.', // 110 n
'---', // 111 o
'.--.', // 112 p
'--.-', // 113 q
'.-.', // 114 r
'...', // 115 s
'-', // 116 t
'..-', // 117 u
'...-', // 118 v
'.--', // 119 w
'-..-', // 120 x
'-.--', // 121 y
'--..', // 122 z
'', // 123 {
'', // 124 |
'', // 125 }
'', // 126 ~
'', // 127
];
OutputMatrix = [];
M = asciimat(InputString);
for i = 1 : length(M)
// printf("\n%03d %s %s", M(i), ascii(M(i)), morse(M(i)));
OutputMatrix(i) = morse(M(i));
end
endfunction
//////////////////////////////////////////////////////////////////////////////
function f = GaussDistribution(x, mu, s)
p1 = -.5 * ((x - mu) / s) .^ 2;
p2 = (s * sqrt(2 * %pi));
f = exp(p1) ./ p2;
endfunction
//////////////////////////////////////////////////////////////////////////////
// http://www.kent-engineers.com/codespeed.htm
// http://www.arrl.org/files/file/Technology/x9004008.pdf
UnitsPerDit = 1;
UnitsPerDah = 3;
UnitsBetweenElements = 1;
UnitsBetweenCharacters = 3;
UnitsBetweenWords = 7;
UnitDuration = 1.2 / WordsPerMinute; // seconds
InterElements = zeros(1, (UnitsBetweenElements * UnitDuration * SamplingRate))
InterCharacters = zeros(1, (UnitsBetweenCharacters * UnitDuration * SamplingRate))
InterWords = zeros(1, (UnitsBetweenWords * UnitDuration * SamplingRate))
Dit = 0 : 1 : (UnitsPerDit * UnitDuration * SamplingRate);
Dah = 0 : 1 : (UnitsPerDah * UnitDuration * SamplingRate);
Dit = sind((Dit / SamplingRate) * ToneFrequency * 360);
Dah = sind((Dah / SamplingRate) * ToneFrequency * 360);
if EnvelopeSmoothingMethod > 0 then
// http://www.w8ji.com/cw_bandwidth_described.htm
// http://fermi.la.asu.edu/w9cf/articles/click/index.html
SmoothRise = 1 : 1 : (RiseTime * SamplingRate);
SmoothFall = (FallTime * SamplingRate) : -1 : 1;
select EnvelopeSmoothingMethod
case 1 then
SmoothRise = sind((SmoothRise / SamplingRate) * (1 / RiseTime) * 90);
SmoothFall = sind((SmoothFall / SamplingRate) * (1 / FallTime) * 90);
case 2 then
mu = RiseTime * SamplingRate;
s = (RiseTime * SamplingRate) / 2;
SmoothRise = GaussDistribution(SmoothRise, mu, s);
SmoothFall = GaussDistribution(SmoothFall, mu, s);
SmoothRise = SmoothRise * (1 / max(SmoothRise));
SmoothFall = SmoothFall * (1 / max(SmoothFall));
else abort;
end
plot(SmoothRise, 'g')
plot(SmoothFall, 'r')
legend(['SmoothRise';'SmoothFall']);
FileName = sprintf("%sMorseSoundFileGenerator_Envelope_%da.jpg", OutputDirectory, EnvelopeSmoothingMethod);
xs2jpg(gcf(), FileName); // Export to a JPG file.
input('Check the graphic window then press enter to continue.')
clf;
[_rows, _columns] = size(SmoothRise);
Dit(1:_columns) = Dit(1:_columns) .* SmoothRise;
Dah(1:_columns) = Dah(1:_columns) .* SmoothRise;
[_rows, _columns] = size(SmoothFall);
Dit($ - _columns + 1 : $) = Dit($ - _columns + 1 : $) .* SmoothFall;
Dah($ - _columns + 1 : $) = Dah($ - _columns + 1 : $) .* SmoothFall;
end
plot(Dit)
FileName = sprintf("%sMorseSoundFileGenerator_Dit_%db.jpg", OutputDirectory, EnvelopeSmoothingMethod);
xs2jpg(gcf(), FileName); // Export to a JPG file.
input('Check the graphic window then press enter to continue.')
clf;
MorseText = TextToMorse(text)
[_rows, _columns] = size(MorseText);
SoundData = [];
for i = 1 : _rows
// printf("\n%s", MorseText(i));
if MorseText(i) == '' then
SoundData = [SoundData, InterWords];
else
for j = 1 : length(MorseText(i))
// printf("\n%03d %03d %s", i, j, part(MorseText(i), j:j));
select part(MorseText(i), j:j),
case '-' then SoundData = [SoundData, Dah],
case '.' then SoundData = [SoundData, Dit],
else printf("\nCannot send %s", part(MorseText(i), j:j));
end
SoundData = [SoundData, InterElements]
end
SoundData = [SoundData, InterCharacters]
end
end
analyze(SoundData, ToneFrequency - 300, ToneFrequency + 300, SamplingRate, 8192);
FileName = sprintf("%sMorseSoundFileGenerator_Analysis_%dc.jpg", OutputDirectory, EnvelopeSmoothingMethod);
xs2jpg(gcf(), FileName); // Export to a JPG file.
// wavwrite expects data arranged with one channel per column
FileName = sprintf("%sMorseSoundFileGenerator_%d.wav", OutputDirectory, EnvelopeSmoothingMethod);
wavwrite(SoundData', SamplingRate, BitDepth, FileName);
printf('Check the %s directory.', OutputDirectory)
//////////////////////////////////////////////////////////////////////////////