From 2e264317ad02b2a0d4d2004d3f5414e6a4387fe3 Mon Sep 17 00:00:00 2001 From: yunus Date: Tue, 10 Oct 2023 06:16:43 +0300 Subject: [PATCH 01/18] Micro-Optimization and updated the xml docs. --- Example/ConsoleExample.csproj | 4 ++ MorseSharp/Converter/TextMorseConverter.cs | 45 +++++++++++----------- MorseSharp/MorseRepo/IMorseConverter.cs | 11 +++--- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/Example/ConsoleExample.csproj b/Example/ConsoleExample.csproj index e671f00..e914797 100644 --- a/Example/ConsoleExample.csproj +++ b/Example/ConsoleExample.csproj @@ -8,6 +8,10 @@ enable + + + + diff --git a/MorseSharp/Converter/TextMorseConverter.cs b/MorseSharp/Converter/TextMorseConverter.cs index 5054d0e..6b307e5 100644 --- a/MorseSharp/Converter/TextMorseConverter.cs +++ b/MorseSharp/Converter/TextMorseConverter.cs @@ -3,21 +3,20 @@ namespace MorseSharp.Converter { - /// /// Decode/encode's morse text. /// public class TextMorseConverter : IMorseConverter { - private readonly StringBuilder? strBuilder; + private readonly StringBuilder strBuilder; private readonly Dictionary morseChar; private readonly Language language; private readonly Language nonLatin = Language.Kurdish | Language.Arabic; /// - /// Create a new instance of type . + /// Initializes a new instance of the class. /// - /// The language convert from it to morse code. + /// The language to convert from to morse code. public TextMorseConverter(Language Language) { language = Language; @@ -28,30 +27,32 @@ public TextMorseConverter(Language Language) /// /// Converts the given string sentence to morse code. /// - /// The to convert to morse code. - /// of the morse result. - /// Throws if a character doesn't presented. - /// Throws if the string text was null. + /// The text to convert to morse code. + /// A string containing the morse code result. + /// Thrown if a character is not presented in the character mapping. + /// Thrown if the input string is null. public string ConvertTextToMorse(string Text) { - strBuilder!.Clear(); if (Text is not null) { + strBuilder.Clear(); if ((language & nonLatin) == 0) - Text = Text.ToUpper(); + Text = Text.ToUpper(); + - for (int i = 0; i < Text.Length; i++) { - if (morseChar.ContainsKey(Text[i])) + if (morseChar.TryGetValue(Text[i], out string val)) { - strBuilder.Append(morseChar[Text[i]]); + // Check if it's not the last character before appending a space if (i < Text.Length - 1) { - strBuilder.Append(' '); + strBuilder.Append(val).Append(' '); } + else + strBuilder.Append(val); } else @@ -67,18 +68,18 @@ public string ConvertTextToMorse(string Text) } /// - /// Converts a morse message to english sentence + /// Converts a morse message to an English sentence. /// - /// The of the morse message. - /// of the converted morse. - /// Throws if the morse letter was not presented. - /// Throw if Morse param was null. + /// The morse message to convert. + /// A string containing the converted morse message. + /// Thrown if a morse letter is not presented in the character mapping. + /// Thrown if the input morse message is null. public string ConvertMorseToText(string Morse) { - strBuilder!.Clear(); if (Morse is not null) { + strBuilder.Clear(); var words = Morse.Split(' '); for (int i = 0; i < words.Length; i++) { @@ -88,11 +89,11 @@ public string ConvertMorseToText(string Morse) { if (morseChar.Values.Contains(words[i])) { - var word = morseChar.FirstOrDefault(x => x.Value == words[i]).Key; + var word = morseChar.First(x => x.Value == words[i]).Key; strBuilder.Append(word); } else - throw new KeyNotFoundException($"The {Morse[i]} is not presented."); + throw new KeyNotFoundException($"The {words[i]} is not presented."); } else strBuilder.Append(' '); diff --git a/MorseSharp/MorseRepo/IMorseConverter.cs b/MorseSharp/MorseRepo/IMorseConverter.cs index cad78ea..aec7365 100644 --- a/MorseSharp/MorseRepo/IMorseConverter.cs +++ b/MorseSharp/MorseRepo/IMorseConverter.cs @@ -8,14 +8,15 @@ public interface IMorseConverter /// /// Converts the given string sentence to morse code. /// - /// The to convert to morse code. - /// of the morse result. + /// The text to convert to morse code. + /// A string containing the morse code result. string ConvertTextToMorse(string Text); + /// - /// Converts a morse message to english sentence + /// Converts a morse message to an English sentence. /// - /// The of the morse message. - /// of the converted morse. + /// The morse message to convert. + /// A string containing the converted morse message. string ConvertMorseToText(string Morse); } } From 9b86113827848a2f93a74732d1a308f209f0c161 Mon Sep 17 00:00:00 2001 From: p6laris Date: Wed, 15 Nov 2023 03:22:20 +0300 Subject: [PATCH 02/18] Upgrade to .NET 8 --- AudioExample/AudioExample.csproj | 22 ++++++++++------------ Example/ConsoleExample.csproj | 9 ++------- MorseSharp.sln | 4 ++-- MorseSharp/MorseSharp.csproj | 3 ++- MorseTest/MorseTest.csproj | 9 ++------- 5 files changed, 18 insertions(+), 29 deletions(-) diff --git a/AudioExample/AudioExample.csproj b/AudioExample/AudioExample.csproj index f611b1c..1372271 100644 --- a/AudioExample/AudioExample.csproj +++ b/AudioExample/AudioExample.csproj @@ -1,14 +1,12 @@  - - - WinExe - net7.0-windows - enable - true - enable - - - - - + + WinExe + net8.0-windows + enable + true + enable + + + + \ No newline at end of file diff --git a/Example/ConsoleExample.csproj b/Example/ConsoleExample.csproj index e914797..d23c224 100644 --- a/Example/ConsoleExample.csproj +++ b/Example/ConsoleExample.csproj @@ -1,19 +1,14 @@ - Exe - net7.0 - + net8.0 enable enable - - - - + \ No newline at end of file diff --git a/MorseSharp.sln b/MorseSharp.sln index c08c193..c2c67c7 100644 --- a/MorseSharp.sln +++ b/MorseSharp.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.3.32819.101 @@ -9,7 +9,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MorseTest", "MorseTest\Mors EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleExample", "Example\ConsoleExample.csproj", "{5209A0AB-CB2B-4E0E-8A53-5193607516C3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AudioExample", "AudioExample\AudioExample.csproj", "{4A028B6C-2BB1-4A57-B259-9AB94EE3D353}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioExample", "AudioExample\AudioExample.csproj", "{4A028B6C-2BB1-4A57-B259-9AB94EE3D353}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/MorseSharp/MorseSharp.csproj b/MorseSharp/MorseSharp.csproj index 771beb5..096609a 100644 --- a/MorseSharp/MorseSharp.csproj +++ b/MorseSharp/MorseSharp.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 enable enable True @@ -21,6 +21,7 @@ README.md MIT True + true diff --git a/MorseTest/MorseTest.csproj b/MorseTest/MorseTest.csproj index deb43ea..597c534 100644 --- a/MorseTest/MorseTest.csproj +++ b/MorseTest/MorseTest.csproj @@ -1,13 +1,10 @@ - - net7.0 + net8.0 enable enable - false - @@ -20,9 +17,7 @@ all - - - + \ No newline at end of file From 9bf31da852ab54c492597031cd52a4f21cc1ba3c Mon Sep 17 00:00:00 2001 From: p6laris Date: Wed, 15 Nov 2023 03:30:16 +0300 Subject: [PATCH 03/18] Added Helpers NumericBitConverter and SpanByteWritter, Updated the chunks and audio converter with ref structs --- MorseSharp/Audio/AudioConverter.cs | 260 ++++++++-------------- MorseSharp/Audio/Chunks/DataChunk.cs | 67 ++++-- MorseSharp/Audio/Chunks/FormatChunk.cs | 46 ---- MorseSharp/Audio/Chunks/FromatChunk.cs | 52 +++++ MorseSharp/Audio/Chunks/HeaderChunk.cs | 72 +++--- MorseSharp/Audio/Chunks/WaveChunk.cs | 16 -- MorseSharp/Helpers/NumericBitConverter.cs | 101 +++++++++ MorseSharp/Helpers/SpanByteWritter.cs | 76 +++++++ 8 files changed, 407 insertions(+), 283 deletions(-) delete mode 100644 MorseSharp/Audio/Chunks/FormatChunk.cs create mode 100644 MorseSharp/Audio/Chunks/FromatChunk.cs delete mode 100644 MorseSharp/Audio/Chunks/WaveChunk.cs create mode 100644 MorseSharp/Helpers/NumericBitConverter.cs create mode 100644 MorseSharp/Helpers/SpanByteWritter.cs diff --git a/MorseSharp/Audio/AudioConverter.cs b/MorseSharp/Audio/AudioConverter.cs index 9b04704..291f402 100644 --- a/MorseSharp/Audio/AudioConverter.cs +++ b/MorseSharp/Audio/AudioConverter.cs @@ -1,201 +1,131 @@ using MorseSharp.Audio.Chunks; -namespace MorseSharp.Audio +namespace MorseSharp.Audio; + +[StructLayout(LayoutKind.Sequential)] +public ref struct AudioConverter { - public class AudioConverter - { - // Character speed in WPM - private int CharacterSpeed { get; set; } - // Overall speed in WPM (must be <= character speed) - private int WordSpeed { get; set; } - // Tone frequency - private double Frequency { get; set; } - - //Language - private Language Language { get; set; } - - // NonLatin Languages. - Language NonLatin = Language.Kurdish | Language.Arabic; - public AudioConverter(Language language, int charSpeed, int wordSpeed, double frequency) - { - CharacterSpeed = charSpeed; - WordSpeed = wordSpeed; - Frequency = frequency; - Language = language; - } - public AudioConverter(Language language, int charSpeed, int wordSpeed) : this(language, charSpeed, wordSpeed, 600.0) { } - public AudioConverter(Language language, int wpm) : this(language, wpm, wpm) { } - public AudioConverter(Language language) : this(language, 20) { } - public AudioConverter() : this(Language.English) { } + private readonly int _characterSpeed; + private readonly int _wordSpeed; + private readonly double _frequency; - // Return given number of seconds of sine wave - private Span GetWave(double seconds) - { - Span waveArray; - int samples = (int)(11025 * seconds); + public AudioConverter(int characterSpeed, int wordSpeed, double frequency) + { + if (characterSpeed < wordSpeed) + throw new SmallerWordSpeedException(characterSpeed, wordSpeed); - waveArray = new short[samples]; - for (int i = 0; i < samples; i++) - { - waveArray[i] = Convert.ToInt16(32760 * Math.Sin(i * 2 * Math.PI * Frequency / 11025)); - } + _characterSpeed = characterSpeed; + _wordSpeed = wordSpeed; + _frequency = frequency; - return waveArray; - } + } - // Return given number of seconds of flatline. This could also be - // achieved with slnt chunks inside a wavl chunk, but the resulting - // file might not be universally readable. If saving space is that - // important, it would be better to compress the output as mp3 or ogg - // anyway. - private Span GetSilence(double seconds) - { - Span waveArray; - int samples = (int)(11025 * seconds); - waveArray = new short[samples]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Span GetDot() => GetWave(1.2 / _characterSpeed); - return waveArray; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Span GetDash() => GetWave(3.6 / _characterSpeed); - // Dot -- 1 unit long - private Span GetDot() - { - return GetWave(1.2 / CharacterSpeed); - } - // Dash -- 3 units long - private Span GetDash() - { - return GetWave(3.6 / CharacterSpeed); - } + private Span GetEleCharSpace() => GetSilence(1.2 / _characterSpeed); - // Inter-element space -- 1 unit long - private Span GetInterEltSpace() - { - return GetSilence(1.2 / CharacterSpeed); - } + private Span GetInterCharSpace() + { + double delay = (60.0 / _wordSpeed) - (32.0 / _characterSpeed); + double spaceLength = 3 * delay / 19; + return GetSilence(spaceLength); + } - // Space between letters -- nominally 3 units, but adjusted for - // Farnsworth timing (if word speed is lower than character - // speed) based on ARRL's Morse code timing standard: - // http://www.arrl.org/files/file/Technology/x9004008.pdf - private Span GetInterCharSpace() - { - double delay = (60.0 / WordSpeed) - (32.0 / CharacterSpeed); - double spaceLength = 3 * delay / 19; - return GetSilence(spaceLength); - } + private Span GetInterWordSpace() + { + double delay = (60.0 / _wordSpeed) - (32.0 / _characterSpeed); + double spaceLength = 7 * delay / 19; + return GetSilence(spaceLength); + } + [SkipLocalsInit] + private Span GetWave(double seconds) + { + int samples = (int)(11025 * seconds); + using SpanOwner owner = SpanOwner.Allocate(samples); + Span data = owner.Span; - // Space between words -- nominally 7 units, but adjusted for - // Farnsworth timing in case word speed is lower than character - // speed. - private Span GetInterWordSpace() + for (int i = 0; i < samples; i++) { - double delay = (60.0 / WordSpeed) - (32.0 / CharacterSpeed); - double spaceLength = 7 * delay / 19; - return GetSilence(spaceLength); + data[i] = Convert.ToInt16(32760 * Math.Sin(i * 2 * Math.PI * _frequency / 11025)); } - // Return a single character as a waveform - private Span GetCharacter(string character) - { - Span space = GetInterEltSpace(); - Span dot = GetDot(); - Span dash = GetDash(); - List morseChar = new List(); - - string morseSymbol = string.Empty; - - if (MorseCharacters.GetLanguageCharacter(Language).ContainsKey(character[0])) - morseSymbol = MorseCharacters.GetLanguageCharacter(Language)[character[0]]; - else - throw new KeyNotFoundException($"{character} is not presented in {nameof(Language)} language."); + return data; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Span GetSilence(double seconds) + { + int samples = (int)(11025 * seconds); + using SpanOwner owner = SpanOwner.Allocate(samples, AllocationMode.Clear); + Span data = owner.Span; + return data; - for (int i = 0; i < morseSymbol.Length; i++) - { - if (i > 0) - morseChar.AddRange(space.ToArray()); - if (morseSymbol[i] == '_') - morseChar.AddRange(dash.ToArray()); - else if (morseSymbol[i] == '.') - morseChar.AddRange(dot.ToArray()); - } + } - return morseChar.ToArray(); - } + private Span GetCharacter(string morseSymbol) + { + List data = new List(); - // Return a word as a waveform - private Span GetWord(string word) + for (int i = 0; i < morseSymbol.Length; i++) { - List data = new List(); - - for (int i = 0; i < word.Length; i++) - { - if (i > 0) - data.AddRange(GetInterCharSpace().ToArray()); - if (word[i] == '<') - { - // Prosign - int end = word.IndexOf('>', i); - if (end < 0) - throw new ArgumentException(); - data.AddRange(GetCharacter(word.Substring(i, end + 1 - i)).ToArray()); - i = end; - } - else - { - data.AddRange(GetCharacter(word[i].ToString()).ToArray()); - } - } - - return data.ToArray(); + if (i > 0) + data.AddRange(GetEleCharSpace()); + if (morseSymbol[i] == '_') + data.AddRange(GetDash()); + else if (morseSymbol[i] == '.') + data.AddRange(GetDot()); } - // Return a string (lower case text only, unrecognized characters - // throw an exception -- see Characters.cs for the list of recognized - // characters) as a waveform wrapped in a DataChunk, ready to by added - // to a wave file. - private DataChunk GetText(string text) - { - List data = new List(); + return CollectionsMarshal.AsSpan(data); + } - string[] words = text.Split(' '); + private Span GenerateWav(string text) + { + List data = new List(); - for (int i = 0; i < words.Length; i++) - { - if (i > 0) - data.AddRange(GetInterWordSpace().ToArray()); - data.AddRange(GetWord(words[i]).ToArray()); - } + ReadOnlySpan sMorse = text.AsSpan(); + Span splitedRange = new Span(new Range[sMorse.Count(' ') + 1]); - // Pad the end with a little bit of silence. Otherwise the last - // character may sound funny in some media players. - data.AddRange(GetInterCharSpace().ToArray()); + sMorse.Split(splitedRange, ' ', StringSplitOptions.None); - DataChunk dataChunk = new DataChunk(data.ToArray()); + for (int i = 0; i < splitedRange.Length; i++) + { + var morseSymbol = sMorse.Slice(splitedRange[i].Start.Value, splitedRange[i].End.Value - splitedRange[i].Start.Value); + if (i > 0) + data.AddRange(GetInterWordSpace()); - return dataChunk; + data.AddRange(GetCharacter(morseSymbol.ToString())); } + // Pad the end with a little bit of silence. Otherwise, the last character may sound funny in some media players. + data.AddRange(GetInterCharSpace()); - // Returns a byte array in the Wave file format containing the given - // text in morse code - internal Memory ConvertToMorse(string text) - { - DataChunk data = default!; - if ((Language & NonLatin) == 0) - data = GetText(text.ToUpper()); - else - data = GetText(text); - - FormatChunk formatChunk = new FormatChunk(); - HeaderChunk headerChunk = new HeaderChunk(formatChunk, data); - return headerChunk.ToBytes(); - } + return CollectionsMarshal.AsSpan(data); + } + [SkipLocalsInit] + internal void ConvertToAudio(string morse, out Span destination) + { + var data = GenerateWav(morse); + ValueDataChunk dataChunk = new ValueDataChunk(data); + + ValueFormatChunk formatChunk = new ValueFormatChunk(); + ValueHeaderChunk headerChunk = new ValueHeaderChunk(in dataChunk, in formatChunk); + + Span bytes = headerChunk.ToBytes(); + using SpanOwner owner = SpanOwner.Allocate(bytes.Length); + destination = owner.Span; + + bytes.CopyTo(destination); } -} + + +} \ No newline at end of file diff --git a/MorseSharp/Audio/Chunks/DataChunk.cs b/MorseSharp/Audio/Chunks/DataChunk.cs index f46957a..737a9aa 100644 --- a/MorseSharp/Audio/Chunks/DataChunk.cs +++ b/MorseSharp/Audio/Chunks/DataChunk.cs @@ -1,34 +1,55 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using MorseSharp.Helpers; -namespace MorseSharp.Audio.Chunks +namespace MorseSharp.Audio.Chunks; +/// +/// Represents a data chunk for WAVE files. +/// +[StructLayout(LayoutKind.Sequential)] +public readonly ref struct ValueDataChunk { - internal class DataChunk : WaveChunk + + private readonly Span _chunkData; + private readonly uint _chunkSize; + + /// + /// Initializes a new instance of the struct with the provided data. + /// + /// The data to be stored in the chunk. + public ValueDataChunk(Span data) { - public Memory ChunkData { get; set; } + _chunkData = data; + _chunkSize = (uint)(data.Length * 2); + } - public DataChunk(short[] data) - { - ChunkId = "data".ToCharArray(); - ChunkSize = (uint)(data.Length * 2); - ChunkData = data; - } + public int Capacity => (2 * 4) + (_chunkData.Length * 2); + /// + /// Gets the size of the data chunk in bytes. + /// + /// The size of the data chunk in bytes. + public uint GetChunkSize() => _chunkSize; - public override Memory ToBytes() - { - List bytes = new List(); + /// + /// Converts the data chunk to a byte array. + /// + /// A containing the data chunk as bytes. + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span ToBytes() + { + Span chunkId = [100, 97, 116, 97]; - bytes.AddRange(Encoding.UTF8.GetBytes(ChunkId.ToArray())); - bytes.AddRange(BitConverter.GetBytes(ChunkSize)); + using SpanOwner owner = SpanOwner.Allocate(Capacity); + Span data = owner.Span; - - foreach (short datum in ChunkData.ToArray()) - bytes.AddRange(BitConverter.GetBytes(datum)); + SpanByteWriter writer = new SpanByteWriter(ref data); - return bytes.ToArray(); + writer.AddRange(chunkId); + writer.AddRangeBit(_chunkSize); + for (int i = 0; i < _chunkData.Length; i++) + { + writer.AddRangeBit(_chunkData[i]); } + + return owner.Span; } } diff --git a/MorseSharp/Audio/Chunks/FormatChunk.cs b/MorseSharp/Audio/Chunks/FormatChunk.cs deleted file mode 100644 index 139c1af..0000000 --- a/MorseSharp/Audio/Chunks/FormatChunk.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MorseSharp.Audio.Chunks -{ - internal class FormatChunk : WaveChunk - { - public short CompressionCode { get; set; } - public short NumChannels { get; set; } - public uint SampleRate { get; set; } - public uint BytesPerSecond { get; set; } - public short BlockAlign { get; set; } - public short SignificantBits { get; set; } - - public FormatChunk() - { - ChunkId = "fmt ".ToCharArray(); - ChunkSize = 16; - CompressionCode = 1; - NumChannels = 1; - SampleRate = 11025; - BytesPerSecond = 22050; - BlockAlign = 2; - SignificantBits = 16; - } - - public override Memory ToBytes() - { - List bytes = new List(); - - bytes.AddRange(Encoding.UTF8.GetBytes(ChunkId.ToArray())); - bytes.AddRange(BitConverter.GetBytes(ChunkSize)); - bytes.AddRange(BitConverter.GetBytes(CompressionCode)); - bytes.AddRange(BitConverter.GetBytes(NumChannels)); - bytes.AddRange(BitConverter.GetBytes(SampleRate)); - bytes.AddRange(BitConverter.GetBytes(BytesPerSecond)); - bytes.AddRange(BitConverter.GetBytes(BlockAlign)); - bytes.AddRange(BitConverter.GetBytes(SignificantBits)); - - return bytes.ToArray(); - } - } -} diff --git a/MorseSharp/Audio/Chunks/FromatChunk.cs b/MorseSharp/Audio/Chunks/FromatChunk.cs new file mode 100644 index 0000000..deb7c08 --- /dev/null +++ b/MorseSharp/Audio/Chunks/FromatChunk.cs @@ -0,0 +1,52 @@ +using MorseSharp.Helpers; + +namespace MorseSharp.Audio.Chunks; + +[StructLayout(LayoutKind.Sequential)] +public readonly ref struct ValueFormatChunk +{ + private readonly short CompressionCode; + private readonly short NumChannels; + private readonly uint SampleRate; + private readonly uint BytesPerSecond; + private readonly short BlockAlign; + private readonly short SignificantBits; + private readonly uint ChunkSize; + + + public ValueFormatChunk() + { + CompressionCode = 1; + NumChannels = 1; + SampleRate = 11025; + BytesPerSecond = 22050; + BlockAlign = 2; + SignificantBits = 16; + ChunkSize = 16; + } + + public int Capacity => (4 * 4) + (2 * 4); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span ToBytes() + { + Span chunkId = [102, 109, 116, 32]; + + using SpanOwner owner = SpanOwner.Allocate(Capacity); + Span data = owner.Span; + + SpanByteWriter writer = new SpanByteWriter(ref data); + + writer.AddRange(chunkId); + writer.AddRangeBit(ChunkSize); + writer.AddRangeBit(CompressionCode); + writer.AddRangeBit(NumChannels); + writer.AddRangeBit(SampleRate); + writer.AddRangeBit(BytesPerSecond); + writer.AddRangeBit(BlockAlign); + writer.AddRangeBit(SignificantBits); + + return owner.Span; + } + +} \ No newline at end of file diff --git a/MorseSharp/Audio/Chunks/HeaderChunk.cs b/MorseSharp/Audio/Chunks/HeaderChunk.cs index e0e6bf2..5e4a5b9 100644 --- a/MorseSharp/Audio/Chunks/HeaderChunk.cs +++ b/MorseSharp/Audio/Chunks/HeaderChunk.cs @@ -1,37 +1,43 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using MorseSharp.Helpers; -namespace MorseSharp.Audio.Chunks +namespace MorseSharp.Audio.Chunks; + +public readonly ref struct ValueHeaderChunk { - internal class HeaderChunk : WaveChunk + readonly ValueDataChunk _dataChunk; + readonly ValueFormatChunk _formatChunk; + readonly uint _chunkSize; + readonly int _bufferSize; + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueHeaderChunk(in ValueDataChunk dataChunk, in ValueFormatChunk formatChunk) + { + _dataChunk = dataChunk; + _formatChunk = formatChunk; + _chunkSize = 36 + dataChunk.GetChunkSize(); + _bufferSize = (3 * 4) + dataChunk.Capacity + formatChunk.Capacity; + + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span ToBytes() { - public ReadOnlyMemory RiffType { get; set; } - public FormatChunk FormatChunk { get; set; } - public DataChunk DataChunk { get; set; } - - public HeaderChunk(FormatChunk formatChunk, DataChunk dataChunk) - { - ChunkId = "RIFF".ToCharArray(); - RiffType = "WAVE".ToCharArray(); - FormatChunk = formatChunk; - DataChunk = dataChunk; - ChunkSize = 36 + DataChunk.ChunkSize; - } - - public override Memory ToBytes() - { - List bytes = new List(); - - bytes.AddRange(Encoding.UTF8.GetBytes(ChunkId.ToArray() )); - bytes.AddRange(BitConverter.GetBytes(ChunkSize)); - bytes.AddRange(Encoding.UTF8.GetBytes(RiffType.ToArray())); - bytes.AddRange(FormatChunk.ToBytes().ToArray()); - bytes.AddRange(DataChunk.ToBytes().ToArray()); - - return bytes.ToArray(); - } + Span riffType = [87, 65, 86, 69]; + Span chunkId = [82, 73, 70, 70]; + + using SpanOwner owner = SpanOwner.Allocate(_bufferSize); + Span data = owner.Span; + + SpanByteWriter writer = new SpanByteWriter(ref data); + + writer.AddRange(chunkId); + writer.AddRangeBit(_chunkSize); + writer.AddRange(riffType); + writer.AddRange(_formatChunk.ToBytes()); + writer.AddRange(_dataChunk.ToBytes()); + + + return owner.Span; } -} +} \ No newline at end of file diff --git a/MorseSharp/Audio/Chunks/WaveChunk.cs b/MorseSharp/Audio/Chunks/WaveChunk.cs deleted file mode 100644 index f78e897..0000000 --- a/MorseSharp/Audio/Chunks/WaveChunk.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MorseSharp.Audio.Chunks -{ - internal abstract class WaveChunk - { - public ReadOnlyMemory ChunkId { get; set; } - public uint ChunkSize { get; set; } - - public abstract Memory ToBytes(); - } -} diff --git a/MorseSharp/Helpers/NumericBitConverter.cs b/MorseSharp/Helpers/NumericBitConverter.cs new file mode 100644 index 0000000..ca49be0 --- /dev/null +++ b/MorseSharp/Helpers/NumericBitConverter.cs @@ -0,0 +1,101 @@ +namespace MorseSharp.Helpers; + +/// +/// Provides static methods to convert numeric values to little-endian byte representations. +/// +internal static class NumericBitConverter +{ + /// + /// Converts a 16-bit signed integer to a little-endian byte representation. + /// + /// The 16-bit signed integer to convert. + /// The destination span where the byte representation will be written. + /// Thrown when the destination span does not have a length of at least 2 bytes. + public static void GetBytes(short value, Span destination) + { + if (destination.Length < 2) + { + throw new ArgumentException("Destination span must have a length of at least 2 bytes.", + nameof(destination)); + } + + unsafe + { + fixed (byte* bPtr = &destination.GetPinnableReference()) + { + short* shPtr = (short*)bPtr; + *shPtr = value; + } + } + } + /// + /// Converts a 32-bit signed integer to a little-endian byte representation. + /// + /// The 32-bit signed integer to convert. + /// The destination span where the byte representation will be written. + /// Thrown when the destination span does not have a length of at least 4 bytes. + public static void GetBytes(int value, Span destination) + { + if (destination.Length < 4) + { + throw new ArgumentException("Destination span must have a length of at least 4 bytes.", + nameof(destination)); + } + + unsafe + { + fixed (byte* bPtr = &destination.GetPinnableReference()) + { + int* intPtr = (int*)bPtr; + *intPtr = value; + } + } + } + /// + /// Converts a 32-bit unsigned integer to a little-endian byte representation. + /// + /// The 32-bit unsigned integer to convert. + /// The destination span where the byte representation will be written. + /// Thrown when the destination span does not have a length of at least 4 bytes. + public static void GetBytes(uint value, Span destination) + { + if (destination.Length < 4) + { + throw new ArgumentException("Destination span must have a length of at least 4 bytes.", + nameof(destination)); + } + + unsafe + { + fixed (byte* bPtr = &destination.GetPinnableReference()) + { + uint* uintPtr = (uint*)bPtr; + *uintPtr = value; + } + } + } + + /// + /// Converts a 64-bit signed integer to a little-endian byte representation. + /// + /// The 64-bit signed integer to convert. + /// The destination span where the byte representation will be written. + /// Thrown when the destination span does not have a length of at least 8 bytes. + public static void GetBytes(long value, Span destination) + { + if (destination.Length < 8) + { + throw new ArgumentException("Destination span must have a length of at least 8 bytes.", + nameof(destination)); + } + + unsafe + { + fixed (byte* bPtr = &destination.GetPinnableReference()) + { + long* longPtr = (long*)bPtr; + *longPtr = value; + } + } + } +} \ No newline at end of file diff --git a/MorseSharp/Helpers/SpanByteWritter.cs b/MorseSharp/Helpers/SpanByteWritter.cs new file mode 100644 index 0000000..22cd9b9 --- /dev/null +++ b/MorseSharp/Helpers/SpanByteWritter.cs @@ -0,0 +1,76 @@ +namespace MorseSharp.Helpers +{ + /// + /// A helper struct for writing spans of bytes efficiently. + /// + [StructLayout(LayoutKind.Sequential)] + internal ref struct SpanByteWriter + { + private int offset; + private Span buffer; + + /// + /// Initializes a new instance of the struct with the provided buffer. + /// + /// The target buffer for writing bytes. + public SpanByteWriter(ref Span buffer) + { + this.buffer = buffer; + offset = 0; + } + + /// + /// Adds a range of bytes from a source span to the target buffer. + /// + /// The source span containing the bytes to be added. + /// Thrown when there is not enough space in the buffer to add the source data. + public void AddRange(Span source) + { + if (offset + source.Length > buffer.Length) + throw new ArgumentException("Not enough space in the buffer to add the source data."); + + source.CopyTo(buffer.Slice(offset)); + offset += source.Length; + } + + /// + /// Adds a range of bytes from a 32-bit unsigned integer (value) to the target buffer. + /// + /// The 32-bit unsigned integer value to be added as bytes. + /// true if there is enough space in the buffer for the operation; otherwise, false. + [SkipLocalsInit] + public bool AddRangeBit(uint value) + { + Span bytes = stackalloc byte[4]; + NumericConverter.GetBytes(value, bytes); + + if (offset + bytes.Length > buffer.Length) + { + return false; // Not enough space in the buffer. + } + + bytes.TryCopyTo(buffer.Slice(offset)); + offset += bytes.Length; + return true; + } + + /// + /// Adds a range of bytes from a 16-bit signed integer (value) to the target buffer. + /// + /// The 16-bit signed integer value to be added as bytes. + /// true if there is enough space in the buffer for the operation; otherwise, false. + [SkipLocalsInit] + public bool AddRangeBit(short value) + { + Span bytes = stackalloc byte[2]; + NumericConverter.GetBytes(value, bytes); + + if (offset + bytes.Length > buffer.Length) + return false; // Not enough space in the buffer. + + bytes.TryCopyTo(buffer.Slice(offset)); + offset += bytes.Length; + return true; + } + } +} From d82e86df63671043cf6500628b1061a9378d329b Mon Sep 17 00:00:00 2001 From: p6laris Date: Wed, 15 Nov 2023 03:33:09 +0300 Subject: [PATCH 04/18] Added the exceptions. --- .../Exceptions/SequenceNotFoundException.cs | 19 +++++++++++++++ .../Exceptions/SmallerWordSpeedException.cs | 24 +++++++++++++++++++ .../Exceptions/WordNotPresentedException.cs | 20 ++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 MorseSharp/Exceptions/SequenceNotFoundException.cs create mode 100644 MorseSharp/Exceptions/SmallerWordSpeedException.cs create mode 100644 MorseSharp/Exceptions/WordNotPresentedException.cs diff --git a/MorseSharp/Exceptions/SequenceNotFoundException.cs b/MorseSharp/Exceptions/SequenceNotFoundException.cs new file mode 100644 index 0000000..ff47cdf --- /dev/null +++ b/MorseSharp/Exceptions/SequenceNotFoundException.cs @@ -0,0 +1,19 @@ +namespace MorseSharp.Exceptions +{ + [Serializable] + internal class SequenceNotFoundException : Exception + { + public SequenceNotFoundException() { } + + public SequenceNotFoundException(string sequence) : + base($"The Morse sequence '{sequence}' is not found.") + { + + } + public SequenceNotFoundException(string sequence, Language language) : + base($"The Morse sequence '{sequence}' is not found in the {language} language.") + { + + } + } +} diff --git a/MorseSharp/Exceptions/SmallerWordSpeedException.cs b/MorseSharp/Exceptions/SmallerWordSpeedException.cs new file mode 100644 index 0000000..f298bf6 --- /dev/null +++ b/MorseSharp/Exceptions/SmallerWordSpeedException.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MorseSharp.Exceptions +{ + [Serializable] + internal class SmallerWordSpeedException : Exception + { + public SmallerWordSpeedException() { } + + public SmallerWordSpeedException(string message) : base(message) + { + + } + public SmallerWordSpeedException(int charSpeed, int wordSpeed) + : base($"The character speed must not be smaller than word speed: {charSpeed} < {wordSpeed}") + { + + } + } +} diff --git a/MorseSharp/Exceptions/WordNotPresentedException.cs b/MorseSharp/Exceptions/WordNotPresentedException.cs new file mode 100644 index 0000000..a3dd3a8 --- /dev/null +++ b/MorseSharp/Exceptions/WordNotPresentedException.cs @@ -0,0 +1,20 @@ + +namespace MorseSharp.Exceptions +{ + [Serializable] + internal class WordNotPresentedException : Exception + { + + public WordNotPresentedException() { } + public WordNotPresentedException(char word) + : base($"The {word} word is not presented in the specified Language.") + { + + } + public WordNotPresentedException(char word, Language language) + : base($"The {word} word is not presented in {Enum.GetName(language)} Language.") + { + + } + } +} From 7340099c62e2b32dffcc0120b247381613b13991 Mon Sep 17 00:00:00 2001 From: p6laris Date: Wed, 15 Nov 2023 03:34:12 +0300 Subject: [PATCH 05/18] Change the MorseRepo dir to Interfaces --- MorseSharp/{MorseRepo => Interfaces}/IMorseConverter.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename MorseSharp/{MorseRepo => Interfaces}/IMorseConverter.cs (100%) diff --git a/MorseSharp/MorseRepo/IMorseConverter.cs b/MorseSharp/Interfaces/IMorseConverter.cs similarity index 100% rename from MorseSharp/MorseRepo/IMorseConverter.cs rename to MorseSharp/Interfaces/IMorseConverter.cs From 6df759a896c5cd13b145043200984e4b8008145f Mon Sep 17 00:00:00 2001 From: p6laris Date: Wed, 15 Nov 2023 03:40:15 +0300 Subject: [PATCH 06/18] Added the Fluent API chain Interfaces. --- MorseSharp/Interfaces/ICanConvertToAudio.cs | 14 ++++++++++++ MorseSharp/Interfaces/ICanConvertToLight.cs | 13 +++++++++++ .../Interfaces/ICanGenerateAudioAndLight.cs | 17 ++++++++++++++ MorseSharp/Interfaces/ICanSetAudioOptions.cs | 17 ++++++++++++++ .../Interfaces/ICanSetBlinkerOptions.cs | 13 +++++++++++ .../Interfaces/ICanSetConversionOption.cs | 22 +++++++++++++++++++ MorseSharp/Interfaces/ICanSpecifyLanguage.cs | 15 +++++++++++++ MorseSharp/Interfaces/IMorseConverter.cs | 22 ------------------- 8 files changed, 111 insertions(+), 22 deletions(-) create mode 100644 MorseSharp/Interfaces/ICanConvertToAudio.cs create mode 100644 MorseSharp/Interfaces/ICanConvertToLight.cs create mode 100644 MorseSharp/Interfaces/ICanGenerateAudioAndLight.cs create mode 100644 MorseSharp/Interfaces/ICanSetAudioOptions.cs create mode 100644 MorseSharp/Interfaces/ICanSetBlinkerOptions.cs create mode 100644 MorseSharp/Interfaces/ICanSetConversionOption.cs create mode 100644 MorseSharp/Interfaces/ICanSpecifyLanguage.cs delete mode 100644 MorseSharp/Interfaces/IMorseConverter.cs diff --git a/MorseSharp/Interfaces/ICanConvertToAudio.cs b/MorseSharp/Interfaces/ICanConvertToAudio.cs new file mode 100644 index 0000000..d84ef68 --- /dev/null +++ b/MorseSharp/Interfaces/ICanConvertToAudio.cs @@ -0,0 +1,14 @@ +namespace MorseSharp.Interfaces +{ + /// + /// Represents an interface that allows conversion to audio. + /// + public interface ICanConvertToAudio + { + /// + /// Retrieves the audio data as a span of bytes. + /// + /// An output span where the audio data will be stored. + void GetBytes(out Span destination); + } +} \ No newline at end of file diff --git a/MorseSharp/Interfaces/ICanConvertToLight.cs b/MorseSharp/Interfaces/ICanConvertToLight.cs new file mode 100644 index 0000000..3548905 --- /dev/null +++ b/MorseSharp/Interfaces/ICanConvertToLight.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MorseSharp.Interfaces +{ + public interface ICanConvertToLight + { + Task DoBlinks(Action blinkerAction); + } +} diff --git a/MorseSharp/Interfaces/ICanGenerateAudioAndLight.cs b/MorseSharp/Interfaces/ICanGenerateAudioAndLight.cs new file mode 100644 index 0000000..25105cb --- /dev/null +++ b/MorseSharp/Interfaces/ICanGenerateAudioAndLight.cs @@ -0,0 +1,17 @@ +namespace MorseSharp.Interfaces +{ + /// + /// Represents an interface that allows specifying audio options for Morse code conversion. + /// + public interface ICanSetAudioOptions + { + /// + /// Sets audio options for Morse code conversion. + /// + /// The character speed in words per minute (WPM). + /// The word speed in words per minute (WPM). + /// The frequency (in Hertz) of the audio signal. + /// An object that allows further conversion to audio with the specified options. + ICanConvertToAudio SetAudioOptions(int charSpeed, int wordSpeed, double frequency); + } +} \ No newline at end of file diff --git a/MorseSharp/Interfaces/ICanSetAudioOptions.cs b/MorseSharp/Interfaces/ICanSetAudioOptions.cs new file mode 100644 index 0000000..25105cb --- /dev/null +++ b/MorseSharp/Interfaces/ICanSetAudioOptions.cs @@ -0,0 +1,17 @@ +namespace MorseSharp.Interfaces +{ + /// + /// Represents an interface that allows specifying audio options for Morse code conversion. + /// + public interface ICanSetAudioOptions + { + /// + /// Sets audio options for Morse code conversion. + /// + /// The character speed in words per minute (WPM). + /// The word speed in words per minute (WPM). + /// The frequency (in Hertz) of the audio signal. + /// An object that allows further conversion to audio with the specified options. + ICanConvertToAudio SetAudioOptions(int charSpeed, int wordSpeed, double frequency); + } +} \ No newline at end of file diff --git a/MorseSharp/Interfaces/ICanSetBlinkerOptions.cs b/MorseSharp/Interfaces/ICanSetBlinkerOptions.cs new file mode 100644 index 0000000..2a2121a --- /dev/null +++ b/MorseSharp/Interfaces/ICanSetBlinkerOptions.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MorseSharp.Interfaces +{ + public interface ICanSetBlinkerOptions + { + ICanConvertToLight SetBlinkerOptions(int charSpeed, int wordSpeed); + } +} diff --git a/MorseSharp/Interfaces/ICanSetConversionOption.cs b/MorseSharp/Interfaces/ICanSetConversionOption.cs new file mode 100644 index 0000000..c8e5d6e --- /dev/null +++ b/MorseSharp/Interfaces/ICanSetConversionOption.cs @@ -0,0 +1,22 @@ +namespace MorseSharp.Interfaces +{ + /// + /// Represents an interface that allows setting conversion options for Morse code. + /// + public interface ICanSetConversionOption + { + /// + /// Converts Morse code to text. + /// + /// The Morse code to be converted to text. + /// The converted text. + string Decode(string morse); + + /// + /// Converts text to Morse code. + /// + /// The text to be converted to Morse code. + /// The converted Morse code. + ICanGenerateAudioAndLight ToMorse(string text); + } +} \ No newline at end of file diff --git a/MorseSharp/Interfaces/ICanSpecifyLanguage.cs b/MorseSharp/Interfaces/ICanSpecifyLanguage.cs new file mode 100644 index 0000000..09c62c8 --- /dev/null +++ b/MorseSharp/Interfaces/ICanSpecifyLanguage.cs @@ -0,0 +1,15 @@ +namespace MorseSharp.Interfaces +{ + /// + /// Represents an interface that allows specifying a target language for conversion. + /// + public interface ICanSpecifyLanguage + { + /// + /// Specifies the target language for conversion. + /// + /// The target language to be set. + /// An object that allows setting conversion options for the specified language. + ICanSetConversionOption ForLanguage(Language language); + } +} \ No newline at end of file diff --git a/MorseSharp/Interfaces/IMorseConverter.cs b/MorseSharp/Interfaces/IMorseConverter.cs deleted file mode 100644 index aec7365..0000000 --- a/MorseSharp/Interfaces/IMorseConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace MorseSharp.MorseRepo -{ - /// - /// Morse text converter abstraction. - /// - public interface IMorseConverter - { - /// - /// Converts the given string sentence to morse code. - /// - /// The text to convert to morse code. - /// A string containing the morse code result. - string ConvertTextToMorse(string Text); - - /// - /// Converts a morse message to an English sentence. - /// - /// The morse message to convert. - /// A string containing the converted morse message. - string ConvertMorseToText(string Morse); - } -} From b7f0716e45f711a8daad02666508425309185959 Mon Sep 17 00:00:00 2001 From: p6laris Date: Wed, 15 Nov 2023 03:41:55 +0300 Subject: [PATCH 07/18] Added Enum dir and put the language enum into there. --- MorseSharp/Enums/Language.cs | 55 ++++++++++++++++++++++++++++++++++++ MorseSharp/Language.cs | 53 ---------------------------------- 2 files changed, 55 insertions(+), 53 deletions(-) create mode 100644 MorseSharp/Enums/Language.cs delete mode 100644 MorseSharp/Language.cs diff --git a/MorseSharp/Enums/Language.cs b/MorseSharp/Enums/Language.cs new file mode 100644 index 0000000..6e38631 --- /dev/null +++ b/MorseSharp/Enums/Language.cs @@ -0,0 +1,55 @@ +namespace MorseSharp; + +/// +/// Describes the languages for morse endcoding/decoding. +/// + +[Flags] +public enum Language +{ + /// + /// English Language. + /// + English = 1, + /// + /// Kurdish Language. + /// + Kurdish = 1 << 1, + /// + /// Kurdish Language. + /// + KurdishLatin = 1 << 2, + /// + /// Arabic Language. + /// + Arabic = 1 << 3, + /// + /// Deutsch Language. + /// + Deutsch = 1 << 4, + /// + /// Espaneol Language. + /// + Espanol = 1 << 5, + /// + /// Francais Language. + /// + Francais = 1 << 6, + /// + /// Italiano Language. + /// + Italiano = 1 << 7, + /// + /// Japanese Language. + /// + Japanese = 1 << 8, + /// + /// Portugues Language. + /// + Portugues = 1 << 9, + /// + /// Russian Language. + /// + Russian = 1 << 10, + +} \ No newline at end of file diff --git a/MorseSharp/Language.cs b/MorseSharp/Language.cs deleted file mode 100644 index f530b39..0000000 --- a/MorseSharp/Language.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace MorseSharp -{ - - /// - /// Describes the languages for morse endcoding/decoding. - /// - - [Flags] - public enum Language - { - /// - /// English Language. - /// - English = 1, - /// - /// Kurdish Language. - /// - Kurdish = 1 << 1, - /// - /// Kurdish Language. - /// - KurdishLatin = 1 << 2, - /// - /// Arabic Language. - /// - Arabic = 1 << 3, - /// - /// Deutsch Language. - /// - Deutsch = 1 << 4, - /// - /// Espaneol Language. - /// - Espanol = 1 << 5, - /// - /// Francais Language. - /// - Francais = 1 << 6, - /// - /// Italiano Language. - /// - Italiano = 1 << 7, - /// - /// Japanese Language. - /// - Japanese = 1 << 8, - /// - /// Portugues Language. - /// - Portugues = 1<< 9, - - } -} From aff5b5f34d3eeafb5ee4591830e699068e9fa942 Mon Sep 17 00:00:00 2001 From: p6laris Date: Wed, 15 Nov 2023 03:43:45 +0300 Subject: [PATCH 08/18] Added light blinker support. --- MorseSharp/Light/LightBlinker.cs | 90 ++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 MorseSharp/Light/LightBlinker.cs diff --git a/MorseSharp/Light/LightBlinker.cs b/MorseSharp/Light/LightBlinker.cs new file mode 100644 index 0000000..8c382f1 --- /dev/null +++ b/MorseSharp/Light/LightBlinker.cs @@ -0,0 +1,90 @@ + +using MorseSharp.Exceptions; + +namespace MorseSharp.Light +{ + internal partial class LightBlinker + { + private readonly int _characterSpeed; + private readonly int _wordSpeed; + + private Action _blinkerAction; + + + public LightBlinker(int characterSpeed, int wordSpeed, Action blinkerAction) + { + if (characterSpeed < wordSpeed) + throw new SmallerWordSpeedException(characterSpeed, wordSpeed); + + if (blinkerAction == null) + throw new ArgumentNullException(nameof(blinkerAction)); + + _characterSpeed = characterSpeed; + _wordSpeed = wordSpeed; + _blinkerAction = blinkerAction; + } + private Task GetDotDurationAsync() => GetBlinkAsync(1.2 / _characterSpeed); + + private Task GetDashDurationAsync() => GetBlinkAsync(3.6 / _characterSpeed); + + private Task GetElementCharDurationAsync() => GetSilenceAsync(1.2 / _characterSpeed); + + private Task GetInterWordDurationAsync() + { + double delay = (60.0 / _wordSpeed) - (32.0 / _characterSpeed); + double spaceLength = 7 * delay / 19; + return GetSilenceAsync(spaceLength); + } + + private Task GetInterCharDurationAsync() + { + double delay = (60.0 / _wordSpeed) - (32.0 / _characterSpeed); + double spaceLength = 3 * delay / 19; + return GetSilenceAsync(spaceLength); + } + private Task GetBlinkAsync(double seconds) + { + _blinkerAction?.Invoke(true); + return Task.Delay(TimeSpan.FromSeconds(seconds)); + } + + private Task GetSilenceAsync(double seconds) + { + _blinkerAction?.Invoke(false); + return Task.Delay(TimeSpan.FromSeconds(seconds)); + } + + private async Task GetCharacterDurations(string morseSymbol) + { + + for (int i = 0; i < morseSymbol.Length; i++) + { + if (i > 0) + await GetElementCharDurationAsync(); + if (morseSymbol[i] == '_') + await GetDashDurationAsync(); + else if (morseSymbol[i] == '.') + await GetDotDurationAsync(); + } + + } + + public async Task BlinkLight(string morse) + { + + var splitedText = morse.Split(' '); + + for (int i = 0; i < splitedText.Length; i++) + { + if (i > 0) + await GetInterWordDurationAsync(); + + await GetCharacterDurations(splitedText[i]); + } + + // Pad the end with a little bit of silence. + await GetInterCharDurationAsync(); + } + + } +} From 158028fa9c8e81dd7b810a41c70be8bcdbec0595 Mon Sep 17 00:00:00 2001 From: p6laris Date: Wed, 15 Nov 2023 03:44:47 +0300 Subject: [PATCH 09/18] Re-write the fluent converter. --- MorseSharp/Converter/MorseAudioConverter.cs | 63 ----- MorseSharp/Converter/TextMorseConverter.cs | 108 --------- MorseSharp/Morse.cs | 242 ++++++++++++++++++++ 3 files changed, 242 insertions(+), 171 deletions(-) delete mode 100644 MorseSharp/Converter/MorseAudioConverter.cs delete mode 100644 MorseSharp/Converter/TextMorseConverter.cs create mode 100644 MorseSharp/Morse.cs diff --git a/MorseSharp/Converter/MorseAudioConverter.cs b/MorseSharp/Converter/MorseAudioConverter.cs deleted file mode 100644 index acd7695..0000000 --- a/MorseSharp/Converter/MorseAudioConverter.cs +++ /dev/null @@ -1,63 +0,0 @@ -using MorseSharp.Audio; - -namespace MorseSharp.Converter -{ - - /// - /// - /// Converts morse code to a wav format audio of dash and dots. - /// This class is a wrapper of jstoddard's CWLibrary Library. - /// - /// - public class MorseAudioConverter : AudioConverter - { - /// - /// initializes an instance of type with custom audio configuration. - /// - /// An enum of type to specifies the language - /// Character speed in WPM - /// Overall speed in WPM (must be <= character speed) - /// Tone frequency - public MorseAudioConverter(Language language,int charSpeed, int wordSpeed, double frequency) : base(language,charSpeed, wordSpeed, frequency){} - /// - /// initializes an instance of type with custom audio configuration. - /// - /// An enum of type to specifies the language - /// Character speed in WPM - /// Overall speed in WPM (must be <= character speed) - public MorseAudioConverter(Language language, int charSpeed, int wordSpeed) : base(language,charSpeed, wordSpeed, 600.0) { } - /// - /// initializes an instance of type with custom audio configuration. - /// - /// An enum of type to specifies the language - /// Character speed in WPM - public MorseAudioConverter(Language language,int charSpeed) : base(language,charSpeed, charSpeed) { } - /// - /// initializes an instance of type with custom audio configuration. - /// An enum of type to specifies the language - /// - public MorseAudioConverter(Language language) : base(language,20) { } - /// - /// initializes an instance of type with custom audio configuration. - /// - public MorseAudioConverter() : base(Language.English){} - - /// - /// Generate's audio for the given text. - /// - /// Text to generate audio for. - /// of the generated audio. - /// Throws if the text parameter was null. - public async Task ConvertMorseToAudio(string text) - { - if(text is not null) - { - Memory audioByte = ConvertToMorse(text); - return await Task.Run(() => audioByte.ToArray()); - } - throw new ArgumentNullException(nameof(text)); - } - - - } -} diff --git a/MorseSharp/Converter/TextMorseConverter.cs b/MorseSharp/Converter/TextMorseConverter.cs deleted file mode 100644 index 6b307e5..0000000 --- a/MorseSharp/Converter/TextMorseConverter.cs +++ /dev/null @@ -1,108 +0,0 @@ -using MorseSharp.MorseRepo; -using System.Text; - -namespace MorseSharp.Converter -{ - /// - /// Decode/encode's morse text. - /// - public class TextMorseConverter : IMorseConverter - { - private readonly StringBuilder strBuilder; - private readonly Dictionary morseChar; - private readonly Language language; - private readonly Language nonLatin = Language.Kurdish | Language.Arabic; - - /// - /// Initializes a new instance of the class. - /// - /// The language to convert from to morse code. - public TextMorseConverter(Language Language) - { - language = Language; - strBuilder = new StringBuilder(); - morseChar = MorseCharacters.GetLanguageCharacter(Language: language); - } - - /// - /// Converts the given string sentence to morse code. - /// - /// The text to convert to morse code. - /// A string containing the morse code result. - /// Thrown if a character is not presented in the character mapping. - /// Thrown if the input string is null. - public string ConvertTextToMorse(string Text) - { - - if (Text is not null) - { - strBuilder.Clear(); - if ((language & nonLatin) == 0) - Text = Text.ToUpper(); - - - for (int i = 0; i < Text.Length; i++) - { - if (morseChar.TryGetValue(Text[i], out string val)) - { - - // Check if it's not the last character before appending a space - if (i < Text.Length - 1) - { - strBuilder.Append(val).Append(' '); - } - else - strBuilder.Append(val); - } - - else - { - throw new KeyNotFoundException($"The {Text[i]} character is not presented."); - } - - } - return strBuilder.ToString(); - } - - throw new ArgumentNullException(nameof(Text)); - } - - /// - /// Converts a morse message to an English sentence. - /// - /// The morse message to convert. - /// A string containing the converted morse message. - /// Thrown if a morse letter is not presented in the character mapping. - /// Thrown if the input morse message is null. - public string ConvertMorseToText(string Morse) - { - - if (Morse is not null) - { - strBuilder.Clear(); - var words = Morse.Split(' '); - for (int i = 0; i < words.Length; i++) - { - if (!String.IsNullOrEmpty(words[i])) - { - if (words[i] != "/") - { - if (morseChar.Values.Contains(words[i])) - { - var word = morseChar.First(x => x.Value == words[i]).Key; - strBuilder.Append(word); - } - else - throw new KeyNotFoundException($"The {words[i]} is not presented."); - } - else - strBuilder.Append(' '); - } - } - - return strBuilder.ToString(); - } - throw new ArgumentNullException(nameof(Morse)); - } - } -} diff --git a/MorseSharp/Morse.cs b/MorseSharp/Morse.cs new file mode 100644 index 0000000..1bbd24e --- /dev/null +++ b/MorseSharp/Morse.cs @@ -0,0 +1,242 @@ + +using MorseSharp.Audio; +using MorseSharp.Exceptions; +using MorseSharp.Interfaces; +using MorseSharp.Light; + +namespace MorseSharp; + + +/// +/// Decode/encodes morse text. +/// +public sealed class Morse : ICanSpecifyLanguage, ICanSetConversionOption, + ICanConvertToAudio, ICanSetAudioOptions, ICanGenerateAudioAndLight, + ICanSetBlinkerOptions, ICanConvertToLight +{ + private Lazy> _morseChar = default!; + + private Language _sLanguage; + private const Language NonLatin = Language.Kurdish | Language.Arabic; + private static Morse? _instance; + private static readonly StringBuilder _strBuilder = new StringBuilder(); + private static readonly object ObjLock = new(); + private int _charSpeed; + private int _wordSpeed; + private double _frequency; + + /// + /// Initializes a new instance of the class. + /// + private Morse() + { + } + + /// + /// Gets an instance of the class for conversion. + /// + /// An instance of the interface. + + public static ICanSpecifyLanguage GetConverter() + { + if (_instance is null) + { + lock (ObjLock) + { + _instance = new Morse(); + } + } + + return _instance; + } + + /// + /// Specify the language for Morse code conversion. + /// + /// The language for which to set conversion options. + /// An instance of the interface. + public ICanSetConversionOption ForLanguage(Language language) + { + //cache the language + if (language != _sLanguage) + { + _sLanguage = language; + _morseChar = new Lazy>(MorseCharacters.GetLanguageCharacter(language)!); + } + + return this; + } + + /// + /// Convert Morse code to text. + /// + /// The Morse code to convert to text. + /// The converted text. + public string Decode(string morse) + { + if (!string.IsNullOrEmpty(morse)) + { + + if (_strBuilder.Length > 0) + _strBuilder.Length = 0; + + + ReadOnlySpan sMorse = morse.AsSpan(); + + int count = 0; + + unsafe + { + fixed (char* ptr = morse) + { + // Count occurrences of spaces to split Morse code into words + count = StringCounter.CountOccurrences(ptr, ' '); + } + + } + + Span splitedRange = new Span(new Range[count + 1]); + + // Split Morse code into individual words + sMorse.Split(splitedRange, ' ', StringSplitOptions.None); + + for (int i = 0; i < splitedRange.Length; i++) + { + var sequence = sMorse.Slice(splitedRange[i].Start.Value, splitedRange[i].End.Value - splitedRange[i].Start.Value); + + if (sequence.IsWhiteSpace()) + continue; + + if (sequence != "/") + { + if (_morseChar.Value.Values.Contains(sequence.ToString())) + { + var value = _morseChar.Value.KeyByValue(sequence.ToString()); + _strBuilder.Append(value); + } + else + throw new SequenceNotFoundException(sequence.ToString(), language: _sLanguage); + } + } + return _strBuilder.ToString(); + } + else + throw new ArgumentNullException(nameof(morse)); + + + + } + + /// + /// Convert text to Morse code. + /// + /// The text to convert to Morse code. + /// The Morse code representation of the text. + public ICanGenerateAudioAndLight ToMorse(string text) + { + if (!string.IsNullOrEmpty(text)) + { + + if (_strBuilder.Length > 0) + _strBuilder.Length = 0; + + + ReadOnlySpan sText = text.AsSpan(); + + + var length = sText.Length; + + + if ((_sLanguage & NonLatin) == 0) + { + unsafe + { + fixed (char* ptr = text) + fixed (char* outPtr = sText) + { + // Convert text to uppercase using pointers + ToUpperSpanExtention.ToUpperWithPointers(ptr, outPtr, text.Length); + } + } + } + + for (int i = 0; i < length; i++) + { + if (_morseChar.Value.TryGetValue(sText[i], out string? val)) + { + // Check if it's not the last character before appending a space + if (i < text.Length - 1) + { + _strBuilder.Append(val.AsSpan()).Append(' '); + } + else + _strBuilder.Append(val.AsSpan()); + } + else + throw new WordNotPresentedException(sText[i], language: _sLanguage); + } + + return this; + } + else + throw new ArgumentException(nameof(text)); + + } + + + /// + /// Generate audio bytes based on the set options. + /// + /// The destination span to store the audio bytes. + public void GetBytes(out Span destination) + { + AudioConverter a = new AudioConverter(_charSpeed, _wordSpeed, _frequency); + a.ConvertToAudio(_strBuilder.ToString(), out Span bytes); + destination = bytes; + } + + /// + /// Set audio conversion options. + /// + /// The character speed for audio. + /// The word speed for audio. + /// The frequency for audio. + /// An instance of the interface. + public ICanConvertToAudio SetAudioOptions(int charSpeed = 25, int wordSpeed = 25, double frequency = 700) + { + if (charSpeed < wordSpeed) + throw new SmallerWordSpeedException(charSpeed, wordSpeed); + + _charSpeed = charSpeed; + _wordSpeed = wordSpeed; + _frequency = frequency; + + return this; + } + + public string Encode() => _strBuilder.ToString(); + + public ICanSetAudioOptions ToAudio() => this; + + public ICanSetBlinkerOptions ToLight() => this; + + public ICanConvertToLight SetBlinkerOptions(int charSpeed = 25, int wordSpeed = 25) + { + if (charSpeed < wordSpeed) + throw new SmallerWordSpeedException(charSpeed, wordSpeed); + + _charSpeed = charSpeed; + _wordSpeed = wordSpeed; + + return this; + } + + public async Task DoBlinks(Action blinkerAction) + { + if (blinkerAction == null) + throw new ArgumentNullException(nameof(blinkerAction)); + + LightBlinker lightBlinker = new LightBlinker(_charSpeed, _wordSpeed, blinkerAction); + await lightBlinker.BlinkLight(_strBuilder.ToString()); + } +} From e44364a65a8d19603ff0afd98faa7b780a87d8ad Mon Sep 17 00:00:00 2001 From: p6laris Date: Wed, 15 Nov 2023 04:13:42 +0300 Subject: [PATCH 10/18] Resove some bugs. --- Example/Program.cs | 3 +- MorseSharp/Audio/AudioConverter.cs | 3 +- MorseSharp/Audio/Chunks/DataChunk.cs | 4 +-- MorseSharp/Audio/Chunks/FromatChunk.cs | 5 ++- MorseSharp/Audio/Chunks/HeaderChunk.cs | 7 ++--- .../Exceptions/SmallerWordSpeedException.cs | 7 +---- MorseSharp/Extentions/GetDictionaryKey.cs | 31 +++++++++++++++++++ MorseSharp/Extentions/StringCounter.cs | 28 +++++++++++++++++ MorseSharp/Extentions/ToUpperSpan.cs | 27 ++++++++++++++++ MorseSharp/Helpers/SpanByteWritter.cs | 4 +-- MorseSharp/Interfaces/ICanConvertToLight.cs | 8 +---- .../Interfaces/ICanGenerateAudioAndLight.cs | 20 ++++++------ .../Interfaces/ICanSetBlinkerOptions.cs | 8 +---- MorseSharp/Light/LightBlinker.cs | 5 +-- MorseSharp/Morse.cs | 8 +---- MorseSharp/MorseSharp.csproj | 4 +++ MorseSharp/Usings.cs | 11 +++++++ MorseTest/Languages.cs | 10 ++---- global.json | 4 ++- 19 files changed, 132 insertions(+), 65 deletions(-) create mode 100644 MorseSharp/Extentions/GetDictionaryKey.cs create mode 100644 MorseSharp/Extentions/StringCounter.cs create mode 100644 MorseSharp/Extentions/ToUpperSpan.cs create mode 100644 MorseSharp/Usings.cs diff --git a/Example/Program.cs b/Example/Program.cs index 2ec4f12..e5f8e21 100644 --- a/Example/Program.cs +++ b/Example/Program.cs @@ -1,5 +1,4 @@ -using MorseSharp; -using MorseSharp.Converter; + TextMorseConverter Converter = new TextMorseConverter(Language.English); diff --git a/MorseSharp/Audio/AudioConverter.cs b/MorseSharp/Audio/AudioConverter.cs index 291f402..59dbfe0 100644 --- a/MorseSharp/Audio/AudioConverter.cs +++ b/MorseSharp/Audio/AudioConverter.cs @@ -1,5 +1,4 @@ -using MorseSharp.Audio.Chunks; - + namespace MorseSharp.Audio; [StructLayout(LayoutKind.Sequential)] diff --git a/MorseSharp/Audio/Chunks/DataChunk.cs b/MorseSharp/Audio/Chunks/DataChunk.cs index 737a9aa..86f19d4 100644 --- a/MorseSharp/Audio/Chunks/DataChunk.cs +++ b/MorseSharp/Audio/Chunks/DataChunk.cs @@ -1,4 +1,4 @@ -using MorseSharp.Helpers; + namespace MorseSharp.Audio.Chunks; /// @@ -36,7 +36,7 @@ public ValueDataChunk(Span data) [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span ToBytes() { - Span chunkId = [100, 97, 116, 97]; + Span chunkId = stackalloc byte[4] {100, 97, 116, 97}; using SpanOwner owner = SpanOwner.Allocate(Capacity); Span data = owner.Span; diff --git a/MorseSharp/Audio/Chunks/FromatChunk.cs b/MorseSharp/Audio/Chunks/FromatChunk.cs index deb7c08..b2f40d5 100644 --- a/MorseSharp/Audio/Chunks/FromatChunk.cs +++ b/MorseSharp/Audio/Chunks/FromatChunk.cs @@ -1,5 +1,4 @@ -using MorseSharp.Helpers; - + namespace MorseSharp.Audio.Chunks; [StructLayout(LayoutKind.Sequential)] @@ -30,7 +29,7 @@ public ValueFormatChunk() [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span ToBytes() { - Span chunkId = [102, 109, 116, 32]; + Span chunkId = stackalloc byte[4]{102, 109, 116, 32}; using SpanOwner owner = SpanOwner.Allocate(Capacity); Span data = owner.Span; diff --git a/MorseSharp/Audio/Chunks/HeaderChunk.cs b/MorseSharp/Audio/Chunks/HeaderChunk.cs index 5e4a5b9..20defaf 100644 --- a/MorseSharp/Audio/Chunks/HeaderChunk.cs +++ b/MorseSharp/Audio/Chunks/HeaderChunk.cs @@ -1,5 +1,4 @@ -using MorseSharp.Helpers; - + namespace MorseSharp.Audio.Chunks; public readonly ref struct ValueHeaderChunk @@ -23,8 +22,8 @@ public ValueHeaderChunk(in ValueDataChunk dataChunk, in ValueFormatChunk formatC [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span ToBytes() { - Span riffType = [87, 65, 86, 69]; - Span chunkId = [82, 73, 70, 70]; + Span riffType = stackalloc byte[4]{87, 65, 86, 69}; + Span chunkId = stackalloc byte[4] {82, 73, 70, 70}; using SpanOwner owner = SpanOwner.Allocate(_bufferSize); Span data = owner.Span; diff --git a/MorseSharp/Exceptions/SmallerWordSpeedException.cs b/MorseSharp/Exceptions/SmallerWordSpeedException.cs index f298bf6..af5f40c 100644 --- a/MorseSharp/Exceptions/SmallerWordSpeedException.cs +++ b/MorseSharp/Exceptions/SmallerWordSpeedException.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - + namespace MorseSharp.Exceptions { [Serializable] diff --git a/MorseSharp/Extentions/GetDictionaryKey.cs b/MorseSharp/Extentions/GetDictionaryKey.cs new file mode 100644 index 0000000..bebf55d --- /dev/null +++ b/MorseSharp/Extentions/GetDictionaryKey.cs @@ -0,0 +1,31 @@ +namespace MorseSharp.Extentions; + + +/// +/// Provides extension methods for dictionaries to retrieve keys by their associated values. +/// +internal static class GetDictionaryKey +{ + /// + /// Retrieves the key associated with the specified value from a dictionary. + /// + /// The type of keys in the dictionary. + /// The type of values in the dictionary. + /// The dictionary to search for the key-value pair. + /// The value to look for in the dictionary. + /// The key associated with the specified value, or the default value of type if not found. + public static T KeyByValue(this Dictionary dict, V val) + { + T key = default!; + foreach (KeyValuePair pair in dict) + { + if (EqualityComparer.Default.Equals(pair.Value, val)) + { + key = pair.Key; + break; + } + } + + return key; + } +} \ No newline at end of file diff --git a/MorseSharp/Extentions/StringCounter.cs b/MorseSharp/Extentions/StringCounter.cs new file mode 100644 index 0000000..a640e9b --- /dev/null +++ b/MorseSharp/Extentions/StringCounter.cs @@ -0,0 +1,28 @@ +namespace MorseSharp.Extentions +{ + /// + /// Provides extension methods for counting occurrences of a character in a character span. + /// + internal static class StringCounter + { + /// + /// Counts the occurrences of a specified character within a character span. + /// + /// A pointer to the character span to be searched. + /// The character to count within the span. + /// The number of times the target character appears within the span. + public static unsafe int CountOccurrences(char* input, char targetCharacter) + { + int count = 0; + while (*input != '\0') + { + if (*input == targetCharacter) + { + count++; + } + input++; + } + return count; + } + } +} \ No newline at end of file diff --git a/MorseSharp/Extentions/ToUpperSpan.cs b/MorseSharp/Extentions/ToUpperSpan.cs new file mode 100644 index 0000000..3d6613b --- /dev/null +++ b/MorseSharp/Extentions/ToUpperSpan.cs @@ -0,0 +1,27 @@ +namespace MorseSharp.Extentions; + +/// +/// Provides extension methods for converting characters to uppercase using pointers. +/// +internal static class ToUpperSpanExtention +{ + /// + /// Converts characters in a span to uppercase using pointers. + /// + /// A pointer to the input character span. + /// A pointer to the destination character span where the uppercase characters will be written. + /// The length of the character span. + /// Thrown when or is null, or when is less than 0. + public static unsafe void ToUpperWithPointers(char* inputPtr, char* destPtr, int length) + { + if (inputPtr == null || destPtr == null || length < 0) + throw new ArgumentNullException(); + + for (int i = 0; i < length; i++) + { + char c = inputPtr[i]; + destPtr[i] = (c >= 'a' && c <= 'z') ? (char) (c - 32) : c; + } + } + +} \ No newline at end of file diff --git a/MorseSharp/Helpers/SpanByteWritter.cs b/MorseSharp/Helpers/SpanByteWritter.cs index 22cd9b9..babac4a 100644 --- a/MorseSharp/Helpers/SpanByteWritter.cs +++ b/MorseSharp/Helpers/SpanByteWritter.cs @@ -42,7 +42,7 @@ public void AddRange(Span source) public bool AddRangeBit(uint value) { Span bytes = stackalloc byte[4]; - NumericConverter.GetBytes(value, bytes); + NumericBitConverter.GetBytes(value, bytes); if (offset + bytes.Length > buffer.Length) { @@ -63,7 +63,7 @@ public bool AddRangeBit(uint value) public bool AddRangeBit(short value) { Span bytes = stackalloc byte[2]; - NumericConverter.GetBytes(value, bytes); + NumericBitConverter.GetBytes(value, bytes); if (offset + bytes.Length > buffer.Length) return false; // Not enough space in the buffer. diff --git a/MorseSharp/Interfaces/ICanConvertToLight.cs b/MorseSharp/Interfaces/ICanConvertToLight.cs index 3548905..9cf2834 100644 --- a/MorseSharp/Interfaces/ICanConvertToLight.cs +++ b/MorseSharp/Interfaces/ICanConvertToLight.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MorseSharp.Interfaces +namespace MorseSharp.Interfaces { public interface ICanConvertToLight { diff --git a/MorseSharp/Interfaces/ICanGenerateAudioAndLight.cs b/MorseSharp/Interfaces/ICanGenerateAudioAndLight.cs index 25105cb..b259e15 100644 --- a/MorseSharp/Interfaces/ICanGenerateAudioAndLight.cs +++ b/MorseSharp/Interfaces/ICanGenerateAudioAndLight.cs @@ -1,17 +1,17 @@ namespace MorseSharp.Interfaces { - /// - /// Represents an interface that allows specifying audio options for Morse code conversion. - /// - public interface ICanSetAudioOptions + public interface ICanGenerateAudioAndLight { + + string Encode(); /// - /// Sets audio options for Morse code conversion. + /// Converts text to audio with specified options. /// - /// The character speed in words per minute (WPM). - /// The word speed in words per minute (WPM). - /// The frequency (in Hertz) of the audio signal. - /// An object that allows further conversion to audio with the specified options. - ICanConvertToAudio SetAudioOptions(int charSpeed, int wordSpeed, double frequency); + /// The text to be converted to audio. + /// An object that allows specifying audio options for the conversion. + ICanSetAudioOptions ToAudio(); + + ICanSetBlinkerOptions ToLight(); + } } \ No newline at end of file diff --git a/MorseSharp/Interfaces/ICanSetBlinkerOptions.cs b/MorseSharp/Interfaces/ICanSetBlinkerOptions.cs index 2a2121a..79f350d 100644 --- a/MorseSharp/Interfaces/ICanSetBlinkerOptions.cs +++ b/MorseSharp/Interfaces/ICanSetBlinkerOptions.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MorseSharp.Interfaces +namespace MorseSharp.Interfaces { public interface ICanSetBlinkerOptions { diff --git a/MorseSharp/Light/LightBlinker.cs b/MorseSharp/Light/LightBlinker.cs index 8c382f1..754e618 100644 --- a/MorseSharp/Light/LightBlinker.cs +++ b/MorseSharp/Light/LightBlinker.cs @@ -1,7 +1,4 @@ - -using MorseSharp.Exceptions; - -namespace MorseSharp.Light +namespace MorseSharp.Light { internal partial class LightBlinker { diff --git a/MorseSharp/Morse.cs b/MorseSharp/Morse.cs index 1bbd24e..429615c 100644 --- a/MorseSharp/Morse.cs +++ b/MorseSharp/Morse.cs @@ -1,10 +1,4 @@ - -using MorseSharp.Audio; -using MorseSharp.Exceptions; -using MorseSharp.Interfaces; -using MorseSharp.Light; - -namespace MorseSharp; +namespace MorseSharp; /// diff --git a/MorseSharp/MorseSharp.csproj b/MorseSharp/MorseSharp.csproj index 096609a..92b1c83 100644 --- a/MorseSharp/MorseSharp.csproj +++ b/MorseSharp/MorseSharp.csproj @@ -35,5 +35,9 @@ + + + + diff --git a/MorseSharp/Usings.cs b/MorseSharp/Usings.cs new file mode 100644 index 0000000..8bc592d --- /dev/null +++ b/MorseSharp/Usings.cs @@ -0,0 +1,11 @@ +global using System.Text; +global using MorseSharp.Extentions; +global using MorseSharp.Interfaces; +global using MorseSharp.Audio.Chunks; +global using MorseSharp.Audio; +global using MorseSharp.Light; +global using MorseSharp.Exceptions; +global using MorseSharp.Helpers; +global using System.Runtime.CompilerServices; +global using System.Runtime.InteropServices; +global using CommunityToolkit.HighPerformance.Buffers; diff --git a/MorseTest/Languages.cs b/MorseTest/Languages.cs index 2910144..feab28a 100644 --- a/MorseTest/Languages.cs +++ b/MorseTest/Languages.cs @@ -1,17 +1,11 @@ -using MorseSharp.Converter; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MorseTest +namespace MorseTest { public class Languages { [Fact] public void EnglishToMorse() { + TextMorseConverter Converter = new TextMorseConverter(Language.English); var morse = Converter.ConvertTextToMorse("The quick brown fox jumps over the lazy dog"); diff --git a/global.json b/global.json index 1a974fd..dad2db5 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,7 @@ { "sdk": { - "version": "7.0.102" + "version": "8.0.0", + "rollForward": "latestMajor", + "allowPrerelease": true } } \ No newline at end of file From 89df808a08e6b3c0b5ca2d773f24fb2f7eb448b7 Mon Sep 17 00:00:00 2001 From: p6laris Date: Sat, 18 Nov 2023 04:59:08 +0300 Subject: [PATCH 11/18] Update the tests and formated the codes. --- .idea/.idea.MorseSharp/.idea/workspace.xml | 126 ++++++++++++++++++ AudioExample/MainWindow.Designer.cs | 120 ++++++++++------- AudioExample/MainWindow.cs | 76 ++++++++--- AudioExample/MainWindow.resx | 62 ++++++++- Example/Program.cs | 38 ++++-- MorseSharp/Audio/AudioConverter.cs | 34 +++-- .../Exceptions/SequenceNotFoundException.cs | 2 +- .../Exceptions/SmallerWordSpeedException.cs | 2 +- .../Exceptions/WordNotPresentedException.cs | 2 +- .../Interfaces/ICanSetConversionOption.cs | 2 + MorseSharp/MorseSharp.csproj | 1 + MorseTest/Languages.cs | 69 ++++++---- MorseTest/MorseAudioConverter.cs | 41 +++--- MorseTest/TextMorseConverter.cs | 61 ++++++--- MorseTest/Usings.cs | 3 +- 15 files changed, 474 insertions(+), 165 deletions(-) create mode 100644 .idea/.idea.MorseSharp/.idea/workspace.xml diff --git a/.idea/.idea.MorseSharp/.idea/workspace.xml b/.idea/.idea.MorseSharp/.idea/workspace.xml new file mode 100644 index 0000000..fbcf250 --- /dev/null +++ b/.idea/.idea.MorseSharp/.idea/workspace.xml @@ -0,0 +1,126 @@ + + + + AudioExample/AudioExample.csproj + Example/ConsoleExample.csproj + + + + + + + + + + + + + + + + + + + + + + + + + + { + "customColor": "", + "associatedIndex": 3 +} + + + + + + + + + + + + + + + 1700009927672 + + + + + + + + \ No newline at end of file diff --git a/AudioExample/MainWindow.Designer.cs b/AudioExample/MainWindow.Designer.cs index b266533..af6588f 100644 --- a/AudioExample/MainWindow.Designer.cs +++ b/AudioExample/MainWindow.Designer.cs @@ -28,73 +28,93 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.ToMorseEnglishBtn = new System.Windows.Forms.Button(); - this.MessageMorseTxt = new System.Windows.Forms.TextBox(); - this.PlayBtn = new System.Windows.Forms.Button(); - this.MorseTxt = new System.Windows.Forms.RichTextBox(); - this.ToMorseKurdishBtn = new System.Windows.Forms.Button(); - this.SuspendLayout(); + ToMorseEnglishBtn = new Button(); + MessageMorseTxt = new TextBox(); + PlayBtn = new Button(); + MorseTxt = new RichTextBox(); + ToMorseKurdishBtn = new Button(); + blinkerPl = new Panel(); + blinkBtn = new Button(); + SuspendLayout(); // // ToMorseEnglishBtn // - this.ToMorseEnglishBtn.Location = new System.Drawing.Point(304, 35); - this.ToMorseEnglishBtn.Name = "ToMorseEnglishBtn"; - this.ToMorseEnglishBtn.Size = new System.Drawing.Size(173, 29); - this.ToMorseEnglishBtn.TabIndex = 0; - this.ToMorseEnglishBtn.Text = "To Morse English"; - this.ToMorseEnglishBtn.UseVisualStyleBackColor = true; - this.ToMorseEnglishBtn.Click += new System.EventHandler(this.ToAudioBtn_Click); + ToMorseEnglishBtn.Location = new Point(304, 35); + ToMorseEnglishBtn.Name = "ToMorseEnglishBtn"; + ToMorseEnglishBtn.Size = new Size(173, 29); + ToMorseEnglishBtn.TabIndex = 0; + ToMorseEnglishBtn.Text = "To Morse English"; + ToMorseEnglishBtn.UseVisualStyleBackColor = true; + ToMorseEnglishBtn.Click += ToAudioBtn_Click; // // MessageMorseTxt // - this.MessageMorseTxt.Location = new System.Drawing.Point(12, 37); - this.MessageMorseTxt.Name = "MessageMorseTxt"; - this.MessageMorseTxt.Size = new System.Drawing.Size(286, 27); - this.MessageMorseTxt.TabIndex = 1; + MessageMorseTxt.Location = new Point(12, 37); + MessageMorseTxt.Name = "MessageMorseTxt"; + MessageMorseTxt.Size = new Size(286, 27); + MessageMorseTxt.TabIndex = 1; // // PlayBtn // - this.PlayBtn.Location = new System.Drawing.Point(304, 105); - this.PlayBtn.Name = "PlayBtn"; - this.PlayBtn.Size = new System.Drawing.Size(142, 29); - this.PlayBtn.TabIndex = 2; - this.PlayBtn.Text = "Play"; - this.PlayBtn.UseVisualStyleBackColor = true; - this.PlayBtn.Click += new System.EventHandler(this.PlayBtn_Click); + PlayBtn.Location = new Point(304, 105); + PlayBtn.Name = "PlayBtn"; + PlayBtn.Size = new Size(173, 29); + PlayBtn.TabIndex = 2; + PlayBtn.Text = "Play"; + PlayBtn.UseVisualStyleBackColor = true; + PlayBtn.Click += PlayBtn_Click; // // MorseTxt // - this.MorseTxt.Location = new System.Drawing.Point(12, 70); - this.MorseTxt.Name = "MorseTxt"; - this.MorseTxt.Size = new System.Drawing.Size(286, 109); - this.MorseTxt.TabIndex = 3; - this.MorseTxt.Text = ""; + MorseTxt.Location = new Point(12, 70); + MorseTxt.Name = "MorseTxt"; + MorseTxt.Size = new Size(286, 109); + MorseTxt.TabIndex = 3; + MorseTxt.Text = ""; // // ToMorseKurdishBtn // - this.ToMorseKurdishBtn.Location = new System.Drawing.Point(304, 70); - this.ToMorseKurdishBtn.Name = "ToMorseKurdishBtn"; - this.ToMorseKurdishBtn.Size = new System.Drawing.Size(173, 29); - this.ToMorseKurdishBtn.TabIndex = 4; - this.ToMorseKurdishBtn.Text = "To Morse Kurdish"; - this.ToMorseKurdishBtn.UseVisualStyleBackColor = true; - this.ToMorseKurdishBtn.Click += new System.EventHandler(this.ToMorseKurdish_Click); + ToMorseKurdishBtn.Location = new Point(304, 70); + ToMorseKurdishBtn.Name = "ToMorseKurdishBtn"; + ToMorseKurdishBtn.Size = new Size(173, 29); + ToMorseKurdishBtn.TabIndex = 4; + ToMorseKurdishBtn.Text = "To Morse Kurdish"; + ToMorseKurdishBtn.UseVisualStyleBackColor = true; + ToMorseKurdishBtn.Click += ToMorseKurdish_Click; + // + // blinkerPl + // + blinkerPl.Location = new Point(12, 207); + blinkerPl.Name = "blinkerPl"; + blinkerPl.Size = new Size(465, 179); + blinkerPl.TabIndex = 5; + // + // blinkBtn + // + blinkBtn.Location = new Point(304, 140); + blinkBtn.Name = "blinkBtn"; + blinkBtn.Size = new Size(173, 29); + blinkBtn.TabIndex = 6; + blinkBtn.Text = "Light"; + blinkBtn.UseVisualStyleBackColor = true; + blinkBtn.Click += blinkBtn_Click; // // MainWindow // - this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 20F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(592, 191); - this.Controls.Add(this.ToMorseKurdishBtn); - this.Controls.Add(this.MorseTxt); - this.Controls.Add(this.PlayBtn); - this.Controls.Add(this.MessageMorseTxt); - this.Controls.Add(this.ToMorseEnglishBtn); - this.Name = "MainWindow"; - this.Text = "MorseToAudio"; - this.ResumeLayout(false); - this.PerformLayout(); - + AutoScaleDimensions = new SizeF(8F, 20F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(500, 398); + Controls.Add(blinkBtn); + Controls.Add(blinkerPl); + Controls.Add(ToMorseKurdishBtn); + Controls.Add(MorseTxt); + Controls.Add(PlayBtn); + Controls.Add(MessageMorseTxt); + Controls.Add(ToMorseEnglishBtn); + Name = "MainWindow"; + Text = "MorseToAudio"; + ResumeLayout(false); + PerformLayout(); } #endregion @@ -104,5 +124,7 @@ private void InitializeComponent() private Button PlayBtn; private RichTextBox MorseTxt; private Button ToMorseKurdishBtn; + private Panel blinkerPl; + private Button blinkBtn; } } \ No newline at end of file diff --git a/AudioExample/MainWindow.cs b/AudioExample/MainWindow.cs index e39b0d7..528d410 100644 --- a/AudioExample/MainWindow.cs +++ b/AudioExample/MainWindow.cs @@ -1,5 +1,4 @@ using MorseSharp; -using MorseSharp.Converter; using System.IO; using System.Media; namespace AudioExample @@ -7,7 +6,10 @@ namespace AudioExample public partial class MainWindow : Form { //Declare memory of bytes - Memory bytes = default; + byte[] bytes = default; + + //Language + Language language; //Declare SoundPlayer object to play the sound form the stream SoundPlayer soundPlayer; @@ -20,13 +22,8 @@ public MainWindow() soundPlayer = new(); } - private async void ToAudioBtn_Click(object sender, EventArgs e) + private void ToAudioBtn_Click(object sender, EventArgs e) { - //Delcare and init MorseSharp MorseAudioConverter object to convert morse to audio of dash and dots. - MorseAudioConverter converter = new MorseAudioConverter(Language.English); - //Delcare and init MorseSharp MorseTextConverter object to convert sentence to morse dash and dots. - TextMorseConverter textConverter = new TextMorseConverter(Language.English); - try { @@ -34,12 +31,23 @@ private async void ToAudioBtn_Click(object sender, EventArgs e) if (MessageMorseTxt.Text.Length > 0) { //Get wav bytes from the ConvertMorseAudio method and then assigned to memory of bytes - bytes = await converter.ConvertMorseToAudio(MessageMorseTxt.Text); + Morse.GetConverter() + .ForLanguage(Language.English) + .ToMorse(MessageMorseTxt.Text) + .ToAudio() + .SetAudioOptions(25, 25, 600) + .GetBytes(out Span bytes); + + this.bytes = bytes.ToArray(); //Update the richtextbox text to morse dash and dots. - MorseTxt.Text = textConverter.ConvertTextToMorse(MessageMorseTxt.Text); + MorseTxt.Text = Morse.GetConverter() + .ForLanguage(Language.English) + .ToMorse(MessageMorseTxt.Text) + .Encode(); + language = Language.English; //Enable the play button to play PlayBtn.Enabled = true; } @@ -64,13 +72,13 @@ private void PlayBtn_Click(object sender, EventArgs e) } } - private async void ToMorseKurdish_Click(object sender, EventArgs e) + private void ToMorseKurdish_Click(object sender, EventArgs e) { - //Delcare and init MorseSharp MorseAudioConverter object to convert morse to audio of dash and dots. - MorseAudioConverter converter = new MorseAudioConverter(Language.Kurdish); + ////Delcare and init MorseSharp MorseAudioConverter object to convert morse to audio of dash and dots. + //MorseAudioConverter converter = new MorseAudioConverter(Language.Kurdish); - //Delcare and init MorseSharp MorseTextConverter object to convert sentence to morse dash and dots. - TextMorseConverter textConverter = new TextMorseConverter(Language.Kurdish); + ////Delcare and init MorseSharp MorseTextConverter object to convert sentence to morse dash and dots. + //TextMorseConverter textConverter = new TextMorseConverter(Language.Kurdish); try { @@ -78,12 +86,23 @@ private async void ToMorseKurdish_Click(object sender, EventArgs e) if (MessageMorseTxt.Text.Length > 0) { //Get wav bytes from the ConvertMorseAudio method and then assigned to memory of bytes - bytes = await converter.ConvertMorseToAudio(MessageMorseTxt.Text); + Morse.GetConverter() + .ForLanguage(Language.Kurdish) + .ToMorse(MessageMorseTxt.Text) + .ToAudio() + .SetAudioOptions(25, 25, 600) + .GetBytes(out Span bytes); + + this.bytes = bytes.ToArray(); //Update the richtextbox text to morse dash and dots. - MorseTxt.Text = textConverter.ConvertTextToMorse(MessageMorseTxt.Text); + MorseTxt.Text = Morse.GetConverter() + .ForLanguage(Language.Kurdish) + .ToMorse(MessageMorseTxt.Text) + .Encode(); + language = Language.Kurdish; //Enable the play button to play PlayBtn.Enabled = true; } @@ -94,5 +113,28 @@ private async void ToMorseKurdish_Click(object sender, EventArgs e) } } + + private void blinkBtn_Click(object sender, EventArgs e) + { + if (MessageMorseTxt.Text.Length > 0) + { + Morse.GetConverter() + .ForLanguage(this.language) + .ToMorse(MessageMorseTxt.Text) + .ToLight() + .SetBlinkerOptions(25, 25) + .DoBlinks((hasToBlink) => + { + if (hasToBlink) + { + blinkerPl.BackColor = Color.Black; + } + else + { + blinkerPl.BackColor = Color.White; + } + }); + } + } } } diff --git a/AudioExample/MainWindow.resx b/AudioExample/MainWindow.resx index f298a7b..af32865 100644 --- a/AudioExample/MainWindow.resx +++ b/AudioExample/MainWindow.resx @@ -1,4 +1,64 @@ - + + + diff --git a/Example/Program.cs b/Example/Program.cs index e5f8e21..149d827 100644 --- a/Example/Program.cs +++ b/Example/Program.cs @@ -1,21 +1,35 @@ - +using MorseSharp; -TextMorseConverter Converter = new TextMorseConverter(Language.English); +try +{ + //Encoding + var morse = Morse.GetConverter() + .ForLanguage(Language.English) + .ToMorse("Hi") + .Encode(); + //Decoding + var text = Morse.GetConverter() + .ForLanguage(Language.English) + .Decode(".... .."); -string morse = string.Empty; -string text = string.Empty; + //Light Blinking + await Morse.GetConverter() + .ForLanguage(Language.English) + .ToMorse("Hi") + .ToLight() + .SetBlinkerOptions(25, 25) + .DoBlinks((hasToBlink) => + { + if (hasToBlink) + Console.BackgroundColor = ConsoleColor.White; + else + Console.BackgroundColor = ConsoleColor.Black; + }); -try -{ - morse = Converter.ConvertTextToMorse("The quick brown fox jumps over the lazy dog"); - text = Converter.ConvertMorseToText("_ .... . / __._ .._ .. _._. _._ / _... ._. ___ .__ _. / .._. ___ _.._ / .___ .._ __ .__. ... / ___ ..._ . ._. / _ .... . / ._.. ._ __.. _.__ / _.. ___ __."); } -catch (Exception ex) +catch(Exception ex) { Console.WriteLine(ex.Message); } - -Console.WriteLine(morse); -Console.WriteLine(text); \ No newline at end of file diff --git a/MorseSharp/Audio/AudioConverter.cs b/MorseSharp/Audio/AudioConverter.cs index 59dbfe0..66ef649 100644 --- a/MorseSharp/Audio/AudioConverter.cs +++ b/MorseSharp/Audio/AudioConverter.cs @@ -1,4 +1,6 @@ - +using ListPool; +using System.Buffers; + namespace MorseSharp.Audio; [StructLayout(LayoutKind.Sequential)] @@ -72,7 +74,7 @@ private Span GetSilence(double seconds) private Span GetCharacter(string morseSymbol) { - List data = new List(); + using ListPool data = new ListPool(); for (int i = 0; i < morseSymbol.Length; i++) { @@ -84,21 +86,33 @@ private Span GetCharacter(string morseSymbol) data.AddRange(GetDot()); } - return CollectionsMarshal.AsSpan(data); + return data.AsSpan(); } private Span GenerateWav(string text) { - List data = new List(); + using ListPool data = new ListPool(); + + int count = 0; + unsafe + { + fixed (char* ptr = text) + { + // Count occurrences of spaces to split Morse code into words + count = StringCounter.CountOccurrences(ptr, ' '); + } + + } - ReadOnlySpan sMorse = text.AsSpan(); - Span splitedRange = new Span(new Range[sMorse.Count(' ') + 1]); + Range[]? rngArray = null; + Span splitedRange = count + 1 < 256 + ? stackalloc Range[count + 1] : rngArray = ArrayPool.Shared.Rent(count + 1); - sMorse.Split(splitedRange, ' ', StringSplitOptions.None); + text.AsSpan().Split(splitedRange, ' ', StringSplitOptions.None); for (int i = 0; i < splitedRange.Length; i++) { - var morseSymbol = sMorse.Slice(splitedRange[i].Start.Value, splitedRange[i].End.Value - splitedRange[i].Start.Value); + var morseSymbol = text.AsSpan().Slice(splitedRange[i].Start.Value, splitedRange[i].End.Value - splitedRange[i].Start.Value); if (i > 0) data.AddRange(GetInterWordSpace()); @@ -107,7 +121,9 @@ private Span GenerateWav(string text) // Pad the end with a little bit of silence. Otherwise, the last character may sound funny in some media players. data.AddRange(GetInterCharSpace()); - return CollectionsMarshal.AsSpan(data); + if (rngArray is not null) + ArrayPool.Shared.Return(rngArray); + return data.AsSpan(); } [SkipLocalsInit] diff --git a/MorseSharp/Exceptions/SequenceNotFoundException.cs b/MorseSharp/Exceptions/SequenceNotFoundException.cs index ff47cdf..c492a72 100644 --- a/MorseSharp/Exceptions/SequenceNotFoundException.cs +++ b/MorseSharp/Exceptions/SequenceNotFoundException.cs @@ -1,7 +1,7 @@ namespace MorseSharp.Exceptions { [Serializable] - internal class SequenceNotFoundException : Exception + public class SequenceNotFoundException : Exception { public SequenceNotFoundException() { } diff --git a/MorseSharp/Exceptions/SmallerWordSpeedException.cs b/MorseSharp/Exceptions/SmallerWordSpeedException.cs index af5f40c..5f93469 100644 --- a/MorseSharp/Exceptions/SmallerWordSpeedException.cs +++ b/MorseSharp/Exceptions/SmallerWordSpeedException.cs @@ -2,7 +2,7 @@ namespace MorseSharp.Exceptions { [Serializable] - internal class SmallerWordSpeedException : Exception + public class SmallerWordSpeedException : Exception { public SmallerWordSpeedException() { } diff --git a/MorseSharp/Exceptions/WordNotPresentedException.cs b/MorseSharp/Exceptions/WordNotPresentedException.cs index a3dd3a8..5d55992 100644 --- a/MorseSharp/Exceptions/WordNotPresentedException.cs +++ b/MorseSharp/Exceptions/WordNotPresentedException.cs @@ -2,7 +2,7 @@ namespace MorseSharp.Exceptions { [Serializable] - internal class WordNotPresentedException : Exception + public class WordNotPresentedException : Exception { public WordNotPresentedException() { } diff --git a/MorseSharp/Interfaces/ICanSetConversionOption.cs b/MorseSharp/Interfaces/ICanSetConversionOption.cs index c8e5d6e..561b75a 100644 --- a/MorseSharp/Interfaces/ICanSetConversionOption.cs +++ b/MorseSharp/Interfaces/ICanSetConversionOption.cs @@ -18,5 +18,7 @@ public interface ICanSetConversionOption /// The text to be converted to Morse code. /// The converted Morse code. ICanGenerateAudioAndLight ToMorse(string text); + ICanSetAudioOptions ToAudio(string morse); + ICanSetBlinkerOptions ToLight(string morse); } } \ No newline at end of file diff --git a/MorseSharp/MorseSharp.csproj b/MorseSharp/MorseSharp.csproj index 92b1c83..6f67f65 100644 --- a/MorseSharp/MorseSharp.csproj +++ b/MorseSharp/MorseSharp.csproj @@ -37,6 +37,7 @@ + diff --git a/MorseTest/Languages.cs b/MorseTest/Languages.cs index feab28a..b9b8fdc 100644 --- a/MorseTest/Languages.cs +++ b/MorseTest/Languages.cs @@ -5,10 +5,11 @@ public class Languages [Fact] public void EnglishToMorse() { - - TextMorseConverter Converter = new TextMorseConverter(Language.English); - var morse = Converter.ConvertTextToMorse("The quick brown fox jumps over the lazy dog"); + var morse = Morse.GetConverter() + .ForLanguage(Language.English) + .ToMorse("The quick brown fox jumps over the lazy dog") + .Encode(); Assert.Equal("_ .... . / __._ .._ .. _._. _._ / _... ._. ___ .__ _. / .._. ___ _.._ / .___ .._ __ .__. ... / ___ ..._ . ._. / _ .... . / ._.. ._ __.. _.__ / _.. ___ __.", morse); @@ -16,9 +17,11 @@ public void EnglishToMorse() [Fact] public void KurdishToMorse() { - TextMorseConverter Converter = new TextMorseConverter(Language.Kurdish); - var morse = Converter.ConvertTextToMorse("کۆژین و ڤیان چوونە بۆ باغەکە ئاوی ساردیان دا بە خرینگ و عەگ و قوڵینگەکان ئینجا پەروازەکانیان فڕاند دواتریش هەموو حاجیلەکانیان چنی"); + var morse = Morse.GetConverter() + .ForLanguage(Language.Kurdish) + .ToMorse("کۆژین و ڤیان چوونە بۆ باغەکە ئاوی ساردیان دا بە خرینگ و عەگ و قوڵینگەکان ئینجا پەروازەکانیان فڕاند دواتریش هەموو حاجیلەکانیان چنی") + .Encode(); Assert.Equal("_.... ._._ __. .. _. / .__ / .._.. .. ._ _. / ___. .__ .__ _. . / _... ._._ / _... ._ ..__ . _.... . / .._.. ._ .__ .. / ... ._ _._ _.. .. ._ _. / _.. ._ / _... . / _.._ _._ .. _. __._ / .__ / ___ . __._ / .__ / ...___ .__ ..._ .. _. __._ . _.... ._ _. / .._.. .. _. .___ ._ / .__. . _._ .__ ._ __.. . _.... ._ _. .. ._ _. / .._. ._. ._ _. _.. / _.. .__ ._ _ _._ .. ____ / _._. . __ .__ .__ / .... ._ .___ .. ._.. . _.... ._ _. .. ._ _. / ___. _. ..", morse); @@ -26,9 +29,10 @@ public void KurdishToMorse() [Fact] public void KurdishLatinToMorse() { - TextMorseConverter Converter = new TextMorseConverter(Language.KurdishLatin); - - var morse = Converter.ConvertTextToMorse("Cem vî Fekoyê pîs zêdetir ji çar gulên xweşik hebûn"); + var morse = Morse.GetConverter() + .ForLanguage(Language.KurdishLatin) + .ToMorse("Cem vî Fekoyê pîs zêdetir ji çar gulên xweşik hebûn") + .Encode(); Assert.Equal(".___ . __ / .._.. .. / .._. . _.... ._._ ..__ .._ / .__. .. ... / __.. .._ _.. . _ .._.. _._ / __. .._.. / ___. ._ _._ / __._ .__ ._.. .._ _. / _.._ ___ . ____ .._.. _.... / _._. . _... .__.__ _.", morse); @@ -36,9 +40,10 @@ public void KurdishLatinToMorse() [Fact] public void ArabicToMorse() { - TextMorseConverter Converter = new TextMorseConverter(Language.Arabic); - - var morse = Converter.ConvertTextToMorse("ابجد هوز حطي كلمن سعفص قرشت ثخذ ضظغ"); + var morse = Morse.GetConverter() + .ForLanguage(Language.Arabic) + .ToMorse("ابجد هوز حطي كلمن سعفص قرشت ثخذ ضظغ") + .Encode(); Assert.Equal("._ _... .___ _.. / .._.. .__ ___. / .... .._ .. / _._ ._.. __ _. / ... ._._ .._. _.._ / __._ ._. ____ _ / _._. ___ __.. / ..._ _.__ __.", morse); @@ -46,9 +51,10 @@ public void ArabicToMorse() [Fact] public void DeutschToMorse() { - TextMorseConverter Converter = new TextMorseConverter(Language.Deutsch); - - var morse = Converter.ConvertTextToMorse("Victor jagt zwölf Boxkämpfer quer über den groẞen Sylter Deich"); + var morse = Morse.GetConverter() + .ForLanguage(Language.Deutsch) + .ToMorse("Victor jagt zwölf Boxkämpfer quer über den groẞen Sylter Deich") + .Encode(); Assert.Equal("..._ .. _._. _ ___ _._ / .___ ._ __. _ / __.. .__ ___. ._.. .._. / _... ___ _.._ _._ ._._ __ .__. .._. . _._ / __._ .._ . _._ / ..__ _... . _._ / _.. . _. / __. _._ ___ ...... . _. / ... _.__ ._.. _ . _._ / _.. . .. _._. ....", morse); @@ -57,9 +63,10 @@ public void DeutschToMorse() [Fact] public void EspanolToMorse() { - TextMorseConverter Converter = new TextMorseConverter(Language.Espanol); - - var morse = Converter.ConvertTextToMorse("El jefe buscó el éxtasis en un imprevisto baño de whisky y gozó como un duque"); + var morse = Morse.GetConverter() + .ForLanguage(Language.Espanol) + .ToMorse("El jefe buscó el éxtasis en un imprevisto baño de whisky y gozó como un duque") + .Encode(); Assert.Equal(". ._.. / .___ . .._. . / _... .._ ... _._. ___. / . ._.. / .._.. _.._ _ ._ ... .. ... / . _. / .._ _. / .. __ .__. ._. . ..._ .. ... _ ___ / _... ._ __.__ ___ / _.. . / .__ .... .. ... _._ _.__ / _.__ / __. ___ __.. ___. / _._. ___ __ ___ / .._ _. / _.. .._ __._ .._ .", morse); @@ -67,9 +74,10 @@ public void EspanolToMorse() [Fact] public void FrancaisToMorse() { - TextMorseConverter Converter = new TextMorseConverter(Language.Francais); - - var morse = Converter.ConvertTextToMorse("Portez ce vieux whisky au juge blond qui fume"); + var morse = Morse.GetConverter() + .ForLanguage(Language.Francais) + .ToMorse("Portez ce vieux whisky au juge blond qui fume") + .Encode(); Assert.Equal(".__. ___ ._. _ . __.. / _._. . / ..._ .. . .._ _.._ / .__ .... .. ... _._ _.__ / ._ .._ / .___ .._ __. . / _... ._.. ___ _. _.. / __._ .._ .. / .._. .._ __ .", morse); @@ -77,9 +85,10 @@ public void FrancaisToMorse() [Fact] public void ItalianoToMorse() { - TextMorseConverter Converter = new TextMorseConverter(Language.Italiano); - - var morse = Converter.ConvertTextToMorse("Pranzo d'acqua fa volti sghembi"); + var morse = Morse.GetConverter() + .ForLanguage(Language.Italiano) + .ToMorse("Pranzo d'acqua fa volti sghembi") + .Encode(); Assert.Equal(".__. ._. ._ _. __.. ___ / _.. .____. ._ _._. __._ .._ ._ / .._. ._ / ..._ ___ ._.. _ .. / ... __. .... . __ _... ..", morse); @@ -87,9 +96,10 @@ public void ItalianoToMorse() [Fact] public void JapaneseToMorse() { - TextMorseConverter Converter = new TextMorseConverter(Language.Japanese); - - var morse = Converter.ConvertTextToMorse("アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン"); + var morse = Morse.GetConverter() + .ForLanguage(Language.Japanese) + .ToMorse("アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン") + .Encode(); Assert.Equal("__.__ ._ .._ _.___ ._... ._.. _._.. ..._ _.__ ____ _._._ __._. ___._ .___. ___. _. .._. .__. ._.__ .._.. ._. _._. .... __._ ..__ _... __.._ __.. . _.. _.._ .._._ _ _..._ _.._. .__ _..__ __ ... __. _.__. ___ ._._ _._ .___ ._._.", morse); @@ -98,9 +108,10 @@ public void JapaneseToMorse() [Fact] public void PortuguesToMorse() { - TextMorseConverter Converter = new TextMorseConverter(Language.Portugues); - - var morse = Converter.ConvertTextToMorse("Um pequeno jabuti xereta viu dez cegonhas felizes"); + var morse = Morse.GetConverter() + .ForLanguage(Language.Portugues) + .ToMorse("Um pequeno jabuti xereta viu dez cegonhas felizes") + .Encode(); Assert.Equal(".._ __ / .__. . __._ .._ . _. ___ / .___ ._ _... .._ _ .. / _.._ . ._. . _ ._ / ..._ .. .._ / _.. . __.. / _._. . __. ___ _. .... ._ ... / .._. . ._.. .. __.. . ...", morse); diff --git a/MorseTest/MorseAudioConverter.cs b/MorseTest/MorseAudioConverter.cs index b3853e0..baaf252 100644 --- a/MorseTest/MorseAudioConverter.cs +++ b/MorseTest/MorseAudioConverter.cs @@ -1,40 +1,33 @@ -using MorseSharp.Converter; - -namespace MorseTest +namespace MorseTest { public class MorseAudioConverterTest { //Latin [Fact] - public async void ConvertToAudioEnglish() + public void ConvertToAudioEnglish() { - MorseSharp.Converter.MorseAudioConverter converter = new(); - var morse = await converter.ConvertMorseToAudio("Hello Morse"); - Assert.True(morse.Length > 0); - } + Morse.GetConverter() + .ForLanguage(Language.English) + .ToMorse("Hello Morse") + .ToAudio() + .SetAudioOptions(25, 25, 600) + .GetBytes(out Span morse); - [Fact] - public void ConvertToAudioWithNullArgumentEnglish() - { - MorseSharp.Converter.MorseAudioConverter converter = new(); - Assert.ThrowsAsync(async () => await converter.ConvertMorseToAudio(null)); + Assert.True(morse.Length > 0); } //NonLatin [Fact] - public async void ConvertToAudioKurdish() + public void ConvertToAudioKurdish() { - MorseAudioConverter converter = new(Language.Kurdish); - var morse = await converter.ConvertMorseToAudio("زانا"); - Assert.True(morse.Length > 0); - } + Morse.GetConverter() + .ForLanguage(Language.Kurdish) + .ToMorse("کووی") + .ToAudio() + .SetAudioOptions(25, 25, 600) + .GetBytes(out Span morse); - //Argumnet Null Exception - [Fact] - public void ConvertToAudioWithNullArgumentKurdish() - { - MorseAudioConverter converter = new(Language.Kurdish); - Assert.ThrowsAsync(async () => await converter.ConvertMorseToAudio(null)); + Assert.True(morse.Length > 0); } } } diff --git a/MorseTest/TextMorseConverter.cs b/MorseTest/TextMorseConverter.cs index 6574768..e5695e0 100644 --- a/MorseTest/TextMorseConverter.cs +++ b/MorseTest/TextMorseConverter.cs @@ -1,47 +1,68 @@ -using MorseSharp.Converter; - -namespace MorseTest +namespace MorseTest { public class TextMorseConverterTest { [Fact] - public void ConvertMorseWithValidString() + public void ConvertToMorseWithValidString() { - TextMorseConverter converter = new TextMorseConverter(Language.English); - var morse = converter.ConvertTextToMorse("Hi There"); + var morse = Morse.GetConverter() + .ForLanguage(Language.English) + .ToMorse("The quick brown fox jumps over the lazy dog") + .Encode(); + Assert.True(morse.Length > 0); } [Fact] - public void ConvertMorseWithNullString() + public void ConvertToMorseWithNullString() { - TextMorseConverter converter = new TextMorseConverter(Language.English); - Assert.Throws(() => converter.ConvertTextToMorse(null)); + Assert.Throws(() => + { + var morse = Morse.GetConverter() + .ForLanguage(Language.English) + .ToMorse(null) + .Encode(); + }); } [Fact] - public void ConvertMorseWithInvalidCharacter() + public void ConvertToMorseWithInvalidCharacter() { - var converter = new TextMorseConverter(Language.Kurdish); - Assert.Throws(() => converter.ConvertTextToMorse("Hi~")); + Assert.Throws(() => + { + var morse = Morse.GetConverter() + .ForLanguage(Language.Kurdish) + .ToMorse("~") + .Encode(); + }); } [Fact] - public void ConvertTextWithValidMorse() + public void ConvertToTextWithValidMorse() { - TextMorseConverter converter = new TextMorseConverter(Language.Kurdish); - var morse = converter.ConvertMorseToText("... ..._ ._ .__"); + var morse = Morse.GetConverter() + .ForLanguage(Language.English) + .Decode(".... .."); + Assert.True(morse.Length > 0); } [Fact] - public void ConvertTextWithNullMorse() + public void ConvertToTextWithNullMorse() { - TextMorseConverter converter = new TextMorseConverter(Language.Kurdish); - Assert.Throws(() => converter.ConvertMorseToText(null)); + Assert.Throws(() => + { + var morse = Morse.GetConverter() + .ForLanguage(Language.Kurdish) + .Decode(null); + }); } [Fact] public void ConvertTextWithInvalidMorse() { - TextMorseConverter converter = new TextMorseConverter(Language.Kurdish); - Assert.Throws(() => converter.ConvertMorseToText("........")); + Assert.Throws(() => + { + var morse = Morse.GetConverter() + .ForLanguage(Language.Kurdish) + .Decode("............"); + }); } diff --git a/MorseTest/Usings.cs b/MorseTest/Usings.cs index 6d60dca..55c23bf 100644 --- a/MorseTest/Usings.cs +++ b/MorseTest/Usings.cs @@ -1,2 +1,3 @@ global using Xunit; -global using MorseSharp; \ No newline at end of file +global using MorseSharp; +global using MorseSharp.Exceptions; \ No newline at end of file From 572c55dda1977b2f876e554b0b11701db4e0c1b5 Mon Sep 17 00:00:00 2001 From: p6laris Date: Sat, 18 Nov 2023 05:00:18 +0300 Subject: [PATCH 12/18] Made the converter class thread safe. --- MorseSharp/Morse.cs | 219 ++++++++++++++++++++++++++------------------ 1 file changed, 128 insertions(+), 91 deletions(-) diff --git a/MorseSharp/Morse.cs b/MorseSharp/Morse.cs index 429615c..0ebc3bf 100644 --- a/MorseSharp/Morse.cs +++ b/MorseSharp/Morse.cs @@ -1,4 +1,6 @@ -namespace MorseSharp; +using System.Buffers; + +namespace MorseSharp; /// @@ -8,22 +10,31 @@ public sealed class Morse : ICanSpecifyLanguage, ICanSetConversionOption, ICanConvertToAudio, ICanSetAudioOptions, ICanGenerateAudioAndLight, ICanSetBlinkerOptions, ICanConvertToLight { - private Lazy> _morseChar = default!; - private Language _sLanguage; + private static ThreadLocal>> _morseChar; + private static ThreadLocal _sLanguage; + private static ThreadLocal _strBuilder; + private static ThreadLocal _charSpeed; + private static ThreadLocal _wordSpeed; + private static ThreadLocal _frequency; + private const Language NonLatin = Language.Kurdish | Language.Arabic; + private static Morse? _instance; - private static readonly StringBuilder _strBuilder = new StringBuilder(); - private static readonly object ObjLock = new(); - private int _charSpeed; - private int _wordSpeed; - private double _frequency; + private static object ObjLock = new(); /// /// Initializes a new instance of the class. /// private Morse() { + _sLanguage = new ThreadLocal(() => default(Language)); + _morseChar = new ThreadLocal>>(() => new Lazy>()); + _strBuilder = new ThreadLocal(() => new StringBuilder()); + _charSpeed = new ThreadLocal(() => 0); + _wordSpeed = new ThreadLocal(() => 0); + _frequency = new ThreadLocal(() => 0.0); + } /// @@ -37,7 +48,11 @@ public static ICanSpecifyLanguage GetConverter() { lock (ObjLock) { - _instance = new Morse(); + if (_instance is null) + { + _instance = new Morse(); + } + } } @@ -51,11 +66,12 @@ public static ICanSpecifyLanguage GetConverter() /// An instance of the interface. public ICanSetConversionOption ForLanguage(Language language) { + //cache the language - if (language != _sLanguage) + if (language != _sLanguage.Value) { - _sLanguage = language; - _morseChar = new Lazy>(MorseCharacters.GetLanguageCharacter(language)!); + _sLanguage.Value = language; + _morseChar.Value = new Lazy>(MorseCharacters.GetLanguageCharacter(_sLanguage.Value)!); } return this; @@ -68,57 +84,55 @@ public ICanSetConversionOption ForLanguage(Language language) /// The converted text. public string Decode(string morse) { - if (!string.IsNullOrEmpty(morse)) - { - - if (_strBuilder.Length > 0) - _strBuilder.Length = 0; + if (string.IsNullOrEmpty(morse)) + throw new ArgumentNullException(nameof(morse)); - ReadOnlySpan sMorse = morse.AsSpan(); + if (_strBuilder.Value!.Length > 0) + _strBuilder.Value.Length = 0; - int count = 0; + int count = 0; - unsafe + unsafe + { + fixed (char* ptr = morse) { - fixed (char* ptr = morse) - { - // Count occurrences of spaces to split Morse code into words - count = StringCounter.CountOccurrences(ptr, ' '); - } - + // Count occurrences of spaces to split Morse code into words + count = StringCounter.CountOccurrences(ptr, ' '); } - Span splitedRange = new Span(new Range[count + 1]); + } + + Range[]? rngArray = null; + Span splitedRange = count + 1 < 256 + ? stackalloc Range[count + 1] : rngArray = ArrayPool.Shared.Rent(count + 1); - // Split Morse code into individual words - sMorse.Split(splitedRange, ' ', StringSplitOptions.None); + // Split Morse code into individual words + morse.AsSpan().Split(splitedRange, ' ', StringSplitOptions.None); - for (int i = 0; i < splitedRange.Length; i++) + for (int i = 0; i < splitedRange.Length; i++) + { + var sequence = morse.AsSpan().Slice(splitedRange[i].Start.Value, splitedRange[i].End.Value - splitedRange[i].Start.Value); + + if (sequence.IsWhiteSpace()) + continue; + + if (sequence != "/") { - var sequence = sMorse.Slice(splitedRange[i].Start.Value, splitedRange[i].End.Value - splitedRange[i].Start.Value); + var value = _morseChar.Value!.Value.KeyByValue(sequence.ToString()); - if (sequence.IsWhiteSpace()) - continue; + if (value != '\0') + _strBuilder.Value.Append(value); - if (sequence != "/") - { - if (_morseChar.Value.Values.Contains(sequence.ToString())) - { - var value = _morseChar.Value.KeyByValue(sequence.ToString()); - _strBuilder.Append(value); - } - else - throw new SequenceNotFoundException(sequence.ToString(), language: _sLanguage); - } + else + throw new SequenceNotFoundException(sequence.ToString(), language: _sLanguage.Value); } - return _strBuilder.ToString(); } - else - throw new ArgumentNullException(nameof(morse)); - + if (rngArray is not null) + ArrayPool.Shared.Return(rngArray); + return _strBuilder.Value.ToString(); } /// @@ -128,52 +142,48 @@ public string Decode(string morse) /// The Morse code representation of the text. public ICanGenerateAudioAndLight ToMorse(string text) { - if (!string.IsNullOrEmpty(text)) + if (string.IsNullOrEmpty(text)) + throw new ArgumentNullException(nameof(text)); + + lock (_strBuilder) { + if (_strBuilder.Value!.Length > 0) + _strBuilder.Value.Length = 0; + } - if (_strBuilder.Length > 0) - _strBuilder.Length = 0; + var length = text.Length; + char[]? txtArray = null; + Span txt = length < 256 + ? stackalloc char[length] : txtArray = ArrayPool.Shared.Rent(length); - ReadOnlySpan sText = text.AsSpan(); + if ((_sLanguage.Value & NonLatin) == 0) + text.AsSpan().ToUpperInvariant(txt); + else + text.AsSpan().CopyTo(txt); - var length = sText.Length; - if ((_sLanguage & NonLatin) == 0) + for (int i = 0; i < length; i++) + { + if (_morseChar.Value!.Value.TryGetValue(txt[i], out string? val)) { - unsafe - { - fixed (char* ptr = text) - fixed (char* outPtr = sText) - { - // Convert text to uppercase using pointers - ToUpperSpanExtention.ToUpperWithPointers(ptr, outPtr, text.Length); - } - } - } + // Check if it's not the last character before appending a space + if (i < text.Length - 1) + _strBuilder.Value.Append(val.AsSpan()).Append(' '); - for (int i = 0; i < length; i++) - { - if (_morseChar.Value.TryGetValue(sText[i], out string? val)) - { - // Check if it's not the last character before appending a space - if (i < text.Length - 1) - { - _strBuilder.Append(val.AsSpan()).Append(' '); - } - else - _strBuilder.Append(val.AsSpan()); - } else - throw new WordNotPresentedException(sText[i], language: _sLanguage); + _strBuilder.Value.Append(val.AsSpan()); } - - return this; + else + throw new WordNotPresentedException(txt[i], language: _sLanguage.Value); } - else - throw new ArgumentException(nameof(text)); + + if (txtArray is not null) + ArrayPool.Shared.Return(txtArray); + + return this; } @@ -184,8 +194,8 @@ public ICanGenerateAudioAndLight ToMorse(string text) /// The destination span to store the audio bytes. public void GetBytes(out Span destination) { - AudioConverter a = new AudioConverter(_charSpeed, _wordSpeed, _frequency); - a.ConvertToAudio(_strBuilder.ToString(), out Span bytes); + AudioConverter a = new AudioConverter(_charSpeed.Value, _wordSpeed.Value, _frequency.Value); + a.ConvertToAudio(_strBuilder.Value!.ToString(), out Span bytes); destination = bytes; } @@ -201,36 +211,63 @@ public ICanConvertToAudio SetAudioOptions(int charSpeed = 25, int wordSpeed = 25 if (charSpeed < wordSpeed) throw new SmallerWordSpeedException(charSpeed, wordSpeed); - _charSpeed = charSpeed; - _wordSpeed = wordSpeed; - _frequency = frequency; + _charSpeed.Value = charSpeed; + _wordSpeed.Value = wordSpeed; + _frequency.Value = frequency; return this; } - public string Encode() => _strBuilder.ToString(); + public string Encode() => _strBuilder.Value!.ToString(); public ICanSetAudioOptions ToAudio() => this; public ICanSetBlinkerOptions ToLight() => this; + public ICanSetAudioOptions ToAudio(string morse) + { + if (string.IsNullOrEmpty(morse)) + throw new ArgumentException(nameof(morse)); + + + if (_strBuilder.Value!.Length > 0) + _strBuilder.Value.Length = 0; + + _strBuilder.Value.Append(morse); + return this; + } + + public ICanSetBlinkerOptions ToLight(string morse) + { + if (string.IsNullOrEmpty(morse)) + throw new ArgumentException(nameof(morse)); + + + if (_strBuilder.Value!.Length > 0) + _strBuilder.Value.Length = 0; + + _strBuilder.Value.Append(morse); + return this; + } public ICanConvertToLight SetBlinkerOptions(int charSpeed = 25, int wordSpeed = 25) { if (charSpeed < wordSpeed) throw new SmallerWordSpeedException(charSpeed, wordSpeed); - _charSpeed = charSpeed; - _wordSpeed = wordSpeed; + _charSpeed.Value = charSpeed; + _wordSpeed.Value = wordSpeed; return this; } public async Task DoBlinks(Action blinkerAction) { - if (blinkerAction == null) + if (blinkerAction is null) throw new ArgumentNullException(nameof(blinkerAction)); - LightBlinker lightBlinker = new LightBlinker(_charSpeed, _wordSpeed, blinkerAction); - await lightBlinker.BlinkLight(_strBuilder.ToString()); + LightBlinker lightBlinker = new LightBlinker(_charSpeed.Value, _wordSpeed.Value, blinkerAction); + await lightBlinker.BlinkLight(_strBuilder.Value!.ToString()); } + + } From 1f0380511cbe421d8496c894f869172429ede19c Mon Sep 17 00:00:00 2001 From: p6laris Date: Sun, 19 Nov 2023 10:48:37 +0300 Subject: [PATCH 13/18] Update the audio example. --- AudioExample/MainWindow.cs | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/AudioExample/MainWindow.cs b/AudioExample/MainWindow.cs index 528d410..ba5087e 100644 --- a/AudioExample/MainWindow.cs +++ b/AudioExample/MainWindow.cs @@ -8,6 +8,7 @@ public partial class MainWindow : Form //Declare memory of bytes byte[] bytes = default; + string morse; //Language Language language; @@ -30,11 +31,13 @@ private void ToAudioBtn_Click(object sender, EventArgs e) //Check if the message length is greater than zero. if (MessageMorseTxt.Text.Length > 0) { + morse = Morse.GetConverter() + .ForLanguage(Language.English) + .ToMorse(MessageMorseTxt.Text).Encode(); //Get wav bytes from the ConvertMorseAudio method and then assigned to memory of bytes Morse.GetConverter() .ForLanguage(Language.English) - .ToMorse(MessageMorseTxt.Text) - .ToAudio() + .ToAudio(morse) .SetAudioOptions(25, 25, 600) .GetBytes(out Span bytes); @@ -42,10 +45,7 @@ private void ToAudioBtn_Click(object sender, EventArgs e) //Update the richtextbox text to morse dash and dots. - MorseTxt.Text = Morse.GetConverter() - .ForLanguage(Language.English) - .ToMorse(MessageMorseTxt.Text) - .Encode(); + MorseTxt.Text = morse; language = Language.English; //Enable the play button to play @@ -85,11 +85,13 @@ private void ToMorseKurdish_Click(object sender, EventArgs e) //Check if the message length is greater than zero. if (MessageMorseTxt.Text.Length > 0) { + morse = Morse.GetConverter() + .ForLanguage(Language.Kurdish) + .ToMorse(MessageMorseTxt.Text).Encode(); //Get wav bytes from the ConvertMorseAudio method and then assigned to memory of bytes Morse.GetConverter() .ForLanguage(Language.Kurdish) - .ToMorse(MessageMorseTxt.Text) - .ToAudio() + .ToAudio(morse) .SetAudioOptions(25, 25, 600) .GetBytes(out Span bytes); @@ -97,12 +99,9 @@ private void ToMorseKurdish_Click(object sender, EventArgs e) //Update the richtextbox text to morse dash and dots. - MorseTxt.Text = Morse.GetConverter() - .ForLanguage(Language.Kurdish) - .ToMorse(MessageMorseTxt.Text) - .Encode(); + MorseTxt.Text = morse; - language = Language.Kurdish; + language = Language.English; //Enable the play button to play PlayBtn.Enabled = true; } @@ -116,12 +115,11 @@ private void ToMorseKurdish_Click(object sender, EventArgs e) private void blinkBtn_Click(object sender, EventArgs e) { - if (MessageMorseTxt.Text.Length > 0) + if (MessageMorseTxt.Text.Length > 0 && morse.Length > 0) { Morse.GetConverter() .ForLanguage(this.language) - .ToMorse(MessageMorseTxt.Text) - .ToLight() + .ToLight(morse) .SetBlinkerOptions(25, 25) .DoBlinks((hasToBlink) => { From aa609dbaa1f58ae488c1193b364b05626eac62fa Mon Sep 17 00:00:00 2001 From: p6laris Date: Sun, 19 Nov 2023 10:49:35 +0300 Subject: [PATCH 14/18] Update the console example. --- Example/Program.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Example/Program.cs b/Example/Program.cs index 149d827..d3a022a 100644 --- a/Example/Program.cs +++ b/Example/Program.cs @@ -17,8 +17,7 @@ //Light Blinking await Morse.GetConverter() .ForLanguage(Language.English) - .ToMorse("Hi") - .ToLight() + .ToLight(".... ..") .SetBlinkerOptions(25, 25) .DoBlinks((hasToBlink) => { From 8581eeb249363b633afa996943ce461a9ef55f8c Mon Sep 17 00:00:00 2001 From: p6laris Date: Sun, 19 Nov 2023 10:53:26 +0300 Subject: [PATCH 15/18] Refactor and format the code base. --- MorseSharp/Audio/AudioConverter.cs | 7 ++--- MorseSharp/Audio/Chunks/DataChunk.cs | 6 ++--- MorseSharp/Audio/Chunks/FromatChunk.cs | 5 ++-- MorseSharp/Audio/Chunks/HeaderChunk.cs | 5 ++-- .../Exceptions/SmallerWordSpeedException.cs | 3 +-- .../Exceptions/WordNotPresentedException.cs | 3 +-- MorseSharp/Extentions/ToUpperSpan.cs | 27 ------------------- MorseSharp/Morse.cs | 4 +-- MorseSharp/Usings.cs | 2 ++ MorseTest/MorseTest.csproj | 2 +- 10 files changed, 14 insertions(+), 50 deletions(-) delete mode 100644 MorseSharp/Extentions/ToUpperSpan.cs diff --git a/MorseSharp/Audio/AudioConverter.cs b/MorseSharp/Audio/AudioConverter.cs index 66ef649..967f791 100644 --- a/MorseSharp/Audio/AudioConverter.cs +++ b/MorseSharp/Audio/AudioConverter.cs @@ -1,10 +1,7 @@ -using ListPool; -using System.Buffers; - -namespace MorseSharp.Audio; +namespace MorseSharp.Audio; [StructLayout(LayoutKind.Sequential)] -public ref struct AudioConverter +internal ref struct AudioConverter { private readonly int _characterSpeed; diff --git a/MorseSharp/Audio/Chunks/DataChunk.cs b/MorseSharp/Audio/Chunks/DataChunk.cs index 86f19d4..59d5b8d 100644 --- a/MorseSharp/Audio/Chunks/DataChunk.cs +++ b/MorseSharp/Audio/Chunks/DataChunk.cs @@ -1,11 +1,9 @@ - - -namespace MorseSharp.Audio.Chunks; +namespace MorseSharp.Audio.Chunks; /// /// Represents a data chunk for WAVE files. /// [StructLayout(LayoutKind.Sequential)] -public readonly ref struct ValueDataChunk +internal readonly ref struct ValueDataChunk { private readonly Span _chunkData; diff --git a/MorseSharp/Audio/Chunks/FromatChunk.cs b/MorseSharp/Audio/Chunks/FromatChunk.cs index b2f40d5..650463f 100644 --- a/MorseSharp/Audio/Chunks/FromatChunk.cs +++ b/MorseSharp/Audio/Chunks/FromatChunk.cs @@ -1,8 +1,7 @@ - -namespace MorseSharp.Audio.Chunks; +namespace MorseSharp.Audio.Chunks; [StructLayout(LayoutKind.Sequential)] -public readonly ref struct ValueFormatChunk +internal readonly ref struct ValueFormatChunk { private readonly short CompressionCode; private readonly short NumChannels; diff --git a/MorseSharp/Audio/Chunks/HeaderChunk.cs b/MorseSharp/Audio/Chunks/HeaderChunk.cs index 20defaf..bc4268b 100644 --- a/MorseSharp/Audio/Chunks/HeaderChunk.cs +++ b/MorseSharp/Audio/Chunks/HeaderChunk.cs @@ -1,7 +1,6 @@ - -namespace MorseSharp.Audio.Chunks; +namespace MorseSharp.Audio.Chunks; -public readonly ref struct ValueHeaderChunk +internal readonly ref struct ValueHeaderChunk { readonly ValueDataChunk _dataChunk; readonly ValueFormatChunk _formatChunk; diff --git a/MorseSharp/Exceptions/SmallerWordSpeedException.cs b/MorseSharp/Exceptions/SmallerWordSpeedException.cs index 5f93469..c03fee8 100644 --- a/MorseSharp/Exceptions/SmallerWordSpeedException.cs +++ b/MorseSharp/Exceptions/SmallerWordSpeedException.cs @@ -1,5 +1,4 @@ - -namespace MorseSharp.Exceptions +namespace MorseSharp.Exceptions { [Serializable] public class SmallerWordSpeedException : Exception diff --git a/MorseSharp/Exceptions/WordNotPresentedException.cs b/MorseSharp/Exceptions/WordNotPresentedException.cs index 5d55992..d675301 100644 --- a/MorseSharp/Exceptions/WordNotPresentedException.cs +++ b/MorseSharp/Exceptions/WordNotPresentedException.cs @@ -1,5 +1,4 @@ - -namespace MorseSharp.Exceptions +namespace MorseSharp.Exceptions { [Serializable] public class WordNotPresentedException : Exception diff --git a/MorseSharp/Extentions/ToUpperSpan.cs b/MorseSharp/Extentions/ToUpperSpan.cs deleted file mode 100644 index 3d6613b..0000000 --- a/MorseSharp/Extentions/ToUpperSpan.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace MorseSharp.Extentions; - -/// -/// Provides extension methods for converting characters to uppercase using pointers. -/// -internal static class ToUpperSpanExtention -{ - /// - /// Converts characters in a span to uppercase using pointers. - /// - /// A pointer to the input character span. - /// A pointer to the destination character span where the uppercase characters will be written. - /// The length of the character span. - /// Thrown when or is null, or when is less than 0. - public static unsafe void ToUpperWithPointers(char* inputPtr, char* destPtr, int length) - { - if (inputPtr == null || destPtr == null || length < 0) - throw new ArgumentNullException(); - - for (int i = 0; i < length; i++) - { - char c = inputPtr[i]; - destPtr[i] = (c >= 'a' && c <= 'z') ? (char) (c - 32) : c; - } - } - -} \ No newline at end of file diff --git a/MorseSharp/Morse.cs b/MorseSharp/Morse.cs index 0ebc3bf..9f7eaa8 100644 --- a/MorseSharp/Morse.cs +++ b/MorseSharp/Morse.cs @@ -1,6 +1,4 @@ -using System.Buffers; - -namespace MorseSharp; +namespace MorseSharp; /// diff --git a/MorseSharp/Usings.cs b/MorseSharp/Usings.cs index 8bc592d..d7162ec 100644 --- a/MorseSharp/Usings.cs +++ b/MorseSharp/Usings.cs @@ -6,6 +6,8 @@ global using MorseSharp.Light; global using MorseSharp.Exceptions; global using MorseSharp.Helpers; +global using ListPool; +global using System.Buffers; global using System.Runtime.CompilerServices; global using System.Runtime.InteropServices; global using CommunityToolkit.HighPerformance.Buffers; diff --git a/MorseTest/MorseTest.csproj b/MorseTest/MorseTest.csproj index 597c534..47b2851 100644 --- a/MorseTest/MorseTest.csproj +++ b/MorseTest/MorseTest.csproj @@ -1,4 +1,4 @@ - + net8.0 enable From d54a2ff99cba876eefdb3b4d2b73e834b6bd72d7 Mon Sep 17 00:00:00 2001 From: p6laris Date: Mon, 20 Nov 2023 04:20:08 +0300 Subject: [PATCH 16/18] Added additional tests and documented the code. --- MorseSharp/Audio/AudioConverter.cs | 2 +- ...eption.cs => SmallerCharSpeedException.cs} | 8 +- MorseSharp/Light/LightBlinker.cs | 2 +- MorseSharp/Morse.cs | 69 ++++++- MorseTest/MorseAudioAndLightConverters.cs | 168 ++++++++++++++++++ MorseTest/MorseAudioConverter.cs | 33 ---- 6 files changed, 235 insertions(+), 47 deletions(-) rename MorseSharp/Exceptions/{SmallerWordSpeedException.cs => SmallerCharSpeedException.cs} (50%) create mode 100644 MorseTest/MorseAudioAndLightConverters.cs delete mode 100644 MorseTest/MorseAudioConverter.cs diff --git a/MorseSharp/Audio/AudioConverter.cs b/MorseSharp/Audio/AudioConverter.cs index 967f791..b80a03a 100644 --- a/MorseSharp/Audio/AudioConverter.cs +++ b/MorseSharp/Audio/AudioConverter.cs @@ -11,7 +11,7 @@ internal ref struct AudioConverter public AudioConverter(int characterSpeed, int wordSpeed, double frequency) { if (characterSpeed < wordSpeed) - throw new SmallerWordSpeedException(characterSpeed, wordSpeed); + throw new SmallerCharSpeedException(characterSpeed, wordSpeed); _characterSpeed = characterSpeed; diff --git a/MorseSharp/Exceptions/SmallerWordSpeedException.cs b/MorseSharp/Exceptions/SmallerCharSpeedException.cs similarity index 50% rename from MorseSharp/Exceptions/SmallerWordSpeedException.cs rename to MorseSharp/Exceptions/SmallerCharSpeedException.cs index c03fee8..5f8403e 100644 --- a/MorseSharp/Exceptions/SmallerWordSpeedException.cs +++ b/MorseSharp/Exceptions/SmallerCharSpeedException.cs @@ -1,15 +1,15 @@ namespace MorseSharp.Exceptions { [Serializable] - public class SmallerWordSpeedException : Exception + public class SmallerCharSpeedException : Exception { - public SmallerWordSpeedException() { } + public SmallerCharSpeedException() { } - public SmallerWordSpeedException(string message) : base(message) + public SmallerCharSpeedException(string message) : base(message) { } - public SmallerWordSpeedException(int charSpeed, int wordSpeed) + public SmallerCharSpeedException(int charSpeed, int wordSpeed) : base($"The character speed must not be smaller than word speed: {charSpeed} < {wordSpeed}") { diff --git a/MorseSharp/Light/LightBlinker.cs b/MorseSharp/Light/LightBlinker.cs index 754e618..a9a421b 100644 --- a/MorseSharp/Light/LightBlinker.cs +++ b/MorseSharp/Light/LightBlinker.cs @@ -11,7 +11,7 @@ internal partial class LightBlinker public LightBlinker(int characterSpeed, int wordSpeed, Action blinkerAction) { if (characterSpeed < wordSpeed) - throw new SmallerWordSpeedException(characterSpeed, wordSpeed); + throw new SmallerCharSpeedException(characterSpeed, wordSpeed); if (blinkerAction == null) throw new ArgumentNullException(nameof(blinkerAction)); diff --git a/MorseSharp/Morse.cs b/MorseSharp/Morse.cs index 9f7eaa8..5ec106c 100644 --- a/MorseSharp/Morse.cs +++ b/MorseSharp/Morse.cs @@ -2,7 +2,8 @@ /// -/// Decode/encodes morse text. +/// Provides functionality to decode/encode Morse code, convert text to Morse code, +/// generate audio bytes, and control light blinking based on Morse code. /// public sealed class Morse : ICanSpecifyLanguage, ICanSetConversionOption, ICanConvertToAudio, ICanSetAudioOptions, ICanGenerateAudioAndLight, @@ -80,6 +81,10 @@ public ICanSetConversionOption ForLanguage(Language language) /// /// The Morse code to convert to text. /// The converted text. + /// Thrown when the input Morse code is null or empty. + /// + /// Thrown when an invalid Morse code sequence is encountered, and the corresponding character cannot be found. + /// public string Decode(string morse) { if (string.IsNullOrEmpty(morse)) @@ -138,6 +143,10 @@ public string Decode(string morse) /// /// The text to convert to Morse code. /// The Morse code representation of the text. + /// Thrown when the input text is null or empty. + /// + /// Thrown when a character in the input text does not have a corresponding Morse code representation. + /// public ICanGenerateAudioAndLight ToMorse(string text) { if (string.IsNullOrEmpty(text)) @@ -204,10 +213,13 @@ public void GetBytes(out Span destination) /// The word speed for audio. /// The frequency for audio. /// An instance of the interface. + /// + /// Thrown when the character speed is less than the word speed, which is invalid for audio conversion. + /// public ICanConvertToAudio SetAudioOptions(int charSpeed = 25, int wordSpeed = 25, double frequency = 700) { if (charSpeed < wordSpeed) - throw new SmallerWordSpeedException(charSpeed, wordSpeed); + throw new SmallerCharSpeedException(charSpeed, wordSpeed); _charSpeed.Value = charSpeed; _wordSpeed.Value = wordSpeed; @@ -216,15 +228,36 @@ public ICanConvertToAudio SetAudioOptions(int charSpeed = 25, int wordSpeed = 25 return this; } + + /// + /// Encode the Morse code. + /// + /// The encoded Morse code as a string. public string Encode() => _strBuilder.Value!.ToString(); + + /// + /// Switch to audio conversion mode. + /// + /// An instance of the interface. public ICanSetAudioOptions ToAudio() => this; + /// + /// Switch to light blinking mode. + /// + /// An instance of the interface. public ICanSetBlinkerOptions ToLight() => this; + + /// + /// Switch to audio conversion mode with a specified Morse code. + /// + /// The Morse code to convert to audio. + /// An instance of the interface. + /// Thrown when the input Morse code is null or empty. public ICanSetAudioOptions ToAudio(string morse) { if (string.IsNullOrEmpty(morse)) - throw new ArgumentException(nameof(morse)); + throw new ArgumentNullException(nameof(morse)); if (_strBuilder.Value!.Length > 0) @@ -234,10 +267,17 @@ public ICanSetAudioOptions ToAudio(string morse) return this; } + /// + /// Switch to light blinking mode with a specified Morse code. + /// + /// The Morse code to convert to light blinking. + /// An instance of the interface. + /// Thrown when the input Morse code is null or empty. + public ICanSetBlinkerOptions ToLight(string morse) { if (string.IsNullOrEmpty(morse)) - throw new ArgumentException(nameof(morse)); + throw new ArgumentNullException(nameof(morse)); if (_strBuilder.Value!.Length > 0) @@ -247,10 +287,19 @@ public ICanSetBlinkerOptions ToLight(string morse) return this; } + /// + /// Set light blinking options. + /// + /// The character speed for light blinking. + /// The word speed for light blinking. + /// An instance of the interface. + /// + /// Thrown when the character speed is less than the word speed, which is invalid for light blinking. + /// public ICanConvertToLight SetBlinkerOptions(int charSpeed = 25, int wordSpeed = 25) { if (charSpeed < wordSpeed) - throw new SmallerWordSpeedException(charSpeed, wordSpeed); + throw new SmallerCharSpeedException(charSpeed, wordSpeed); _charSpeed.Value = charSpeed; _wordSpeed.Value = wordSpeed; @@ -258,6 +307,12 @@ public ICanConvertToLight SetBlinkerOptions(int charSpeed = 25, int wordSpeed = return this; } + /// + /// Perform light blinking based on the set options. + /// + /// The action to perform for each blink (true for on, false for off). + /// An asynchronous task. + /// Thrown when the blinker action is null. public async Task DoBlinks(Action blinkerAction) { if (blinkerAction is null) @@ -266,6 +321,4 @@ public async Task DoBlinks(Action blinkerAction) LightBlinker lightBlinker = new LightBlinker(_charSpeed.Value, _wordSpeed.Value, blinkerAction); await lightBlinker.BlinkLight(_strBuilder.Value!.ToString()); } - - -} +} \ No newline at end of file diff --git a/MorseTest/MorseAudioAndLightConverters.cs b/MorseTest/MorseAudioAndLightConverters.cs new file mode 100644 index 0000000..a042a6d --- /dev/null +++ b/MorseTest/MorseAudioAndLightConverters.cs @@ -0,0 +1,168 @@ +namespace MorseTest +{ + public class MorseAudioConverterTest + { + //Latin + [Fact] + public void ConvertToAudioEnglish() + { + Morse.GetConverter() + .ForLanguage(Language.English) + .ToMorse("Hello Morse") + .ToAudio() + .SetAudioOptions(25, 25, 600) + .GetBytes(out Span morse); + + Assert.True(morse.Length > 0); + } + + //NonLatin + [Fact] + public void ConvertToAudioKurdish() + { + Morse.GetConverter() + .ForLanguage(Language.Kurdish) + .ToMorse("کووی") + .ToAudio() + .SetAudioOptions(25, 25, 600) + .GetBytes(out Span morse); + + Assert.True(morse.Length > 0); + } + + [Fact] + public void ConvertToAudioSmallerWordSpeed() + { + + Assert.Throws(() => + { + Morse.GetConverter() + .ForLanguage(Language.English) + .ToMorse("Hello Morse") + .ToAudio() + .SetAudioOptions(20, 25, 600) + .GetBytes(out Span morse); + }); + } + + [Fact] + public void ConvertToAudioWithoutConversion() + { + Morse.GetConverter() + .ForLanguage(Language.English) + .ToAudio(".... ..") + .SetAudioOptions (25, 25, 600) + .GetBytes(out Span morse); + + Assert.True(morse.Length > 0); + } + [Fact] + public void ConvertToAudioWithoutConversionSmallerCharSpeed() + { + Assert.Throws(() => + { + Morse.GetConverter() + .ForLanguage(Language.English) + .ToAudio(".... ..") + .SetAudioOptions(20, 25, 600) + .GetBytes(out Span morse); + }); + + } + [Fact] + public void ConvertToAudioWithoutConversionNullMorse() + { + Assert.Throws(() => + { + Morse.GetConverter() + .ForLanguage(Language.English) + .ToAudio(null) + .SetAudioOptions(25, 25, 600) + .GetBytes(out Span morse); + }); + } + //Light Blinker + //Latin + [Fact] + public void ConvertToLight() + { + List blinkValues = new(); + Morse.GetConverter() + .ForLanguage(Language.English) + .ToMorse("Hi") + .ToLight() + .SetBlinkerOptions(25, 25) + .DoBlinks((hasToBlink) => + { + if (hasToBlink) + blinkValues.Add(true); + else + blinkValues.Add(false); + }); + + Assert.True(blinkValues.Count > 0); + } + [Fact] + public void ConvertToLightWithNull() + { + Assert.Throws(() => + { + Morse.GetConverter() + .ForLanguage(Language.English) + .ToMorse(null) + .ToLight() + .SetBlinkerOptions(25, 25) + .DoBlinks((hasToBlink) => + { + }); + }); + + } + [Fact] + public void ConvertToLightWithSmallerCharSpeed() + { + Assert.Throws(() => + { + Morse.GetConverter() + .ForLanguage(Language.English) + .ToMorse("Hi") + .ToLight() + .SetBlinkerOptions(20, 25) + .DoBlinks((hasToBlink) => + { + }); + }); + + } + [Fact] + public void ConvertToLightWithoutConversionNull() + { + Assert.Throws(() => + { + Morse.GetConverter() + .ForLanguage(Language.English) + .ToLight(null) + .SetBlinkerOptions(25, 25) + .DoBlinks((hasToBlink) => + { + }); + }); + + } + [Fact] + public void ConvertToLightWithoutConversionSmallerCharSpeed() + { + Assert.Throws(() => + { + Morse.GetConverter() + .ForLanguage(Language.English) + .ToLight(".... ..") + .SetBlinkerOptions(20, 25) + .DoBlinks((hasToBlink) => + { + }); + }); + + } + } +} diff --git a/MorseTest/MorseAudioConverter.cs b/MorseTest/MorseAudioConverter.cs deleted file mode 100644 index baaf252..0000000 --- a/MorseTest/MorseAudioConverter.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace MorseTest -{ - public class MorseAudioConverterTest - { - //Latin - [Fact] - public void ConvertToAudioEnglish() - { - Morse.GetConverter() - .ForLanguage(Language.English) - .ToMorse("Hello Morse") - .ToAudio() - .SetAudioOptions(25, 25, 600) - .GetBytes(out Span morse); - - Assert.True(morse.Length > 0); - } - - //NonLatin - [Fact] - public void ConvertToAudioKurdish() - { - Morse.GetConverter() - .ForLanguage(Language.Kurdish) - .ToMorse("کووی") - .ToAudio() - .SetAudioOptions(25, 25, 600) - .GetBytes(out Span morse); - - Assert.True(morse.Length > 0); - } - } -} From 10a247529bf7b59b9a8bf38ca616e6cbb866f7f6 Mon Sep 17 00:00:00 2001 From: p6laris Date: Mon, 20 Nov 2023 04:22:34 +0300 Subject: [PATCH 17/18] Changed tags and description --- MorseSharp/MorseSharp.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MorseSharp/MorseSharp.csproj b/MorseSharp/MorseSharp.csproj index 6f67f65..4e740bb 100644 --- a/MorseSharp/MorseSharp.csproj +++ b/MorseSharp/MorseSharp.csproj @@ -7,16 +7,16 @@ True MorseSharp p0laris - A simple lightweight .NET library to encode/decode morse up to 8+ lanuages and generating dash and dots in wav audio + A .NET library to encode/decode morse up to 9+ lanuages and generating dash and dots in wav audio with light blinking support. https://github.com/p6laris/MorseSharp https://github.com/p6laris/MorseSharp git - MorseCode Morse Converter MorseKurdish KurdishLanguage + MorseCode Morse Converter MorseKurdish KurdishLanguage MorseSharp False en-GB 2022 False - 3.2.0$(VersionPrefix) + 4.0.0$(VersionPrefix) MorseSharp.png README.md MIT From e701a8e06772c33ebc69da3b24678aab4c85c7c9 Mon Sep 17 00:00:00 2001 From: p6laris Date: Mon, 20 Nov 2023 04:27:01 +0300 Subject: [PATCH 18/18] Deleted the idea folder --- .idea/.idea.MorseSharp/.idea/workspace.xml | 126 --------------------- 1 file changed, 126 deletions(-) delete mode 100644 .idea/.idea.MorseSharp/.idea/workspace.xml diff --git a/.idea/.idea.MorseSharp/.idea/workspace.xml b/.idea/.idea.MorseSharp/.idea/workspace.xml deleted file mode 100644 index fbcf250..0000000 --- a/.idea/.idea.MorseSharp/.idea/workspace.xml +++ /dev/null @@ -1,126 +0,0 @@ - - - - AudioExample/AudioExample.csproj - Example/ConsoleExample.csproj - - - - - - - - - - - - - - - - - - - - - - - - - - { - "customColor": "", - "associatedIndex": 3 -} - - - - - - - - - - - - - - - 1700009927672 - - - - - - - - \ No newline at end of file