commit 37f310a1b18937640c84b39c6eee1b5a48452c56 Author: Starbeamrainbowlabs Date: Sat May 27 17:48:59 2017 +0100 Initial commit. That was a fun challenge! diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..08185c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,434 @@ + +# Created by https://www.gitignore.io/api/visualstudio,monodevelop,csharp + +### Csharp ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# 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 + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +*_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 +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# 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 add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# 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 database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# 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 +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Typescript v1 declaration files +typings/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +### MonoDevelop ### +#User Specific +*.usertasks + +#Mono Project Files +*.resources +test-results/ + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files + +# User-specific files (MonoDevelop/Xamarin Studio) + +# Build results + +# Visual Studio 2015 cache/options directory +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results + +# NUNIT + +# Build Results of an ATL Project + +# .NET Core + + +# Chutzpah Test files + +# Visual C++ cache files + +# Visual Studio profiler + +# TFS 2012 Local Workspace + +# Guidance Automation Toolkit + +# ReSharper is a .NET coding add-in + +# JustCode is a .NET coding add-in + +# TeamCity is a build add-in + +# DotCover is a Code Coverage Tool + +# Visual Studio code coverage results + +# NCrunch + +# MightyMoose + +# Web workbench (sass) + +# Installshield output folder + +# DocProject is a documentation generator add-in + +# Click-Once directory + +# Publish Web Output +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted + +# NuGet Packages +# The packages folder can be ignored because of Package Restore +# except build/, which is used as an MSBuild target. +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignorable files + +# Microsoft Azure Build Output + +# Microsoft Azure Emulator + +# Windows Store app package directories and files + +# Visual Studio cache files +# files ending in .cache can be ignored +# but keep track of directories ending in .cache + +# Others + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) + +# SQL Server files + +# Business Intelligence projects + +# Microsoft Fakes + +# GhostDoc plugin setting file + +# Node.js Tools for Visual Studio + +# Typescript v1 declaration files + +# Visual Studio 6 build log + +# Visual Studio 6 workspace options file + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) + +# Visual Studio LightSwitch build output + +# Paket dependency manager + +# FAKE - F# Make + +# JetBrains Rider + +# CodeRush + +# Python Tools for Visual Studio (PTVS) + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Telerik's JustMock configuration file + +# BizTalk build output + +# End of https://www.gitignore.io/api/visualstudio,monodevelop,csharp diff --git a/MorseCodeParser.sln b/MorseCodeParser.sln new file mode 100644 index 0000000..f1dd176 --- /dev/null +++ b/MorseCodeParser.sln @@ -0,0 +1,17 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MorseCodeParser", "MorseCodeParser\MorseCodeParser.csproj", "{431E17FD-CF16-4B9E-B282-D71CFD2B3AE6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {431E17FD-CF16-4B9E-B282-D71CFD2B3AE6}.Debug|x86.ActiveCfg = Debug|x86 + {431E17FD-CF16-4B9E-B282-D71CFD2B3AE6}.Debug|x86.Build.0 = Debug|x86 + {431E17FD-CF16-4B9E-B282-D71CFD2B3AE6}.Release|x86.ActiveCfg = Release|x86 + {431E17FD-CF16-4B9E-B282-D71CFD2B3AE6}.Release|x86.Build.0 = Release|x86 + EndGlobalSection +EndGlobal diff --git a/MorseCodeParser/AudioMorseDecoder.cs b/MorseCodeParser/AudioMorseDecoder.cs new file mode 100644 index 0000000..a345437 --- /dev/null +++ b/MorseCodeParser/AudioMorseDecoder.cs @@ -0,0 +1,201 @@ +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"); + } + } + } +} diff --git a/MorseCodeParser/MorseCodeParser.csproj b/MorseCodeParser/MorseCodeParser.csproj new file mode 100644 index 0000000..03c19c6 --- /dev/null +++ b/MorseCodeParser/MorseCodeParser.csproj @@ -0,0 +1,49 @@ + + + + Debug + x86 + {431E17FD-CF16-4B9E-B282-D71CFD2B3AE6} + Exe + MorseCodeParser + MorseCodeParser + v4.6.1 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + true + x86 + + + true + bin\Release + prompt + 4 + true + x86 + + + Project + "/home/sbrl/Music/Morse Code 2.wav" + + + + + ..\packages\NAudio.1.8.0\lib\net35\NAudio.dll + + + + + + + + + + + \ No newline at end of file diff --git a/MorseCodeParser/MorseDecoder.cs b/MorseCodeParser/MorseDecoder.cs new file mode 100644 index 0000000..43144ec --- /dev/null +++ b/MorseCodeParser/MorseDecoder.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; + +namespace SBRL.Algorithms.MorseCodeTranslator +{ + /// + /// A simple class to translate a morse code string into a normal string. + /// + /// Mozilla Public License version 2.0 + /// + /// Starbeamrainbowlabs (https://starbeamrainbowlabs.com/) + /// + /// v0.1 - 26th May 2017: + /// - Creation! 😁 + /// + public static class MorseDecoder + { + /// + /// The morse code lookup table. Use the methods in this class is possible, + /// rather than accessing this lookup table directly! + /// + public static Dictionary morseCodeLookup = 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', + [".----"] = '1', + ["..---"] = '2', + ["...--"] = '3', + ["....-"] = '4', + ["....."] = '5', + ["-...."] = '6', + ["--..."] = '7', + ["---.."] = '8', + ["----."] = '9', + ["-----"] = '0', + }; + + /// + /// Translates a single letter from morse code. + /// + /// The morse code to translate. + /// The translated letter. + public static char TranslateLetter(string morseSource) + { + return morseCodeLookup[morseSource.Trim()]; + } + + /// + /// Translates a string of space-separated morse code strings from morse code. + /// + /// The morse code to translate. + /// The translated word. + public static string TranslateWord(string morseSource) + { + string result = string.Empty; + + string[] morseLetters = morseSource.Split(" ".ToCharArray()); + + foreach(string morseLetter in morseLetters) + result += TranslateLetter(morseLetter); + + return result; + } + + /// + /// Translates a list of morse-encoded words. + /// + /// The morse-encoded words to decipher. + /// The decoded text. + public static string TranslateText(IEnumerable morseSources) + { + string result = string.Empty; + foreach(string morseSource in morseSources) + result += $"{TranslateWord(morseSource)} "; + return result.Trim(); + } + } +} diff --git a/MorseCodeParser/Program.cs b/MorseCodeParser/Program.cs new file mode 100644 index 0000000..ec99f4f --- /dev/null +++ b/MorseCodeParser/Program.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +using SBRL.Algorithms.MorseCodeTranslator; + +namespace MorseCodeParser +{ + class MainClass + { + public static void Main(string[] args) + { + AudioMorseDecoder decoder = new AudioMorseDecoder(args[0]); + List morseWords = decoder.ExtractWords(true); + Console.WriteLine("Decoded morse code:"); + Console.WriteLine(string.Join("\n", morseWords)); + Console.WriteLine("Deciphered text: {0}", MorseDecoder.TranslateText(morseWords)); + } + } +} diff --git a/MorseCodeParser/Properties/AssemblyInfo.cs b/MorseCodeParser/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5dc09f8 --- /dev/null +++ b/MorseCodeParser/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("MorseCodeParser")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/MorseCodeParser/packages.config b/MorseCodeParser/packages.config new file mode 100644 index 0000000..668a814 --- /dev/null +++ b/MorseCodeParser/packages.config @@ -0,0 +1,4 @@ + + + +