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.