Initial commit.

This commit is contained in:
Mark Hamann 2016-02-24 16:18:32 -08:00
parent 93399784ba
commit 88f596fb0b
26 changed files with 4674 additions and 0 deletions

215
.gitignore vendored Normal file
View file

@ -0,0 +1,215 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
x64/
build/
bld/
[Bb]in/
[Oo]bj/
# Roslyn cache directories
*.ide/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
#NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding addin-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
## TODO: Comment the next line if you want to checkin your
## web deploy settings but do note that will include unencrypted
## passwords
#*.pubxml
# NuGet Packages Directory
packages/*
## TODO: If the tool you use requires repositories.config
## uncomment the next line
#!packages/repositories.config
# Enable "build/" folder in the NuGet Packages folder since
# NuGet packages use it for MSBuild targets.
# This line needs to be after the ignore of the build folder
# (and the packages folder if the line above has been uncommented)
!packages/build/
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# LightSwitch generated files
GeneratedArtifacts/
_Pvt_Extensions/
ModelManifest.xml
# Lattice
work/
synwork/
syntmp/
synlog/
# GVIM
*.v~
*.swp
*.rel
*.sym
*.lst
*.asm
*.adb
*.ihx
*.lk
*.map
*.mem
*.noi
*.d
*.lib
*.rst
sta[0-9][0-9][0-9][0-9][0-9]

22
MorseTrainer.sln Normal file
View file

@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.24720.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MorseTrainer", "MorseTrainer\MorseTrainer.csproj", "{48E042F6-B6A3-4ABF-B272-A76F9533FDF9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{48E042F6-B6A3-4ABF-B272-A76F9533FDF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{48E042F6-B6A3-4ABF-B272-A76F9533FDF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{48E042F6-B6A3-4ABF-B272-A76F9533FDF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{48E042F6-B6A3-4ABF-B272-A76F9533FDF9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

54
MorseTrainer/Analyzer.cs Normal file
View file

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MorseTrainer
{
public class Analyzer
{
public Analyzer(System.Windows.Forms.RichTextBox resultsRTB)
{
_resultsRTB = resultsRTB;
}
public void Analyze(String sent, String recorded)
{
_resultsRTB.Clear();
MorseCompareResults results = Comparer.Compare(sent, recorded);
Write("I sent : ");
ResultsDisplayFlags flags = ResultsDisplayFlags.Valid | ResultsDisplayFlags.Dropped;
foreach (MorseSubstring substring in results.SubStrings)
{
Write(substring.Str(flags), substring.Color);
}
Write(Environment.NewLine);
Write("You typed: ");
flags = ResultsDisplayFlags.Valid | ResultsDisplayFlags.Extra;
foreach (MorseSubstring substring in results.SubStrings)
{
Write(substring.Str(flags), substring.Color);
}
Write(Environment.NewLine);
}
private void Write(String text)
{
_resultsRTB.AppendText(text);
}
private void Write(String text, System.Drawing.Color color)
{
_resultsRTB.SelectionStart = _resultsRTB.TextLength;
_resultsRTB.SelectionLength = 0;
_resultsRTB.SelectionColor = color;
_resultsRTB.AppendText(text);
_resultsRTB.SelectionColor = _resultsRTB.ForeColor;
}
private System.Windows.Forms.RichTextBox _resultsRTB;
}
}

6
MorseTrainer/App.config Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
</configuration>

View file

@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MorseTrainer
{
public class CharGenerator
{
public const int STRING_LENGTH_MIN = 2;
public const int STRING_LENGTH_MAX = 7;
public enum Method
{
Koch,
Custom
};
public CharGenerator()
{
_randomizer = new Random();
}
/// <summary>
/// Get or set the generation method
/// </summary>
public Method GenerationMethod
{
get
{
return _method;
}
set
{
_method = value;
}
}
/// <summary>
/// Get or set the index into the Koch order. Must be at least 1
/// </summary>
public int KochIndex
{
get
{
return _kochIndex;
}
set
{
if (value < 0 || value > Koch.Length)
{
throw new ArgumentException("KochIndex");
}
_kochIndex = Math.Max(value, 1);
}
}
/// <summary>
/// Get or set whether to favor new characters in Koch order
/// </summary>
public bool FavorNew
{
get
{
return _favorNew;
}
set
{
_favorNew = value;
}
}
/// <summary>
/// Gets or sets the custom string for custom. Characters are pulled from
/// this string randomly in the custom method.
/// </summary>
public String Custom
{
get
{
return _custom;
}
set
{
_custom = value;
}
}
/// <summary>
/// Creates a random string. The caller is responsible for spaces
/// </summary>
/// <returns>A stringf containing characters to send</returns>
public String CreateRandomString()
{
int size = 2 + _randomizer.Next() % (STRING_LENGTH_MAX - STRING_LENGTH_MIN);
StringBuilder cc = new StringBuilder();
for (int i = 0; i < size; ++i)
{
cc.Append(CreateRandomChar());
}
return cc.ToString();
}
/// <summary>
/// Gets a random character
/// </summary>
/// <returns>A character</returns>
private char CreateRandomChar()
{
int rangeStart;
int rangeLength;
int index;
char c;
String s;
if (_method == Method.Koch)
{
rangeStart = 0;
rangeLength = _kochIndex+1;
if (_favorNew)
{
// reduce the range to the upper part once in a while
if (_randomizer.Next() % 4 == 0)
{
rangeStart = Math.Max(0, rangeLength - 4);
rangeLength -= rangeStart;
}
}
s = Koch.Order;
}
else
{
rangeStart = 0;
rangeLength = _custom.Length;
s = _custom;
}
index = rangeStart + _randomizer.Next() % rangeLength;
c = s[index];
return c;
}
private Method _method;
private int _kochIndex;
private String _custom;
private bool _favorNew;
private Random _randomizer;
}
}

140
MorseTrainer/Comparer.cs Normal file
View file

@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MorseTrainer
{
public class Comparer
{
private static readonly int[] matchLengthThresholdArray = { 5, 3, 2 };
public static MorseCompareResults Compare(string sent, string recorded)
{
const int SEARCH_AMOUNT = 20;
int sentStart = 0;
int recordedStart = 0;
int sentLength = sent.Length;
int recordedLength = recorded.Length;
int sum = 0;
int sentOffset = 0;
int recordedOffset = 0;
int matched = 0;
int threshold;
int matchedTotal = 0;
List<MorseSubstring> substringList = new List<MorseSubstring>();
// As long as there are unprocessed characters
while (sentStart < sentLength || recordedStart < recordedLength)
{
bool searching = true;
int sentRemaining = sentLength - sentStart;
int recordedRemaining = recordedLength - recordedStart;
int maxSum = sentRemaining + recordedRemaining;
// start with a higher threshold to filter false positives, then loosen
for (int thresholdIndex = 0; searching && (thresholdIndex < matchLengthThresholdArray.Length); ++thresholdIndex)
{
// a match is considered a match if it is contains at least 'threshold' contiguous matching characters
threshold = matchLengthThresholdArray[thresholdIndex];
int searchAmount = Math.Min(SEARCH_AMOUNT, maxSum);
// The algorithm here is to match positive offsets on each string
// starting from (0,0) (0,1) (1,0) (0,2) (1,1) (2,0) (0,3) (1,2) (2,1) (3,0) (0,4) ...
// until (0,searchAmount) ... (searchAmount,0)
for (sum = 0; searching && (sum < searchAmount); ++sum)
{
for (sentOffset = 0; sentOffset <= sum; ++sentOffset)
{
// get the respective offsets from the start
recordedOffset = sum - sentOffset;
// number of matching characters starting at the respective offsets
matched = matchCount(sent, recorded, sentStart + sentOffset, recordedStart + recordedOffset);
// got matched, leave sentOffset for loop
if (matched >= threshold)
{
// found!
searching = false;
break;
}
}
}
}
// At this point we have matched and the offsets
if (searching)
{
// didn't find a match--punt and just put everything into the mismatch output
sentOffset = sentLength - sentStart;
recordedOffset = recordedLength - recordedStart;
}
// sum > 0 means that there was either extra or dropped characters detected before a substring match
if (sum > 0)
{
if (sentOffset > 0)
{
// at least one character was dropped
MorseSubstring dropped = new MorseDropped(sent.Substring(sentStart, sentOffset));
substringList.Add(dropped);
// skip over the dropped characters to the start of the match
sentStart += sentOffset;
}
if (recordedOffset > 0)
{
// at least one unsent character was detected before a substring match
MorseSubstring extra = new MorseExtra(recorded.Substring(recordedStart, recordedOffset));
substringList.Add(extra);
// skip over the extra characters to the start of the match
recordedStart += recordedOffset;
}
}
if (matched > 0)
{
// there was a match
MorseSubstring valid = new MorseValid(sent.Substring(sentStart, matched));
substringList.Add(valid);
// skip over the match in each string
recordedStart += matched;
sentStart += matched;
// keep track of the matched total
matchedTotal += matched;
}
}
MorseCompareResults results = new MorseCompareResults(substringList, sent, recorded);
return results;
}
private static int matchCount(string string1, string string2, int str1Offset, int str2Offset)
{
if (str1Offset >= string1.Length || str2Offset >= string2.Length)
{
return 0;
}
int str1Comparable = string1.Length - str1Offset;
int str2Comparable = string2.Length - str2Offset;
int maxCount = Math.Min(str1Comparable, str2Comparable);
int match = 0;
for (; match < maxCount; ++match)
{
if (string1[str1Offset + match] != string2[str2Offset + match])
{
return match;
}
}
return match;
}
}
}

218
MorseTrainer/Config.cs Normal file
View file

@ -0,0 +1,218 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MorseTrainer
{
[Serializable]
public class Config
{
public static readonly Config Default;
static Config()
{
Default = new Config();
Default._frequency = 1000;
Default._wpm = 20;
Default._farnsworthWpm = 13;
Default._duration = 60;
Default._startDelay = 2;
Default._stopDelay = 2;
Default._volume = 1.0f;
Default._method = CharGenerator.Method.Koch;
Default._kochIndex = 1;
Default._favorNew = true;
Default._custom = "";
}
private UInt16 _frequency;
/// <summary>
/// Gets or sets the frequency in Hz
/// </summary>
public UInt16 Frequency
{
get
{
return _frequency;
}
set
{
_frequency = value;
}
}
private float _wpm;
/// <summary>
/// Gets or sets the frequency in WPM in 0.5 increments
/// </summary>
public float WPM
{
get
{
return _wpm;
}
set
{
_wpm = value;
}
}
private float _farnsworthWpm;
/// <summary>
/// Gets or sets the frequency in Farnsworth WPM in 0.5 increments
/// </summary>
public float FarnsworthWPM
{
get
{
return _farnsworthWpm;
}
set
{
_farnsworthWpm = value;
}
}
private UInt16 _duration;
/// <summary>
/// Gets or sets the running duration in seconds increments
/// </summary>
public UInt16 Duration
{
get
{
return _duration;
}
set
{
_duration = value;
}
}
private UInt16 _startDelay;
/// <summary>
/// Gets or sets the start delay in seconds
/// </summary>
public UInt16 StartDelay
{
get
{
return _startDelay;
}
set
{
_startDelay = value;
}
}
private UInt16 _stopDelay;
/// <summary>
/// Gets or sets the stop delay in seconds
/// </summary>
public UInt16 StopDelay
{
get
{
return _stopDelay;
}
set
{
_stopDelay = value;
}
}
private float _volume;
/// <summary>
/// Gets or sets the volume in 0.0f - 1.0f
/// </summary>
public float Volume
{
get
{
return _volume;
}
set
{
_volume = value;
}
}
private UInt16 _kochIndex;
/// <summary>
/// Gets or sets the Koch order index
/// </summary>
public UInt16 KochIndex
{
get
{
return _kochIndex;
}
set
{
_kochIndex = value;
}
}
private CharGenerator.Method _method;
/// <summary>
/// Gets or sets the generation method (Koch or Custom)
/// </summary>
public CharGenerator.Method GenerationMethod
{
get
{
return _method;
}
set
{
_method = value;
}
}
private String _custom;
/// <summary>
/// Gets or sets the custom string generator
/// </summary>
public String Custom
{
get
{
return _custom;
}
set
{
_custom = value;
}
}
private bool _favorNew;
/// <summary>
/// Gets or sets whether to favor newly learned Koch characters
/// </summary>
public bool FavorNew
{
get
{
return _favorNew;
}
set
{
_favorNew = value;
}
}
}
}

437
MorseTrainer/Form1.Designer.cs generated Normal file
View file

@ -0,0 +1,437 @@
namespace MorseTrainer
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
// Manually entered disposals
if (_player != null)
{
_player.Dispose();
_player = null;
}
if (_runner != null)
{
_runner.Dispose();
_runner = null;
}
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label();
this.sliderFrequency = new System.Windows.Forms.TrackBar();
this.sliderWPM = new System.Windows.Forms.TrackBar();
this.sliderDuration = new System.Windows.Forms.TrackBar();
this.sliderFarnsworth = new System.Windows.Forms.TrackBar();
this.txtAnalysis = new System.Windows.Forms.RichTextBox();
this.label5 = new System.Windows.Forms.Label();
this.label6 = new System.Windows.Forms.Label();
this.label7 = new System.Windows.Forms.Label();
this.sliderStartDelay = new System.Windows.Forms.TrackBar();
this.sliderStopDelay = new System.Windows.Forms.TrackBar();
this.txtFrequency = new System.Windows.Forms.TextBox();
this.txtWPM = new System.Windows.Forms.TextBox();
this.txtFarnsworth = new System.Windows.Forms.TextBox();
this.txtDuration = new System.Windows.Forms.TextBox();
this.btnStartStop = new System.Windows.Forms.Button();
this.btnKoch = new System.Windows.Forms.RadioButton();
this.btnCustom = new System.Windows.Forms.RadioButton();
this.cmbKoch = new System.Windows.Forms.ComboBox();
this.txtCustom = new System.Windows.Forms.TextBox();
this.chkFavorNew = new System.Windows.Forms.CheckBox();
this.sliderVolume = new System.Windows.Forms.TrackBar();
this.label8 = new System.Windows.Forms.Label();
this.txtStartDelay = new System.Windows.Forms.TextBox();
this.txtStopDelay = new System.Windows.Forms.TextBox();
this.txtVolume = new System.Windows.Forms.TextBox();
((System.ComponentModel.ISupportInitialize)(this.sliderFrequency)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.sliderWPM)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.sliderDuration)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.sliderFarnsworth)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.sliderStartDelay)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.sliderStopDelay)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.sliderVolume)).BeginInit();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(16, 23);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(84, 20);
this.label1.TabIndex = 0;
this.label1.Text = "Frequency";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(16, 62);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(47, 20);
this.label2.TabIndex = 1;
this.label2.Text = "WPM";
//
// label3
//
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(16, 135);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(112, 20);
this.label3.TabIndex = 3;
this.label3.Text = "Send Duration";
//
// label4
//
this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(16, 96);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(131, 20);
this.label4.TabIndex = 2;
this.label4.Text = "Farnsworth WPM";
//
// sliderFrequency
//
this.sliderFrequency.LargeChange = 100;
this.sliderFrequency.Location = new System.Drawing.Point(229, 25);
this.sliderFrequency.Maximum = 1000;
this.sliderFrequency.Minimum = 400;
this.sliderFrequency.Name = "sliderFrequency";
this.sliderFrequency.Size = new System.Drawing.Size(637, 69);
this.sliderFrequency.SmallChange = 50;
this.sliderFrequency.TabIndex = 4;
this.sliderFrequency.TickFrequency = 50;
this.sliderFrequency.Value = 700;
this.sliderFrequency.Scroll += new System.EventHandler(this.sliderFrequency_Scroll);
//
// sliderWPM
//
this.sliderWPM.Location = new System.Drawing.Point(229, 62);
this.sliderWPM.Maximum = 80;
this.sliderWPM.Minimum = 8;
this.sliderWPM.Name = "sliderWPM";
this.sliderWPM.Size = new System.Drawing.Size(637, 69);
this.sliderWPM.TabIndex = 5;
this.sliderWPM.Value = 20;
this.sliderWPM.Scroll += new System.EventHandler(this.sliderWPM_Scroll);
//
// sliderDuration
//
this.sliderDuration.Location = new System.Drawing.Point(229, 137);
this.sliderDuration.Minimum = 1;
this.sliderDuration.Name = "sliderDuration";
this.sliderDuration.Size = new System.Drawing.Size(637, 69);
this.sliderDuration.TabIndex = 7;
this.sliderDuration.Value = 2;
this.sliderDuration.Scroll += new System.EventHandler(this.sliderDuration_Scroll);
//
// sliderFarnsworth
//
this.sliderFarnsworth.Location = new System.Drawing.Point(229, 100);
this.sliderFarnsworth.Maximum = 80;
this.sliderFarnsworth.Minimum = 8;
this.sliderFarnsworth.Name = "sliderFarnsworth";
this.sliderFarnsworth.Size = new System.Drawing.Size(637, 69);
this.sliderFarnsworth.TabIndex = 6;
this.sliderFarnsworth.Value = 13;
this.sliderFarnsworth.Scroll += new System.EventHandler(this.sliderFarnsworth_Scroll);
//
// txtAnalysis
//
this.txtAnalysis.Location = new System.Drawing.Point(20, 288);
this.txtAnalysis.Name = "txtAnalysis";
this.txtAnalysis.ReadOnly = true;
this.txtAnalysis.Size = new System.Drawing.Size(955, 177);
this.txtAnalysis.TabIndex = 8;
this.txtAnalysis.Text = "";
//
// label5
//
this.label5.AutoSize = true;
this.label5.Location = new System.Drawing.Point(22, 247);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(182, 20);
this.label5.TabIndex = 9;
this.label5.Text = "Your Recording/Analysis";
//
// label6
//
this.label6.AutoSize = true;
this.label6.Location = new System.Drawing.Point(16, 186);
this.label6.Name = "label6";
this.label6.Size = new System.Drawing.Size(88, 20);
this.label6.TabIndex = 10;
this.label6.Text = "Start Delay";
//
// label7
//
this.label7.AutoSize = true;
this.label7.Location = new System.Drawing.Point(428, 186);
this.label7.Name = "label7";
this.label7.Size = new System.Drawing.Size(87, 20);
this.label7.TabIndex = 11;
this.label7.Text = "Stop Delay";
//
// sliderStartDelay
//
this.sliderStartDelay.Location = new System.Drawing.Point(229, 175);
this.sliderStartDelay.Maximum = 5;
this.sliderStartDelay.Name = "sliderStartDelay";
this.sliderStartDelay.Size = new System.Drawing.Size(135, 69);
this.sliderStartDelay.TabIndex = 12;
this.sliderStartDelay.Value = 2;
this.sliderStartDelay.Scroll += new System.EventHandler(this.sliderStartDelay_Scroll);
//
// sliderStopDelay
//
this.sliderStopDelay.Location = new System.Drawing.Point(521, 175);
this.sliderStopDelay.Maximum = 5;
this.sliderStopDelay.Name = "sliderStopDelay";
this.sliderStopDelay.Size = new System.Drawing.Size(128, 69);
this.sliderStopDelay.TabIndex = 13;
this.sliderStopDelay.Value = 2;
this.sliderStopDelay.Scroll += new System.EventHandler(this.sliderStopDelay_Scroll);
//
// txtFrequency
//
this.txtFrequency.Location = new System.Drawing.Point(887, 27);
this.txtFrequency.Name = "txtFrequency";
this.txtFrequency.Size = new System.Drawing.Size(88, 26);
this.txtFrequency.TabIndex = 14;
this.txtFrequency.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.txtFrequency_KeyPress);
//
// txtWPM
//
this.txtWPM.Location = new System.Drawing.Point(887, 64);
this.txtWPM.Name = "txtWPM";
this.txtWPM.Size = new System.Drawing.Size(88, 26);
this.txtWPM.TabIndex = 15;
this.txtWPM.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.txtWPM_KeyPress);
//
// txtFarnsworth
//
this.txtFarnsworth.Location = new System.Drawing.Point(887, 102);
this.txtFarnsworth.Name = "txtFarnsworth";
this.txtFarnsworth.Size = new System.Drawing.Size(88, 26);
this.txtFarnsworth.TabIndex = 16;
this.txtFarnsworth.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.txtFarnsworth_KeyPress);
//
// txtDuration
//
this.txtDuration.Location = new System.Drawing.Point(887, 137);
this.txtDuration.Name = "txtDuration";
this.txtDuration.Size = new System.Drawing.Size(88, 26);
this.txtDuration.TabIndex = 17;
this.txtDuration.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.txtDuration_KeyPress);
//
// btnStartStop
//
this.btnStartStop.Location = new System.Drawing.Point(229, 239);
this.btnStartStop.Name = "btnStartStop";
this.btnStartStop.Size = new System.Drawing.Size(118, 43);
this.btnStartStop.TabIndex = 18;
this.btnStartStop.Text = "Start";
this.btnStartStop.UseVisualStyleBackColor = true;
this.btnStartStop.Click += new System.EventHandler(this.btnStartStop_Click);
//
// btnKoch
//
this.btnKoch.AutoSize = true;
this.btnKoch.Location = new System.Drawing.Point(24, 484);
this.btnKoch.Name = "btnKoch";
this.btnKoch.Size = new System.Drawing.Size(70, 24);
this.btnKoch.TabIndex = 19;
this.btnKoch.TabStop = true;
this.btnKoch.Text = "Koch";
this.btnKoch.UseVisualStyleBackColor = true;
this.btnKoch.Click += new System.EventHandler(this.btnKoch_Click);
//
// btnCustom
//
this.btnCustom.AutoSize = true;
this.btnCustom.Location = new System.Drawing.Point(24, 522);
this.btnCustom.Name = "btnCustom";
this.btnCustom.Size = new System.Drawing.Size(89, 24);
this.btnCustom.TabIndex = 20;
this.btnCustom.TabStop = true;
this.btnCustom.Text = "Custom";
this.btnCustom.UseVisualStyleBackColor = true;
this.btnCustom.Click += new System.EventHandler(this.btnCustom_Click);
//
// cmbKoch
//
this.cmbKoch.FormattingEnabled = true;
this.cmbKoch.Location = new System.Drawing.Point(139, 483);
this.cmbKoch.Name = "cmbKoch";
this.cmbKoch.Size = new System.Drawing.Size(100, 28);
this.cmbKoch.TabIndex = 21;
this.cmbKoch.SelectedIndexChanged += new System.EventHandler(this.cmbKoch_SelectedIndexChanged);
//
// txtCustom
//
this.txtCustom.Location = new System.Drawing.Point(139, 522);
this.txtCustom.Name = "txtCustom";
this.txtCustom.Size = new System.Drawing.Size(510, 26);
this.txtCustom.TabIndex = 22;
//
// chkFavorNew
//
this.chkFavorNew.AutoSize = true;
this.chkFavorNew.Location = new System.Drawing.Point(259, 484);
this.chkFavorNew.Name = "chkFavorNew";
this.chkFavorNew.Size = new System.Drawing.Size(192, 24);
this.chkFavorNew.TabIndex = 23;
this.chkFavorNew.Text = "Favor New Characters";
this.chkFavorNew.UseVisualStyleBackColor = true;
this.chkFavorNew.CheckStateChanged += new System.EventHandler(this.chkFavorNew_CheckStateChanged);
//
// sliderVolume
//
this.sliderVolume.LargeChange = 10;
this.sliderVolume.Location = new System.Drawing.Point(805, 175);
this.sliderVolume.Name = "sliderVolume";
this.sliderVolume.Size = new System.Drawing.Size(116, 69);
this.sliderVolume.TabIndex = 25;
this.sliderVolume.TickFrequency = 2;
this.sliderVolume.Value = 10;
this.sliderVolume.Scroll += new System.EventHandler(this.sliderVolume_Scroll);
//
// label8
//
this.label8.AutoSize = true;
this.label8.Location = new System.Drawing.Point(712, 186);
this.label8.Name = "label8";
this.label8.Size = new System.Drawing.Size(63, 20);
this.label8.TabIndex = 24;
this.label8.Text = "Volume";
//
// txtStartDelay
//
this.txtStartDelay.Location = new System.Drawing.Point(370, 180);
this.txtStartDelay.Name = "txtStartDelay";
this.txtStartDelay.Size = new System.Drawing.Size(35, 26);
this.txtStartDelay.TabIndex = 26;
//
// txtStopDelay
//
this.txtStopDelay.Location = new System.Drawing.Point(642, 180);
this.txtStopDelay.Name = "txtStopDelay";
this.txtStopDelay.Size = new System.Drawing.Size(35, 26);
this.txtStopDelay.TabIndex = 27;
//
// txtVolume
//
this.txtVolume.Location = new System.Drawing.Point(927, 180);
this.txtVolume.Name = "txtVolume";
this.txtVolume.Size = new System.Drawing.Size(35, 26);
this.txtVolume.TabIndex = 28;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(991, 631);
this.Controls.Add(this.txtVolume);
this.Controls.Add(this.txtStopDelay);
this.Controls.Add(this.txtStartDelay);
this.Controls.Add(this.sliderVolume);
this.Controls.Add(this.label8);
this.Controls.Add(this.chkFavorNew);
this.Controls.Add(this.txtCustom);
this.Controls.Add(this.cmbKoch);
this.Controls.Add(this.btnCustom);
this.Controls.Add(this.btnKoch);
this.Controls.Add(this.btnStartStop);
this.Controls.Add(this.txtDuration);
this.Controls.Add(this.txtFarnsworth);
this.Controls.Add(this.txtWPM);
this.Controls.Add(this.txtFrequency);
this.Controls.Add(this.sliderStopDelay);
this.Controls.Add(this.sliderStartDelay);
this.Controls.Add(this.label7);
this.Controls.Add(this.label6);
this.Controls.Add(this.label5);
this.Controls.Add(this.txtAnalysis);
this.Controls.Add(this.sliderDuration);
this.Controls.Add(this.sliderFarnsworth);
this.Controls.Add(this.sliderWPM);
this.Controls.Add(this.sliderFrequency);
this.Controls.Add(this.label3);
this.Controls.Add(this.label4);
this.Controls.Add(this.label2);
this.Controls.Add(this.label1);
this.KeyPreview = true;
this.Name = "Form1";
this.Text = "Morse Code Trainer";
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.Form1_FormClosed);
this.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.Form1_KeyPress);
((System.ComponentModel.ISupportInitialize)(this.sliderFrequency)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.sliderWPM)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.sliderDuration)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.sliderFarnsworth)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.sliderStartDelay)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.sliderStopDelay)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.sliderVolume)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.TrackBar sliderFrequency;
private System.Windows.Forms.TrackBar sliderWPM;
private System.Windows.Forms.TrackBar sliderDuration;
private System.Windows.Forms.TrackBar sliderFarnsworth;
private System.Windows.Forms.RichTextBox txtAnalysis;
private System.Windows.Forms.Label label5;
private System.Windows.Forms.Label label6;
private System.Windows.Forms.Label label7;
private System.Windows.Forms.TrackBar sliderStartDelay;
private System.Windows.Forms.TrackBar sliderStopDelay;
private System.Windows.Forms.TextBox txtFrequency;
private System.Windows.Forms.TextBox txtWPM;
private System.Windows.Forms.TextBox txtFarnsworth;
private System.Windows.Forms.TextBox txtDuration;
private System.Windows.Forms.Button btnStartStop;
private System.Windows.Forms.RadioButton btnKoch;
private System.Windows.Forms.RadioButton btnCustom;
private System.Windows.Forms.ComboBox cmbKoch;
private System.Windows.Forms.TextBox txtCustom;
private System.Windows.Forms.CheckBox chkFavorNew;
private System.Windows.Forms.TrackBar sliderVolume;
private System.Windows.Forms.Label label8;
private System.Windows.Forms.TextBox txtStartDelay;
private System.Windows.Forms.TextBox txtStopDelay;
private System.Windows.Forms.TextBox txtVolume;
}
}

1263
MorseTrainer/Form1.cs Normal file

File diff suppressed because it is too large Load diff

120
MorseTrainer/Form1.resx Normal file
View file

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

66
MorseTrainer/Koch.cs Normal file
View file

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MorseTrainer
{
public class Koch
{
/// <summary>
/// Gets the
/// </summary>
/// <param name="c"></param>
/// <returns>A string with the characters</returns>
static public String CharsUpToAndIncluding(Char c)
{
int end = IndexOf(c);
return CharsUpToAndIncluding(end);
}
/// <summary>
/// Gets the
/// </summary>
/// <param name="index"></param>
/// <returns>A string with the characters</returns>
static public String CharsUpToAndIncluding(int index)
{
String ret = null;
if (index > 0 && index < Length)
{
ret = Order.Substring(0, index);
}
return ret;
}
static public Char[] RecentFromChar(Char c, int numberOfChars)
{
int end = IndexOf(c);
int start = Math.Max(0, end - numberOfChars);
return Order.Substring(start, numberOfChars).ToCharArray();
}
static public Char[] RecentFromIndex(Char c, int numberOfChars)
{
int end = IndexOf(c);
int start = Math.Max(0, end - numberOfChars);
return Order.Substring(start, numberOfChars).ToCharArray();
}
static public int IndexOf(Char c)
{
return Order.IndexOf(c);
}
static Koch()
{
Order = String.Concat("KMRSUAPTLOWI.NJEF0Y,VG5/Q9ZH38B?427C1D6X",
"\x80\x81\x82");
Length = Order.Length;
}
static public readonly String Order;
static public readonly int Length;
}
}

View file

@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MorseTrainer
{
[Flags]
public enum ResultsDisplayFlags
{
Valid = 0x01,
Dropped = 0x02,
Extra = 0x04,
All = 0x07
}
public class MorseCompareResults
{
public MorseCompareResults(IEnumerable<MorseSubstring> substrings, String sent, String recorded)
{
_substringList = new List<MorseSubstring>(substrings);
_sent = sent;
_recorded = recorded;
}
public String Sent
{
get
{
return _sent;
}
}
public String Recorded
{
get
{
return _recorded;
}
}
public IList<MorseSubstring> SubStrings
{
get
{
return _substringList.AsReadOnly();
}
}
private String _sent;
private String _recorded;
private List<MorseSubstring> _substringList;
}
public abstract class MorseSubstring
{
protected MorseSubstring() { }
protected MorseSubstring(String str)
{
_str = str;
}
public String Chars
{
get
{
return _str;
}
}
public abstract System.Drawing.Color Color
{
get;
}
public abstract String Str(ResultsDisplayFlags flags = ResultsDisplayFlags.All);
protected string _str;
}
public class MorseValid : MorseSubstring
{
public MorseValid(String str) : base(str)
{
}
public override string Str(ResultsDisplayFlags flags)
{
String ret = "";
if ((flags & ResultsDisplayFlags.Valid) != 0)
{
ret = MorseInfo.ExpandProsigns(_str);
}
return ret;
}
public override Color Color
{
get
{
return Color.Black;
}
}
}
public class MorseDropped : MorseSubstring
{
public MorseDropped(String str) : base(str)
{
}
public override string Str(ResultsDisplayFlags flags)
{
String ret = "";
if ((flags & ResultsDisplayFlags.Dropped) != 0)
{
ret = MorseInfo.ExpandProsigns(_str);
}
return ret;
}
public override Color Color
{
get
{
return Color.Red;
}
}
}
public class MorseExtra : MorseSubstring
{
public MorseExtra(String str) : base(str)
{
}
public override string Str(ResultsDisplayFlags flags)
{
String ret = "";
if ((flags & ResultsDisplayFlags.Extra) != 0)
{
ret = MorseInfo.ExpandProsigns(_str);
}
return ret;
}
public override Color Color
{
get
{
return Color.Orange;
}
}
}
}

149
MorseTrainer/MorseInfo.cs Normal file
View file

@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MorseTrainer
{
public class MorseInfo
{
public const char PROSIGN_BT = '\x80';
public const char PROSIGN_SK = '\x81';
public const char PROSIGN_AR = '\x82';
static MorseInfo()
{
// save the morse conversions into an array
__conversions = new String[256];
__conversions['A'] = ".-";
__conversions['B'] = "-...";
__conversions['C'] = "-.-.";
__conversions['D'] = "-..";
__conversions['E'] = ".";
__conversions['F'] = "..-.";
__conversions['G'] = "--.";
__conversions['H'] = "....";
__conversions['I'] = "..";
__conversions['J'] = ".---";
__conversions['K'] = "-.-";
__conversions['L'] = ".-..";
__conversions['M'] = "--";
__conversions['N'] = "-.";
__conversions['O'] = "---";
__conversions['P'] = ".--.";
__conversions['Q'] = "--.-";
__conversions['R'] = ".-.";
__conversions['S'] = "...";
__conversions['T'] = "-";
__conversions['U'] = "..-";
__conversions['V'] = "...-";
__conversions['W'] = ".--";
__conversions['X'] = "";
__conversions['Y'] = "-.--";
__conversions['Z'] = "--..";
__conversions['0'] = "-----";
__conversions['1'] = ".----";
__conversions['2'] = "..---";
__conversions['3'] = "...--";
__conversions['4'] = "....-";
__conversions['5'] = ".....";
__conversions['6'] = "-....";
__conversions['7'] = "--...";
__conversions['8'] = "---..";
__conversions['9'] = "----.";
__conversions['.'] = ".-.-.-";
__conversions[','] = "--..--";
__conversions['?'] = "..--..";
__conversions['/'] = "-..-.";
__conversions[PROSIGN_BT] = "-...-";
__conversions[PROSIGN_SK] = "...-.-";
__conversions[PROSIGN_AR] = ".-.-.";
__prosignExpansionToValue = new Dictionary<string, char>();
__prosignExpansionToValue.Add("<BT>", PROSIGN_BT);
__prosignExpansionToValue.Add("<SK>", PROSIGN_SK);
__prosignExpansionToValue.Add("<AR>", PROSIGN_AR);
__prosignValueToExpansion = new Dictionary<char, string>();
foreach (KeyValuePair<String, char> kv in __prosignExpansionToValue)
{
__prosignValueToExpansion.Add(kv.Value, kv.Key);
}
// save the number of elements--needed to calculate Farnsworth timing
__elements = new int[256];
for (int i = 0; i < __elements.Length; ++i)
{
int elements = 0;
String s = __conversions[i];
if (!String.IsNullOrEmpty(s))
{
foreach (char c in s)
{
if (c == '.')
{
elements += 1;
}
else if (c == '-')
{
elements += 3;
}
}
}
__elements[i] = elements;
}
}
private static Dictionary<String, char> __prosignExpansionToValue;
private static Dictionary<char, String> __prosignValueToExpansion;
private static String[] __conversions;
private static int[] __elements;
public static int ToElements(Char c)
{
if (c < 0 || c >= __elements.Length)
{
throw new ArgumentException("c");
}
return __elements[c];
}
public static String ToMorse(Char c)
{
if (c < 0 || c >= __elements.Length)
{
throw new ArgumentException("c");
}
String s = __conversions[c];
return s;
}
public static String ReplaceProsigns(String expandedProsigns)
{
String replaced = expandedProsigns;
foreach (KeyValuePair<String, char> kv in __prosignExpansionToValue)
{
String expansion = kv.Key;
String value = kv.Value.ToString();
replaced = replaced.Replace(expansion, value);
}
return replaced;
}
public static String ExpandProsigns(String valuedProsigns)
{
String replaced = valuedProsigns;
foreach (KeyValuePair<String, char> kv in __prosignExpansionToValue)
{
String expansion = kv.Key;
String value = kv.Value.ToString();
replaced = replaced.Replace(value, expansion);
}
return replaced;
}
}
}

View file

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{48E042F6-B6A3-4ABF-B272-A76F9533FDF9}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MorseTrainer</RootNamespace>
<AssemblyName>MorseTrainer</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Analyzer.cs" />
<Compile Include="CharGenerator.cs" />
<Compile Include="Config.cs" />
<Compile Include="MorseInfo.cs" />
<Compile Include="Comparer.cs" />
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="Koch.cs" />
<Compile Include="MorseCompareResults.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Runner.cs" />
<Compile Include="SoundPlayerAsync.cs" />
<Compile Include="ToneGenerator.cs" />
<Compile Include="WaveBuilder.cs" />
<Compile Include="WaveStream.cs" />
<Compile Include="WordToToneBuilder.cs" />
<EmbeddedResource Include="Form1.resx">
<DependentUpon>Form1.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

22
MorseTrainer/Program.cs Normal file
View file

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace MorseTrainer
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}

View file

@ -0,0 +1,36 @@
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("MorseTrainer")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MorseTrainer")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("48e042f6-b6a3-4abf-b272-a76f9533fdf9")]
// 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")]

View file

@ -0,0 +1,71 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace MorseTrainer.Properties
{
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MorseTrainer.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
}
}

View file

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View file

@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace MorseTrainer.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
}

View file

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

377
MorseTrainer/Runner.cs Normal file
View file

@ -0,0 +1,377 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MorseTrainer
{
/// <summary>
/// The Runner class handles the timing of the morse code runnning.
/// It tracks 5 basic states:
/// Idle: no tests being run
/// StartDelay: countdown to starting the morse code
/// Sending: Sending morse code
/// SendFinished: Send time is up, waiting for last word to finish up
/// StopDelay: Still allowing user to enter characters
/// </summary>
public class Runner : IDisposable
{
private enum State
{
// waiting
Idle,
// waiting
StartRequest,
// user has started and delay is counting
StartDelay,
// sending morse
Sending,
// send is done, but finishing up the rest of the word
SendFinished,
// send is
StopDelay,
// stop requested
StopRequest
}
public const int MIN_DURATION = 30;
public const int MAX_DURATION = 60*10;
public const int MIN_START_DELAY = 0;
public const int MAX_START_DELAY = 5;
public const int MIN_STOP_DELAY = 0;
public const int MAX_STOP_DELAY = 5;
public Runner()
{
_timer = new System.Timers.Timer();
_timer.Enabled = true;
_timer.AutoReset = false;
_timer.Elapsed += _timer_Elapsed;
}
/// <summary>
/// Gets whether the runner is idle (false) or in one of the running states (true)
/// </summary>
public bool IsRunning
{
get
{
return _state != State.Idle;
}
}
/// <summary>
/// Gets is the runner is in a mode where the user input should be recorded.
/// These states are Sending, SendFinished, and StopDelay
/// </summary>
public bool IsListenMode
{
get
{
return _state == State.Sending ||
_state == State.SendFinished ||
_state == State.StopDelay;
}
}
/// <summary>
/// Gets a bool that indicate if the morse is being sent and a new word
/// should be sent to the tone generator.
/// </summary>
public bool ContinueMorse
{
get
{
return _state == State.Sending;
}
}
private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
State nextState = StateExit();
StateEnter(nextState);
}
private State StateExit()
{
State nextState = _state;
switch (_state)
{
case State.StartDelay:
_timer.Stop();
OnStartDelayExit();
nextState = State.Sending;
break;
case State.Sending:
_timer.Stop();
OnMorseExit();
nextState = State.SendFinished;
break;
case State.StopDelay:
_timer.Stop();
OnStopDelayExit();
nextState = State.Idle;
break;
}
return nextState;
}
private void StateEnter(State nextState)
{
_state = nextState;
switch (_state)
{
case State.StartDelay:
_timer.Interval = _startDelay * 1000;
_timer.Start();
OnStartDelayEnter();
break;
case State.Sending:
_timer.Interval = _sendDuration * 1000;
_timer.Start();
OnMorseEnter();
break;
case State.StopDelay:
_timer.Interval = _stopDelay * 1000;
_timer.Start();
OnStopDelayEnter();
break;
}
}
/// <summary>
/// Gets or sets the start delay in seconds. This is the time for the user
/// to recover from pressing the start button to positioning their hands
/// for input, for example.
/// </summary>
public int StartDelay
{
get
{
return _startDelay;
}
set
{
_startDelay = value;
}
}
/// <summary>
/// Gets or sets the stop delay in seconds. This is the time after the last word
/// was sent to let the user input the remaining letters.
/// </summary>
public int StopDelay
{
get
{
return _stopDelay;
}
set
{
_stopDelay = value;
}
}
/// <summary>
/// Gets or sets the send duration in seconds.
/// </summary>
public int SendDuration
{
get
{
return _sendDuration;
}
set
{
_sendDuration = value;
}
}
/// <summary>
/// Starts the runner.
/// </summary>
public void RequestStart()
{
State startState = (_startDelay > 0) ? State.StartDelay : State.Sending;
StateEnter(startState);
}
/// <summary>
/// Exits the current state and then fires the Abort event. The abort event
/// is only fired if the runner is not running
/// </summary>
public void RequestStop()
{
bool abort = _state != State.Idle;
_state = StateExit();
// stop request during start delay
if (_state == State.Sending)
{
_state = State.Idle;
}
if (abort)
{
OnAbort();
}
}
/// <summary>
/// Used when the SendMorseEnd is sent while sending is still occurring.
/// Call this in the ToneGenerator.CharactersSent event
/// </summary>
public void AcknowledgeSendEnd()
{
if (_state == State.SendFinished)
{
StateEnter(State.StopDelay);
}
}
/// <summary>
/// Countdown to morse code sending has started
/// </summary>
public event EventHandler StartDelayEnter;
protected void OnStartDelayEnter()
{
EventHandler handler = StartDelayEnter;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
/// <summary>
/// The countdown delay has ended
/// </summary>
public event EventHandler StartDelayExit;
protected void OnStartDelayExit()
{
EventHandler handler = StartDelayExit;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
/// <summary>
/// Start morse code sending.
/// </summary>
public event EventHandler MorseEnter;
protected void OnMorseEnter()
{
EventHandler handler = MorseEnter;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
/// <summary>
/// Stop feeding tone generator with characters and let it finish up
/// </summary>
public event EventHandler MorseExit;
protected void OnMorseExit()
{
EventHandler handler = MorseExit;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
/// <summary>
/// Morse code has stopped, but the user is still allowed to type in keys
/// </summary>
public event EventHandler StopDelayEnter;
protected void OnStopDelayEnter()
{
EventHandler handler = StopDelayEnter;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
/// <summary>
/// Post sending delay is over, time to analyze
/// </summary>
public event EventHandler StopDelayExit;
protected void OnStopDelayExit()
{
EventHandler handler = StopDelayExit;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
/// <summary>
/// Abort
/// </summary>
public event EventHandler Abort;
protected void OnAbort()
{
EventHandler handler = Abort;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
private State _state;
private System.Timers.Timer _timer;
private int _startDelay;
private int _sendDuration;
private int _stopDelay;
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
if (_timer != null)
{
_timer.Stop();
_timer.Dispose();
_timer = null;
}
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
disposedValue = true;
}
}
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
// ~Runner() {
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
// Dispose(false);
// }
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion
}
}

View file

@ -0,0 +1,230 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MorseTrainer
{
public class SoundPlayerAsync : IDisposable
{
public SoundPlayerAsync()
{
_mediaSoundPlayer = new System.Media.SoundPlayer();
_queue = new Queue<WaveStream>();
_sentString = new StringBuilder();
_stopThread = false;
_thread = new System.Threading.Thread(ThreadMain);
_thread.Name = "Async Player";
_thread.Start();
}
private void ThreadMain()
{
while (!_stopThread)
{
WaveStream waveToPlay = Dequeue();
if (waveToPlay != null)
{
_mediaSoundPlayer.Stream = waveToPlay.Stream;
_mediaSoundPlayer.Load();
_mediaSoundPlayer.PlaySync();
_sentString.Append(waveToPlay.Text);
_sentString.Append(' ');
// All done
if (Count == 0)
{
OnPlayingFinished();
}
}
}
lock(this)
{
_stopped = true;
System.Threading.Monitor.Pulse(this);
}
}
private WaveStream Dequeue()
{
WaveStream wave = null;
lock (this)
{
while (_queue.Count == 0 && !_stopThread)
{
System.Threading.Monitor.Wait(this);
}
if (_queue.Count > 0)
{
wave = _queue.Dequeue();
if (_queue.Count == 0)
{
OnQueueEmpty();
}
}
}
return wave;
}
public void Start(WaveStream wave)
{
Enqueue(wave);
_sentString.Clear();
}
public String Sent
{
get
{
return _sentString.ToString().TrimEnd();
}
}
/// <summary>
/// Give the SoundPlayerAsync a wave to play immediately (if noty busy) or
/// following the currently playing/enqueued waves
/// </summary>
/// <param name="wave"></param>
public void Enqueue(WaveStream wave)
{
lock(this)
{
_queue.Enqueue(wave);
System.Threading.Monitor.Pulse(this);
}
}
/// <summary>
/// Clear all waves from the queue when aborting
/// </summary>
public void Clear()
{
lock(this)
{
_queue.Clear();
System.Threading.Monitor.Pulse(this);
}
}
/// <summary>
/// Gets the number of enqueued waves
/// </summary>
public int Count
{
get
{
lock(this)
{
return _queue.Count;
}
}
}
/// <summary>
/// Playing has finished and no more waves are enqueued
/// </summary>
public event EventHandler PlayingFinished;
protected void OnPlayingFinished()
{
EventHandler handler = PlayingFinished;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
/// <summary>
/// The queue has been emptied. Action in this should be quick
/// </summary>
public event EventHandler QueueEmpty;
protected void OnQueueEmpty()
{
EventHandler handler = QueueEmpty;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
/// <summary>
/// Close the thread.
/// </summary>
public void CloseAndJoin()
{
lock(this)
{
if (_thread != null && !_stopThread)
{
_stopThread = true;
_stopped = false;
_queue.Clear();
System.Threading.Monitor.Pulse(this);
if (!_stopped)
{
System.Threading.Monitor.Wait(this);
}
_thread.Join();
_thread = null;
}
}
}
private System.Threading.Thread _thread;
private bool _stopThread;
private bool _stopped;
private Queue<WaveStream> _queue;
private System.Media.SoundPlayer _mediaSoundPlayer;
private StringBuilder _sentString;
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects).
CloseAndJoin();
if (_queue != null)
{
_queue.Clear();
_queue = null;
}
if (_mediaSoundPlayer != null)
{
_mediaSoundPlayer.Stop();
_mediaSoundPlayer.Dispose();
_mediaSoundPlayer = null;
}
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
disposedValue = true;
}
}
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
// ~SoundPlayerAsync() {
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
// Dispose(false);
// }
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion
}
}

View file

@ -0,0 +1,297 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Media;
namespace MorseTrainer
{
public class ToneGenerator
{
public const UInt16 MIN_FREQUENCY = 300;
public const UInt16 MAX_FREQUENCY = 1200;
public const float MIN_WPM = 3.0f;
public const float MAX_WPM = 40.0f;
public const float MIN_FARNSWORTH_WPM = 3.0f;
public const float MAX_FARNSWORTH_WPM = 40.0f;
public const float MIN_VOLUME = 0.0f;
public const float MAX_VOLUME = 1.0f;
public const Int32 SAMPLES_PER_SECOND = 8000;
public ToneGenerator()
{
_frequency = 0;
_WPM = 0;
_farnsworthWPM = 0;
_volume = 0.0f;
}
private float WpmToMillesecPerElement(float wpm)
{
float millisecPerElement =
1000.0f /* ms per sec */
* 60.0f /* Sec / min */
/ wpm
/ 50; /* elem per word */
return millisecPerElement;
}
public void Update()
{
// changing tone frequency or WPM will cause the tones to be regenerated
if (_frequency == 0 || _frequency != _newFrequency || _newWPM != _WPM || _newVolume != _volume)
{
_volume = _newVolume;
_frequency = _newFrequency;
_WPM = _newWPM;
// ms per element = WPM * 1000 ms per sec / 60 words per second / 50 elements
_msPerElement = WpmToMillesecPerElement(_WPM);
float samplesPerCycle = SAMPLES_PER_SECOND / _frequency;
_samplesPerCycle = (UInt16)(samplesPerCycle + 0.5);
CreateTones();
}
if (_farnsworthWPM == 0 || _farnsworthWPM != _newFarnsworthWPM)
{
_farnsworthWPM = _newFarnsworthWPM;
_msPerFarnsworthElement = WpmToMillesecPerElement(_farnsworthWPM);
}
}
public UInt16 CurrentFrequency
{
get
{
return _frequency;
}
}
public UInt16 Frequency
{
get
{
return _newFrequency;
}
set
{
if (value < MIN_FREQUENCY || value > MAX_FREQUENCY)
{
throw new ArgumentOutOfRangeException("Frequency");
}
_newFrequency = value;
}
}
public float Volume
{
get
{
return _newVolume;
}
set
{
if (value < MIN_VOLUME || value > MAX_VOLUME)
{
throw new ArgumentOutOfRangeException("Volume");
}
_newVolume = value;
}
}
public float CurrentVolume
{
get
{
return _volume;
}
}
public float WPM
{
get
{
return _newWPM;
}
set
{
if (value < MIN_WPM || value > MAX_WPM)
{
throw new ArgumentOutOfRangeException("WPM");
}
_newWPM = (float)Math.Round(value * 2) / 2;
}
}
public float CurrentFarnsworthWPM
{
get
{
return _farnsworthWPM;
}
}
public float FarnsworthWPM
{
get
{
return _newFarnsworthWPM;
}
set
{
if (value < MIN_WPM || value > MAX_WPM)
{
throw new ArgumentOutOfRangeException("WPM");
}
_newFarnsworthWPM = (float)Math.Round(value * 2) / 2;
}
}
public UInt32 SamplesPerCycle
{
get
{
return _samplesPerCycle;
}
}
private void CreateTones()
{
System.IO.MemoryStream streamDot = new System.IO.MemoryStream();
System.IO.MemoryStream streamDash = new System.IO.MemoryStream();
_dotToneWaveform = CreateTone(_msPerElement);
_dashToneWaveform = CreateTone(_msPerElement*3);
_dotSpaceWaveform = CreateSpace(_msPerElement);
_letterSpaceWaveform = CreateSpace(_msPerElement*3);
_wordSpaceWaveform = CreateSpace(_msPerElement * 7);
}
private Int16[] CreateSpace(float millisec)
{
float samples = SAMPLES_PER_SECOND * millisec / 1000;
UInt32 actualSamples = (UInt32)samples;
Int16[] waveform = new Int16[actualSamples];
// Fill in the space
for (UInt32 sample = 0; sample < actualSamples; ++sample)
{
waveform[sample] = 0;
}
return waveform;
}
private Int16[] CreateTone(float millisec)
{
float samples = SAMPLES_PER_SECOND * millisec / 1000;
// Nyquist check
if (samples < _samplesPerCycle/2)
{
throw new ArgumentException("samples");
}
// Create stream
// Get a number of cyucles to make the tone end at the end of a sinewave to prevent a pop
UInt32 actualSamples = (UInt32)samples;
actualSamples = actualSamples - (actualSamples % _samplesPerCycle) + 1;
Int16[] waveform = new Int16[actualSamples];
uint fade = _samplesPerCycle;
// Fill in the tone
for (UInt32 sample = 0; sample < actualSamples; ++sample)
{
float envelope = 1.0f;
if (sample < fade)
{
envelope = (float)sample / (float)fade;
}
else if (sample > actualSamples - fade)
{
envelope = (float)(actualSamples - sample) / (float)fade;
}
float instantaneous = (float)Math.Sin((float)(sample % _samplesPerCycle) / _samplesPerCycle * (2 * Math.PI )) * _volume * envelope;
waveform[sample] = (Int16)(32767 * instantaneous);
}
return waveform;
}
public Int16[] DotToneWaveform
{
get
{
return _dotToneWaveform;
}
}
public Int16[] DashToneWaveform
{
get
{
return _dashToneWaveform;
}
}
public Int16[] DotSpaceWaveform
{
get
{
return _dotSpaceWaveform;
}
}
public Int16[] LetterSpaceWaveform
{
get
{
return _letterSpaceWaveform;
}
}
public Int16[] WordSpaceWaveform
{
get
{
return _wordSpaceWaveform;
}
}
public Int16[] FarnsworthSpacingWaveform(Char c)
{
// Get the equivalent dots
int elements = MorseInfo.ToElements(c);
// Farnsworth timing stretches time between characters.
// Character has already been sent WPM and now we have to
// wait as if it were sent FarnsworthWPM
float wpmTime = _msPerElement * elements;
float farnsworthTime = _msPerFarnsworthElement * elements;
float difference = farnsworthTime - wpmTime;
return CreateSpace(difference);
}
private UInt16 _frequency;
private UInt16 _newFrequency;
private float _WPM;
private float _newWPM;
private float _farnsworthWPM;
private float _newFarnsworthWPM;
private float _volume;
private float _newVolume;
private float _msPerElement;
private float _msPerFarnsworthElement;
private UInt32 _samplesPerCycle;
private Int16[] _dotToneWaveform;
private Int16[] _dashToneWaveform;
private Int16[] _dotSpaceWaveform;
private Int16[] _letterSpaceWaveform;
private Int16[] _wordSpaceWaveform;
}
}

