using System; using System.Linq; using System.Collections.Generic; using NAudio.Wave; using Cairo; namespace MorseCodeParser { enum MorseTokenType { ToneStart, ToneEnd } struct MorseToken { public MorseTokenType Type; public int Index; public MorseToken(MorseTokenType inType, int inIndex) { Type = inType; Index = inIndex; } public override string ToString() { return string.Format("[MorseToken {0}\tat {1}]", Type, Index); } } class MorseTone { public int Index; public int Length; public MorseTone(int inIndex, int inLength) { Index = inIndex; Length = inLength; } public override string ToString() { return string.Format("[MorseTone at {0} of length {1}]", Index, Length); } } public class AudioMorseDecoder { private string filename; private ISampleProvider samples; private readonly int floatBufferSize = 220500 * 4; private readonly int windowSize = 100; private readonly int stepSize = 25; private readonly float threshold = 0.8f; public AudioMorseDecoder(string inFilename) { filename = inFilename; AudioFileReader reader = new AudioFileReader(filename); samples = reader.ToSampleProvider(); } private List analyzeSamples() { List result = new List(); int buffersRead = 0; float[] rawBuffer = new float[floatBufferSize]; float[] windowedBuffer = new float[(floatBufferSize - windowSize) / stepSize]; int samplesRead; while(true) { samplesRead = samples.Read(rawBuffer, 0, floatBufferSize); for(int i = 0, s = 0; i < samplesRead - windowSize; i += stepSize, s++) windowedBuffer[s] = rawBuffer.Skip(i).Take(windowSize).Max(); for(int i = 1; i < windowedBuffer.Length; i++) { if(windowedBuffer[i - 1] < threshold && windowedBuffer[i] >= threshold) result.Add(new MorseToken(MorseTokenType.ToneStart, (buffersRead * windowedBuffer.Length) + i)); else if(windowedBuffer[i - 1] > threshold && windowedBuffer[i] <= threshold) result.Add(new MorseToken(MorseTokenType.ToneEnd, (buffersRead * windowedBuffer.Length) + i)); } if(samplesRead < floatBufferSize) break; buffersRead++; } return result; } public List ExtractWords(bool renderImage = false) { List tokens = analyzeSamples(); if(renderImage) drawFingerprint(tokens); List tones = new List(); for(int i = 0; i < tokens.Count; i += 2) tones.Add(new MorseTone(tokens[i].Index, tokens[i + 1].Index - tokens[i].Index)); int longestToneLength = tones.Max((MorseTone tone) => tone.Length); int shortestToneLength = tones.Min((MorseTone tone) => tone.Length); int shortestTonePause = tones.Zip(tones.Skip(1), (a, b) => Tuple.Create(a, b)) .Min((Tuple tonePair) => tonePair.Item2.Index - (tonePair.Item1.Index + tonePair.Item1.Length) ); int longestTonePause = tones.Zip(tones.Skip(1), (a, b) => Tuple.Create(a, b)) .Max((Tuple tonePair) => tonePair.Item2.Index - (tonePair.Item1.Index + tonePair.Item1.Length) ); int mediumTonePause = shortestTonePause * 3; List resultWords = new List(); string currentWord = ""; for(int i = 0; i < tones.Count; i++) { if(i > 0) { int toneSpacing = tones[i].Index - (tones[i - 1].Index + tones[i - 1].Length); int distanceToShortestGap = Math.Abs(shortestTonePause - toneSpacing); int distanceToMediumGap = Math.Abs(mediumTonePause - toneSpacing); int distanceToLongestGap = Math.Abs(longestTonePause - toneSpacing); if(distanceToMediumGap < distanceToLongestGap && distanceToMediumGap < distanceToShortestGap) { // It's a letter spacing! currentWord += " "; } if(distanceToLongestGap < distanceToMediumGap && distanceToLongestGap < distanceToShortestGap) { // It's a word spacing! resultWords.Add(currentWord); currentWord = string.Empty; } } if(Math.Abs(longestToneLength - tones[i].Length) < Math.Abs(shortestToneLength - tones[i].Length)) { // It's a long tone currentWord += "-"; } else { // It's a short tone currentWord += "."; } } // Add the last word decoded to the list resultWords.Add(currentWord); return resultWords; } private void drawFingerprint(List tokens) { int fingerprintWidth = 1200, fingerprintHeight = 100; float scaleFactorX = (float)fingerprintWidth / tokens.Last().Index; using(ImageSurface image = new ImageSurface(Format.Argb32, fingerprintWidth, fingerprintHeight)) using(Context context = new Context(image)) { context.Antialias = Antialias.Subpixel; context.SetSourceColor(new Color(1, 0.4901, 0.1019, 0.8)); for(int i = 0; i < tokens.Count; i += 2) { MorseToken startToken = tokens[i]; MorseToken endToken = tokens[i + 1]; context.NewPath(); Rectangle tokenArea = new Rectangle( startToken.Index * scaleFactorX, 0, (endToken.Index - startToken.Index) * scaleFactorX, fingerprintHeight ); context.Rectangle(tokenArea); context.Fill(); } image.WriteToPng("fingerprint.png"); } } } }