diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2966d92..8a2d2bd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,31 +17,24 @@ jobs: runs-on: windows-latest steps: - name: 🛒 Checkout - uses: actions/checkout@v2 - - - name: ✨ Setup .NET 6 - uses: actions/setup-dotnet@v1 + uses: actions/checkout@v4 + - name: ✨ Setup .NET 8 + uses: actions/setup-dotnet@v4 with: - dotnet-version: "6.0.x" - + dotnet-version: "8.0.x" - name: 🚚 Restore run: dotnet restore src - - name: 🛠️ Build - run: dotnet build src --configuration Release --no-restore - + run: dotnet build src --configuration Release - name: 🧪 Test - run: dotnet test src --configuration Release --no-build - + run: dotnet test src --configuration Release - name: 📦 Pack - run: dotnet pack src --configuration Release --no-build - + run: dotnet pack src --configuration Release - name: 🔑 Configure Secrets if: github.event_name == 'release' uses: nuget/setup-nuget@v1 with: nuget-api-key: ${{ secrets.NUGET_API_KEY }} - - - name: 🚀 Deploy Package + - name: 🚀 Deploy NuGet Package if: github.event_name == 'release' run: nuget push "src\Spectrogram\bin\Release\*.nupkg" -SkipDuplicate -Source https://api.nuget.org/v3/index.json diff --git a/src/Spectrogram.MicrophoneDemo/App.config b/src/Spectrogram.MicrophoneDemo/App.config deleted file mode 100644 index 56efbc7..0000000 --- a/src/Spectrogram.MicrophoneDemo/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/Spectrogram.MicrophoneDemo/FormMicrophone.cs b/src/Spectrogram.MicrophoneDemo/FormMicrophone.cs index e6b4964..975c5b1 100644 --- a/src/Spectrogram.MicrophoneDemo/FormMicrophone.cs +++ b/src/Spectrogram.MicrophoneDemo/FormMicrophone.cs @@ -9,6 +9,7 @@ using System.Text; using System.Threading.Tasks; using System.Windows.Forms; +using SkiaSharp.Views.Desktop; namespace Spectrogram.MicrophoneDemo { @@ -64,7 +65,7 @@ private void StartListening() pbSpectrogram.Height = spec.Height; pbScaleVert.Image?.Dispose(); - pbScaleVert.Image = spec.GetVerticalScale(pbScaleVert.Width); + pbScaleVert.Image = spec.GetVerticalScale(pbScaleVert.Width).ToBitmap(); pbScaleVert.Height = spec.Height; } @@ -85,7 +86,7 @@ private void timer1_Tick(object sender, EventArgs e) using (var gfx = Graphics.FromImage(bmpSpec)) using (var pen = new Pen(Color.White)) { - gfx.DrawImage(bmpSpecIndexed, 0, 0); + gfx.DrawImage(bmpSpecIndexed.ToBitmap(), 0, 0); if (cbRoll.Checked) { gfx.DrawLine(pen, spec.NextColumnIndex, 0, spec.NextColumnIndex, pbSpectrogram.Height); diff --git a/src/Spectrogram.MicrophoneDemo/Program.cs b/src/Spectrogram.MicrophoneDemo/Program.cs index e1a34c4..821c47b 100644 --- a/src/Spectrogram.MicrophoneDemo/Program.cs +++ b/src/Spectrogram.MicrophoneDemo/Program.cs @@ -1,16 +1,11 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Runtime.Versioning; using System.Windows.Forms; namespace Spectrogram.MicrophoneDemo { static class Program { - /// - /// The main entry point for the application. - /// [STAThread] static void Main() { diff --git a/src/Spectrogram.MicrophoneDemo/Properties/AssemblyInfo.cs b/src/Spectrogram.MicrophoneDemo/Properties/AssemblyInfo.cs deleted file mode 100644 index 5eaa0f3..0000000 --- a/src/Spectrogram.MicrophoneDemo/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Spectrogram.MicrophoneDemo")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Spectrogram.MicrophoneDemo")] -[assembly: AssemblyCopyright("Copyright © 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("d51abc6a-53f4-4620-88a1-14ea1d779538")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Spectrogram.MicrophoneDemo/Properties/Resources.Designer.cs b/src/Spectrogram.MicrophoneDemo/Properties/Resources.Designer.cs deleted file mode 100644 index 1a71b5a..0000000 --- a/src/Spectrogram.MicrophoneDemo/Properties/Resources.Designer.cs +++ /dev/null @@ -1,71 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Spectrogram.MicrophoneDemo.Properties -{ - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if ((resourceMan == null)) - { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Spectrogram.MicrophoneDemo.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { - return resourceCulture; - } - set - { - resourceCulture = value; - } - } - } -} diff --git a/src/Spectrogram.MicrophoneDemo/Properties/Resources.resx b/src/Spectrogram.MicrophoneDemo/Properties/Resources.resx deleted file mode 100644 index af7dbeb..0000000 --- a/src/Spectrogram.MicrophoneDemo/Properties/Resources.resx +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/src/Spectrogram.MicrophoneDemo/Properties/Settings.Designer.cs b/src/Spectrogram.MicrophoneDemo/Properties/Settings.Designer.cs deleted file mode 100644 index 37d8130..0000000 --- a/src/Spectrogram.MicrophoneDemo/Properties/Settings.Designer.cs +++ /dev/null @@ -1,30 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Spectrogram.MicrophoneDemo.Properties -{ - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { - return defaultInstance; - } - } - } -} diff --git a/src/Spectrogram.MicrophoneDemo/Properties/Settings.settings b/src/Spectrogram.MicrophoneDemo/Properties/Settings.settings deleted file mode 100644 index 3964565..0000000 --- a/src/Spectrogram.MicrophoneDemo/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/Spectrogram.MicrophoneDemo/Spectrogram.Demo.csproj b/src/Spectrogram.MicrophoneDemo/Spectrogram.Demo.csproj index 344f096..e58c01a 100644 --- a/src/Spectrogram.MicrophoneDemo/Spectrogram.Demo.csproj +++ b/src/Spectrogram.MicrophoneDemo/Spectrogram.Demo.csproj @@ -1,19 +1,15 @@  - net6.0-windows + net8.0-windows WinExe - false true true + NU1701 - - - - - - - + + + \ No newline at end of file diff --git a/src/Spectrogram.MicrophoneDemo/packages.config b/src/Spectrogram.MicrophoneDemo/packages.config deleted file mode 100644 index 64d4d6d..0000000 --- a/src/Spectrogram.MicrophoneDemo/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/Spectrogram.Tests/AudioFileTests.cs b/src/Spectrogram.Tests/AudioFileTests.cs index 60248fc..ef99c25 100644 --- a/src/Spectrogram.Tests/AudioFileTests.cs +++ b/src/Spectrogram.Tests/AudioFileTests.cs @@ -1,7 +1,5 @@ -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Text; +using FluentAssertions; +using NUnit.Framework; namespace Spectrogram.Tests { @@ -20,8 +18,8 @@ public void Test_AudioFile_LengthAndSampleRate(string filename, int knownRate, i string filePath = $"../../../../../data/{filename}"; (double[] audio, int sampleRate) = AudioFile.ReadWAV(filePath); - Assert.AreEqual(knownRate, sampleRate); - Assert.AreEqual(knownLength, audio.Length / channels); + sampleRate.Should().Be(knownRate); + (audio.Length / channels).Should().Be(knownLength); } } } diff --git a/src/Spectrogram.Tests/ColormapExamples.cs b/src/Spectrogram.Tests/ColormapExamples.cs deleted file mode 100644 index 276b3f1..0000000 --- a/src/Spectrogram.Tests/ColormapExamples.cs +++ /dev/null @@ -1,41 +0,0 @@ -using NUnit.Framework; -using System; -using System.Diagnostics; - -namespace Spectrogram.Tests -{ - class ColormapExamples - { - [Test] - public void Test_Make_CommonColormaps() - { - (double[] audio, int sampleRate) = AudioFile.ReadWAV("../../../../../data/cant-do-that-44100.wav"); - int fftSize = 1 << 12; - var spec = new SpectrogramGenerator(sampleRate, fftSize, stepSize: 700, maxFreq: 2000); - var window = new FftSharp.Windows.Hanning(); - spec.SetWindow(window.Create(fftSize / 3)); // sharper window than typical - spec.Add(audio); - - // delete old colormap files - foreach (var filePath in System.IO.Directory.GetFiles("../../../../../dev/graphics/", "hal-*.png")) - System.IO.File.Delete(filePath); - - foreach (var cmap in Colormap.GetColormaps()) - { - spec.Colormap = cmap; - spec.SaveImage($"../../../../../dev/graphics/hal-{cmap.Name}.png"); - Debug.WriteLine($"![](dev/graphics/hal-{cmap.Name}.png)"); - } - } - - [Test] - public void Test_Colormaps_ByName() - { - string[] names = Colormap.GetColormapNames(); - Console.WriteLine(string.Join(", ", names)); - - Colormap viridisCmap = Colormap.GetColormap("viridis"); - Assert.AreEqual("Viridis", viridisCmap.Name); - } - } -} diff --git a/src/Spectrogram.Tests/ColormapValues.cs b/src/Spectrogram.Tests/ColormapValues.cs deleted file mode 100644 index 9c5d9d1..0000000 --- a/src/Spectrogram.Tests/ColormapValues.cs +++ /dev/null @@ -1,361 +0,0 @@ -using Microsoft.VisualStudio.TestPlatform.Utilities; -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Text; - -namespace Spectrogram.Tests -{ - class ColormapValues - { - [Test] - public void Test_Colormap_ExtendedFractionsReturnEdgeValues() - { - var cmap = Colormap.Viridis; - - Random rand = new Random(0); - for (double frac = -3; frac < 3; frac += rand.NextDouble() * .2) - { - Console.WriteLine($"{frac}: {cmap.GetRGB(frac)}"); - - if (frac <= 0) - Assert.AreEqual(cmap.GetRGB(0), cmap.GetRGB(frac)); - - if (frac >= 1) - Assert.AreEqual(cmap.GetRGB(1.0), cmap.GetRGB(frac)); - } - } - - [Test] - public void Test_Colormap_IntegerMatchesRGBColors() - { - var cmap = Colormap.Viridis; - - byte pixelIntensity = 123; - var (r, g, b) = cmap.GetRGB(pixelIntensity); - int int32 = cmap.GetInt32(pixelIntensity); - - Color color1 = Color.FromArgb(255, r, g, b); - Color color2 = Color.FromArgb(int32); - Color color3 = cmap.GetColor(pixelIntensity); - - Assert.AreEqual(color1, color2); - Assert.AreEqual(color1, color3); - } - - [Test] - public void Test_colorLookup_integerMatchesTriplet() - { - for (int i = 0; i < 256; i++) - { - byte[] bytes = BitConverter.GetBytes(ints[i]); - - Color color1 = Color.FromArgb(bytes[2], bytes[1], bytes[0]); - - Color color2 = Color.FromArgb(rgb[i, 0], rgb[i, 1], rgb[i, 2]); - - Assert.AreEqual(color2.R, color1.R, 1); - Assert.AreEqual(color2.G, color1.G, 1); - Assert.AreEqual(color2.B, color1.B, 1); - } - } - - private readonly int[] ints = - { - 04456788, 04457045, 04457303, 04523352, 04523610, 04524123, 04589916, 04590430, - 04590687, 04591201, 04656994, 04657507, 04657765, 04658278, 04658535, 04658793, - 04659306, 04725099, 04725356, 04725870, 04726127, 04726384, 04726897, 04727154, - 04727411, 04727668, 04662645, 04662902, 04663159, 04663416, 04663929, 04664186, - 04664443, 04599164, 04599676, 04599933, 04600190, 04534911, 04535423, 04535680, - 04535937, 04470657, 04471170, 04405891, 04406147, 04406404, 04341124, 04341381, - 04341893, 04276614, 04276870, 04211591, 04211847, 04146567, 04147080, 04081800, - 04082057, 04016777, 04017033, 04017289, 03952010, 03952266, 03887242, 03887498, - 03822219, 03822475, 03757195, 03757451, 03692171, 03692428, 03627148, 03627404, - 03562124, 03562380, 03497100, 03497356, 03432077, 03432333, 03367053, 03367309, - 03302029, 03302285, 03237005, 03237261, 03237517, 03172237, 03172493, 03107213, - 03107469, 03042190, 03042446, 03042702, 02977422, 02977678, 02912398, 02912654, - 02912910, 02847630, 02847886, 02782606, 02782862, 02783118, 02717838, 02718094, - 02652814, 02652814, 02653070, 02587790, 02588046, 02588302, 02523022, 02523278, - 02523534, 02458254, 02458509, 02393229, 02393485, 02393741, 02328461, 02328717, - 02328973, 02263437, 02263693, 02263949, 02198669, 02198924, 02199180, 02133900, - 02134156, 02134412, 02069132, 02069387, 02069643, 02069899, 02070155, 02004874, - 02005130, 02005386, 02005386, 02005641, 02005897, 02006153, 02006408, 02006664, - 02006920, 02007175, 02072967, 02073222, 02073478, 02139269, 02139525, 02205317, - 02205572, 02271108, 02336899, 02337154, 02402946, 02468737, 02534529, 02600320, - 02666111, 02731903, 02797694, 02863485, 02929021, 03060348, 03126139, 03191930, - 03323258, 03389049, 03520376, 03586167, 03717494, 03783030, 03914357, 04045684, - 04111475, 04242802, 04374129, 04505200, 04570991, 04702318, 04833645, 04964972, - 05096043, 05227369, 05358696, 05490023, 05621350, 05752421, 05883748, 06015074, - 06211937, 06343008, 06474335, 06605661, 06802524, 06933595, 07064921, 07196248, - 07392854, 07524181, 07655508, 07852114, 07983441, 08180303, 08311374, 08508236, - 08639307, 08836169, 08967495, 09164102, 09295428, 09492035, 09623361, 09819967, - 09951294, 10147900, 10344762, 10475832, 10672695, 10869301, 11000627, 11197234, - 11394096, 11525166, 11722028, 11918635, 12049705, 12246567, 12443174, 12574500, - 12771106, 12967713, 13099039, 13295646, 13492253, 13623580, 13820187, 13951258, - 14148121, 14344728, 14475800, 14672664, 14803736, 15000344, 15197209, 15328281, - 15524890, 15656219, 15852828, 15983902, 16180767, 16311841, 16442914, 16639780, - - }; - - private readonly byte[,] rgb = - { - {68, 1, 84}, - {68, 2, 86}, - {69, 4, 87}, - {69, 5, 89}, - {70, 7, 90}, - {70, 8, 92}, - {70, 10, 93}, - {70, 11, 94}, - {71, 13, 96}, - {71, 14, 97}, - {71, 16, 99}, - {71, 17, 100}, - {71, 19, 101}, - {72, 20, 103}, - {72, 22, 104}, - {72, 23, 105}, - {72, 24, 106}, - {72, 26, 108}, - {72, 27, 109}, - {72, 28, 110}, - {72, 29, 111}, - {72, 31, 112}, - {72, 32, 113}, - {72, 33, 115}, - {72, 35, 116}, - {72, 36, 117}, - {72, 37, 118}, - {72, 38, 119}, - {72, 40, 120}, - {72, 41, 121}, - {71, 42, 122}, - {71, 44, 122}, - {71, 45, 123}, - {71, 46, 124}, - {71, 47, 125}, - {70, 48, 126}, - {70, 50, 126}, - {70, 51, 127}, - {70, 52, 128}, - {69, 53, 129}, - {69, 55, 129}, - {69, 56, 130}, - {68, 57, 131}, - {68, 58, 131}, - {68, 59, 132}, - {67, 61, 132}, - {67, 62, 133}, - {66, 63, 133}, - {66, 64, 134}, - {66, 65, 134}, - {65, 66, 135}, - {65, 68, 135}, - {64, 69, 136}, - {64, 70, 136}, - {63, 71, 136}, - {63, 72, 137}, - {62, 73, 137}, - {62, 74, 137}, - {62, 76, 138}, - {61, 77, 138}, - {61, 78, 138}, - {60, 79, 138}, - {60, 80, 139}, - {59, 81, 139}, - {59, 82, 139}, - {58, 83, 139}, - {58, 84, 140}, - {57, 85, 140}, - {57, 86, 140}, - {56, 88, 140}, - {56, 89, 140}, - {55, 90, 140}, - {55, 91, 141}, - {54, 92, 141}, - {54, 93, 141}, - {53, 94, 141}, - {53, 95, 141}, - {52, 96, 141}, - {52, 97, 141}, - {51, 98, 141}, - {51, 99, 141}, - {50, 100, 142}, - {50, 101, 142}, - {49, 102, 142}, - {49, 103, 142}, - {49, 104, 142}, - {48, 105, 142}, - {48, 106, 142}, - {47, 107, 142}, - {47, 108, 142}, - {46, 109, 142}, - {46, 110, 142}, - {46, 111, 142}, - {45, 112, 142}, - {45, 113, 142}, - {44, 113, 142}, - {44, 114, 142}, - {44, 115, 142}, - {43, 116, 142}, - {43, 117, 142}, - {42, 118, 142}, - {42, 119, 142}, - {42, 120, 142}, - {41, 121, 142}, - {41, 122, 142}, - {41, 123, 142}, - {40, 124, 142}, - {40, 125, 142}, - {39, 126, 142}, - {39, 127, 142}, - {39, 128, 142}, - {38, 129, 142}, - {38, 130, 142}, - {38, 130, 142}, - {37, 131, 142}, - {37, 132, 142}, - {37, 133, 142}, - {36, 134, 142}, - {36, 135, 142}, - {35, 136, 142}, - {35, 137, 142}, - {35, 138, 141}, - {34, 139, 141}, - {34, 140, 141}, - {34, 141, 141}, - {33, 142, 141}, - {33, 143, 141}, - {33, 144, 141}, - {33, 145, 140}, - {32, 146, 140}, - {32, 146, 140}, - {32, 147, 140}, - {31, 148, 140}, - {31, 149, 139}, - {31, 150, 139}, - {31, 151, 139}, - {31, 152, 139}, - {31, 153, 138}, - {31, 154, 138}, - {30, 155, 138}, - {30, 156, 137}, - {30, 157, 137}, - {31, 158, 137}, - {31, 159, 136}, - {31, 160, 136}, - {31, 161, 136}, - {31, 161, 135}, - {31, 162, 135}, - {32, 163, 134}, - {32, 164, 134}, - {33, 165, 133}, - {33, 166, 133}, - {34, 167, 133}, - {34, 168, 132}, - {35, 169, 131}, - {36, 170, 131}, - {37, 171, 130}, - {37, 172, 130}, - {38, 173, 129}, - {39, 173, 129}, - {40, 174, 128}, - {41, 175, 127}, - {42, 176, 127}, - {44, 177, 126}, - {45, 178, 125}, - {46, 179, 124}, - {47, 180, 124}, - {49, 181, 123}, - {50, 182, 122}, - {52, 182, 121}, - {53, 183, 121}, - {55, 184, 120}, - {56, 185, 119}, - {58, 186, 118}, - {59, 187, 117}, - {61, 188, 116}, - {63, 188, 115}, - {64, 189, 114}, - {66, 190, 113}, - {68, 191, 112}, - {70, 192, 111}, - {72, 193, 110}, - {74, 193, 109}, - {76, 194, 108}, - {78, 195, 107}, - {80, 196, 106}, - {82, 197, 105}, - {84, 197, 104}, - {86, 198, 103}, - {88, 199, 101}, - {90, 200, 100}, - {92, 200, 99}, - {94, 201, 98}, - {96, 202, 96}, - {99, 203, 95}, - {101, 203, 94}, - {103, 204, 92}, - {105, 205, 91}, - {108, 205, 90}, - {110, 206, 88}, - {112, 207, 87}, - {115, 208, 86}, - {117, 208, 84}, - {119, 209, 83}, - {122, 209, 81}, - {124, 210, 80}, - {127, 211, 78}, - {129, 211, 77}, - {132, 212, 75}, - {134, 213, 73}, - {137, 213, 72}, - {139, 214, 70}, - {142, 214, 69}, - {144, 215, 67}, - {147, 215, 65}, - {149, 216, 64}, - {152, 216, 62}, - {155, 217, 60}, - {157, 217, 59}, - {160, 218, 57}, - {162, 218, 55}, - {165, 219, 54}, - {168, 219, 52}, - {170, 220, 50}, - {173, 220, 48}, - {176, 221, 47}, - {178, 221, 45}, - {181, 222, 43}, - {184, 222, 41}, - {186, 222, 40}, - {189, 223, 38}, - {192, 223, 37}, - {194, 223, 35}, - {197, 224, 33}, - {200, 224, 32}, - {202, 225, 31}, - {205, 225, 29}, - {208, 225, 28}, - {210, 226, 27}, - {213, 226, 26}, - {216, 226, 25}, - {218, 227, 25}, - {221, 227, 24}, - {223, 227, 24}, - {226, 228, 24}, - {229, 228, 25}, - {231, 228, 25}, - {234, 229, 26}, - {236, 229, 27}, - {239, 229, 28}, - {241, 229, 29}, - {244, 230, 30}, - {246, 230, 32}, - {248, 230, 33}, - {251, 231, 35}, - {253, 231, 37}, - }; - } -} diff --git a/src/Spectrogram.Tests/ImageTests.cs b/src/Spectrogram.Tests/ImageTests.cs index d02a218..c0fe5ab 100644 --- a/src/Spectrogram.Tests/ImageTests.cs +++ b/src/Spectrogram.Tests/ImageTests.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using SkiaSharp; namespace Spectrogram.Tests; @@ -12,10 +13,10 @@ public void Test_Image_Rotations() SpectrogramGenerator sg = new(sampleRate, 4096, 500, maxFreq: 3000); sg.Add(audio); - System.Drawing.Bitmap bmp1 = sg.GetBitmap(rotate: false); - bmp1.Save("test-image-original.png"); + SKBitmap bmp1 = sg.GetBitmap(rotate: false); + bmp1.SaveTo("test-image-original.png", SKEncodedImageFormat.Png); - System.Drawing.Bitmap bmp2 = sg.GetBitmap(rotate: true); - bmp2.Save("test-image-rotated.png"); + SKBitmap bmp2 = sg.GetBitmap(rotate: true); + bmp2.SaveTo("test-image-rotated.png", SKEncodedImageFormat.Png); } } diff --git a/src/Spectrogram.Tests/Mel.cs b/src/Spectrogram.Tests/Mel.cs index 43f0d56..be6b6f3 100644 --- a/src/Spectrogram.Tests/Mel.cs +++ b/src/Spectrogram.Tests/Mel.cs @@ -1,9 +1,6 @@ using NUnit.Framework; using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; -using System.Text; +using SkiaSharp; namespace Spectrogram.Tests { @@ -17,16 +14,38 @@ public void Test_MelSpectrogram_MelScale() var sg = new SpectrogramGenerator(sampleRate, fftSize, stepSize: 500); sg.Add(audio); - Bitmap bmpMel = sg.GetBitmapMel(250); - bmpMel.Save("../../../../../dev/graphics/halMel-MelScale.png", ImageFormat.Png); + // Ottieni l'immagine Mel-scaled come SKBitmap + SKBitmap bmpMel = sg.GetBitmapMel(250); // Presuppone che sg abbia un metodo GetSKBitmapMel + using (var image = SKImage.FromBitmap(bmpMel)) + using (var data = image.Encode(SKEncodedImageFormat.Png, 100)) + { + // Salva l'immagine Mel-scaled + using (var stream = System.IO.File.OpenWrite("../../../../../dev/graphics/halMel-MelScale.png")) + { + data.SaveTo(stream); + } + } + + // Ottieni l'immagine originale come SKBitmap + SKBitmap bmpRaw = sg.GetBitmap(); // Presuppone che sg abbia un metodo GetSKBitmap + SKBitmap bmpCropped = new SKBitmap(bmpRaw.Width, bmpMel.Height); - Bitmap bmpRaw = sg.GetBitmap(); - Bitmap bmpCropped = new Bitmap(bmpRaw.Width, bmpMel.Height); - using (Graphics gfx = Graphics.FromImage(bmpCropped)) + // Disegna bmpRaw su bmpCropped usando SKCanvas + using (var canvas = new SKCanvas(bmpCropped)) { - gfx.DrawImage(bmpRaw, 0, bmpMel.Height - bmpRaw.Height); + canvas.Clear(SKColors.Transparent); + canvas.DrawBitmap(bmpRaw, new SKRect(0, bmpMel.Height - bmpRaw.Height, bmpRaw.Width, bmpMel.Height)); + } + + using (var imageCropped = SKImage.FromBitmap(bmpCropped)) + using (var dataCropped = imageCropped.Encode(SKEncodedImageFormat.Png, 100)) + { + // Salva l'immagine croppata + using (var streamCropped = System.IO.File.OpenWrite("../../../../../dev/graphics/halMel-LinearCropped.png")) + { + dataCropped.SaveTo(streamCropped); + } } - bmpCropped.Save("../../../../../dev/graphics/halMel-LinearCropped.png", ImageFormat.Png); } [Test] @@ -37,11 +56,11 @@ public void Test_Mel_Graph() double maxMel = 2595 * Math.Log10(1 + maxFreq / 700); Random rand = new Random(1); - double[] freq = ScottPlot.DataGen.Consecutive(specPoints, maxFreq / specPoints); - double[] power = ScottPlot.DataGen.RandomWalk(rand, specPoints, .02, .5); + double[] freq = ScottPlot.Generate.Consecutive(specPoints, maxFreq / specPoints); + double[] power = ScottPlot.Generate.RandomWalk(specPoints, .02, .5); - var plt1 = new ScottPlot.Plot(800, 300); - plt1.AddScatter(freq, power, markerSize: 0); + var plt1 = new ScottPlot.Plot(); + plt1.Add.ScatterLine(freq, power); int filterSize = 25; @@ -64,10 +83,9 @@ public void Test_Mel_Graph() double freqCenter = binStartFreqs[binIndex + 1]; double freqHigh = binStartFreqs[binIndex + 2]; - var sctr = plt1.AddScatter( - xs: new double[] { freqLow, freqCenter, freqHigh }, - ys: new double[] { 0, 1, 0 }, - markerSize: 0, lineWidth: 2); + double[] xs = [freqLow, freqCenter, freqHigh]; + double[] ys = [0, 1, 0]; + var sctr = plt1.Add.ScatterLine(xs, ys); int indexLow = (int)(specPoints * freqLow / maxFreq); int indexHigh = (int)(specPoints * freqHigh / maxFreq); @@ -84,10 +102,10 @@ public void Test_Mel_Graph() binValue += power[indexLow + i] * frac; } binValue /= binScaleSum; - plt1.AddPoint(freqCenter, binValue, sctr.Color, 10); + plt1.Add.Marker(freqCenter, binValue, ScottPlot.MarkerShape.FilledCircle, 10, sctr.Color); } - plt1.SaveFig("mel1.png"); + plt1.SavePng("mel1.png", 800, 300); } [Test] diff --git a/src/Spectrogram.Tests/SkExtensions.cs b/src/Spectrogram.Tests/SkExtensions.cs new file mode 100644 index 0000000..f59a544 --- /dev/null +++ b/src/Spectrogram.Tests/SkExtensions.cs @@ -0,0 +1,13 @@ +using SkiaSharp; + +namespace Spectrogram.Tests; + +internal static class SkExtensions +{ + internal static void SaveTo(this SKBitmap bitmap, string fileName, SKEncodedImageFormat format, int quality = 100) + { + using var data = bitmap.Encode(format, quality); + using var stream = System.IO.File.OpenWrite(fileName); + data.SaveTo(stream); + } +} \ No newline at end of file diff --git a/src/Spectrogram.Tests/Spectrogram.Tests.csproj b/src/Spectrogram.Tests/Spectrogram.Tests.csproj index 7754517..83fa10d 100644 --- a/src/Spectrogram.Tests/Spectrogram.Tests.csproj +++ b/src/Spectrogram.Tests/Spectrogram.Tests.csproj @@ -1,17 +1,18 @@ - + - net6.0 + net8.0 false + - - - - - + + + + + diff --git a/src/Spectrogram/Colormap.cs b/src/Spectrogram/Colormap.cs index 1972fa6..1553712 100644 --- a/src/Spectrogram/Colormap.cs +++ b/src/Spectrogram/Colormap.cs @@ -1,95 +1,91 @@ using System; -using System.Drawing; using System.Linq; +using SkiaSharp; -namespace Spectrogram +namespace Spectrogram; + +public class Colormap(ScottPlot.IColormap colormap) { - public class Colormap + private ScottPlot.IColormap _Colormap { get; } = colormap; + + public string Name => _Colormap.Name; + + public override string ToString() => _Colormap.ToString(); + + public static Colormap[] GetColormaps() => ScottPlot.Colormap.GetColormaps() + .Select(x => new Colormap(x)) + .ToArray(); + + public static string[] GetColormapNames() => ScottPlot.Colormap.GetColormaps() + .Select(x => new Colormap(x).Name) + .ToArray(); + + public static Colormap GetColormap(string colormapName) { - public static Colormap Argo => new Colormap(new Colormaps.Argo()); - public static Colormap Blues => new Colormap(new Colormaps.Blues()); - public static Colormap Grayscale => new Colormap(new Colormaps.Grayscale()); - public static Colormap GrayscaleReversed => new Colormap(new Colormaps.GrayscaleR()); - public static Colormap Greens => new Colormap(new Colormaps.Greens()); - public static Colormap Inferno => new Colormap(new Colormaps.Inferno()); - public static Colormap Lopora => new Colormap(new Colormaps.Lopora()); - public static Colormap Magma => new Colormap(new Colormaps.Magma()); - public static Colormap Plasma => new Colormap(new Colormaps.Plasma()); - public static Colormap Turbo => new Colormap(new Colormaps.Turbo()); - public static Colormap Viridis => new Colormap(new Colormaps.Viridis()); - - private readonly IColormap cmap; - public readonly string Name; - public Colormap(IColormap colormap) - { - cmap = colormap ?? new Colormaps.Grayscale(); - Name = cmap.GetType().Name; - } + foreach (Colormap cmap in GetColormaps()) + if (string.Equals(cmap.Name, colormapName, StringComparison.InvariantCultureIgnoreCase)) + return cmap; - public override string ToString() - { - return $"Colormap {Name}"; - } + throw new ArgumentException($"Colormap does not exist: {colormapName}"); + } - public static Colormap[] GetColormaps() => - typeof(Colormap).GetProperties() - .Select(x => (Colormap)x.GetValue(x.Name)) - .ToArray(); + public (byte r, byte g, byte b) GetRGB(byte value) => GetRGB(value / 255.0); - public static string[] GetColormapNames() - { - return GetColormaps().Select(x => x.Name).ToArray(); - } + public (byte r, byte g, byte b) GetRGB(double fraction) + { + ScottPlot.Color color = _Colormap.GetColor(fraction); + return (color.R, color.G, color.B); + } - public static Colormap GetColormap(string colormapName) - { - foreach (Colormap cmap in GetColormaps()) - if (string.Equals(cmap.Name, colormapName, StringComparison.InvariantCultureIgnoreCase)) - return cmap; + public int GetInt32(byte value) + { + var (r, g, b) = GetRGB(value); + return 255 << 24 | r << 16 | g << 8 | b; + } - throw new ArgumentException($"Colormap does not exist: {colormapName}"); - } + public int GetInt32(double fraction) + { + var (r, g, b) = GetRGB(fraction); + return 255 << 24 | r << 16 | g << 8 | b; + } - public (byte r, byte g, byte b) GetRGB(byte value) - { - return cmap.GetRGB(value); - } + public SKColor GetColor(byte value) + { + var color = GetInt32(value); + return new SKColor((uint)color); + } - public (byte r, byte g, byte b) GetRGB(double fraction) - { - fraction = Math.Max(fraction, 0); - fraction = Math.Min(fraction, 1); - return cmap.GetRGB((byte)(fraction * 255)); - } + public SKColor GetColor(double fraction) + { + var color = GetInt32(fraction); + return new SKColor((uint)color); + } - public int GetInt32(byte value) - { - var (r, g, b) = GetRGB(value); - return 255 << 24 | r << 16 | g << 8 | b; - } + public SKBitmap ApplyFilter(SKBitmap bmp) + { + SKImageInfo info = new(bmp.Width, bmp.Height, SKColorType.Rgba8888); + SKBitmap newBitmap = new(info); + using SKCanvas canvas = new(newBitmap); + canvas.Clear(); - public int GetInt32(double fraction) - { - var (r, g, b) = GetRGB(fraction); - return 255 << 24 | r << 16 | g << 8 | b; - } + using SKPaint paint = new SKPaint(); - public Color GetColor(byte value) - { - return Color.FromArgb(GetInt32(value)); - } + byte[] A = new byte[256]; + byte[] R = new byte[256]; + byte[] G = new byte[256]; + byte[] B = new byte[256]; - public Color GetColor(double fraction) + for (int i = 0; i < 256; i++) { - return Color.FromArgb(GetInt32(fraction)); + var color = GetColor((byte)i); + A[i] = color.Alpha; + R[i] = color.Red; + G[i] = color.Green; + B[i] = color.Blue; } + paint.ColorFilter = SKColorFilter.CreateTable(A, R, G, B); - public void Apply(Bitmap bmp) - { - System.Drawing.Imaging.ColorPalette pal = bmp.Palette; - for (int i = 0; i < 256; i++) - pal.Entries[i] = GetColor((byte)i); - bmp.Palette = pal; - } + canvas.DrawBitmap(bmp, 0, 0, paint); + return newBitmap; } } diff --git a/src/Spectrogram/Colormaps/Argo.cs b/src/Spectrogram/Colormaps/Argo.cs deleted file mode 100644 index ac40f2d..0000000 --- a/src/Spectrogram/Colormaps/Argo.cs +++ /dev/null @@ -1,55 +0,0 @@ -/* Argo is a closed-source weak signal spectrogram. - * This colormap was created to mimic the colors used by Argo. - * https://www.i2phd.org/argo/index.html - * https://digilander.libero.it/i2phd/argo/ - */ - -using System; - -namespace Spectrogram.Colormaps -{ - class Argo : IColormap - { - public (byte r, byte g, byte b) GetRGB(byte value) - { - byte[] bytes = BitConverter.GetBytes(rgb[value]); - return (bytes[2], bytes[1], bytes[0]); - } - - private readonly int[] rgb = - { - 00000000, 00000004, 00000264, 00000267, 00000527, 00000530, 00000789, 00066328, - 00066588, 00066591, 00066849, 00132388, 00132647, 00132650, 00132908, 00198447, - 00198706, 00198708, 00264503, 00264505, 00330299, 00330557, 00330560, 00396354, - 00396612, 00462150, 00462408, 00527946, 00528204, 00593998, 00594000, 00659794, - 00660052, 00725590, 00791383, 00791641, 00857435, 00857437, 00923230, 00989024, - 00989026, 01054819, 01120613, 01120870, 01186408, 01252201, 01252459, 01318252, - 01384046, 01384047, 01449841, 01515634, 01515892, 01581429, 01647222, 01713016, - 01713273, 01779066, 01844860, 01910397, 01976190, 01976447, 02042241, 02108034, - 02173827, 02239364, 02239621, 02305415, 02371208, 02437001, 02502794, 02568587, - 02568844, 02634381, 02700174, 02765968, 02831761, 02897554, 02963347, 03029140, - 03029397, 03095190, 03160983, 03226520, 03292313, 03358106, 03423899, 03489692, - 03555485, 03621278, 03687071, 03752864, 03818656, 03884449, 03950242, 04016035, - 04081828, 04147621, 04147878, 04213671, 04279464, 04345256, 04411049, 04476842, - 04542635, 04608428, 04739757, 04805550, 04871342, 04937135, 05002928, 05068721, - 05134514, 05200306, 05266099, 05331892, 05397685, 05463477, 05529270, 05595063, - 05660856, 05726648, 05792441, 05858234, 05924027, 05989819, 06121148, 06186941, - 06252734, 06318526, 06384319, 06450112, 06515904, 06581953, 06647746, 06779074, - 06844867, 06910660, 06976452, 07042245, 07108038, 07173830, 07239623, 07370952, - 07436744, 07502793, 07568586, 07634378, 07700171, 07765964, 07897292, 07963085, - 08028877, 08094670, 08160719, 08226511, 08357840, 08423632, 08489425, 08555218, - 08621010, 08752339, 08818387, 08884180, 08949973, 09015765, 09147094, 09212886, - 09278679, 09344727, 09410520, 09541849, 09607641, 09673434, 09739226, 09870555, - 09936603, 10002396, 10068188, 10133981, 10265309, 10331102, 10397150, 10462943, - 10594272, 10660064, 10725857, 10791905, 10923234, 10989026, 11054819, 11120611, - 11251940, 11317988, 11383781, 11515109, 11580902, 11646694, 11712743, 11844071, - 11909864, 11975656, 12106985, 12173033, 12238826, 12304618, 12435947, 12501995, - 12567787, 12699116, 12764908, 12830701, 12962285, 13028078, 13093870, 13159663, - 13291247, 13357040, 13422832, 13554161, 13619953, 13686001, 13817330, 13883122, - 13948915, 14080499, 14146292, 14212084, 14343412, 14409461, 14475253, 14606582, - 14672374, 14738423, 14869751, 14935543, 15066872, 15132920, 15198713, 15330041, - 15396090, 15461882, 15593210, 15659003, 15725051, 15856380, 15922172, 16053500, - 16119549, 16185341, 16316670, 16382718, 16448510, 16579839, 16645631, 16777215, - }; - } -} diff --git a/src/Spectrogram/Colormaps/Blues.cs b/src/Spectrogram/Colormaps/Blues.cs deleted file mode 100644 index d917d9c..0000000 --- a/src/Spectrogram/Colormaps/Blues.cs +++ /dev/null @@ -1,50 +0,0 @@ -// This colormap was created by Scott Harden on 2020-06-16 and is released under a MIT license. -using System; - -namespace Spectrogram.Colormaps -{ - class Blues : IColormap - { - public (byte r, byte g, byte b) GetRGB(byte value) - { - byte[] bytes = BitConverter.GetBytes(argb[value]); - return (bytes[2], bytes[1], bytes[0]); - } - - private readonly int[] argb = - { - -16767403, -16767402, -16767144, -16766887, -16701093, -16700835, -16700578, -16634784, - -16634527, -16634269, -16568476, -16568218, -16568216, -16567959, -16502165, -16501908, - -16501650, -16435856, -16435599, -16435341, -16369548, -16369290, -16369033, -16303495, - -16303238, -16302980, -16237186, -16236929, -16236671, -16236414, -16170620, -16170363, - -16170105, -16104312, -16104054, -16103797, -16038259, -16038002, -16037745, -15971951, - -15971694, -15971436, -15905643, -15905385, -15905128, -15839335, -15839077, -15838820, - -15773027, -15772769, -15772512, -15706718, -15706717, -15706460, -15706203, -15640409, - -15640152, -15574359, -15574101, -15573844, -15508051, -15507794, -15507537, -15441743, - -15441486, -15441229, -15375436, -15375179, -15309386, -15309128, -15308871, -15243078, - -15242821, -15177284, -15177027, -15111234, -15110977, -15045184, -15044927, -14979134, - -14978877, -14913084, -14912827, -14847034, -14781241, -14780984, -14715191, -14715190, - -14649397, -14583605, -14517812, -14517555, -14451762, -14385969, -14320176, -14319920, - -14254383, -14188590, -14122797, -14057005, -13991212, -13925419, -13859626, -13859626, - -13793833, -13662505, -13596712, -13530919, -13465383, -13399590, -13333797, -13268005, - -13202212, -13136676, -13005347, -12939555, -12873762, -12808226, -12676897, -12611105, - -12545312, -12414240, -12348447, -12282655, -12151327, -12085790, -12019998, -11888669, - -11822877, -11757341, -11626012, -11560220, -11429148, -11363355, -11232027, -11166235, - -11035162, -10969370, -10903578, -10772505, -10706713, -10575385, -10509592, -10378520, - -10312728, -10181400, -10115863, -09984535, -09918743, -09787414, -09721878, -09590550, - -09524758, -09393429, -09327893, -09262101, -09130773, -09065237, -08933908, -08868116, - -08736788, -08671252, -08605459, -08474131, -08408339, -08277267, -08211475, -08145682, - -08014354, -07948818, -07817490, -07751698, -07685905, -07554833, -07489041, -07357713, - -07291921, -07226128, -07095056, -07029264, -06897936, -06832144, -06766351, -06635279, - -06569487, -06503695, -06372367, -06306575, -06175502, -06109710, -06043918, -05912590, - -05846798, -05781261, -05649933, -05584141, -05518349, -05387021, -05321485, -05190156, - -05124364, -05058572, -04927244, -04861452, -04730123, -04664587, -04598795, -04467467, - -04401675, -04335883, -04204554, -04139018, -04007690, -03941898, -03876106, -03744777, - -03678985, -03547657, -03481865, -03416329, -03285000, -03219208, -03087880, -03022088, - -02956296, -02824968, -02759175, -02628103, -02562311, -02430983, -02365191, -02299398, - -02168070, -02102278, -01970950, -01905158, -01839621, -01708293, -01642501, -01511173, - -01445381, -01314052, -01248260, -01182468, -01051140, -00985604, -00854275, -00788483, - -00657155, -00591363, -00525571, -00394242, -00328450, -00197122, -00131330, -00000001, - }; - } -} diff --git a/src/Spectrogram/Colormaps/Grayscale.cs b/src/Spectrogram/Colormaps/Grayscale.cs deleted file mode 100644 index f8d092a..0000000 --- a/src/Spectrogram/Colormaps/Grayscale.cs +++ /dev/null @@ -1,12 +0,0 @@ -// This colormap was created by Scott Harden on 2020-06-16 and is released under a MIT license. - -namespace Spectrogram.Colormaps -{ - class Grayscale : IColormap - { - public (byte r, byte g, byte b) GetRGB(byte value) - { - return (value, value, value); - } - } -} diff --git a/src/Spectrogram/Colormaps/GrayscaleR.cs b/src/Spectrogram/Colormaps/GrayscaleR.cs deleted file mode 100644 index 6c499e5..0000000 --- a/src/Spectrogram/Colormaps/GrayscaleR.cs +++ /dev/null @@ -1,13 +0,0 @@ -// This colormap was created by Scott Harden on 2020-06-16 and is released under a MIT license. - -namespace Spectrogram.Colormaps -{ - public class GrayscaleR : IColormap - { - public (byte r, byte g, byte b) GetRGB(byte value) - { - value = (byte)(255 - value); - return (value, value, value); - } - } -} \ No newline at end of file diff --git a/src/Spectrogram/Colormaps/Greens.cs b/src/Spectrogram/Colormaps/Greens.cs deleted file mode 100644 index 0ee2358..0000000 --- a/src/Spectrogram/Colormaps/Greens.cs +++ /dev/null @@ -1,51 +0,0 @@ -// This colormap was created by Scott Harden on 2020-06-16 and is released under a MIT license. -using System; - -namespace Spectrogram.Colormaps -{ - class Greens : IColormap - { - public (byte r, byte g, byte b) GetRGB(byte value) - { - byte[] bytes = BitConverter.GetBytes(argb[value]); - return (bytes[2], bytes[1], bytes[0]); - } - - private readonly int[] argb = - { - -16761088, -16760832, -16760575, -16760318, -16760061, -16759804, -16759547, -16759290, - -16759033, -16758776, -16758519, -16758006, -16757749, -16757492, -16757235, -16756979, - -16756722, -16756465, -16756208, -16755951, -16755694, -16755437, -16755180, -16754667, - -16754410, -16688617, -16688360, -16688104, -16687847, -16687590, -16687333, -16687076, - -16621283, -16621026, -16620769, -16620512, -16620256, -16554463, -16554206, -16553949, - -16553692, -16487899, -16487642, -16487385, -16421336, -16421080, -16420823, -16355030, - -16354773, -16288980, -16288723, -16222930, -16222930, -16222673, -16156880, -16156623, - -16090830, -16025038, -16024781, -15958988, -15958731, -15892938, -15827145, -15826889, - -15761096, -15695303, -15695046, -15629254, -15563461, -15497924, -15497667, -15431875, - -15366082, -15300289, -15234496, -15234240, -15168447, -15102654, -15037118, -14971325, - -14905532, -14839740, -14773947, -14708154, -14642618, -14576825, -14511033, -14445240, - -14379447, -14313655, -14248118, -14182326, -14116533, -14050741, -13985204, -13919412, - -13853619, -13722291, -13656498, -13590962, -13525169, -13459377, -13328048, -13262512, - -13196720, -13130927, -13065391, -12934063, -12868270, -12802478, -12671406, -12605613, - -12539821, -12408749, -12342957, -12277164, -12146092, -12080300, -12014508, -11883435, - -11817643, -11686315, -11620779, -11554986, -11423914, -11358122, -11226794, -11161258, - -11029929, -10964137, -10833065, -10767273, -10636201, -10570408, -10439080, -10373544, - -10242216, -10176680, -10045351, -09914023, -09848487, -09717159, -09651623, -09520294, - -09389222, -09323430, -09192102, -09061029, -08995237, -08864165, -08798372, -08667300, - -08535972, -08404643, -08339107, -08207779, -08076706, -08010914, -07879842, -07748513, - -07682977, -07551648, -07420576, -07289248, -07223711, -07092383, -06961054, -06895518, - -06764189, -06633116, -06501788, -06436251, -06304923, -06173850, -06108057, -05976984, - -05845656, -05714583, -05648790, -05517717, -05386389, -05320596, -05189523, -05123730, - -04992657, -04861328, -04795791, -04664462, -04598925, -04467596, -04336523, -04270730, - -04139400, -04073863, -03942534, -03876997, -03745667, -03680130, -03614337, -03483007, - -03417470, -03286140, -03220603, -03154809, -03023479, -02957942, -02892148, -02826610, - -02760817, -02629487, -02563949, -02498155, -02432617, -02366823, -02301029, -02235491, - -02169697, -02103903, -02038365, -01972571, -01906777, -01841239, -01775444, -01709650, - -01644112, -01578318, -01512523, -01446985, -01381191, -01315396, -01249858, -01249599, - -01183805, -01118266, -01052472, -00986678, -00986675, -00920880, -00855086, -00789547, - -00723753, -00723494, -00657956, -00592161, -00526367, -00526108, -00460569, -00394775, - -00394516, -00328977, -00263183, -00197388, -00197385, -00131591, -00065796, -00000001, - - }; - } -} diff --git a/src/Spectrogram/Colormaps/Inferno.cs b/src/Spectrogram/Colormaps/Inferno.cs deleted file mode 100644 index 0b45ed5..0000000 --- a/src/Spectrogram/Colormaps/Inferno.cs +++ /dev/null @@ -1,57 +0,0 @@ -/* Inferno is a colormap by Nathaniel J. Smith and Stefan van der Walt - * https://bids.github.io/colormap/ - * https://github.com/BIDS/colormap/blob/master/colormaps.py - * - * This colormap is provided under the CC0 license / public domain dedication - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -using System; - -namespace Spectrogram.Colormaps -{ - class Inferno : IColormap - { - public (byte r, byte g, byte b) GetRGB(byte value) - { - byte[] bytes = BitConverter.GetBytes(rgb[value]); - return (bytes[2], bytes[1], bytes[0]); - } - - private readonly int[] rgb = - { - 00000003, 00000004, 00000006, 00065543, 00065801, 00065803, 00131342, 00131600, - 00197138, 00262932, 00262934, 00328728, 00394267, 00460061, 00525855, 00591393, - 00657187, 00722726, 00854056, 00919594, 00985389, 01050927, 01182258, 01247796, - 01313590, 01444665, 01510203, 01641278, 01706816, 01838147, 01903685, 02034759, - 02100298, 02231116, 02362190, 02493264, 02558802, 02689876, 02820694, 02951768, - 03017306, 03148380, 03279197, 03410271, 03475808, 03606881, 03737954, 03869028, - 03934565, 04065638, 04196710, 04262247, 04393576, 04524649, 04590185, 04721514, - 04852586, 04918379, 05049451, 05180780, 05246316, 05377644, 05443181, 05574509, - 05705581, 05771373, 05902701, 05968238, 06099566, 06230638, 06296430, 06427758, - 06493294, 06624622, 06690158, 06821486, 06952814, 07018350, 07149678, 07215214, - 07346542, 07477613, 07543405, 07674733, 07740269, 07871597, 08002669, 08068460, - 08199532, 08265324, 08396651, 08462187, 08593515, 08724586, 08790378, 08921450, - 08987241, 09118313, 09249641, 09315432, 09446504, 09512295, 09643367, 09774694, - 09840230, 09971557, 10037348, 10168420, 10234211, 10365283, 10496610, 10562401, - 10693473, 10759264, 10890335, 10956127, 11087454, 11218525, 11284316, 11415643, - 11481435, 11612506, 11678297, 11809624, 11875159, 12006486, 12072278, 12203605, - 12269396, 12400467, 12466258, 12532049, 12663376, 12729167, 12860494, 12926285, - 13057612, 13123147, 13188938, 13320265, 13386056, 13451847, 13583430, 13649220, - 13715011, 13780802, 13912129, 13977920, 14043711, 14109502, 14241085, 14306875, - 14372666, 14438457, 14504504, 14570295, 14636086, 14702132, 14833459, 14899250, - 14965297, 15031088, 15096878, 15097389, 15163180, 15229227, 15295018, 15361064, - 15426855, 15492902, 15558693, 15559203, 15625250, 15691041, 15757087, 15757342, - 15823389, 15889436, 15889690, 15955737, 15956248, 16022038, 16088085, 16088596, - 16154642, 16154897, 16220944, 16221454, 16287501, 16287756, 16288267, 16354313, - 16354824, 16355336, 16421127, 16421638, 16422150, 16422662, 16488710, 16489222, - 16489734, 16489991, 16490503, 16491016, 16491530, 16492043, 16492557, 16493070, - 16493584, 16494098, 16494612, 16494870, 16495384, 16495898, 16496412, 16496926, - 16431905, 16432419, 16432933, 16433448, 16368426, 16368940, 16369455, 16304433, - 16304948, 16305463, 16240442, 16240956, 16175935, 16176450, 16111429, 16111944, - 16046923, 16047183, 15982162, 15982678, 15983193, 15918173, 15918688, 15853668, - 15853928, 15854444, 15854960, 15855220, 15855737, 15856253, 15922049, 15922309, - 15988361, 16054157, 16119953, 16186005, 16251801, 16383133, 16448928, 16580260, - }; - } -} diff --git a/src/Spectrogram/Colormaps/Lopora.cs b/src/Spectrogram/Colormaps/Lopora.cs deleted file mode 100644 index a250f7b..0000000 --- a/src/Spectrogram/Colormaps/Lopora.cs +++ /dev/null @@ -1,55 +0,0 @@ -/* Lopora is an open-source weak signal spectrogram by Onno Hoekstra (PA2OHH) - * This colormap was created to mimic the default colors used by Lopora. - * https://www.qsl.net/pa2ohh/11lop.htm - * https://github.com/swharden/Lopora/blob/20afe72416579f8b7d3c8861532c71a95b904066/src/LOPORA-v5a.py#L828-L872 - */ - -using System; - -namespace Spectrogram.Colormaps -{ - class Lopora : IColormap - { - public (byte r, byte g, byte b) GetRGB(byte value) - { - byte[] bytes = BitConverter.GetBytes(rgb[value]); - return (bytes[2], bytes[1], bytes[0]); - } - - private readonly int[] rgb = - { - 0000000000, 0000069696, 0000137036, 0000203860, 0000270426, 0000336991, 0000403300, 0000469608, - 0000535915, 0000602222, 0000668273, 0000734580, 0000800631, 0000866681, 0000932987, 0000999037, - 0001065088, 0001131137, 0001197187, 0001262981, 0001329031, 0001395080, 0001461130, 0001526924, - 0001592973, 0001659023, 0001724816, 0001790865, 0001856659, 0001922708, 0001988501, 0002054550, - 0002120344, 0002186393, 0002252186, 0002317979, 0002384028, 0002449821, 0002515614, 0002581663, - 0002647456, 0002713249, 0002779042, 0002845091, 0002910884, 0002976677, 0003042470, 0003108263, - 0003174056, 0003240105, 0003305898, 0003371690, 0003437483, 0003503276, 0003569069, 0003634862, - 0003700654, 0003766447, 0003832240, 0003898033, 0003963825, 0004029618, 0004095411, 0004161204, - 0004227252, 0004292789, 0004358582, 0004424374, 0004490167, 0004555960, 0004621752, 0004687545, - 0004753338, 0004819130, 0004884923, 0004950716, 0005016508, 0005082301, 0005148093, 0005213886, - 0005279679, 0005345215, 0005411008, 0005476800, 0005542593, 0005608386, 0005674178, 0005739971, - 0005805763, 0005871300, 0005937092, 0006002885, 0006068677, 0006134470, 0006200263, 0006265799, - 0006331592, 0006397384, 0006463177, 0006528969, 0006594506, 0006660298, 0006726091, 0006791883, - 0006857676, 0006923212, 0006989005, 0007054797, 0007120590, 0007186126, 0007251918, 0007317711, - 0007383503, 0007449040, 0007514832, 0007580625, 0007646417, 0007711954, 0007777746, 0007843539, - 0007909331, 0007974867, 0008040660, 0008106452, 0008171989, 0008237781, 0008303574, 0008369366, - 0008434902, 0008435159, 0008500951, 0008566488, 0008632280, 0008698072, 0008763609, 0008829401, - 0008895194, 0008960986, 0009026522, 0009092315, 0009158107, 0009223644, 0009289436, 0009355228, - 0009420765, 0009486557, 0009552350, 0009617886, 0009683678, 0009749471, 0009815007, 0009880799, - 0009946336, 0010012128, 0010077921, 0010143457, 0010209249, 0010275042, 0010340578, 0010406370, - 0010472163, 0010537699, 0010603491, 0010669028, 0010734820, 0010800612, 0010866149, 0010931941, - 0010997734, 0011063270, 0011129062, 0011194599, 0011260391, 0011326183, 0011391720, 0011457512, - 0011523048, 0011588841, 0011654633, 0011720169, 0011785962, 0011851498, 0011917290, 0011983082, - 0012048619, 0012114411, 0012179947, 0012245740, 0012311532, 0012377068, 0012442861, 0012508397, - 0012574189, 0012639726, 0012705518, 0012771310, 0012836847, 0012902639, 0012968175, 0013033967, - 0013099504, 0013165296, 0013231088, 0013296625, 0013362417, 0013427953, 0013493746, 0013559282, - 0013625074, 0013690610, 0013756403, 0013822195, 0013887731, 0013953524, 0014019060, 0014084852, - 0014150388, 0014216181, 0014281717, 0014347509, 0014413046, 0014478838, 0014544374, 0014610166, - 0014675959, 0014741495, 0014807287, 0014872823, 0014938616, 0015004152, 0015069944, 0015135481, - 0015201273, 0015266809, 0015332601, 0015398138, 0015463930, 0015529466, 0015595258, 0015660795, - 0015726587, 0015792123, 0015857915, 0015923452, 0015989244, 0016054780, 0016120572, 0016186109, - 0016251901, 0016317437, 0016383229, 0016448766, 0016514558, 0016580350, 0016645887, 0016711679, - }; - } -} diff --git a/src/Spectrogram/Colormaps/Magma.cs b/src/Spectrogram/Colormaps/Magma.cs deleted file mode 100644 index 105a177..0000000 --- a/src/Spectrogram/Colormaps/Magma.cs +++ /dev/null @@ -1,57 +0,0 @@ -/* Magma is a colormap by Nathaniel J. Smith and Stefan van der Walt - * https://bids.github.io/colormap/ - * https://github.com/BIDS/colormap/blob/master/colormaps.py - * - * This colormap is provided under the CC0 license / public domain dedication - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -using System; - -namespace Spectrogram.Colormaps -{ - class Magma : IColormap - { - public (byte r, byte g, byte b) GetRGB(byte value) - { - byte[] bytes = BitConverter.GetBytes(rgb[value]); - return (bytes[2], bytes[1], bytes[0]); - } - - private readonly int[] rgb = - { - 00000003, 00000004, 00000006, 00065543, 00065801, 00065803, 00131597, 00131599, - 00197393, 00262931, 00263189, 00328727, 00394521, 00460059, 00525853, 00591647, - 00657186, 00722980, 00788774, 00854568, 00920106, 00985900, 01051695, 01117233, - 01183027, 01314101, 01379896, 01445434, 01511228, 01576767, 01708097, 01773636, - 01839174, 01970249, 02036043, 02101581, 02232656, 02298194, 02429269, 02494807, - 02625881, 02756956, 02822494, 02953312, 03084386, 03149925, 03280999, 03412072, - 03477354, 03608428, 03739502, 03870575, 03936113, 04067186, 04198259, 04329332, - 04394869, 04525942, 04657015, 04722808, 04853881, 04919417, 05050746, 05181819, - 05247611, 05378684, 05444476, 05575549, 05706877, 05772670, 05903742, 05969534, - 06100862, 06166399, 06297727, 06363263, 06494591, 06625920, 06691456, 06822784, - 06888576, 07019648, 07085440, 07216769, 07282305, 07413633, 07544705, 07610497, - 07741825, 07807361, 07938689, 08004225, 08135553, 08266881, 08332417, 08463745, - 08529281, 08660609, 08726145, 08857473, 08988801, 09054337, 09185664, 09251200, - 09382528, 09513600, 09579392, 09710464, 09776256, 09907327, 10038655, 10104191, - 10235519, 10366590, 10432382, 10563454, 10694782, 10760317, 10891645, 10957181, - 11088508, 11219836, 11285371, 11416699, 11547771, 11613562, 11744634, 11875961, - 11941497, 12072824, 12138360, 12269687, 12401015, 12466550, 12597877, 12728949, - 12794740, 12926068, 12991603, 13122930, 13254258, 13319793, 13451120, 13516912, - 13648239, 13714030, 13845101, 13910893, 14042220, 14108011, 14239338, 14305129, - 14436457, 14502248, 14568039, 14699366, 14765158, 14830949, 14962276, 15028323, - 15094114, 15159906, 15225953, 15357280, 15423072, 15489119, 15554911, 15620958, - 15621469, 15687261, 15753309, 15819100, 15885148, 15951196, 15951707, 16017499, - 16083547, 16084059, 16150107, 16150619, 16216411, 16216924, 16282972, 16283484, - 16349532, 16350045, 16350557, 16416606, 16416862, 16417375, 16483424, 16483936, - 16484449, 16484962, 16551011, 16551523, 16552036, 16552549, 16552806, 16618855, - 16619368, 16619881, 16620394, 16620907, 16621420, 16621934, 16622191, 16622704, - 16688753, 16689267, 16689780, 16690293, 16690806, 16691064, 16691577, 16692091, - 16692604, 16693117, 16693631, 16694144, 16694402, 16694915, 16695429, 16695942, - 16696456, 16696969, 16697227, 16697741, 16698254, 16633232, 16633746, 16634259, - 16634517, 16635031, 16635544, 16636058, 16636572, 16637085, 16637343, 16637857, - 16638371, 16573349, 16573862, 16574120, 16574634, 16575148, 16575662, 16576176, - 16576689, 16576947, 16577461, 16577975, 16512953, 16513467, 16513725, 16514239, - }; - } -} diff --git a/src/Spectrogram/Colormaps/Plasma.cs b/src/Spectrogram/Colormaps/Plasma.cs deleted file mode 100644 index b03dc59..0000000 --- a/src/Spectrogram/Colormaps/Plasma.cs +++ /dev/null @@ -1,57 +0,0 @@ -/* Plasma is a colormap by Nathaniel J. Smith and Stefan van der Walt - * https://bids.github.io/colormap/ - * https://github.com/BIDS/colormap/blob/master/colormaps.py - * - * This colormap is provided under the CC0 license / public domain dedication - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -using System; - -namespace Spectrogram.Colormaps -{ - class Plasma : IColormap - { - public (byte r, byte g, byte b) GetRGB(byte value) - { - byte[] bytes = BitConverter.GetBytes(rgb[value]); - return (bytes[2], bytes[1], bytes[0]); - } - - private readonly int[] rgb = - { - 00788358, 01050503, 01246857, 01377930, 01574539, 01771148, 01902221, 02033038, - 02164111, 02295184, 02426257, 02557330, 02688403, 02819476, 02950292, 03081365, - 03212438, 03343511, 03409048, 03540120, 03671193, 03802266, 03867546, 03998619, - 04129692, 04195228, 04326301, 04457374, 04522910, 04653727, 04784799, 04850336, - 04981409, 05112481, 05178018, 05308834, 05374371, 05505443, 05636515, 05702052, - 05833124, 05898405, 06029477, 06160549, 06226086, 06357158, 06422694, 06553767, - 06619303, 06750375, 06815911, 06946983, 07078056, 07143592, 07274664, 07340200, - 07471272, 07536808, 07667880, 07733672, 07864744, 07930280, 08061608, 08127143, - 08258471, 08324007, 08455335, 08520871, 08652198, 08717990, 08783782, 08914853, - 08980645, 09111972, 09177764, 09309348, 09375139, 09440931, 09572258, 09638049, - 09769377, 09835168, 09900960, 10032287, 10098078, 10164126, 10295453, 10361244, - 10427035, 10492827, 10624154, 10689945, 10755736, 10821527, 10953111, 11018902, - 11084693, 11150484, 11281811, 11347602, 11413393, 11479184, 11545231, 11611023, - 11676814, 11808141, 11873932, 11939723, 12005514, 12071561, 12137352, 12203143, - 12268934, 12334725, 12400516, 12466307, 12532098, 12598145, 12663936, 12729728, - 12795519, 12861310, 12927101, 12992892, 13058683, 13124730, 13190521, 13256312, - 13322103, 13387894, 13453685, 13519477, 13585268, 13651315, 13717106, 13717361, - 13783152, 13848943, 13914734, 13980525, 14046573, 14112364, 14112619, 14178410, - 14244201, 14309992, 14375783, 14441830, 14442086, 14507877, 14573668, 14639459, - 14639714, 14705761, 14771552, 14837344, 14903135, 14903390, 14969437, 15035228, - 15035483, 15101274, 15167066, 15233113, 15233368, 15299159, 15364950, 15365205, - 15431252, 15497044, 15497299, 15563090, 15563601, 15629392, 15695183, 15695438, - 15761485, 15761741, 15827532, 15893579, 15893834, 15959625, 15959880, 16025927, - 16026183, 16091974, 16092485, 16158276, 16158531, 16159042, 16224833, 16225089, - 16291136, 16291391, 16291902, 16357693, 16357948, 16423995, 16424250, 16424762, - 16425017, 16491064, 16491319, 16491574, 16557621, 16557877, 16558388, 16558643, - 16559154, 16559409, 16625457, 16625712, 16626223, 16626478, 16626989, 16627245, - 16627756, 16628011, 16628523, 16628778, 16629289, 16629801, 16630056, 16630568, - 16630823, 16631334, 16566054, 16566566, 16567077, 16567333, 16567845, 16502820, - 16503076, 16503588, 16438564, 16438820, 16439332, 16374052, 16374564, 16309540, - 16310052, 16244772, 16245285, 16180261, 16180517, 16115494, 16116006, 16050726, - 15985702, 15986214, 15921190, 15921446, 15856422, 15791397, 15791651, 15726625, - }; - } -} diff --git a/src/Spectrogram/Colormaps/Turbo.cs b/src/Spectrogram/Colormaps/Turbo.cs deleted file mode 100644 index e935210..0000000 --- a/src/Spectrogram/Colormaps/Turbo.cs +++ /dev/null @@ -1,53 +0,0 @@ -// This colormap was created by Scott Harden on 2020-06-16 and is released under a MIT license. -// It was designed to mimic Turbo, but is not a copy of or derived from Turbo source code. -// https://ai.googleblog.com/2019/08/turbo-improved-rainbow-colormap-for.html - -using System; - -namespace Spectrogram.Colormaps -{ - class Turbo : IColormap - { - public (byte r, byte g, byte b) GetRGB(byte value) - { - byte[] bytes = BitConverter.GetBytes(argb[value]); - return (bytes[2], bytes[1], bytes[0]); - } - - private readonly int[] argb = - { - -13559489, -13493436, -13427382, -13361328, -13295018, -13228964, -13162911, -13096857, - -13030547, -12964493, -12898440, -12832130, -12766077, -12700023, -12633970, -12567660, - -12501607, -12435554, -12369245, -12303192, -12237139, -12171086, -12170313, -12104260, - -12038208, -12037436, -11971383, -11905331, -11904559, -11838507, -11837991, -11771940, - -11771168, -11770653, -11770138, -11703831, -11703316, -11702801, -11702287, -11701517, - -11701003, -11700489, -11765255, -11764742, -11764228, -11828995, -11828482, -11893506, - -11892737, -11957761, -12022785, -12022017, -12087042, -12152067, -12217092, -12347397, - -12412423, -12477448, -12542218, -12672780, -12737806, -12802577, -12933139, -12998166, - -13128729, -13193500, -13324063, -13389091, -13519654, -13584682, -13714989, -13780017, - -13910581, -13975609, -14106173, -14171201, -14301765, -14366793, -14431822, -14562386, - -14627414, -14692442, -14757471, -14822499, -14887527, -14952556, -14952048, -15017332, - -15082361, -15081853, -15147137, -15146629, -15146121, -15145869, -15145361, -15145109, - -15079065, -15078812, -15013024, -15012515, -14946726, -14880938, -14749356, -14683567, - -14617778, -14486453, -14355127, -14223801, -14092475, -13961405, -13764543, -13633217, - -13436355, -13239748, -13108422, -12911559, -12714952, -12518089, -12255946, -12059339, - -11862476, -11600333, -11403725, -11141582, -10879182, -10682575, -10420431, -10158287, - -09896143, -09633999, -09372111, -09175503, -08913359, -08651215, -08389327, -08127183, - -07865294, -07603150, -07341262, -07079117, -06817229, -06555341, -06293452, -06031308, - -05834955, -05573067, -05311178, -05114826, -04852937, -04656585, -04394952, -04198600, - -04002247, -03740358, -03544262, -03347910, -03217349, -03020997, -02824900, -02628804, - -02497987, -02301891, -02171331, -02040770, -01910210, -01779394, -01648834, -01518273, - -01387713, -01257153, -01126849, -01061824, -00931264, -00866240, -00801216, -00670656, - -00605888, -00540864, -00475840, -00411072, -00346048, -00346560, -00281792, -00282304, - -00217536, -00218048, -00153280, -00153793, -00154561, -00155073, -00155841, -00156354, - -00157122, -00157634, -00158403, -00224451, -00225219, -00291524, -00292036, -00358341, - -00424389, -00490694, -00491206, -00557511, -00623559, -00755400, -00821705, -00887754, - -00954058, -01085643, -01151948, -01283533, -01349837, -01481422, -01613263, -01679312, - -01811153, -01942738, -02074579, -02206164, -02337749, -02469590, -02601175, -02733016, - -02930137, -03061978, -03193563, -03390940, -03522526, -03654111, -03851488, -03983073, - -04180450, -04377572, -04509157, -04706534, -04838119, -05035497, -05232618, -05429739, - -05561580, -05758702, -05956079, -06153200, -06350322, -06482163, -06679284, -06876662, - -07073783, -07270904, -07468282, -07665403, -07862524, -08059902, -08257023, -08388608, - }; - } -} diff --git a/src/Spectrogram/Colormaps/Viridis.cs b/src/Spectrogram/Colormaps/Viridis.cs deleted file mode 100644 index a6e0bb1..0000000 --- a/src/Spectrogram/Colormaps/Viridis.cs +++ /dev/null @@ -1,57 +0,0 @@ -/* Viridis is a colormap by Nathaniel J. Smith, Stefan van der Walt, and Eric Firing - * https://bids.github.io/colormap/ - * https://github.com/BIDS/colormap/blob/master/colormaps.py - * - * This colormap is provided under the CC0 license / public domain dedication - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -using System; - -namespace Spectrogram.Colormaps -{ - class Viridis : IColormap - { - public (byte r, byte g, byte b) GetRGB(byte value) - { - byte[] bytes = BitConverter.GetBytes(rgb[value]); - return (bytes[2], bytes[1], bytes[0]); - } - - private readonly int[] rgb = - { - 04456788, 04457045, 04457303, 04523352, 04523610, 04524123, 04589916, 04590430, - 04590687, 04591201, 04656994, 04657507, 04657765, 04658278, 04658535, 04658793, - 04659306, 04725099, 04725356, 04725870, 04726127, 04726384, 04726897, 04727154, - 04727411, 04727668, 04662645, 04662902, 04663159, 04663416, 04663929, 04664186, - 04664443, 04599164, 04599676, 04599933, 04600190, 04534911, 04535423, 04535680, - 04535937, 04470657, 04471170, 04405891, 04406147, 04406404, 04341124, 04341381, - 04341893, 04276614, 04276870, 04211591, 04211847, 04146567, 04147080, 04081800, - 04082057, 04016777, 04017033, 04017289, 03952010, 03952266, 03887242, 03887498, - 03822219, 03822475, 03757195, 03757451, 03692171, 03692428, 03627148, 03627404, - 03562124, 03562380, 03497100, 03497356, 03432077, 03432333, 03367053, 03367309, - 03302029, 03302285, 03237005, 03237261, 03237517, 03172237, 03172493, 03107213, - 03107469, 03042190, 03042446, 03042702, 02977422, 02977678, 02912398, 02912654, - 02912910, 02847630, 02847886, 02782606, 02782862, 02783118, 02717838, 02718094, - 02652814, 02652814, 02653070, 02587790, 02588046, 02588302, 02523022, 02523278, - 02523534, 02458254, 02458509, 02393229, 02393485, 02393741, 02328461, 02328717, - 02328973, 02263437, 02263693, 02263949, 02198669, 02198924, 02199180, 02133900, - 02134156, 02134412, 02069132, 02069387, 02069643, 02069899, 02070155, 02004874, - 02005130, 02005386, 02005386, 02005641, 02005897, 02006153, 02006408, 02006664, - 02006920, 02007175, 02072967, 02073222, 02073478, 02139269, 02139525, 02205317, - 02205572, 02271108, 02336899, 02337154, 02402946, 02468737, 02534529, 02600320, - 02666111, 02731903, 02797694, 02863485, 02929021, 03060348, 03126139, 03191930, - 03323258, 03389049, 03520376, 03586167, 03717494, 03783030, 03914357, 04045684, - 04111475, 04242802, 04374129, 04505200, 04570991, 04702318, 04833645, 04964972, - 05096043, 05227369, 05358696, 05490023, 05621350, 05752421, 05883748, 06015074, - 06211937, 06343008, 06474335, 06605661, 06802524, 06933595, 07064921, 07196248, - 07392854, 07524181, 07655508, 07852114, 07983441, 08180303, 08311374, 08508236, - 08639307, 08836169, 08967495, 09164102, 09295428, 09492035, 09623361, 09819967, - 09951294, 10147900, 10344762, 10475832, 10672695, 10869301, 11000627, 11197234, - 11394096, 11525166, 11722028, 11918635, 12049705, 12246567, 12443174, 12574500, - 12771106, 12967713, 13099039, 13295646, 13492253, 13623580, 13820187, 13951258, - 14148121, 14344728, 14475800, 14672664, 14803736, 15000344, 15197209, 15328281, - 15524890, 15656219, 15852828, 15983902, 16180767, 16311841, 16442914, 16639780, - }; - } -} diff --git a/src/Spectrogram/IColormap.cs b/src/Spectrogram/IColormap.cs deleted file mode 100644 index 5c9c79e..0000000 --- a/src/Spectrogram/IColormap.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Spectrogram -{ - public interface IColormap - { - (byte r, byte g, byte b) GetRGB(byte value); - } -} diff --git a/src/Spectrogram/Image.cs b/src/Spectrogram/Image.cs index 283543b..7413b38 100644 --- a/src/Spectrogram/Image.cs +++ b/src/Spectrogram/Image.cs @@ -1,16 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; +using SkiaSharp; namespace Spectrogram { public static class Image { - public static Bitmap GetBitmap(List ffts, Colormap cmap, double intensity = 1, + public static SKBitmap GetBitmap(List ffts, Colormap cmap, double intensity = 1, bool dB = false, double dBScale = 1, bool roll = false, int rollOffset = 0, bool rotate = false) { diff --git a/src/Spectrogram/ImageMaker.cs b/src/Spectrogram/ImageMaker.cs index dd4348f..c55ef99 100644 --- a/src/Spectrogram/ImageMaker.cs +++ b/src/Spectrogram/ImageMaker.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; using System.Runtime.InteropServices; -using System.Text; using System.Threading.Tasks; +using SkiaSharp; namespace Spectrogram { @@ -53,37 +51,35 @@ public ImageMaker() { } - - public Bitmap GetBitmap(List ffts) + + public SKBitmap GetBitmap(List ffts) { if (ffts.Count == 0) throw new ArgumentException("Not enough data in FFTs to generate an image yet."); - int Width = IsRotated ? ffts[0].Length : ffts.Count; - int Height = IsRotated ? ffts.Count : ffts[0].Length; - - Bitmap bmp = new(Width, Height, PixelFormat.Format8bppIndexed); - Colormap.Apply(bmp); + int width = IsRotated ? ffts[0].Length : ffts.Count; + int height = IsRotated ? ffts.Count : ffts[0].Length; - Rectangle lockRect = new(0, 0, Width, Height); - BitmapData bitmapData = bmp.LockBits(lockRect, ImageLockMode.ReadOnly, bmp.PixelFormat); - int stride = bitmapData.Stride; + var imageInfo = new SKImageInfo(width, height, SKColorType.Gray8); + var bitmap = new SKBitmap(imageInfo); + + int pixelCount = width * height; + byte[] pixelBuffer = new byte[pixelCount]; - byte[] bytes = new byte[bitmapData.Stride * bmp.Height]; - Parallel.For(0, Width, col => + Parallel.For(0, width, col => { int sourceCol = col; if (IsRoll) { - sourceCol += Width - RollOffset % Width; - if (sourceCol >= Width) - sourceCol -= Width; + sourceCol += width - RollOffset % width; + if (sourceCol >= width) + sourceCol -= width; } - for (int row = 0; row < Height; row++) + for (int row = 0; row < height; row++) { double value = IsRotated - ? ffts[Height - row - 1][sourceCol] + ? ffts[height - row - 1][sourceCol] : ffts[sourceCol][row]; if (IsDecibel) @@ -91,15 +87,18 @@ public Bitmap GetBitmap(List ffts) value *= Intensity; value = Math.Min(value, 255); - int bytePosition = (Height - 1 - row) * stride + col; - bytes[bytePosition] = (byte)value; + + int bytePosition = (height - 1 - row) * width + col; + pixelBuffer[bytePosition] = (byte)value; } }); - Marshal.Copy(bytes, 0, bitmapData.Scan0, bytes.Length); - bmp.UnlockBits(bitmapData); + IntPtr pixelPtr = bitmap.GetPixels(); + Marshal.Copy(pixelBuffer, 0, pixelPtr, pixelBuffer.Length); - return bmp; + SKBitmap newBitmap = Colormap.ApplyFilter(bitmap); + bitmap.Dispose(); + return newBitmap; } } } diff --git a/src/Spectrogram/Scale.cs b/src/Spectrogram/Scale.cs index 767b21b..e4a7788 100644 --- a/src/Spectrogram/Scale.cs +++ b/src/Spectrogram/Scale.cs @@ -1,60 +1,58 @@ -using System; +using SkiaSharp; using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; -using System.Linq; -using System.Text; -namespace Spectrogram +namespace Spectrogram; + +static class Scale { - static class Scale + public static SKBitmap Vertical(int width, Settings settings, int offsetHz = 0, int tickSize = 3, int reduction = 1) { - public static Bitmap Vertical(int width, Settings settings, int offsetHz = 0, int tickSize = 3, int reduction = 1) + double tickHz = 1; + int minSpacingPx = 50; + double[] multipliers = { 2, 2.5, 2 }; + int multiplier = 0; + + while (true) + { + tickHz *= multipliers[multiplier++ % multipliers.Length]; + double tickCount = settings.FreqSpan / tickHz; + double pxBetweenTicks = settings.Height / tickCount; + if (pxBetweenTicks >= minSpacingPx * reduction) + break; + } + + var imageInfo = new SKImageInfo(width, settings.Height / reduction, SKColorType.Rgba8888); + var bitmap = new SKBitmap(imageInfo); + using var canvas = new SKCanvas(bitmap); + canvas.Clear(SKColors.White); + + var paint = new SKPaint + { + Color = SKColors.Black, + TextSize = 10, + IsAntialias = true, + Typeface = SKTypeface.FromFamilyName("Monospace") + }; + + List freqs = new List(); + for (double f = settings.FreqMin; f <= settings.FreqMax; f += tickHz) + freqs.Add(f); + + if (freqs.Count >= 2) { - double tickHz = 1; - int minSpacingPx = 50; - double[] multipliers = { 2, 2.5, 2 }; - int multiplier = 0; - while (true) - { - tickHz *= multipliers[multiplier++ % multipliers.Length]; - double tickCount = settings.FreqSpan / tickHz; - double pxBetweenTicks = settings.Height / tickCount; - if (pxBetweenTicks >= minSpacingPx * reduction) - break; - } - - Bitmap bmp = new Bitmap(width, settings.Height / reduction, PixelFormat.Format32bppPArgb); - - using (var gfx = Graphics.FromImage(bmp)) - using (var pen = new Pen(Color.Black)) - using (var brush = new SolidBrush(Color.Black)) - using (var font = new Font(FontFamily.GenericMonospace, 10)) - using (var sf = new StringFormat() { LineAlignment = StringAlignment.Center }) - { - gfx.Clear(Color.White); - - List freqs = new List(); - - for (double f = settings.FreqMin; f <= settings.FreqMax; f += tickHz) - freqs.Add(f); - - // don't show first or last tick - if (freqs.Count >= 2) - { - freqs.RemoveAt(0); - freqs.RemoveAt(freqs.Count - 1); - } - - foreach (var freq in freqs) - { - int y = settings.PixelY(freq) / reduction; - gfx.DrawLine(pen, 0, y, tickSize, y); - gfx.DrawString($"{freq + offsetHz:N0} Hz", font, brush, tickSize, y, sf); - } - } - - return bmp; + freqs.RemoveAt(0); + freqs.RemoveAt(freqs.Count - 1); } + + foreach (var freq in freqs) + { + int y = settings.PixelY(freq) / reduction; + canvas.DrawLine(0, y, tickSize, y, paint); + + var text = $"{freq + offsetHz:N0} Hz"; + canvas.DrawText(text, tickSize + 2, y + 5, paint); + } + + return bitmap; } -} \ No newline at end of file +} diff --git a/src/Spectrogram/Settings.cs b/src/Spectrogram/Settings.cs index a799260..3ec35f1 100644 --- a/src/Spectrogram/Settings.cs +++ b/src/Spectrogram/Settings.cs @@ -31,7 +31,9 @@ class Settings public Settings(int sampleRate, int fftSize, int stepSize, double minFreq, double maxFreq, int offsetHz) { - if (FftSharp.Transform.IsPowerOfTwo(fftSize) == false) + static bool IsPowerOfTwo(int x) => ((x & (x - 1)) == 0) && (x > 0); + + if (IsPowerOfTwo(fftSize) == false) throw new ArgumentException("FFT size must be a power of 2"); // FFT info diff --git a/src/Spectrogram/Spectrogram.csproj b/src/Spectrogram/Spectrogram.csproj index 4804cff..4bfc42f 100644 --- a/src/Spectrogram/Spectrogram.csproj +++ b/src/Spectrogram/Spectrogram.csproj @@ -1,8 +1,7 @@  - - netstandard2.0;net6.0 - 1.6.1 + netstandard2.0 + 2.0.0-alpha A .NET Standard library for creating spectrograms Scott Harden Harden Technologies, LLC @@ -23,20 +22,13 @@ true latest - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - + + + - - + \ No newline at end of file diff --git a/src/Spectrogram/SpectrogramGenerator.cs b/src/Spectrogram/SpectrogramGenerator.cs index 68a9c3b..edb85b4 100644 --- a/src/Spectrogram/SpectrogramGenerator.cs +++ b/src/Spectrogram/SpectrogramGenerator.cs @@ -1,446 +1,408 @@ using System; using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; using System.IO; using System.Threading.Tasks; +using SkiaSharp; -namespace Spectrogram +namespace Spectrogram; + +public class SpectrogramGenerator { - public class SpectrogramGenerator + /// + /// Number of pixel columns (FFT samples) in the spectrogram image + /// + public int Width { get => FFTs.Count; } + + /// + /// Number of pixel rows (frequency bins) in the spectrogram image + /// + public int Height { get => Settings.Height; } + + /// + /// Number of samples to use for each FFT (must be a power of 2) + /// + public int FftSize { get => Settings.FftSize; } + + /// + /// Vertical resolution (frequency bin size depends on FftSize and SampleRate) + /// + public double HzPerPx { get => Settings.HzPerPixel; } + + /// + /// Horizontal resolution (seconds per pixel depends on StepSize) + /// + public double SecPerPx { get => Settings.StepLengthSec; } + + /// + /// Number of FFTs that remain to be processed for data which has been added but not yet analyzed + /// + public int FftsToProcess { get => (UnprocessedData.Count - Settings.FftSize) / Settings.StepSize; } + + /// + /// Total number of FFT steps processed + /// + public int FftsProcessed { get; private set; } + + /// + /// Index of the pixel column which will be populated next. Location of vertical line for wrap-around displays. + /// + public int NextColumnIndex { get => Width > 0 ? (FftsProcessed + rollOffset) % Width : 0; } + + /// + /// This value is added to displayed frequency axis tick labels + /// + public int OffsetHz { get => Settings.OffsetHz; set { Settings.OffsetHz = value; } } + + /// + /// Number of samples per second + /// + public int SampleRate { get => Settings.SampleRate; } + + /// + /// Number of samples to step forward after each FFT is processed. + /// This value controls the horizontal resolution of the spectrogram. + /// + public int StepSize { get => Settings.StepSize; } + + /// + /// The spectrogram is trimmed to cut-off frequencies below this value. + /// + public double FreqMax { get => Settings.FreqMax; } + + /// + /// The spectrogram is trimmed to cut-off frequencies above this value. + /// + public double FreqMin { get => Settings.FreqMin; } + + /// + /// This module contains detailed FFT/Spectrogram settings + /// + private readonly Settings Settings; + + /// + /// This is the list of FFTs which is translated to the spectrogram image when it is requested. + /// The length of this list is the spectrogram width. + /// The length of the arrays in this list is the spectrogram height. + /// + private readonly List FFTs = []; + + /// + /// This list contains data values which have not yet been processed. + /// Process() processes all unprocessed data. + /// This list may not be empty after processing if there aren't enough values to fill a full FFT (FftSize). + /// + private readonly List UnprocessedData; + + /// + /// Colormap to use when generating future FFTs. + /// + public Colormap Colormap = new(new ScottPlot.Colormaps.Viridis()); + + /// + /// Instantiate a spectrogram generator. + /// This module calculates the FFT over a moving window as data comes in. + /// Using the Add() method to load new data and process it as it arrives. + /// + /// Number of samples per second (Hz) + /// Number of samples to use for each FFT operation. This value must be a power of 2. + /// Number of samples to step forward + /// Frequency data lower than this value (Hz) will not be stored + /// Frequency data higher than this value (Hz) will not be stored + /// Spectrogram output will always be sized to this width (column count) + /// This value will be added to displayed frequency axis tick labels + /// Analyze this data immediately (alternative to calling Add() later) + public SpectrogramGenerator( + int sampleRate, + int fftSize, + int stepSize, + double minFreq = 0, + double maxFreq = double.PositiveInfinity, + int? fixedWidth = null, + int offsetHz = 0, + List initialAudioList = null) { - /// - /// Number of pixel columns (FFT samples) in the spectrogram image - /// - public int Width { get => FFTs.Count; } - - /// - /// Number of pixel rows (frequency bins) in the spectrogram image - /// - public int Height { get => Settings.Height; } - - /// - /// Number of samples to use for each FFT (must be a power of 2) - /// - public int FftSize { get => Settings.FftSize; } - - /// - /// Vertical resolution (frequency bin size depends on FftSize and SampleRate) - /// - public double HzPerPx { get => Settings.HzPerPixel; } - - /// - /// Horizontal resolution (seconds per pixel depends on StepSize) - /// - public double SecPerPx { get => Settings.StepLengthSec; } - - /// - /// Number of FFTs that remain to be processed for data which has been added but not yet analuyzed - /// - public int FftsToProcess { get => (UnprocessedData.Count - Settings.FftSize) / Settings.StepSize; } - - /// - /// Total number of FFT steps processed - /// - public int FftsProcessed { get; private set; } - - /// - /// Index of the pixel column which will be populated next. Location of vertical line for wrap-around displays. - /// - public int NextColumnIndex { get => Width > 0 ? (FftsProcessed + rollOffset) % Width : 0; } - - /// - /// This value is added to displayed frequency axis tick labels - /// - public int OffsetHz { get => Settings.OffsetHz; set { Settings.OffsetHz = value; } } - - /// - /// Number of samples per second - /// - public int SampleRate { get => Settings.SampleRate; } - - /// - /// Number of samples to step forward after each FFT is processed. - /// This value controls the horizontal resolution of the spectrogram. - /// - public int StepSize { get => Settings.StepSize; } - - /// - /// The spectrogram is trimmed to cut-off frequencies below this value. - /// - public double FreqMax { get => Settings.FreqMax; } - - /// - /// The spectrogram is trimmed to cut-off frequencies above this value. - /// - public double FreqMin { get => Settings.FreqMin; } - - /// - /// This module contains detailed FFT/Spectrogram settings - /// - private readonly Settings Settings; - - /// - /// This is the list of FFTs which is translated to the spectrogram image when it is requested. - /// The length of this list is the spectrogram width. - /// The length of the arrays in this list is the spectrogram height. - /// - private readonly List FFTs = new List(); - - /// - /// This list contains data values which have not yet been processed. - /// Process() processes all unprocessed data. - /// This list may not be empty after processing if there aren't enough values to fill a full FFT (FftSize). - /// - private readonly List UnprocessedData; - - /// - /// Colormap to use when generating future FFTs. - /// - public Colormap Colormap = Colormap.Viridis; - - /// - /// Instantiate a spectrogram generator. - /// This module calculates the FFT over a moving window as data comes in. - /// Using the Add() method to load new data and process it as it arrives. - /// - /// Number of samples per second (Hz) - /// Number of samples to use for each FFT operation. This value must be a power of 2. - /// Number of samples to step forward - /// Frequency data lower than this value (Hz) will not be stored - /// Frequency data higher than this value (Hz) will not be stored - /// Spectrogram output will always be sized to this width (column count) - /// This value will be added to displayed frequency axis tick labels - /// Analyze this data immediately (alternative to calling Add() later) - public SpectrogramGenerator( - int sampleRate, - int fftSize, - int stepSize, - double minFreq = 0, - double maxFreq = double.PositiveInfinity, - int? fixedWidth = null, - int offsetHz = 0, - List initialAudioList = null) - { - Settings = new Settings(sampleRate, fftSize, stepSize, minFreq, maxFreq, offsetHz); - - UnprocessedData = initialAudioList ?? new List(); - - if (fixedWidth.HasValue) - SetFixedWidth(fixedWidth.Value); - } + Settings = new Settings(sampleRate, fftSize, stepSize, minFreq, maxFreq, offsetHz); - public override string ToString() - { - double processedSamples = FFTs.Count * Settings.StepSize + Settings.FftSize; - double processedSec = processedSamples / Settings.SampleRate; - string processedTime = (processedSec < 60) ? $"{processedSec:N2} sec" : $"{processedSec / 60.0:N2} min"; - - return $"Spectrogram ({Width}, {Height})" + - $"\n Vertical ({Height} px): " + - $"{Settings.FreqMin:N0} - {Settings.FreqMax:N0} Hz, " + - $"FFT size: {Settings.FftSize:N0} samples, " + - $"{Settings.HzPerPixel:N2} Hz/px" + - $"\n Horizontal ({Width} px): " + - $"{processedTime}, " + - $"window: {Settings.FftLengthSec:N2} sec, " + - $"step: {Settings.StepLengthSec:N2} sec, " + - $"overlap: {Settings.StepOverlapFrac * 100:N0}%"; - } - - [Obsolete("Assign to the Colormap field")] - /// - /// Set the colormap to use for future renders - /// - public void SetColormap(Colormap cmap) - { - Colormap = cmap ?? this.Colormap; - } + UnprocessedData = initialAudioList ?? new List(); - /// - /// Load a custom window kernel to multiply against each FFT sample prior to processing. - /// Windows must be at least the length of FftSize and typically have a sum of 1.0. - /// - public void SetWindow(double[] newWindow) - { - if (newWindow.Length > Settings.FftSize) - throw new ArgumentException("window length cannot exceed FFT size"); + if (fixedWidth.HasValue) + SetFixedWidth(fixedWidth.Value); + } - for (int i = 0; i < Settings.FftSize; i++) - Settings.Window[i] = 0; + /// + /// Load a custom window kernel to multiply against each FFT sample prior to processing. + /// Windows must be at least the length of FftSize and typically have a sum of 1.0. + /// + public void SetWindow(double[] newWindow) + { + if (newWindow.Length > Settings.FftSize) + throw new ArgumentException("window length cannot exceed FFT size"); - int offset = (Settings.FftSize - newWindow.Length) / 2; - Array.Copy(newWindow, 0, Settings.Window, offset, newWindow.Length); - } + for (int i = 0; i < Settings.FftSize; i++) + Settings.Window[i] = 0; - [Obsolete("use the Add() method", true)] - public void AddExtend(float[] values) { } + int offset = (Settings.FftSize - newWindow.Length) / 2; + Array.Copy(newWindow, 0, Settings.Window, offset, newWindow.Length); + } - [Obsolete("use the Add() method", true)] - public void AddCircular(float[] values) { } + /// + /// Load new data into the spectrogram generator + /// + public void Add(IEnumerable audio, bool process = true) + { + UnprocessedData.AddRange(audio); + if (process) + Process(); + } - [Obsolete("use the Add() method", true)] - public void AddScroll(float[] values) { } + /// + /// The roll offset is used to calculate NextColumnIndex and can be set to a positive number + /// to begin adding new columns to the center of the spectrogram. + /// This can also be used to artificially move the next column index to zero even though some + /// data has already been accumulated. + /// + private int rollOffset = 0; + + /// + /// Reset the next column index such that the next processed FFT will appear at the far left of the spectrogram. + /// + /// + public void RollReset(int offset = 0) + { + rollOffset = -FftsProcessed + offset; + } - /// - /// Load new data into the spectrogram generator - /// - public void Add(IEnumerable audio, bool process = true) - { - UnprocessedData.AddRange(audio); - if (process) - Process(); - } + /// + /// Perform FFT analysis on all unprocessed data + /// + public double[][] Process() + { + if (FftsToProcess < 1) + return null; - /// - /// The roll offset is used to calculate NextColumnIndex and can be set to a positive number - /// to begin adding new columns to the center of the spectrogram. - /// This can also be used to artificially move the next column index to zero even though some - /// data has already been accumulated. - /// - private int rollOffset = 0; - - /// - /// Reset the next column index such that the next processed FFT will appear at the far left of the spectrogram. - /// - /// - public void RollReset(int offset = 0) - { - rollOffset = -FftsProcessed + offset; - } + int newFftCount = FftsToProcess; + double[][] newFfts = new double[newFftCount][]; - /// - /// Perform FFT analysis on all unprocessed data - /// - public double[][] Process() + Parallel.For(0, newFftCount, newFftIndex => { - if (FftsToProcess < 1) - return null; - - int newFftCount = FftsToProcess; - double[][] newFfts = new double[newFftCount][]; - - Parallel.For(0, newFftCount, newFftIndex => - { - FftSharp.Complex[] buffer = new FftSharp.Complex[Settings.FftSize]; - int sourceIndex = newFftIndex * Settings.StepSize; - for (int i = 0; i < Settings.FftSize; i++) - buffer[i].Real = UnprocessedData[sourceIndex + i] * Settings.Window[i]; + var buffer = new System.Numerics.Complex[Settings.FftSize]; + int sourceIndex = newFftIndex * Settings.StepSize; + for (int i = 0; i < Settings.FftSize; i++) + buffer[i] = new(UnprocessedData[sourceIndex + i] * Settings.Window[i], 0); - FftSharp.Transform.FFT(buffer); + FftSharp.FFT.Forward(buffer); - newFfts[newFftIndex] = new double[Settings.Height]; - for (int i = 0; i < Settings.Height; i++) - newFfts[newFftIndex][i] = buffer[Settings.FftIndex1 + i].Magnitude / Settings.FftSize; - }); + newFfts[newFftIndex] = new double[Settings.Height]; + for (int i = 0; i < Settings.Height; i++) + newFfts[newFftIndex][i] = buffer[Settings.FftIndex1 + i].Magnitude / Settings.FftSize; + }); - foreach (var newFft in newFfts) - FFTs.Add(newFft); - FftsProcessed += newFfts.Length; + foreach (var newFft in newFfts) + FFTs.Add(newFft); + FftsProcessed += newFfts.Length; - UnprocessedData.RemoveRange(0, newFftCount * Settings.StepSize); - PadOrTrimForFixedWidth(); + UnprocessedData.RemoveRange(0, newFftCount * Settings.StepSize); + PadOrTrimForFixedWidth(); - return newFfts; - } + return newFfts; + } - /// - /// Return a list of the mel-scaled FFTs contained in this spectrogram - /// - /// Total number of output bins to use. Choose a value significantly smaller than Height. - public List GetMelFFTs(int melBinCount) - { - if (Settings.FreqMin != 0) - throw new InvalidOperationException("cannot get Mel spectrogram unless minimum frequency is 0Hz"); + /// + /// Return a list of the mel-scaled FFTs contained in this spectrogram + /// + /// Total number of output bins to use. Choose a value significantly smaller than Height. + public List GetMelFFTs(int melBinCount) + { + if (Settings.FreqMin != 0) + throw new InvalidOperationException("cannot get Mel spectrogram unless minimum frequency is 0Hz"); - var fftsMel = new List(); - foreach (var fft in FFTs) - fftsMel.Add(FftSharp.Transform.MelScale(fft, SampleRate, melBinCount)); + var fftsMel = new List(); + foreach (var fft in FFTs) + fftsMel.Add(FftSharp.Mel.Scale(fft, SampleRate, melBinCount)); - return fftsMel; - } + return fftsMel; + } - /// - /// Create and return a spectrogram bitmap from the FFTs stored in memory. - /// - /// Multiply the output by a fixed value to change its brightness. - /// If true, output will be log-transformed. - /// If dB scaling is in use, this multiplier will be applied before log transformation. - /// Behavior of the spectrogram when it is full of data. - /// If True, the image will be rotated so time flows from top to bottom (rather than left to right). - /// Roll (true) adds new columns on the left overwriting the oldest ones. - /// Scroll (false) slides the whole image to the left and adds new columns to the right. - public Bitmap GetBitmap(double intensity = 1, bool dB = false, double dBScale = 1, bool roll = false, bool rotate = false) - { - if (FFTs.Count == 0) - throw new InvalidOperationException("Not enough data to create an image. " + - $"Ensure {nameof(Width)} is >0 before calling {nameof(GetBitmap)}()."); + /// + /// Create and return a spectrogram bitmap from the FFTs stored in memory. + /// + /// Multiply the output by a fixed value to change its brightness. + /// If true, output will be log-transformed. + /// If dB scaling is in use, this multiplier will be applied before log transformation. + /// Behavior of the spectrogram when it is full of data. + /// If True, the image will be rotated so time flows from top to bottom (rather than left to right). + /// Roll (true) adds new columns on the left overwriting the oldest ones. + /// Scroll (false) slides the whole image to the left and adds new columns to the right. + public SKBitmap GetBitmap(double intensity = 1, bool dB = false, double dBScale = 1, bool roll = false, bool rotate = false) + { + if (FFTs.Count == 0) + throw new InvalidOperationException("Not enough data to create an image. " + + $"Ensure {nameof(Width)} is >0 before calling {nameof(GetBitmap)}()."); - return Image.GetBitmap(FFTs, Colormap, intensity, dB, dBScale, roll, NextColumnIndex, rotate); - } + return Image.GetBitmap(FFTs, Colormap, intensity, dB, dBScale, roll, NextColumnIndex, rotate); + } - /// - /// Create a Mel-scaled spectrogram. - /// - /// Total number of output bins to use. Choose a value significantly smaller than Height. - /// Multiply the output by a fixed value to change its brightness. - /// If true, output will be log-transformed. - /// If dB scaling is in use, this multiplier will be applied before log transformation. - /// Behavior of the spectrogram when it is full of data. - /// Roll (true) adds new columns on the left overwriting the oldest ones. - /// Scroll (false) slides the whole image to the left and adds new columns to the right. - public Bitmap GetBitmapMel(int melBinCount = 25, double intensity = 1, bool dB = false, double dBScale = 1, bool roll = false) => - Image.GetBitmap(GetMelFFTs(melBinCount), Colormap, intensity, dB, dBScale, roll, NextColumnIndex); - - [Obsolete("use SaveImage()", true)] - public void SaveBitmap(Bitmap bmp, string fileName) { } - - /// - /// Generate the spectrogram and save it as an image file. - /// - /// Path of the file to save. - /// Multiply the output by a fixed value to change its brightness. - /// If true, output will be log-transformed. - /// If dB scaling is in use, this multiplier will be applied before log transformation. - /// Behavior of the spectrogram when it is full of data. - /// Roll (true) adds new columns on the left overwriting the oldest ones. - /// Scroll (false) slides the whole image to the left and adds new columns to the right. - public void SaveImage(string fileName, double intensity = 1, bool dB = false, double dBScale = 1, bool roll = false) - { - if (FFTs.Count == 0) - throw new InvalidOperationException("Spectrogram contains no data. Use Add() to add signal data."); - - string extension = Path.GetExtension(fileName).ToLower(); - - ImageFormat fmt; - if (extension == ".bmp") - fmt = ImageFormat.Bmp; - else if (extension == ".png") - fmt = ImageFormat.Png; - else if (extension == ".jpg" || extension == ".jpeg") - fmt = ImageFormat.Jpeg; - else if (extension == ".gif") - fmt = ImageFormat.Gif; - else - throw new ArgumentException("unknown file extension"); - - Image.GetBitmap(FFTs, Colormap, intensity, dB, dBScale, roll, NextColumnIndex).Save(fileName, fmt); - } + /// + /// Create a Mel-scaled spectrogram. + /// + /// Total number of output bins to use. Choose a value significantly smaller than Height. + /// Multiply the output by a fixed value to change its brightness. + /// If true, output will be log-transformed. + /// If dB scaling is in use, this multiplier will be applied before log transformation. + /// Behavior of the spectrogram when it is full of data. + /// Roll (true) adds new columns on the left overwriting the oldest ones. + /// Scroll (false) slides the whole image to the left and adds new columns to the right. + public SKBitmap GetBitmapMel(int melBinCount = 25, double intensity = 1, bool dB = false, double dBScale = 1, bool roll = false) => + Image.GetBitmap(GetMelFFTs(melBinCount), Colormap, intensity, dB, dBScale, roll, NextColumnIndex); + + /// + /// Generate the spectrogram and save it as an image file. + /// + /// Path of the file to save. + /// Multiply the output by a fixed value to change its brightness. + /// If true, output will be log-transformed. + /// If dB scaling is in use, this multiplier will be applied before log transformation. + /// Behavior of the spectrogram when it is full of data. + /// Roll (true) adds new columns on the left overwriting the oldest ones. + /// Scroll (false) slides the whole image to the left and adds new columns to the right. + public void SaveImage(string fileName, double intensity = 1, bool dB = false, double dBScale = 1, bool roll = false) + { + if (FFTs.Count == 0) + throw new InvalidOperationException("Spectrogram contains no data. Use Add() to add signal data."); + + string extension = Path.GetExtension(fileName).ToLower(); + + SKEncodedImageFormat fmt; + if (extension == ".bmp") + fmt = SKEncodedImageFormat.Bmp; + else if (extension == ".png") + fmt = SKEncodedImageFormat.Png; + else if (extension == ".jpg" || extension == ".jpeg") + fmt = SKEncodedImageFormat.Jpeg; + else if (extension == ".gif") + fmt = SKEncodedImageFormat.Gif; + else + throw new ArgumentException("unknown file extension"); + + using var image = Image.GetBitmap(FFTs, Colormap, intensity, dB, dBScale, roll, NextColumnIndex); + using var encodedImage = image.Encode(fmt, 80); + using var fileStream = new FileStream(fileName, FileMode.Create); + encodedImage.SaveTo(fileStream); + } - /// - /// Create and return a spectrogram bitmap from the FFTs stored in memory. - /// The output will be scaled-down vertically by binning according to a reduction factor and keeping the brightest pixel value in each bin. - /// - /// Multiply the output by a fixed value to change its brightness. - /// If true, output will be log-transformed. - /// If dB scaling is in use, this multiplier will be applied before log transformation. - /// Behavior of the spectrogram when it is full of data. - /// - public Bitmap GetBitmapMax(double intensity = 1, bool dB = false, double dBScale = 1, bool roll = false, int reduction = 4) + /// + /// Create and return a spectrogram bitmap from the FFTs stored in memory. + /// The output will be scaled-down vertically by binning according to a reduction factor and keeping the brightest pixel value in each bin. + /// + /// Multiply the output by a fixed value to change its brightness. + /// If true, output will be log-transformed. + /// If dB scaling is in use, this multiplier will be applied before log transformation. + /// Behavior of the spectrogram when it is full of data. + /// + public SKBitmap GetBitmapMax(double intensity = 1, bool dB = false, double dBScale = 1, bool roll = false, int reduction = 4) + { + List ffts2 = new List(); + for (int i = 0; i < FFTs.Count; i++) { - List ffts2 = new List(); - for (int i = 0; i < FFTs.Count; i++) - { - double[] d1 = FFTs[i]; - double[] d2 = new double[d1.Length / reduction]; - for (int j = 0; j < d2.Length; j++) - for (int k = 0; k < reduction; k++) - d2[j] = Math.Max(d2[j], d1[j * reduction + k]); - ffts2.Add(d2); - } - return Image.GetBitmap(ffts2, Colormap, intensity, dB, dBScale, roll, NextColumnIndex); + double[] d1 = FFTs[i]; + double[] d2 = new double[d1.Length / reduction]; + for (int j = 0; j < d2.Length; j++) + for (int k = 0; k < reduction; k++) + d2[j] = Math.Max(d2[j], d1[j * reduction + k]); + ffts2.Add(d2); } + return Image.GetBitmap(ffts2, Colormap, intensity, dB, dBScale, roll, NextColumnIndex); + } - /// - /// Defines the total number of FFTs (spectrogram columns) to store in memory. Determines Width. - /// - private int fixedWidth = 0; + /// + /// Defines the total number of FFTs (spectrogram columns) to store in memory. Determines Width. + /// + private int fixedWidth = 0; - /// - /// Configure the Spectrogram to maintain a fixed number of pixel columns. - /// Zeros will be added to padd existing data to achieve this width, and extra columns will be deleted. - /// - public void SetFixedWidth(int width) - { - fixedWidth = width; - PadOrTrimForFixedWidth(); - } + /// + /// Configure the Spectrogram to maintain a fixed number of pixel columns. + /// Zeros will be added to pad existing data to achieve this width, and extra columns will be deleted. + /// + public void SetFixedWidth(int width) + { + fixedWidth = width; + PadOrTrimForFixedWidth(); + } - private void PadOrTrimForFixedWidth() + private void PadOrTrimForFixedWidth() + { + if (fixedWidth > 0) { - if (fixedWidth > 0) - { - int overhang = Width - fixedWidth; - if (overhang > 0) - FFTs.RemoveRange(0, overhang); + int overhang = Width - fixedWidth; + if (overhang > 0) + FFTs.RemoveRange(0, overhang); - while (FFTs.Count < fixedWidth) - FFTs.Insert(0, new double[Height]); - } + while (FFTs.Count < fixedWidth) + FFTs.Insert(0, new double[Height]); } + } - /// - /// Get a vertical image containing ticks and tick labels for the frequency axis. - /// - /// size (pixels) - /// number to add to each tick label - /// length of each tick mark (pixels) - /// bin size for vertical data reduction - public Bitmap GetVerticalScale(int width, int offsetHz = 0, int tickSize = 3, int reduction = 1) - { - return Scale.Vertical(width, Settings, offsetHz, tickSize, reduction); - } + /// + /// Get a vertical image containing ticks and tick labels for the frequency axis. + /// + /// size (pixels) + /// number to add to each tick label + /// length of each tick mark (pixels) + /// bin size for vertical data reduction + public SKBitmap GetVerticalScale(int width, int offsetHz = 0, int tickSize = 3, int reduction = 1) + { + return Scale.Vertical(width, Settings, offsetHz, tickSize, reduction); + } - /// - /// Return the vertical position (pixel units) for the given frequency - /// - public int PixelY(double frequency, int reduction = 1) - { - int pixelsFromZeroHz = (int)(Settings.PxPerHz * frequency / reduction); - int pixelsFromMinFreq = pixelsFromZeroHz - Settings.FftIndex1 / reduction + 1; - int pixelRow = Settings.Height / reduction - 1 - pixelsFromMinFreq; - return pixelRow - 1; - } + /// + /// Return the vertical position (pixel units) for the given frequency + /// + public int PixelY(double frequency, int reduction = 1) + { + int pixelsFromZeroHz = (int)(Settings.PxPerHz * frequency / reduction); + int pixelsFromMinFreq = pixelsFromZeroHz - Settings.FftIndex1 / reduction + 1; + int pixelRow = Settings.Height / reduction - 1 - pixelsFromMinFreq; + return pixelRow - 1; + } - /// - /// Return the list of FFTs in memory underlying the spectrogram. - /// This list may continue to evolve after it is returned. - /// - public List GetFFTs() - { - return FFTs; - } + /// + /// Return the list of FFTs in memory underlying the spectrogram. + /// This list may continue to evolve after it is returned. + /// + public List GetFFTs() + { + return FFTs; + } - /// - /// Return frequency and magnitude of the dominant frequency. - /// - /// If true, only the latest FFT will be assessed. - public (double freqHz, double magRms) GetPeak(bool latestFft = true) - { - if (FFTs.Count == 0) - return (double.NaN, double.NaN); + /// + /// Return frequency and magnitude of the dominant frequency. + /// + /// If true, only the latest FFT will be assessed. + public (double freqHz, double magRms) GetPeak(bool latestFft = true) + { + if (FFTs.Count == 0) + return (double.NaN, double.NaN); - if (latestFft == false) - throw new NotImplementedException("peak of mean of all FFTs not yet supported"); + if (latestFft == false) + throw new NotImplementedException("peak of mean of all FFTs not yet supported"); - double[] freqs = FFTs[FFTs.Count - 1]; + double[] freqs = FFTs[FFTs.Count - 1]; - int peakIndex = 0; - double peakMagnitude = 0; - for (int i = 0; i < freqs.Length; i++) + int peakIndex = 0; + double peakMagnitude = 0; + for (int i = 0; i < freqs.Length; i++) + { + if (freqs[i] > peakMagnitude) { - if (freqs[i] > peakMagnitude) - { - peakMagnitude = freqs[i]; - peakIndex = i; - } + peakMagnitude = freqs[i]; + peakIndex = i; } + } - double maxFreq = SampleRate / 2; - double peakFreqFrac = peakIndex / (double)freqs.Length; - double peakFreqHz = maxFreq * peakFreqFrac; + double maxFreq = SampleRate / 2; + double peakFreqFrac = peakIndex / (double)freqs.Length; + double peakFreqHz = maxFreq * peakFreqFrac; - return (peakFreqHz, peakMagnitude); - } + return (peakFreqHz, peakMagnitude); } }