View file

@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MorseTrainer
{
public class WaveBuilder
{
public WaveBuilder()
{
}
public Int16[] Dot
{
get
{
throw new NotImplementedException();
}
set
{
}
}
public Int16[] Dash
{
get
{
throw new NotImplementedException();
}
set
{
}
}
public Int16[] DotSpace
{
get
{
throw new NotImplementedException();
}
set
{
}
}
public Int16[] DashSpace
{
get
{
throw new NotImplementedException();
}
set
{
}
}
public UInt16[] WordSpace
{
get
{
throw new NotImplementedException();
}
set
{
}
}
public void Append(String morse)
{
}
public void Clear()
{
}
public WaveStream GetFinalStream()
{
throw new NotImplementedException();
}
}
}

159
MorseTrainer/WaveStream.cs Normal file
View file

@ -0,0 +1,159 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MorseTrainer
{
public class WaveStream
{
public WaveStream(String text, Int16[] waveform, UInt32 sampleRate, UInt32 samplesPerCycle)
{
_text = text;
SetupStream(waveform, sampleRate, samplesPerCycle);
}
public WaveStream(String text, IEnumerable<Int16[]> waveforms, UInt32 sampleRate, UInt32 samplesPerCycle)
{
_text = text;
SetupStream(waveforms, sampleRate, samplesPerCycle);
}
public String Text
{
get
{
return _text;
}
}
public System.IO.Stream Stream
{
get
{
return _stream;
}
}
// Create a stream
// Header
// 4 bytes "RIFF"
// 4 bytes chunkSize
// 4 bytes "WAVE"
//
// Subchunk 1
// 4 bytes subchunk ID ("fmt ")
// 4 bytes subchunk size (16)
// 2 bytes audio format (1)
// 2 bytes num channels (1)
// 4 bytes sample rate (8000)
// 4 bytes byte rate = SampleRate * NumChannels * BitsPerSample/8 ()
// 2 bytes block align = NumChannels * BitsPerSample/8 (2)
// 2 bytes bits per sample (16)
//
// Subchunk 2
// 4 bytes subchunk ID ("data")
// 4 bytes subchunk size (2*waveform.Length)
// copy of waveform
private void SetupStream(
Int16[] waveform,
UInt32 sampleRate,
UInt32 samplesPerCycle
)
{
List<Int16[]> waveforms = new List<Int16[]>();
waveforms.Add(waveform);
SetupStream(waveforms, sampleRate, samplesPerCycle);
}
private void SetupStream(
IEnumerable<Int16[]> waveforms,
UInt32 sampleRate,
UInt32 samplesPerCycle
)
{
// Create stream
_stream = new System.IO.MemoryStream();
UInt32 totalWaveformLength = GetSize(waveforms);
_stream.Write(StringToBytes("RIFF"), 0, 4);
_stream.Write(LittleEndian(36 + 2* totalWaveformLength, 4), 0, 4);
_stream.Write(StringToBytes("WAVE"), 0, 4);
_stream.Write(StringToBytes("fmt "), 0, 4);
_stream.Write(LittleEndian(16, 4), 0, 4); // chunk1Size
_stream.Write(LittleEndian(1, 2), 0, 2); // uncompressed linear
_stream.Write(LittleEndian(1, 2), 0, 2); // 1 channel
_stream.Write(LittleEndian(sampleRate, 4), 0, 4); // sampleRate
_stream.Write(LittleEndian(sampleRate * 2, 4), 0, 4); // sampleRate * channels * bitrate / 8
_stream.Write(LittleEndian(2, 2), 0, 2); // block align
_stream.Write(LittleEndian(16, 2), 0, 2); // bits per sample
// Fill in the tone
_stream.Write(StringToBytes("data"), 0, 4);
_stream.Write(LittleEndian(totalWaveformLength * 2, 4), 0, 4);
foreach (Int16[] waveform in waveforms)
{
foreach (Int16 sample in waveform)
{
_stream.Write(LittleEndian(sample, 2), 0, 2);
}
}
_stream.Seek(0, System.IO.SeekOrigin.Begin);
}
private UInt32 GetSize(IEnumerable<Int16[]> partials)
{
UInt32 count = 0;
foreach (Int16[] waveform in partials)
{
count += (UInt32)waveform.Length;
}
return count;
}
private void FillWaveForms(IEnumerable<UInt16[]> partials)
{
foreach (UInt16[] waveform in partials)
{
FillWaveForm(waveform);
}
}
private void FillWaveForm(UInt16[] waveform)
{
foreach (Int16 sample in waveform)
{
_stream.Write(LittleEndian(sample, 2), 0, 2);
}
}
private byte[] StringToBytes(String str)
{
return System.Text.UTF7Encoding.ASCII.GetBytes(str);
}
private byte[] LittleEndian(UInt32 n, int byteCount)
{
byte[] bytes = new byte[byteCount];
int i = 0;
while (i < byteCount)
{
bytes[i] = (byte)n;
i++;
n = n >> 8;
}
return bytes;
}
private byte[] LittleEndian(Int32 n, int byteCount)
{
return LittleEndian((UInt32)n, byteCount);
}
private String _text;
private System.IO.MemoryStream _stream;
}
}

