AudioMorseDecoder/MorseCodeParser/AudioMorseDecoder.cs

202 lines
6.5 KiB
C#
Raw Permalink Normal View History

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<MorseToken> analyzeSamples()
{
List<MorseToken> result = new List<MorseToken>();
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<string> ExtractWords(bool renderImage = false)
{
List<MorseToken> tokens = analyzeSamples();
if(renderImage)
drawFingerprint(tokens);
List<MorseTone> tones = new List<MorseTone>();
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<MorseTone, MorseTone> 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<MorseTone, MorseTone> tonePair) =>
tonePair.Item2.Index - (tonePair.Item1.Index + tonePair.Item1.Length)
);
int mediumTonePause = shortestTonePause * 3;
List<string> resultWords = new List<string>();
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<MorseToken> 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");
}
}
}
}