From 8e3e24de7541e3ef017d9971e3a9eee0ad430695 Mon Sep 17 00:00:00 2001 From: jeremiah Date: Tue, 28 May 2013 09:53:01 -0700 Subject: [PATCH] Initial commit --- CWLibrary.sln | 25 ++++ CWLibrary/CWLibrary.csproj | 55 +++++++++ CWLibrary/Characters.cs | 57 +++++++++ CWLibrary/DataChunk.cs | 32 +++++ CWLibrary/FormatChunk.cs | 45 +++++++ CWLibrary/HeaderChunk.cs | 36 ++++++ CWLibrary/Properties/AssemblyInfo.cs | 30 +++++ CWLibrary/TextToMorse.cs | 171 +++++++++++++++++++++++++++ CWLibrary/WaveChunk.cs | 15 +++ README.txt | 78 ++++++++++++ 10 files changed, 544 insertions(+) create mode 100644 CWLibrary.sln create mode 100644 CWLibrary/CWLibrary.csproj create mode 100644 CWLibrary/Characters.cs create mode 100644 CWLibrary/DataChunk.cs create mode 100644 CWLibrary/FormatChunk.cs create mode 100644 CWLibrary/HeaderChunk.cs create mode 100644 CWLibrary/Properties/AssemblyInfo.cs create mode 100644 CWLibrary/TextToMorse.cs create mode 100644 CWLibrary/WaveChunk.cs create mode 100644 README.txt diff --git a/CWLibrary.sln b/CWLibrary.sln new file mode 100644 index 0000000..90fcd68 --- /dev/null +++ b/CWLibrary.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CWLibrary", "CWLibrary\CWLibrary.csproj", "{BB1D2263-20CB-4934-9F2B-5A2D1BDAF165}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3375E244-5EE5-45CE-B449-D3913B5CD78D}" + ProjectSection(SolutionItems) = preProject + README.txt = README.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BB1D2263-20CB-4934-9F2B-5A2D1BDAF165}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB1D2263-20CB-4934-9F2B-5A2D1BDAF165}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB1D2263-20CB-4934-9F2B-5A2D1BDAF165}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB1D2263-20CB-4934-9F2B-5A2D1BDAF165}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/CWLibrary/CWLibrary.csproj b/CWLibrary/CWLibrary.csproj new file mode 100644 index 0000000..97c8da3 --- /dev/null +++ b/CWLibrary/CWLibrary.csproj @@ -0,0 +1,55 @@ + + + + + 10.0 + Debug + AnyCPU + {BB1D2263-20CB-4934-9F2B-5A2D1BDAF165} + Library + Properties + CWLibrary + CWLibrary + v4.0 + Profile4 + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CWLibrary/Characters.cs b/CWLibrary/Characters.cs new file mode 100644 index 0000000..2c1e011 --- /dev/null +++ b/CWLibrary/Characters.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CWLibrary +{ + static class Characters + { + public static Dictionary Symbols = new Dictionary() + { + { "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", "----." }, + { ".", ".-.-.-" }, + { ",", "--..--" }, + { "?", "..--.." }, + { "/", "-..-." }, + { "", ".-.-." }, + { "", "-...-.-" }, + { "", "...-.-" } + }; + } +} diff --git a/CWLibrary/DataChunk.cs b/CWLibrary/DataChunk.cs new file mode 100644 index 0000000..343266e --- /dev/null +++ b/CWLibrary/DataChunk.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CWLibrary +{ + class DataChunk : WaveChunk + { + public short[] ChunkData { get; set; } + + public DataChunk(short[] data) + { + ChunkId = "data".ToCharArray(); + ChunkSize = (uint)(data.Length * 2); + ChunkData = data; + } + + public override byte[] ToBytes() + { + List bytes = new List(); + + bytes.AddRange(Encoding.UTF8.GetBytes(ChunkId)); + bytes.AddRange(BitConverter.GetBytes(ChunkSize)); + + foreach (short datum in ChunkData) + bytes.AddRange(BitConverter.GetBytes(datum)); + + return bytes.ToArray(); + } + } +} diff --git a/CWLibrary/FormatChunk.cs b/CWLibrary/FormatChunk.cs new file mode 100644 index 0000000..9ee26a8 --- /dev/null +++ b/CWLibrary/FormatChunk.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CWLibrary +{ + class FormatChunk : WaveChunk + { + public short CompressionCode { get; set; } + public short NumChannels { get; set; } + public uint SampleRate { get; set; } + public uint BytesPerSecond { get; set; } + public short BlockAlign { get; set; } + public short SignificantBits { get; set; } + + public FormatChunk() + { + ChunkId = "fmt ".ToCharArray(); + ChunkSize = 16; + CompressionCode = 1; + NumChannels = 1; + SampleRate = 44100; + BytesPerSecond = 88200; + BlockAlign = 2; + SignificantBits = 16; + } + + public override byte[] ToBytes() + { + List bytes = new List(); + + bytes.AddRange(Encoding.UTF8.GetBytes(ChunkId)); + bytes.AddRange(BitConverter.GetBytes(ChunkSize)); + bytes.AddRange(BitConverter.GetBytes(CompressionCode)); + bytes.AddRange(BitConverter.GetBytes(NumChannels)); + bytes.AddRange(BitConverter.GetBytes(SampleRate)); + bytes.AddRange(BitConverter.GetBytes(BytesPerSecond)); + bytes.AddRange(BitConverter.GetBytes(BlockAlign)); + bytes.AddRange(BitConverter.GetBytes(SignificantBits)); + + return bytes.ToArray(); + } + } +} diff --git a/CWLibrary/HeaderChunk.cs b/CWLibrary/HeaderChunk.cs new file mode 100644 index 0000000..87307d2 --- /dev/null +++ b/CWLibrary/HeaderChunk.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CWLibrary +{ + class HeaderChunk : WaveChunk + { + public char[] RiffType { get; set; } + public FormatChunk FormatChunk { get; set; } + public DataChunk DataChunk { get; set; } + + public HeaderChunk(FormatChunk formatChunk, DataChunk dataChunk) + { + ChunkId = "RIFF".ToCharArray(); + RiffType = "WAVE".ToCharArray(); + FormatChunk = formatChunk; + DataChunk = dataChunk; + ChunkSize = 36 + DataChunk.ChunkSize; + } + + public override byte[] ToBytes() + { + List bytes = new List(); + + bytes.AddRange(Encoding.UTF8.GetBytes(ChunkId)); + bytes.AddRange(BitConverter.GetBytes(ChunkSize)); + bytes.AddRange(Encoding.UTF8.GetBytes(RiffType)); + bytes.AddRange(FormatChunk.ToBytes()); + bytes.AddRange(DataChunk.ToBytes()); + + return bytes.ToArray(); + } + } +} diff --git a/CWLibrary/Properties/AssemblyInfo.cs b/CWLibrary/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6eadd47 --- /dev/null +++ b/CWLibrary/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CWLibrary")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CWLibrary")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/CWLibrary/TextToMorse.cs b/CWLibrary/TextToMorse.cs new file mode 100644 index 0000000..70ef69a --- /dev/null +++ b/CWLibrary/TextToMorse.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CWLibrary +{ + public class TextToMorse + { + // Character speed in WPM + public int CharacterSpeed { get; set; } + // Overall speed in WPM (must be <= character speed) + public int WordSpeed { get; set; } + // Tone frequency + public double Frequency { get; set; } + + public TextToMorse(int charSpeed, int wordSpeed, double frequency) + { + CharacterSpeed = charSpeed; + WordSpeed = wordSpeed; + Frequency = frequency; + } + + public TextToMorse(int charSpeed, int wordSpeed) : this(charSpeed, wordSpeed, 600.0) { } + public TextToMorse(int wpm) : this(wpm, wpm) { } + public TextToMorse() : this(20) { } + + // Return given number of seconds of sine wave + private short[] GetWave(double seconds) + { + short[] waveArray; + int samples = (int)(44100 * seconds); + + waveArray = new short[samples]; + + for (int i = 0; i < samples; i++) + { + waveArray[i] = Convert.ToInt16(32760 * Math.Sin(i * 2 * Math.PI * Frequency / 44100)); + } + + return waveArray; + } + + // Return given number of seconds of flatline. This could also be + // achieved with slnt chunks inside a wavl chunk, but the resulting + // file might not be universally readable. If saving space is that + // important, it would be better to compress the output as mp3 or ogg + // anyway. + private short[] GetSilence(double seconds) + { + short[] waveArray; + int samples = (int)(44100 * seconds); + + waveArray = new short[samples]; + + return waveArray; + } + + // Dot -- 1 unit long + private short[] GetDot() + { + return GetWave(1.2 / CharacterSpeed); + } + + // Dash -- 3 units long + private short[] GetDash() + { + return GetWave(3.6 / CharacterSpeed); + } + + // Inter-element space -- 1 unit long + private short[] GetInterEltSpace() + { + return GetSilence(1.2 / CharacterSpeed); + } + + // Space between letters -- nominally 3 units, but adjusted for + // Farnsworth timing (if word speed is lower than character + // speed) based on ARRL's Morse code timing standard: + // http://www.arrl.org/files/file/Technology/x9004008.pdf + private short[] GetInterCharSpace() + { + double delay = (60.0 / WordSpeed) - (32.0 / CharacterSpeed); + double spaceLength = 3 * delay / 19; + return GetSilence(spaceLength); + } + + // Space between words -- nominally 7 units, but adjusted for + // Farnsworth timing in case word speed is lower than character + // speed. + private short[] GetInterWordSpace() + { + double delay = (60.0 / WordSpeed) - (32.0 / CharacterSpeed); + double spaceLength = 7 * delay / 19; + return GetSilence(spaceLength); + } + + // Return a single character as a waveform + private short[] GetCharacter(string character) + { + short[] space = GetInterEltSpace(); + short[] dot = GetDot(); + short[] dash = GetDash(); + List morseChar = new List(); + + string morseSymbol = Characters.Symbols[character]; + for (int i = 0; i < morseSymbol.Length; i++) + { + if (i > 0) + morseChar.AddRange(space); + if (morseSymbol[i] == '-') + morseChar.AddRange(dash); + else if (morseSymbol[i] == '.') + morseChar.AddRange(dot); + } + + return morseChar.ToArray(); + } + + // Return a word as a waveform + private short[] GetWord(string word) + { + List data = new List(); + + for (int i = 0; i < word.Length; i++) + { + if (i > 0) + data.AddRange(GetInterCharSpace()); + data.AddRange(GetCharacter(word[i].ToString())); + } + + return data.ToArray(); + } + + // Return a string (lower case text only, unrecognized characters + // throw an exception -- see Characters.cs for the list of recognized + // characters) as a waveform wrapped in a DataChunk, ready to by added + // to a wave file. + private DataChunk GetText(string text) + { + List data = new List(); + + string[] words = text.Split(' '); + + for (int i = 0; i < words.Length; i++) + { + if (i > 0) + data.AddRange(GetInterWordSpace()); + data.AddRange(GetWord(words[i])); + } + + // Pad the end with a little bit of silence. Otherwise the last + // character may sound funny in some media players. + data.AddRange(GetInterCharSpace()); + + DataChunk dataChunk = new DataChunk(data.ToArray()); + + return dataChunk; + } + + // Returns a byte array in the Wave file format containing the given + // text in morse code + public byte[] ConvertToMorse(string text) + { + DataChunk data = GetText(text.ToLower()); + FormatChunk formatChunk = new FormatChunk(); + HeaderChunk headerChunk = new HeaderChunk(formatChunk, data); + return headerChunk.ToBytes(); + } + } +} diff --git a/CWLibrary/WaveChunk.cs b/CWLibrary/WaveChunk.cs new file mode 100644 index 0000000..fb81881 --- /dev/null +++ b/CWLibrary/WaveChunk.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CWLibrary +{ + abstract class WaveChunk + { + public char[] ChunkId { get; set; } + public uint ChunkSize { get; set; } + + public abstract byte[] ToBytes(); + } +} diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..15dbcdd --- /dev/null +++ b/README.txt @@ -0,0 +1,78 @@ +CW Library + +Jeremiah Stoddard - AG6HC + +This is my personal library for working with Morse code. Presently it contains +the TextToMorse class which takes a string and returns a byte array in WAVE +file format containing the contents of the string translated into Morse code. +This is somewhat like what ebook2cw does, but not nearly as flexible (only +WAVE output rather than mp3 or ogg, a much more limited number of characters +supported). On the other hand, CWLibrary is written in C# using only the .NET +framework libraries, so if you're working on a .NET project it may be a better +fit for your needs... + +Here is an example program that uses this library to write out a file named +"hello.wav" that plays "hello, world" in Morse code: + ++-----------------------------------------------------------------------------+ +using CWLibrary; +using System.IO; + +class Program +{ + static void Main(string[] args) + { + TextToMorse converter = new TextToMorse(); + string text = "Hello, World"; + + byte[] outfile = converter.ConvertToMorse(text); + + FileStream stream = new FileStream("hello.wav", FileMode.Create, + FileAccess.Write); + stream.Write(outfile, 0, outfile.Length); + stream.Close(); + } +} ++-----------------------------------------------------------------------------+ + +The above program will generate 20 WPM Morse code with a pitch of 600Hz. If +you want 15 WPM, just pass it to the constructor: + + TextToMorse converter = new TextToMorse(15); + +If you want 5/18 WPM (character speed as if sending at 18 WPM, but spacing +between characters and words added for an overall speed of 5 WPM) you would +call the constructor thus: + + TextToMorse converter = new TextToMorse(18, 5); + +If you also wanted to raise the pitch to 800 Hz, the following would do so: + + TextToMorse converter = new TextToMorse(18, 5, 800.0); + +The code is licensed under the MIT license, so you can do pretty much whatever +you want with it. + +LICENSE: + +CW Library + +Copyright © 2013 Jeremiah Stoddard + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.