View file

@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MorseTrainer
{
/// <summary>
/// The WordToToneBuilder class builds a tone in a worker
/// thread and makes it available.
/// </summary>
public class WordToToneBuilder
{
public WordToToneBuilder(ToneGenerator toneGenerator)
{
_toneGenerator = toneGenerator;
}
public IAsyncResult StartBuildAsync(String word, AsyncCallback callback)
{
BuildWaverformAsync asyncResult = new BuildWaverformAsync(word, callback);
WaitCallback cb = new WaitCallback(MakeWaveform);
if (System.Threading.ThreadPool.QueueUserWorkItem(cb, asyncResult))
{
}
return asyncResult;
}
private void MakeWaveform(object state)
{
BuildWaverformAsync buildInfo = (BuildWaverformAsync)state;
List<Int16[]> soundsList = new List<short[]>();
_toneGenerator.Update();
foreach (Char c in buildInfo.Word)
{
String morse = MorseInfo.ToMorse(c);
bool first = true;
foreach (Char d in morse)
{
if (first)
{
first = false;
}
else
{
soundsList.Add(_toneGenerator.DotSpaceWaveform);
}
switch (d)
{
case '.':
soundsList.Add(_toneGenerator.DotToneWaveform);
break;
case '-':
soundsList.Add(_toneGenerator.DashToneWaveform);
break;
case ' ':
soundsList.Add(_toneGenerator.WordSpaceWaveform);
break;
}
}
soundsList.Add(_toneGenerator.LetterSpaceWaveform);
// Farnsworth timing
if (_toneGenerator.FarnsworthWPM < _toneGenerator.WPM)
{
soundsList.Add(_toneGenerator.FarnsworthSpacingWaveform(c));
}
}
soundsList.Add(_toneGenerator.WordSpaceWaveform);
WaveStream stream = new WaveStream(buildInfo.Word, soundsList, ToneGenerator.SAMPLES_PER_SECOND, _toneGenerator.SamplesPerCycle);
buildInfo.SetWaveform(stream);
buildInfo.Callback();
}
private ToneGenerator _toneGenerator;
}
public class BuildWaverformAsync : IAsyncResult
{
public BuildWaverformAsync(String word, AsyncCallback callback)
{
_word = word;
_callback = callback;
_waveStream = null;
}
public void SetWaveform(WaveStream wavestream)
{
_waveStream = wavestream;
}
public object AsyncState
{
get
{
return _waveStream;
}
}
public WaitHandle AsyncWaitHandle
{
get
{
throw new NotImplementedException();
}
}
public bool CompletedSynchronously
{
get
{
return false;
}
}
public bool IsCompleted
{
get
{
return _waveStream != null;
}
}
public String Word
{
get
{
return _word;
}
}
public void Callback()
{
if (_callback != null)
{
_callback(this);
}
}
private String _word;
private AsyncCallback _callback;
private WaveStream _waveStream;
}
}