diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..8a2d2bd --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,40 @@ +name: CI/CD + +on: + workflow_dispatch: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + release: + types: + - created + +jobs: + build: + name: Build, Test, and Deploy + runs-on: windows-latest + steps: + - name: 🛒 Checkout + uses: actions/checkout@v4 + - name: ✨ Setup .NET 8 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "8.0.x" + - name: 🚚 Restore + run: dotnet restore src + - name: 🛠️ Build + run: dotnet build src --configuration Release + - name: 🧪 Test + run: dotnet test src --configuration Release + - name: 📦 Pack + 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 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/.gitignore b/.gitignore index 3e759b7..a2796a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.vscode + ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## @@ -179,6 +181,7 @@ PublishScripts/ # NuGet Packages *.nupkg +*.snupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. @@ -297,6 +300,7 @@ paket-files/ # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc +.ropeproject/ # Cake - Uncomment if you are using it # tools/** diff --git a/README.md b/README.md index 2157a38..aee7e88 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,175 @@ # Spectrogram -**Spectrogram** is a .NET library which makes it easy to create spectrograms from pre-recorded signals or live audio from the sound card. This library supports .NET Framework (4.5) and .NET Core (3.0) and can be installed using [NuGet](https://www.nuget.org/packages/Spectrogram/). -![](data/mozart.jpg) +[![CI](https://github.com/swharden/Spectrogram/actions/workflows/ci.yaml/badge.svg)](https://github.com/swharden/Spectrogram/actions/workflows/ci.yaml) +[![Nuget](https://img.shields.io/nuget/v/Spectrogram?label=NuGet&logo=nuget)](https://www.nuget.org/packages/Spectrogram/) + +**Spectrogram** is a .NET library for creating spectrograms from pre-recorded signals or live audio from the sound card. Spectrogram uses FFT algorithms and window functions provided by the [FftSharp](https://github.com/swharden/FftSharp) project, and it targets .NET Standard so it can be used in .NET Framework and .NET Core projects. + +
+ +![](dev/graphics/hal-spectrogram.png) + +_"I'm sorry Dave... I'm afraid I can't do that"_ + +
+ ## Quickstart -### Song to Spectrogram -The code below converts a WAV file to a spectrograph and saves it as an image. This code analyzed [Mozart's Piano Sonata No. 11 in A major](https://www.youtube.com/watch?v=aeEmGvm7kDk) to produce the picture above. +* This code generates the spectrogram above. + +* Source code for the WAV reading method is at the bottom of this page. ```cs -// load audio and process FFT -var spec = new Spectrogram.Spectrogram(sampleRate: 8000, fftSize: 2048, step: 700); -float[] values = Spectrogram.Tools.ReadWav("mozart.wav"); -spec.AddExtend(values); - -// convert FFT to an image and save it -Bitmap bmp = spec.GetBitmap(intensity: 2, freqHigh: 2500); -spec.SaveBitmap(bmp, "mozart.jpg"); +(double[] audio, int sampleRate) = ReadMono("hal.wav"); +var sg = new SpectrogramGenerator(sampleRate, fftSize: 4096, stepSize: 500, maxFreq: 3000); +sg.Add(audio); +sg.SaveImage("hal.png"); ``` -### Human Voice -This code analyzes audio from HAL's famous quote, "I'm sorry Dave, I'm afraid I can't do that". The output is can be rendered using different colormaps. + +## Windows Forms + +If you're using Spectrogram in a graphical application you may find it helpful to retrieve the output as a Bitmap which can be displayed on a Picturebox: ```cs -// load audio and process FFT -var spec = new Spectrogram.Spectrogram(sampleRate: 15000, fftSize: 4096, step: 400); -float[] values = Spectrogram.Tools.ReadMp3("cant-do-that.mp3"); -spec.AddExtend(values); - -// convert FFT to an image and save it -Bitmap bmp = spec.GetBitmap(intensity: .2, freqHigh: 1000, - colormap: Spectrogram.Colormap.grayscaleInverted); -spec.SaveBitmap(bmp, "cant-do-that-grayscale-inverted.jpg"); +pictureBox1.Image = sg.GetBitmap(); ``` -colormap | sample output ----|--- -**Grayscale Inverted** is used in many scientific publications when analyzing things like human voices and bird sounds|![](/data/cant-do-that-grayscale-inverted.jpg) -**Grayscale** provides highest contrast output but does not benefit from color vision|![](/data/cant-do-that-grayscale.jpg) -**Viridis** is the default colormap. It was specifically designed to represent 2D data in a way [ideally suited for human vision](https://www.youtube.com/watch?v=xAoljeRJ3lU).|![](/data/cant-do-that.jpg) -**vdGreen** is the default colormap used for [QRSS-VD](https://github.com/swharden/QRSS-VD), a very old software project of mine. |![](/data/cant-do-that-green.jpg) -### QRSS Analysis +I find it helpful to put the Picturebox inside a Panel with auto-scroll enabled, so large spectrograms which are bigger than the size of the window can be interactively displayed. + +## Real-Time Spectrogram + +An example program is included in this repository which demonstrates how to use NAudio to get samples from the sound card and display them as a spectrogram. Spectrogram was designed to be able to display spectrograms with live or growing data, so this is exceptionally easy to implement. -Experimenters with ultra-narrowband radio transmissions often use continuous wave frequency-shifting radio transmitters to send data at very low rates over very long distances using very little power. See [_What is QRSS?_](https://www.qsl.net/m0ayf/What-is-QRSS.html) for more information. +* **Click-to-run demo** for 64-bit Windows: [SpectrogramDemo.exe](dev/SpectrogramDemo.zip) -The following code produces a QRSS spectrogram from an MP3 file. This program took less than 5 seconds to analyze 30 minutes of audio, producing the image below. +![](dev/microphone-spectrogram.gif) +To do this, keep your Spectrogram at the class level: ```cs -// load audio and process FFT -var spec = new Spectrogram.Spectrogram(sampleRate: 8000, fftSize: 16384, step: 8000); -float[] values = Spectrogram.Tools.ReadMp3("qrss-w4hbk.mp3"); -spec.AddExtend(values); - -// convert FFT to an image and save it -Bitmap bmp = spec.GetBitmap(intensity: 1.5, freqLow: 1100, freqHigh: 1500, - showTicks: true, tickSpacingHz: 50, tickSpacingSec: 60); -spec.SaveBitmap(bmp, "qrss.png"); +SpectrogramGenerator sg; + +public Form1() +{ + InitializeComponent(); + sg = new SpectrogramGenerator(sampleRate, fftSize: 4096, stepSize: 500, maxFreq: 3000); +} +``` + +Whenever an audio buffer gets filled, add the data to your Spectrogram: +```cs +private void GotNewBuffer(double[] audio) +{ + sg.Add(audio); +} +``` + +Then set up a timer to trigger rendering: +```cs +private void timer1_Tick(object sender, EventArgs e){ + Bitmap bmp = sg.GetBitmap(intensity: .4); + pictureBox1.Image?.Dispose(); + pictureBox1.Image = bmp; +} ``` +Review the source code of the demo application for additional details and considerations. You'll found I abstracted the audio interfacing code into its own class, isolating it from the GUI code. + +## Song-to-Spectrogram -![](data/qrss.png) +This example demonstrates how to convert a MP3 file to a spectrogram image. A sample MP3 audio file in the [data folder](data) contains the audio track from Ken Barker's excellent piano performance of George Frideric Handel's Suite No. 5 in E major for harpsichord ([_The Harmonious Blacksmith_](https://en.wikipedia.org/wiki/The_Harmonious_Blacksmith)). This audio file is included [with permission](dev/Handel%20-%20Air%20and%20Variations.txt), and the [original video can be viewed on YouTube](https://www.youtube.com/watch?v=Mza-xqk770k). -## Demo Applications -This project comes with a few interactive applications which serve as useful references for some of the ways this Spectrogram library can be used. +```cs +(double[] audio, int sampleRate) = ReadMono("song.wav"); -### Download Demo EXE Files -If you want to see what this library can do without downloading source code, click-to-run (EXE) demos are available in **[SpectrogramDemo.zip](https://github.com/swharden/Spectrogram/raw/master/dev/compiled-demos/SpectrogramDemo.zip)** +int fftSize = 16384; +int targetWidthPx = 3000; +int stepSize = audio.Length / targetWidthPx; -### Audio Monitor +var sg = new SpectrogramGenerator(sampleRate, fftSize, stepSize, maxFreq: 2200); +sg.Add(audio); +sg.SaveImage("song.png", intensity: 5, dB: true); +``` -A demo program is included which monitors the sound card and continuously creates spectrograms from microphone input. It runs fast enough that the entire bitmap can be recreated on each render. This means brightness and color adjustments can be applied to the whole image, not just new parts. +Notice the optional conversion to Decibels while saving the image. -![](data/screenshot4.gif) +![](dev/graphics/spectrogram-song.png) -### Waterfall with Graphs -This demo program was created to demonstrate Spectrogram and ScottPlot working together. +If you [listen to the audio track](https://www.youtube.com/watch?v=Mza-xqk770k) while closely inspecting the spectrogram you can identify individual piano notes and chords, and may be surprised by the interesting patterns that emerge around trills and glissandos. -![](data/screenshot7.gif) +## Spectrogram Information -## Resources +The Spectrogram's `ToString()` method displays detailed information about the spectrogram: -### Similar Software -* Argo ([website](http://digilander.libero.it/i2phd/argo/)) - closed-source QRSS viewer for Windows -* SpectrumLab ([website](http://www.qsl.net/dl4yhf/spectra1.html)) - closed-source spectrum analyzer for Windows -* QrssPIG ([GitLab](https://gitlab.com/hb9fxx/qrsspig)) - open-source spectrograph for Raspberry Pi (C++) -* Lopora ([GitHub](https://github.com/swharden/Lopora)) - open-source spectrograph (Python 3) -* QRSS VD ([GitHub](https://github.com/swharden/QRSS-VD)) - open source spectrograph (Python 2) +```cs +Console.WriteLine(sg); +``` -### QRSS Information - * [What is QRSS?](https://www.qsl.net/m0ayf/What-is-QRSS.html) - * [QRSS and you](http://www.ka7oei.com/qrss1.html) - * [QRSS (slow CW)](https://sites.google.com/site/qrssinfo/QRSS-Slow-CW) +``` +Spectrogram (2993, 817) + Vertical (817 px): 0 - 2,199 Hz, FFT size: 16,384 samples, 2.69 Hz/px + Horizontal (2993 px): 2.96 min, window: 0.37 sec, step: 0.06 sec, overlap: 84% +``` + +## Colormaps + +These examples demonstrate the identical spectrogram analyzed with a variety of different colormaps. Spectrogram colormaps can be changed by calling the `SetColormap()` method: + +```cs +(double[] audio, int sampleRate) = ReadMono("hal.wav"); +var sg = new SpectrogramGenerator(sampleRate, fftSize: 8192, stepSize: 200, maxFreq: 3000); +sg.Add(audio); +sg.SetColormap(Colormap.Jet); +sg.SaveImage($"jet.png"); +``` + +Viridis | Greens | Blues | Grayscale | GrayscaleR +---|---|---|---|--- +![](dev/graphics/hal-Viridis.png)|![](dev/graphics/hal-Greens.png)|![](dev/graphics/hal-Blues.png)|![](dev/graphics/hal-Grayscale.png)|![](dev/graphics/hal-GrayscaleR.png) + +## Mel Spectrogram + +Analytical spectrograms aimed at achieving maximum frequency resolution are presented using linear scaling, where every row of pixels is evenly spaced in the frequency domain. However, biological sensory systems tend to be logarithmic, and the human ear can differentiate frequency shifts better at lower frequencies than at higher ones. + +**To visualize frequency in a way that mimics human perception** we create a spectrogram that represents lower frequencies using a large portion of the image, and condense higher frequency ranges into smaller rows of pixels toward the top of the image. The [Mel Scale](https://en.wikipedia.org/wiki/Mel_scale) is commonly used to represent power spectral density this way, and the resulting _Mel Spectrogram_ has greatly reduced vertical resolution but is a better representation of human frequency perception. + +Cropped Linear Scale (0-3kHz) | Mel Scale (0-22 kHz) +---|--- +![](dev/graphics/halMel-LinearCropped.png)|![](dev/graphics/halMel-MelScale.png) + +Amplitude perception in humans, like frequency perception, is logarithmic. Therefore, Mel spectrograms typically display log-transformed spectral power and are presented using Decibel units. + +```cs +(double[] audio, int sampleRate) = ReadMono("hal.wav"); +var sg = new SpectrogramGenerator(sampleRate, fftSize: 4096, stepSize: 500, maxFreq: 3000); +sg.Add(audio); + +// Create a traditional (linear) Spectrogram +sg.SaveImage("hal.png"); + +// Create a Mel Spectrogram +Bitmap bmp = sg.GetBitmapMel(melSizePoints: 250); +bmp.Save("halMel.png", ImageFormat.Png); +``` + +## Read Data from an Audio File + +You should customize your file-reading method to suit your specific application. I frequently use the NAudio package to read data from WAV and MP3 files. This function reads audio data from a mono WAV file and will be used for the examples on this page. + +```cs +(double[] audio, int sampleRate) ReadMono(string filePath, double multiplier = 16_000) +{ + using var afr = new NAudio.Wave.AudioFileReader(filePath); + int sampleRate = afr.WaveFormat.SampleRate; + int bytesPerSample = afr.WaveFormat.BitsPerSample / 8; + int sampleCount = (int)(afr.Length / bytesPerSample); + int channelCount = afr.WaveFormat.Channels; + var audio = new List(sampleCount); + var buffer = new float[sampleRate * channelCount]; + int samplesRead = 0; + while ((samplesRead = afr.Read(buffer, 0, buffer.Length)) > 0) + audio.AddRange(buffer.Take(samplesRead).Select(x => x * multiplier)); + return (audio.ToArray(), sampleRate); +} +``` \ No newline at end of file diff --git a/data/03-02-03-01-02-01-19.wav b/data/03-02-03-01-02-01-19.wav new file mode 100644 index 0000000..5975883 Binary files /dev/null and b/data/03-02-03-01-02-01-19.wav differ diff --git a/data/Handel - Air and Variations.mp3 b/data/Handel - Air and Variations.mp3 new file mode 100644 index 0000000..7e08491 Binary files /dev/null and b/data/Handel - Air and Variations.mp3 differ diff --git a/data/Handel - Air and Variations.txt b/data/Handel - Air and Variations.txt new file mode 100644 index 0000000..d55a9fd --- /dev/null +++ b/data/Handel - Air and Variations.txt @@ -0,0 +1,9 @@ +Sep 3, 2019, 9:47 AM + +Hi Scott. + +I confirm that I am the sole copyright owner of the recording of the Handel piece "Air and Variations (The Harmonious Blacksmith)" in the following video published on YouTube: https://www.youtube.com/watch?v=Mza-xqk770k + +I hereby grant Scott W. Harden permission to use the above recording in whole or in part as a sample audio file for the Spectrogram audio analysis library (https://github.com/swharden/Spectrogram). + +Ken Barker \ No newline at end of file diff --git a/data/asehgal-original.wav b/data/asehgal-original.wav new file mode 100644 index 0000000..6ba04e5 Binary files /dev/null and b/data/asehgal-original.wav differ diff --git a/data/cant-do-that-11025-stereo.wav b/data/cant-do-that-11025-stereo.wav new file mode 100644 index 0000000..e2a3172 Binary files /dev/null and b/data/cant-do-that-11025-stereo.wav differ diff --git a/data/cant-do-that-44100.wav b/data/cant-do-that-44100.wav new file mode 100644 index 0000000..ac98f00 Binary files /dev/null and b/data/cant-do-that-44100.wav differ diff --git a/data/cant-do-that-grayscale-inverted.jpg b/data/cant-do-that-grayscale-inverted.jpg deleted file mode 100644 index 6301b2c..0000000 Binary files a/data/cant-do-that-grayscale-inverted.jpg and /dev/null differ diff --git a/data/cant-do-that-grayscale.jpg b/data/cant-do-that-grayscale.jpg deleted file mode 100644 index 3e9e670..0000000 Binary files a/data/cant-do-that-grayscale.jpg and /dev/null differ diff --git a/data/cant-do-that-green.jpg b/data/cant-do-that-green.jpg deleted file mode 100644 index 43c0a6d..0000000 Binary files a/data/cant-do-that-green.jpg and /dev/null differ diff --git a/data/cant-do-that.jpg b/data/cant-do-that.jpg deleted file mode 100644 index d26a7ca..0000000 Binary files a/data/cant-do-that.jpg and /dev/null differ diff --git a/data/mozart.jpg b/data/mozart.jpg deleted file mode 100644 index 80851a5..0000000 Binary files a/data/mozart.jpg and /dev/null differ diff --git a/data/mozart.wav b/data/mozart.wav deleted file mode 100644 index d3ff833..0000000 Binary files a/data/mozart.wav and /dev/null differ diff --git a/data/mozartSmall.jpg b/data/mozartSmall.jpg deleted file mode 100644 index b19203e..0000000 Binary files a/data/mozartSmall.jpg and /dev/null differ diff --git a/data/qrss-10min.wav b/data/qrss-10min.wav new file mode 100644 index 0000000..a1b7b1d Binary files /dev/null and b/data/qrss-10min.wav differ diff --git a/data/qrss.png b/data/qrss.png deleted file mode 100644 index 0026f60..0000000 Binary files a/data/qrss.png and /dev/null differ diff --git a/data/screenshot2.gif b/data/screenshot2.gif deleted file mode 100644 index 33bf087..0000000 Binary files a/data/screenshot2.gif and /dev/null differ diff --git a/data/screenshot3.gif b/data/screenshot3.gif deleted file mode 100644 index b72b2c9..0000000 Binary files a/data/screenshot3.gif and /dev/null differ diff --git a/data/screenshot4.gif b/data/screenshot4.gif deleted file mode 100644 index eb1984c..0000000 Binary files a/data/screenshot4.gif and /dev/null differ diff --git a/data/screenshot6.gif b/data/screenshot6.gif deleted file mode 100644 index 6f3f29a..0000000 Binary files a/data/screenshot6.gif and /dev/null differ diff --git a/dev/FFTcalc.exe b/dev/FFTcalc.exe deleted file mode 100644 index ea971f9..0000000 Binary files a/dev/FFTcalc.exe and /dev/null differ diff --git a/dev/FFTcalc/FFTcalc.csproj b/dev/FFTcalc/FFTcalc.csproj deleted file mode 100644 index 9b82ca0..0000000 --- a/dev/FFTcalc/FFTcalc.csproj +++ /dev/null @@ -1,83 +0,0 @@ - - - - - Debug - AnyCPU - {6C742189-D1C3-46CE-84EB-EB5CEC9312F0} - WinExe - FFTcalc - FFTcalc - v4.7.2 - 512 - true - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - Form - - - Form1.cs - - - - - Form1.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - - - - - \ No newline at end of file diff --git a/dev/FFTcalc/FFTcalc.sln b/dev/FFTcalc/FFTcalc.sln deleted file mode 100644 index 369b7bc..0000000 --- a/dev/FFTcalc/FFTcalc.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30011.22 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FFTcalc", "FFTcalc.csproj", "{6C742189-D1C3-46CE-84EB-EB5CEC9312F0}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6C742189-D1C3-46CE-84EB-EB5CEC9312F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6C742189-D1C3-46CE-84EB-EB5CEC9312F0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6C742189-D1C3-46CE-84EB-EB5CEC9312F0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6C742189-D1C3-46CE-84EB-EB5CEC9312F0}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {374A3752-3B58-489B-B9C4-83295AFB4DE9} - EndGlobalSection -EndGlobal diff --git a/dev/FFTcalc/Form1.Designer.cs b/dev/FFTcalc/Form1.Designer.cs deleted file mode 100644 index df49abf..0000000 --- a/dev/FFTcalc/Form1.Designer.cs +++ /dev/null @@ -1,735 +0,0 @@ -namespace FFTcalc -{ - partial class Form1 - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.cbRate = new System.Windows.Forms.ComboBox(); - this.label2 = new System.Windows.Forms.Label(); - this.label3 = new System.Windows.Forms.Label(); - this.nudLength = new System.Windows.Forms.NumericUpDown(); - this.cbLengthMult = new System.Windows.Forms.ComboBox(); - this.lblSamples = new System.Windows.Forms.Label(); - this.label1 = new System.Windows.Forms.Label(); - this.cbFftSize = new System.Windows.Forms.ComboBox(); - this.label5 = new System.Windows.Forms.Label(); - this.lblMaxFreq = new System.Windows.Forms.Label(); - this.pictureBox1 = new System.Windows.Forms.PictureBox(); - this.pictureBox2 = new System.Windows.Forms.PictureBox(); - this.pictureBox3 = new System.Windows.Forms.PictureBox(); - this.label6 = new System.Windows.Forms.Label(); - this.lblFftHeight = new System.Windows.Forms.Label(); - this.lblFftResolution = new System.Windows.Forms.Label(); - this.label9 = new System.Windows.Forms.Label(); - this.label7 = new System.Windows.Forms.Label(); - this.lblFftTime = new System.Windows.Forms.Label(); - this.groupBox1 = new System.Windows.Forms.GroupBox(); - this.label14 = new System.Windows.Forms.Label(); - this.pictureBox4 = new System.Windows.Forms.PictureBox(); - this.pictureBox5 = new System.Windows.Forms.PictureBox(); - this.pictureBox6 = new System.Windows.Forms.PictureBox(); - this.pictureBox7 = new System.Windows.Forms.PictureBox(); - this.pictureBox8 = new System.Windows.Forms.PictureBox(); - this.pictureBox9 = new System.Windows.Forms.PictureBox(); - this.pictureBox10 = new System.Windows.Forms.PictureBox(); - this.pictureBox11 = new System.Windows.Forms.PictureBox(); - this.label8 = new System.Windows.Forms.Label(); - this.label10 = new System.Windows.Forms.Label(); - this.label11 = new System.Windows.Forms.Label(); - this.groupBox2 = new System.Windows.Forms.GroupBox(); - this.lblOverlap = new System.Windows.Forms.Label(); - this.label12 = new System.Windows.Forms.Label(); - this.nudStepMsec = new System.Windows.Forms.NumericUpDown(); - this.groupBox3 = new System.Windows.Forms.GroupBox(); - this.groupBox4 = new System.Windows.Forms.GroupBox(); - this.pictureBox12 = new System.Windows.Forms.PictureBox(); - this.pictureBox13 = new System.Windows.Forms.PictureBox(); - this.lblSpecWidth = new System.Windows.Forms.Label(); - this.lblSpecHeight = new System.Windows.Forms.Label(); - this.pictureBox14 = new System.Windows.Forms.PictureBox(); - this.label4 = new System.Windows.Forms.Label(); - this.label13 = new System.Windows.Forms.Label(); - ((System.ComponentModel.ISupportInitialize)(this.nudLength)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox3)).BeginInit(); - this.groupBox1.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox4)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox5)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox6)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox7)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox8)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox9)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox10)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox11)).BeginInit(); - this.groupBox2.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.nudStepMsec)).BeginInit(); - this.groupBox3.SuspendLayout(); - this.groupBox4.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox12)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox13)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox14)).BeginInit(); - this.SuspendLayout(); - // - // cbRate - // - this.cbRate.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cbRate.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cbRate.FormattingEnabled = true; - this.cbRate.Items.AddRange(new object[] { - "4k", - "8k", - "16k", - "32k", - "44.1k", - "48k", - "50k", - "64k", - "88.2k", - "96k"}); - this.cbRate.Location = new System.Drawing.Point(20, 57); - this.cbRate.Name = "cbRate"; - this.cbRate.Size = new System.Drawing.Size(199, 29); - this.cbRate.TabIndex = 3; - this.cbRate.SelectedIndexChanged += new System.EventHandler(this.cbRate_SelectedIndexChanged); - // - // label2 - // - this.label2.AutoSize = true; - this.label2.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label2.Location = new System.Drawing.Point(16, 33); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(97, 21); - this.label2.TabIndex = 4; - this.label2.Text = "Sample Rate"; - // - // label3 - // - this.label3.AutoSize = true; - this.label3.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label3.Location = new System.Drawing.Point(12, 27); - this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(58, 21); - this.label3.TabIndex = 5; - this.label3.Text = "Length"; - // - // nudLength - // - this.nudLength.DecimalPlaces = 2; - this.nudLength.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.nudLength.Location = new System.Drawing.Point(16, 52); - this.nudLength.Maximum = new decimal(new int[] { - 100000, - 0, - 0, - 0}); - this.nudLength.Minimum = new decimal(new int[] { - 1, - 0, - 0, - 131072}); - this.nudLength.Name = "nudLength"; - this.nudLength.Size = new System.Drawing.Size(92, 29); - this.nudLength.TabIndex = 6; - this.nudLength.ThousandsSeparator = true; - this.nudLength.Value = new decimal(new int[] { - 20, - 0, - 0, - 0}); - this.nudLength.ValueChanged += new System.EventHandler(this.nudLength_ValueChanged); - // - // cbLengthMult - // - this.cbLengthMult.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cbLengthMult.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cbLengthMult.FormattingEnabled = true; - this.cbLengthMult.Items.AddRange(new object[] { - "sec", - "min", - "hr", - "day"}); - this.cbLengthMult.Location = new System.Drawing.Point(114, 52); - this.cbLengthMult.Name = "cbLengthMult"; - this.cbLengthMult.Size = new System.Drawing.Size(67, 29); - this.cbLengthMult.TabIndex = 7; - this.cbLengthMult.SelectedIndexChanged += new System.EventHandler(this.cbLengthMult_SelectedIndexChanged); - // - // lblSamples - // - this.lblSamples.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; - this.lblSamples.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lblSamples.Location = new System.Drawing.Point(16, 84); - this.lblSamples.Name = "lblSamples"; - this.lblSamples.Size = new System.Drawing.Size(165, 29); - this.lblSamples.TabIndex = 10; - this.lblSamples.Text = "Samples"; - this.lblSamples.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // label1 - // - this.label1.AutoSize = true; - this.label1.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label1.Location = new System.Drawing.Point(16, 94); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(66, 21); - this.label1.TabIndex = 11; - this.label1.Text = "FFT Size"; - // - // cbFftSize - // - this.cbFftSize.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cbFftSize.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cbFftSize.FormattingEnabled = true; - this.cbFftSize.Location = new System.Drawing.Point(20, 118); - this.cbFftSize.Name = "cbFftSize"; - this.cbFftSize.Size = new System.Drawing.Size(199, 29); - this.cbFftSize.TabIndex = 12; - this.cbFftSize.SelectedIndexChanged += new System.EventHandler(this.cbFftSize_SelectedIndexChanged); - // - // label5 - // - this.label5.AutoSize = true; - this.label5.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label5.Location = new System.Drawing.Point(16, 161); - this.label5.Name = "label5"; - this.label5.Size = new System.Drawing.Size(156, 21); - this.label5.TabIndex = 13; - this.label5.Text = "Maximum Frequency"; - // - // lblMaxFreq - // - this.lblMaxFreq.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; - this.lblMaxFreq.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lblMaxFreq.Location = new System.Drawing.Point(20, 185); - this.lblMaxFreq.Name = "lblMaxFreq"; - this.lblMaxFreq.Size = new System.Drawing.Size(199, 29); - this.lblMaxFreq.TabIndex = 14; - this.lblMaxFreq.Text = "Samples"; - this.lblMaxFreq.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // pictureBox1 - // - this.pictureBox1.BackColor = System.Drawing.Color.SteelBlue; - this.pictureBox1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.pictureBox1.Location = new System.Drawing.Point(15, 28); - this.pictureBox1.Name = "pictureBox1"; - this.pictureBox1.Size = new System.Drawing.Size(68, 150); - this.pictureBox1.TabIndex = 15; - this.pictureBox1.TabStop = false; - // - // pictureBox2 - // - this.pictureBox2.BackColor = System.Drawing.SystemColors.ControlDark; - this.pictureBox2.Location = new System.Drawing.Point(15, 184); - this.pictureBox2.Name = "pictureBox2"; - this.pictureBox2.Size = new System.Drawing.Size(68, 5); - this.pictureBox2.TabIndex = 16; - this.pictureBox2.TabStop = false; - // - // pictureBox3 - // - this.pictureBox3.BackColor = System.Drawing.SystemColors.ControlDark; - this.pictureBox3.Location = new System.Drawing.Point(89, 28); - this.pictureBox3.Name = "pictureBox3"; - this.pictureBox3.Size = new System.Drawing.Size(5, 150); - this.pictureBox3.TabIndex = 17; - this.pictureBox3.TabStop = false; - // - // label6 - // - this.label6.AutoSize = true; - this.label6.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label6.Location = new System.Drawing.Point(100, 41); - this.label6.Name = "label6"; - this.label6.Size = new System.Drawing.Size(56, 21); - this.label6.TabIndex = 18; - this.label6.Text = "Height"; - // - // lblFftHeight - // - this.lblFftHeight.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; - this.lblFftHeight.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lblFftHeight.Location = new System.Drawing.Point(104, 66); - this.lblFftHeight.Name = "lblFftHeight"; - this.lblFftHeight.Size = new System.Drawing.Size(118, 29); - this.lblFftHeight.TabIndex = 20; - this.lblFftHeight.Text = "Samples"; - this.lblFftHeight.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // lblFftResolution - // - this.lblFftResolution.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; - this.lblFftResolution.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lblFftResolution.Location = new System.Drawing.Point(104, 136); - this.lblFftResolution.Name = "lblFftResolution"; - this.lblFftResolution.Size = new System.Drawing.Size(118, 29); - this.lblFftResolution.TabIndex = 22; - this.lblFftResolution.Text = "Samples"; - this.lblFftResolution.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // label9 - // - this.label9.AutoSize = true; - this.label9.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label9.Location = new System.Drawing.Point(100, 111); - this.label9.Name = "label9"; - this.label9.Size = new System.Drawing.Size(84, 21); - this.label9.TabIndex = 21; - this.label9.Text = "Resolution"; - // - // label7 - // - this.label7.AutoSize = true; - this.label7.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label7.Location = new System.Drawing.Point(11, 192); - this.label7.Name = "label7"; - this.label7.Size = new System.Drawing.Size(58, 21); - this.label7.TabIndex = 23; - this.label7.Text = "Length"; - // - // lblFftTime - // - this.lblFftTime.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; - this.lblFftTime.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lblFftTime.Location = new System.Drawing.Point(15, 217); - this.lblFftTime.Name = "lblFftTime"; - this.lblFftTime.Size = new System.Drawing.Size(126, 29); - this.lblFftTime.TabIndex = 24; - this.lblFftTime.Text = "Samples"; - this.lblFftTime.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // groupBox1 - // - this.groupBox1.Controls.Add(this.label14); - this.groupBox1.Controls.Add(this.pictureBox1); - this.groupBox1.Controls.Add(this.lblFftTime); - this.groupBox1.Controls.Add(this.pictureBox2); - this.groupBox1.Controls.Add(this.label7); - this.groupBox1.Controls.Add(this.pictureBox3); - this.groupBox1.Controls.Add(this.lblFftResolution); - this.groupBox1.Controls.Add(this.label6); - this.groupBox1.Controls.Add(this.label9); - this.groupBox1.Controls.Add(this.lblFftHeight); - this.groupBox1.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.groupBox1.Location = new System.Drawing.Point(32, 278); - this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(237, 259); - this.groupBox1.TabIndex = 25; - this.groupBox1.TabStop = false; - this.groupBox1.Text = "Single FFT"; - // - // label14 - // - this.label14.AutoSize = true; - this.label14.BackColor = System.Drawing.Color.SteelBlue; - this.label14.Font = new System.Drawing.Font("Segoe UI Semibold", 20.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label14.ForeColor = System.Drawing.Color.White; - this.label14.Location = new System.Drawing.Point(19, 89); - this.label14.Name = "label14"; - this.label14.Size = new System.Drawing.Size(60, 37); - this.label14.TabIndex = 47; - this.label14.Text = "FFT"; - // - // pictureBox4 - // - this.pictureBox4.BackColor = System.Drawing.SystemColors.ControlLight; - this.pictureBox4.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.pictureBox4.Location = new System.Drawing.Point(396, 148); - this.pictureBox4.Name = "pictureBox4"; - this.pictureBox4.Size = new System.Drawing.Size(68, 150); - this.pictureBox4.TabIndex = 26; - this.pictureBox4.TabStop = false; - // - // pictureBox5 - // - this.pictureBox5.BackColor = System.Drawing.SystemColors.ControlLight; - this.pictureBox5.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.pictureBox5.Location = new System.Drawing.Point(416, 138); - this.pictureBox5.Name = "pictureBox5"; - this.pictureBox5.Size = new System.Drawing.Size(68, 150); - this.pictureBox5.TabIndex = 28; - this.pictureBox5.TabStop = false; - // - // pictureBox6 - // - this.pictureBox6.BackColor = System.Drawing.SystemColors.ControlLight; - this.pictureBox6.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.pictureBox6.Location = new System.Drawing.Point(436, 128); - this.pictureBox6.Name = "pictureBox6"; - this.pictureBox6.Size = new System.Drawing.Size(68, 150); - this.pictureBox6.TabIndex = 29; - this.pictureBox6.TabStop = false; - // - // pictureBox7 - // - this.pictureBox7.BackColor = System.Drawing.SystemColors.ControlLight; - this.pictureBox7.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.pictureBox7.Location = new System.Drawing.Point(496, 98); - this.pictureBox7.Name = "pictureBox7"; - this.pictureBox7.Size = new System.Drawing.Size(68, 150); - this.pictureBox7.TabIndex = 32; - this.pictureBox7.TabStop = false; - // - // pictureBox8 - // - this.pictureBox8.BackColor = System.Drawing.SystemColors.ControlLight; - this.pictureBox8.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.pictureBox8.Location = new System.Drawing.Point(476, 108); - this.pictureBox8.Name = "pictureBox8"; - this.pictureBox8.Size = new System.Drawing.Size(68, 150); - this.pictureBox8.TabIndex = 31; - this.pictureBox8.TabStop = false; - // - // pictureBox9 - // - this.pictureBox9.BackColor = System.Drawing.Color.SteelBlue; - this.pictureBox9.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.pictureBox9.Location = new System.Drawing.Point(456, 118); - this.pictureBox9.Name = "pictureBox9"; - this.pictureBox9.Size = new System.Drawing.Size(68, 150); - this.pictureBox9.TabIndex = 30; - this.pictureBox9.TabStop = false; - // - // pictureBox10 - // - this.pictureBox10.BackColor = System.Drawing.Color.Crimson; - this.pictureBox10.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.pictureBox10.Location = new System.Drawing.Point(345, 60); - this.pictureBox10.Name = "pictureBox10"; - this.pictureBox10.Size = new System.Drawing.Size(242, 25); - this.pictureBox10.TabIndex = 33; - this.pictureBox10.TabStop = false; - // - // pictureBox11 - // - this.pictureBox11.BackColor = System.Drawing.Color.SteelBlue; - this.pictureBox11.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.pictureBox11.Location = new System.Drawing.Point(456, 55); - this.pictureBox11.Name = "pictureBox11"; - this.pictureBox11.Size = new System.Drawing.Size(68, 25); - this.pictureBox11.TabIndex = 34; - this.pictureBox11.TabStop = false; - // - // label8 - // - this.label8.AutoSize = true; - this.label8.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label8.Location = new System.Drawing.Point(452, 31); - this.label8.Name = "label8"; - this.label8.Size = new System.Drawing.Size(81, 21); - this.label8.TabIndex = 35; - this.label8.Text = "Single FFT"; - // - // label10 - // - this.label10.AutoSize = true; - this.label10.BackColor = System.Drawing.Color.Crimson; - this.label10.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label10.ForeColor = System.Drawing.Color.White; - this.label10.Location = new System.Drawing.Point(348, 62); - this.label10.Name = "label10"; - this.label10.Size = new System.Drawing.Size(79, 21); - this.label10.TabIndex = 36; - this.label10.Text = "Audio File"; - // - // label11 - // - this.label11.AutoSize = true; - this.label11.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label11.Location = new System.Drawing.Point(373, 307); - this.label11.Name = "label11"; - this.label11.Size = new System.Drawing.Size(219, 21); - this.label11.TabIndex = 37; - this.label11.Text = "Sequence of Overlapping FFTs"; - // - // groupBox2 - // - this.groupBox2.Controls.Add(this.lblOverlap); - this.groupBox2.Controls.Add(this.label12); - this.groupBox2.Controls.Add(this.nudStepMsec); - this.groupBox2.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.groupBox2.Location = new System.Drawing.Point(676, 36); - this.groupBox2.Name = "groupBox2"; - this.groupBox2.Size = new System.Drawing.Size(200, 138); - this.groupBox2.TabIndex = 38; - this.groupBox2.TabStop = false; - this.groupBox2.Text = "Sequential FFT Step Size"; - // - // lblOverlap - // - this.lblOverlap.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; - this.lblOverlap.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lblOverlap.Location = new System.Drawing.Point(17, 94); - this.lblOverlap.Name = "lblOverlap"; - this.lblOverlap.Size = new System.Drawing.Size(164, 29); - this.lblOverlap.TabIndex = 32; - this.lblOverlap.Text = "Samples"; - this.lblOverlap.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // label12 - // - this.label12.AutoSize = true; - this.label12.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label12.Location = new System.Drawing.Point(13, 33); - this.label12.Name = "label12"; - this.label12.Size = new System.Drawing.Size(110, 21); - this.label12.TabIndex = 26; - this.label12.Text = "Step Size (ms):"; - // - // nudStepMsec - // - this.nudStepMsec.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.nudStepMsec.Location = new System.Drawing.Point(17, 59); - this.nudStepMsec.Maximum = new decimal(new int[] { - 999, - 0, - 0, - 0}); - this.nudStepMsec.Minimum = new decimal(new int[] { - 1, - 0, - 0, - 0}); - this.nudStepMsec.Name = "nudStepMsec"; - this.nudStepMsec.Size = new System.Drawing.Size(73, 29); - this.nudStepMsec.TabIndex = 7; - this.nudStepMsec.ThousandsSeparator = true; - this.nudStepMsec.Value = new decimal(new int[] { - 10, - 0, - 0, - 0}); - this.nudStepMsec.ValueChanged += new System.EventHandler(this.nudOverlap_ValueChanged); - // - // groupBox3 - // - this.groupBox3.Controls.Add(this.label2); - this.groupBox3.Controls.Add(this.cbRate); - this.groupBox3.Controls.Add(this.label1); - this.groupBox3.Controls.Add(this.cbFftSize); - this.groupBox3.Controls.Add(this.label5); - this.groupBox3.Controls.Add(this.lblMaxFreq); - this.groupBox3.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.groupBox3.Location = new System.Drawing.Point(32, 36); - this.groupBox3.Name = "groupBox3"; - this.groupBox3.Size = new System.Drawing.Size(237, 229); - this.groupBox3.TabIndex = 39; - this.groupBox3.TabStop = false; - this.groupBox3.Text = "FFT Parameters"; - // - // groupBox4 - // - this.groupBox4.Controls.Add(this.label3); - this.groupBox4.Controls.Add(this.nudLength); - this.groupBox4.Controls.Add(this.cbLengthMult); - this.groupBox4.Controls.Add(this.lblSamples); - this.groupBox4.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.groupBox4.Location = new System.Drawing.Point(676, 194); - this.groupBox4.Name = "groupBox4"; - this.groupBox4.Size = new System.Drawing.Size(200, 134); - this.groupBox4.TabIndex = 40; - this.groupBox4.TabStop = false; - this.groupBox4.Text = "Audio File"; - // - // pictureBox12 - // - this.pictureBox12.BackColor = System.Drawing.Color.SteelBlue; - this.pictureBox12.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.pictureBox12.Location = new System.Drawing.Point(371, 394); - this.pictureBox12.Name = "pictureBox12"; - this.pictureBox12.Size = new System.Drawing.Size(216, 107); - this.pictureBox12.TabIndex = 41; - this.pictureBox12.TabStop = false; - // - // pictureBox13 - // - this.pictureBox13.BackColor = System.Drawing.SystemColors.ControlDark; - this.pictureBox13.Location = new System.Drawing.Point(371, 507); - this.pictureBox13.Name = "pictureBox13"; - this.pictureBox13.Size = new System.Drawing.Size(216, 5); - this.pictureBox13.TabIndex = 42; - this.pictureBox13.TabStop = false; - // - // lblSpecWidth - // - this.lblSpecWidth.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lblSpecWidth.Location = new System.Drawing.Point(371, 518); - this.lblSpecWidth.Name = "lblSpecWidth"; - this.lblSpecWidth.Size = new System.Drawing.Size(216, 21); - this.lblSpecWidth.TabIndex = 43; - this.lblSpecWidth.Text = "Length"; - this.lblSpecWidth.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // lblSpecHeight - // - this.lblSpecHeight.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lblSpecHeight.Location = new System.Drawing.Point(604, 394); - this.lblSpecHeight.Name = "lblSpecHeight"; - this.lblSpecHeight.Size = new System.Drawing.Size(116, 107); - this.lblSpecHeight.TabIndex = 44; - this.lblSpecHeight.Text = "Height"; - this.lblSpecHeight.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // pictureBox14 - // - this.pictureBox14.BackColor = System.Drawing.SystemColors.ControlDark; - this.pictureBox14.Location = new System.Drawing.Point(593, 394); - this.pictureBox14.Name = "pictureBox14"; - this.pictureBox14.Size = new System.Drawing.Size(5, 107); - this.pictureBox14.TabIndex = 25; - this.pictureBox14.TabStop = false; - // - // label4 - // - this.label4.AutoSize = true; - this.label4.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label4.Location = new System.Drawing.Point(377, 331); - this.label4.Name = "label4"; - this.label4.Size = new System.Drawing.Size(212, 21); - this.label4.TabIndex = 45; - this.label4.Text = "(each FFT becomes 1px wide)"; - // - // label13 - // - this.label13.AutoSize = true; - this.label13.BackColor = System.Drawing.Color.SteelBlue; - this.label13.Font = new System.Drawing.Font("Segoe UI Semibold", 20.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label13.ForeColor = System.Drawing.Color.White; - this.label13.Location = new System.Drawing.Point(396, 431); - this.label13.Name = "label13"; - this.label13.Size = new System.Drawing.Size(175, 37); - this.label13.TabIndex = 46; - this.label13.Text = "Spectrogram"; - // - // Form1 - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(912, 565); - this.Controls.Add(this.label13); - this.Controls.Add(this.label4); - this.Controls.Add(this.pictureBox14); - this.Controls.Add(this.lblSpecHeight); - this.Controls.Add(this.lblSpecWidth); - this.Controls.Add(this.pictureBox13); - this.Controls.Add(this.pictureBox12); - this.Controls.Add(this.groupBox4); - this.Controls.Add(this.groupBox3); - this.Controls.Add(this.groupBox2); - this.Controls.Add(this.label11); - this.Controls.Add(this.label10); - this.Controls.Add(this.label8); - this.Controls.Add(this.pictureBox11); - this.Controls.Add(this.pictureBox10); - this.Controls.Add(this.pictureBox7); - this.Controls.Add(this.pictureBox8); - this.Controls.Add(this.pictureBox9); - this.Controls.Add(this.pictureBox6); - this.Controls.Add(this.pictureBox5); - this.Controls.Add(this.pictureBox4); - this.Controls.Add(this.groupBox1); - this.Name = "Form1"; - this.Text = "FFTcalc"; - ((System.ComponentModel.ISupportInitialize)(this.nudLength)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox3)).EndInit(); - this.groupBox1.ResumeLayout(false); - this.groupBox1.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox4)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox5)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox6)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox7)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox8)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox9)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox10)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox11)).EndInit(); - this.groupBox2.ResumeLayout(false); - this.groupBox2.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.nudStepMsec)).EndInit(); - this.groupBox3.ResumeLayout(false); - this.groupBox3.PerformLayout(); - this.groupBox4.ResumeLayout(false); - this.groupBox4.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox12)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox13)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox14)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - private System.Windows.Forms.ComboBox cbRate; - private System.Windows.Forms.Label label2; - private System.Windows.Forms.Label label3; - private System.Windows.Forms.NumericUpDown nudLength; - private System.Windows.Forms.ComboBox cbLengthMult; - private System.Windows.Forms.Label lblSamples; - private System.Windows.Forms.Label label1; - private System.Windows.Forms.ComboBox cbFftSize; - private System.Windows.Forms.Label label5; - private System.Windows.Forms.Label lblMaxFreq; - private System.Windows.Forms.PictureBox pictureBox1; - private System.Windows.Forms.PictureBox pictureBox2; - private System.Windows.Forms.PictureBox pictureBox3; - private System.Windows.Forms.Label label6; - private System.Windows.Forms.Label lblFftHeight; - private System.Windows.Forms.Label lblFftResolution; - private System.Windows.Forms.Label label9; - private System.Windows.Forms.Label label7; - private System.Windows.Forms.Label lblFftTime; - private System.Windows.Forms.GroupBox groupBox1; - private System.Windows.Forms.PictureBox pictureBox4; - private System.Windows.Forms.PictureBox pictureBox5; - private System.Windows.Forms.PictureBox pictureBox6; - private System.Windows.Forms.PictureBox pictureBox7; - private System.Windows.Forms.PictureBox pictureBox8; - private System.Windows.Forms.PictureBox pictureBox9; - private System.Windows.Forms.PictureBox pictureBox10; - private System.Windows.Forms.PictureBox pictureBox11; - private System.Windows.Forms.Label label8; - private System.Windows.Forms.Label label10; - private System.Windows.Forms.Label label11; - private System.Windows.Forms.GroupBox groupBox2; - private System.Windows.Forms.NumericUpDown nudStepMsec; - private System.Windows.Forms.Label label12; - private System.Windows.Forms.GroupBox groupBox3; - private System.Windows.Forms.GroupBox groupBox4; - private System.Windows.Forms.Label label14; - private System.Windows.Forms.PictureBox pictureBox12; - private System.Windows.Forms.PictureBox pictureBox13; - private System.Windows.Forms.Label lblSpecWidth; - private System.Windows.Forms.Label lblSpecHeight; - private System.Windows.Forms.PictureBox pictureBox14; - private System.Windows.Forms.Label label4; - private System.Windows.Forms.Label label13; - private System.Windows.Forms.Label lblOverlap; - } -} - diff --git a/dev/FFTcalc/Form1.cs b/dev/FFTcalc/Form1.cs deleted file mode 100644 index 1deee88..0000000 --- a/dev/FFTcalc/Form1.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; - -namespace FFTcalc -{ - public partial class Form1 : Form - { - public Form1() - { - InitializeComponent(); - - cbRate.SelectedIndex = cbRate.Items.Count - 1; - cbLengthMult.SelectedIndex = 0; - - for (int i = 5; i < 15; i++) - cbFftSize.Items.Add($"2^{i} ({Math.Pow(2, i):N0})"); - cbFftSize.SelectedIndex = 5; - } - - private void UpdateAll() - { - double sampleRate = (double.Parse(cbRate.Text.Replace("k", "")) * 1000); - - double lengthSec; - if (cbLengthMult.Text == "sec" || string.IsNullOrWhiteSpace(cbLengthMult.Text)) - lengthSec = (double)nudLength.Value * 1; - else if (cbLengthMult.Text == "min") - lengthSec = (double)nudLength.Value * 60; - else if (cbLengthMult.Text == "hr") - lengthSec = (double)nudLength.Value * 60 * 60; - else if (cbLengthMult.Text == "day") - lengthSec = (double)nudLength.Value * 60 * 60 * 24; - else - throw new ArgumentException("unknown length multiplier"); - double lengthSamples = lengthSec * sampleRate; - lblSamples.Text = $"{lengthSamples:N0}"; - - int fftBase = cbFftSize.SelectedIndex + 5; - int fftSize = (int)Math.Pow(2, fftBase); - int fftHeightPx = fftSize; - - double maxFreq = sampleRate / 2; - lblMaxFreq.Text = $"{maxFreq:N0} Hz"; - - double fftRowHz = maxFreq / fftSize; - double fftColSec = fftSize / sampleRate * 2; // I think *2? - lblFftHeight.Text = $"{fftHeightPx:N0} px"; - lblFftResolution.Text = $"{fftRowHz:N0} Hz / row"; - lblFftTime.Text = $"{fftColSec * 1000:N2} ms"; - - double fftStepSec = (double)nudStepMsec.Value / 1000; - int fftStepSamples = (int)(fftStepSec * sampleRate); - - double stepFrac = fftStepSec / fftColSec; - double overlapFrac = 1 - stepFrac; - lblOverlap.Text = $"{overlapFrac * 100:N1} %"; - if (overlapFrac <= 0) - lblOverlap.Text = "no overlap"; - - int stepsInAudioFile = (int)((lengthSamples - fftSize) / fftStepSamples); - int fftWidthPx = stepsInAudioFile; - lblSpecWidth.Text = $"{fftWidthPx:N0} px"; - lblSpecHeight.Text = $"{fftHeightPx:N0} px"; - } - - private void cbRate_SelectedIndexChanged(object sender, EventArgs e) => UpdateAll(); - private void nudRate_ValueChanged(object sender, EventArgs e) => UpdateAll(); - private void nudLength_ValueChanged(object sender, EventArgs e) => UpdateAll(); - private void cbLengthMult_SelectedIndexChanged(object sender, EventArgs e) => UpdateAll(); - private void cbFftSize_SelectedIndexChanged(object sender, EventArgs e) => UpdateAll(); - private void nudOverlap_ValueChanged(object sender, EventArgs e) => UpdateAll(); - } -} diff --git a/dev/SpectrogramDemo.zip b/dev/SpectrogramDemo.zip new file mode 100644 index 0000000..68b25ae Binary files /dev/null and b/dev/SpectrogramDemo.zip differ diff --git a/dev/colormap/analyzeImage.py b/dev/colormap/analyzeImage.py new file mode 100644 index 0000000..5db2189 --- /dev/null +++ b/dev/colormap/analyzeImage.py @@ -0,0 +1,33 @@ +""" +This script reverse-engineers the pallette for a spectrogram. +It takes in an image, explodes it into pixels, finds all the +unique values, then attempts to sort them by intensity. +""" + +from matplotlib.image import imread +import numpy as np + + +def rgbToInt32(r, g, b): + return int(r * 2**16 + g * 2**8 + b) + + +def valuesInImage(filePath): + im = imread(filePath) + pixelValues = np.zeros((im.shape[0], im.shape[1]), dtype=int) + for y in range(im.shape[0]): + for x in range(im.shape[1]): + r, g, b = im[y, x] + pixelValues[y, x] = rgbToInt32(r * 255, g * 255, b * 255) + pixelValues = np.unique(pixelValues) + for i, val in enumerate(pixelValues): + print(f"{val:08d}", end=", ") + if (i % 8 == 7): + print() + print(f"Found {len(pixelValues)} unique pixel values") + + +if __name__ == "__main__": + assert rgbToInt32(111, 222, 121) == 7331449 + pixelValues = valuesInImage("analyzed/argo1.png") + print("DONE") diff --git a/dev/colormap/analyzeValues.py b/dev/colormap/analyzeValues.py new file mode 100644 index 0000000..9b93299 --- /dev/null +++ b/dev/colormap/analyzeValues.py @@ -0,0 +1,40 @@ + +import matplotlib.pyplot as plt +import numpy as np + + +def analyze(valuesTextBlock, title): + values = [x.strip() for x in valuesTextBlock.replace(",", " ").split(" ")] + values = [int(x) for x in values if len(x)] + assert (len(values) == 256) + + rgbValues = np.zeros((256, 4)) + for i, intVal in enumerate(values): + rgbValues[i, 2] = intVal % 256 + intVal = intVal >> 8 + rgbValues[i, 1] = intVal % 256 + intVal = intVal >> 8 + rgbValues[i, 0] = intVal + intVal = intVal >> 8 + assert (intVal == 0) + rgbValues[i, 3] = np.sum(rgbValues[i]) + + #rgbValues = rgbValues[rgbValues[:,3].argsort()] + + plt.figure(figsize=(6, 4)) + plt.grid(alpha=.2, ls='--') + plt.plot(rgbValues[:, 0], 'r-') + plt.plot(rgbValues[:, 1], 'g-') + plt.plot(rgbValues[:, 2], 'b-') + plt.plot(rgbValues[:, 3]/3, 'k:') + plt.ylabel("Intensity by Color") + plt.xlabel("Signal Intensity") + plt.title(title) + + +if __name__ == "__main__": + import analyzed.argo + #analyze(analyzed.argo.myScreenshot, "Argo Local Screenshot") + #analyze(analyzed.argo.webScreenshot, "Argo Website Screenshot") + analyze(analyzed.argo.myScreenshot, "Argo Colormap") + plt.show() diff --git a/dev/colormap/analyzed/argo.py b/dev/colormap/analyzed/argo.py new file mode 100644 index 0000000..31eab98 --- /dev/null +++ b/dev/colormap/analyzed/argo.py @@ -0,0 +1,90 @@ +# This is the output of analyzeImage.py (each value indicates a RGB color) +myScreenshot = """ + 0 4 264 267 527 530 789 66328 + 66588 66591 66849 132388 132647 132650 132908 198447 + 198706 198708 264503 264505 330299 330557 330560 396354 + 396612 462150 462408 527946 528204 593998 594000 659794 + 660052 725590 791383 791641 857435 857437 923230 989024 + 989026 1054819 1120613 1120870 1186408 1252201 1252459 1318252 + 1384046 1384047 1449841 1515634 1515892 1581429 1647222 1713016 + 1713273 1779066 1844860 1910397 1976190 1976447 2042241 2108034 + 2173827 2239364 2239621 2305415 2371208 2437001 2502794 2568587 + 2568844 2634381 2700174 2765968 2831761 2897554 2963347 3029140 + 3029397 3095190 3160983 3226520 3292313 3358106 3423899 3489692 + 3555485 3621278 3687071 3752864 3818656 3884449 3950242 4016035 + 4081828 4147621 4147878 4213671 4279464 4345256 4411049 4476842 + 4542635 4608428 4739757 4805550 4871342 4937135 5002928 5068721 + 5134514 5200306 5266099 5331892 5397685 5463477 5529270 5595063 + 5660856 5726648 5792441 5858234 5924027 5989819 6121148 6186941 + 6252734 6318526 6384319 6450112 6515904 6581953 6647746 6779074 + 6844867 6910660 6976452 7042245 7108038 7173830 7239623 7370952 + 7436744 7502793 7568586 7634378 7700171 7765964 7897292 7963085 + 8028877 8094670 8160719 8226511 8357840 8423632 8489425 8555218 + 8621010 8752339 8818387 8884180 8949973 9015765 9147094 9212886 + 9278679 9344727 9410520 9541849 9607641 9673434 9739226 9870555 + 9936603 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 +""" + +# https://www.i2phd.org/images/argo143screen.gif +webScreenshot = """ + 0 4 264 267 527 530 789 66328 + 66588 66591 66849 132388 132647 132650 132908 198447 + 198706 198708 264503 264505 330299 330557 330560 396354 + 396612 462150 462408 527946 528204 593998 594000 659794 + 660052 725590 791383 791641 857435 857437 923230 989024 + 989026 1054819 1120613 1120870 1186408 1252201 1252459 1318252 + 1384046 1384047 1449841 1515634 1515892 1581429 1647222 1713016 + 1713273 1779066 1844860 1910397 1976190 1976447 2042241 2108034 + 2173827 2239364 2239621 2305415 2371208 2437001 2502794 2568587 + 2568844 2634381 2700174 2765968 2831761 2897554 2963347 3029140 + 3029397 3095190 3160983 3226520 3292313 3358106 3423899 3489692 + 3555485 3621278 3687071 3752864 3818656 3884449 3950242 4016035 + 4081828 4147621 4147878 4213671 4279464 4345256 4411049 4476842 + 4542635 4608428 4739757 4805550 4871342 4937135 5002928 5068721 + 5134514 5200306 5266099 5331892 5397685 5463477 5529270 5595063 + 5660856 5726648 5792441 5858234 5924027 5989819 6121148 6186941 + 6252734 6318526 6384319 6450112 6515904 6581953 6647746 6779074 + 6844867 6910660 6976452 7042245 7108038 7173830 7239623 7370952 + 7436744 7502793 7568586 7634378 7700171 7765964 7897292 7963085 + 8028877 8094670 8160719 8226511 8357840 8423632 8489425 8555218 + 8621010 8752339 8818387 8884180 8949973 9015765 9147094 9212886 + 9278679 9344727 9410520 9541849 9607641 9673434 9739226 9870555 + 9936603 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 +""" + + +if __name__ == "__main__": + values = [x.strip() for x in myScreenshot.replace(",", " ").split(" ")] + values = [int(x) for x in values if len(x)] + assert (len(values) == 256) + + txt = f"# colormap argo\n" + + for i in range(256): + int32 = values[i] * 255 # alpha + txt += f"{int32:010d}, " + if i % 8 == 7: + txt += "\n" + + with open(f"../analyzed2/argo.csv", 'w') as f: + f.write(txt) + + print(txt) diff --git a/dev/colormap/analyzed/argo1.png b/dev/colormap/analyzed/argo1.png new file mode 100644 index 0000000..14e3a62 Binary files /dev/null and b/dev/colormap/analyzed/argo1.png differ diff --git a/dev/colormap/analyzed/argo2.png b/dev/colormap/analyzed/argo2.png new file mode 100644 index 0000000..423782b Binary files /dev/null and b/dev/colormap/analyzed/argo2.png differ diff --git a/dev/colormap/analyzed2/argo.csv b/dev/colormap/analyzed2/argo.csv new file mode 100644 index 0000000..46c3c79 --- /dev/null +++ b/dev/colormap/analyzed2/argo.csv @@ -0,0 +1,33 @@ +# colormap argo +0000000000, 0000001020, 0000067320, 0000068085, 0000134385, 0000135150, 0000201195, 0016913640, +0016979940, 0016980705, 0017046495, 0033758940, 0033824985, 0033825750, 0033891540, 0050603985, +0050670030, 0050670540, 0067448265, 0067448775, 0084226245, 0084292035, 0084292800, 0101070270, +0101136060, 0117848250, 0117914040, 0134626230, 0134692020, 0151469490, 0151470000, 0168247470, +0168313260, 0185025450, 0201802665, 0201868455, 0218645925, 0218646435, 0235423650, 0252201120, +0252201630, 0268978845, 0285756315, 0285821850, 0302534040, 0319311255, 0319377045, 0336154260, +0352931730, 0352931985, 0369709455, 0386486670, 0386552460, 0403264395, 0420041610, 0436819080, +0436884615, 0453661830, 0470439300, 0487151235, 0503928450, 0503993985, 0520771455, 0537548670, +0554325885, 0571037820, 0571103355, 0587880825, 0604658040, 0621435255, 0638212470, 0654989685, +0655055220, 0671767155, 0688544370, 0705321840, 0722099055, 0738876270, 0755653485, 0772430700, +0772496235, 0789273450, 0806050665, 0822762600, 0839539815, 0856317030, 0873094245, 0889871460, +0906648675, 0923425890, 0940203105, 0956980320, 0973757280, 0990534495, 1007311710, 1024088925, +1040866140, 1057643355, 1057708890, 1074486105, 1091263320, 1108040280, 1124817495, 1141594710, +1158371925, 1175149140, 1208638035, 1225415250, 1242192210, 1258969425, 1275746640, 1292523855, +1309301070, 1326078030, 1342855245, 1359632460, 1376409675, 1393186635, 1409963850, 1426741065, +1443518280, 1460295240, 1477072455, 1493849670, 1510626885, 1527403845, 1560892740, 1577669955, +1594447170, 1611224130, 1628001345, 1644778560, 1661555520, 1678398015, 1695175230, 1728663870, +1745441085, 1762218300, 1778995260, 1795772475, 1812549690, 1829326650, 1846103865, 1879592760, +1896369720, 1913212215, 1929989430, 1946766390, 1963543605, 1980320820, 2013809460, 2030586675, +2047363635, 2064140850, 2080983345, 2097760305, 2131249200, 2148026160, 2164803375, 2181580590, +2198357550, 2231846445, 2248688685, 2265465900, 2282243115, 2299020075, 2332508970, 2349285930, +2366063145, 2382905385, 2399682600, 2433171495, 2449948455, 2466725670, 2483502630, 2516991525, +2533833765, 2550610980, 2567387940, 2584165155, 2617653795, 2634431010, 2651273250, 2668050465, +2701539360, 2718316320, 2735093535, 2751935775, 2785424670, 2802201630, 2818978845, 2835755805, +2869244700, 2886086940, 2902864155, 2936352795, 2953130010, 2969906970, 2986749465, 3020238105, +3037015320, 3053792280, 3087281175, 3104123415, 3120900630, 3137677590, 3171166485, 3188008725, +3204785685, 3238274580, 3255051540, 3271828755, 3305382675, 3322159890, 3338936850, 3355714065, +3389267985, 3406045200, 3422822160, 3456311055, 3473088015, 3489930255, 3523419150, 3540196110, +3556973325, 3590527245, 3607304460, 3624081420, 3657570060, 3674412555, 3691189515, 3724678410, +3741455370, 3758297865, 3791786505, 3808563465, 3842052360, 3858894600, 3875671815, 3909160455, +3926002950, 3942779910, 3976268550, 3993045765, 4009888005, 4043376900, 4060153860, 4093642500, +4110484995, 4127261955, 4160750850, 4177593090, 4194370050, 4227858945, 4244635905, 4278189825, diff --git a/dev/colormap/analyzed2/argo.png b/dev/colormap/analyzed2/argo.png new file mode 100644 index 0000000..11c453c Binary files /dev/null and b/dev/colormap/analyzed2/argo.png differ diff --git a/dev/colormap/analyzed2/blues.csv b/dev/colormap/analyzed2/blues.csv new file mode 100644 index 0000000..b3cc427 --- /dev/null +++ b/dev/colormap/analyzed2/blues.csv @@ -0,0 +1,32 @@ +# QRSS VD blues00145753, 00146010, 00145753, 00146010, 00146268, 00146525, 00146783, 00147041, +00147298, 00147556, 00147814, 00148071, 00148329, 00148587, 00148844, 00149102, +00149360, 00215409, 00215667, 00215925, 00216182, 00216440, 00216698, 00216955, +00217213, 00217471, 00217728, 00217986, 00218244, 00218501, 00218759, 00219017, +00219530, 00285324, 00285326, 00285583, 00285840, 00285841, 00286098, 00286355, +00286356, 00286613, 00286870, 00286872, 00287129, 00287386, 00287643, 00287644, +00287901, 00288158, 00288159, 00288416, 00288673, 00288675, 00288932, 00289189, +00289190, 00289447, 00289704, 00289961, 00289962, 00290219, 00290476, 00290478, +00290735, 00356528, 00422320, 00553649, 00619441, 00750770, 00882098, 00947891, +01079219, 01145012, 01276340, 01342133, 01473461, 01539254, 01670582, 01736375, +01867703, 01933496, 02064824, 02196153, 02261945, 02393274, 02459066, 02590395, +02656187, 02787516, 02853308, 02984637, 03050429, 03181758, 03247550, 03378879, +03510207, 03576000, 03707328, 03838401, 03969729, 04101058, 04232386, 04363715, +04494787, 04626115, 04757444, 04888772, 05019845, 05151173, 05282502, 05348294, +05479623, 05610695, 05742024, 05873352, 06004681, 06135753, 06267082, 06398410, +06529739, 06661067, 06792139, 06923468, 07054796, 07186125, 07317453, 07448526, +07579854, 07645647, 07776719, 07842511, 07973840, 08104912, 08170705, 08302033, +08367569, 08498898, 08564434, 08695762, 08827091, 08892627, 09023956, 09089748, +09220820, 09286613, 09417685, 09549013, 09614806, 09745878, 09811671, 09942999, +10008535, 10139864, 10271192, 10336728, 10468057, 10533593, 10664922, 10730714, +10861786, 10927579, 11058907, 11124443, 11190236, 11321308, 11387100, 11452893, +11583965, 11649757, 11715550, 11846622, 11912415, 11977951, 12109279, 12175072, +12240608, 12306400, 12437729, 12503265, 12569057, 12700130, 12765922, 12897250, +12962787, 13028579, 13094371, 13225444, 13291236, 13422308, 13488101, 13553893, +13619429, 13685222, 13751014, 13816551, 13882343, 13948135, 14013672, 14013928, +14079720, 14145513, 14211049, 14276842, 14342634, 14408170, 14473963, 14474219, +14539755, 14605548, 14671340, 14736877, 14802669, 14868461, 14934254, 14934254, +15000046, 15065839, 15131375, 15197168, 15262960, 15328496, 15394289, 15460081, +15460081, 15525874, 15591410, 15591666, 15657203, 15657459, 15722995, 15788787, +15788788, 15854580, 15854580, 15920373, 15985909, 15986165, 16051701, 16051958, +16183286, 16183286, 16183287, 16249079, 16249079, 16380408, 16380408, 16380664, +16446200, 16446457, 16577785, 16577785, 16577785, 16643578, 16709370, 16775163, diff --git a/dev/colormap/analyzed2/blues.png b/dev/colormap/analyzed2/blues.png new file mode 100644 index 0000000..16f4862 Binary files /dev/null and b/dev/colormap/analyzed2/blues.png differ diff --git a/dev/colormap/analyzed2/cividis.csv b/dev/colormap/analyzed2/cividis.csv new file mode 100644 index 0000000..34d84ab --- /dev/null +++ b/dev/colormap/analyzed2/cividis.csv @@ -0,0 +1,33 @@ +# colormap cividis +00008781, 00009039, 00009040, 00009298, 00009556, 00009813, 00009815, 00010073, +00010331, 00010332, 00010590, 00010848, 00010850, 00011108, 00011366, 00011367, +00011625, 00011883, 00012141, 00012143, 00012400, 00012400, 00012656, 00012656, +00275056, 00537456, 00734064, 00930928, 01127791, 01324655, 01455727, 01587055, +01718383, 01849454, 01915246, 02046574, 02177902, 02243438, 02374766, 02440557, +02571629, 02637421, 02768749, 02834285, 02900077, 03031404, 03097196, 03162732, +03228524, 03294316, 03425388, 03491180, 03556972, 03622508, 03688300, 03754092, +03819627, 03885419, 04016747, 04082539, 04148075, 04213867, 04279659, 04345195, +04410987, 04476779, 04542315, 04608107, 04673899, 04739435, 04805227, 04871019, +04936812, 05002348, 05068140, 05133932, 05133932, 05199724, 05265516, 05331052, +05396844, 05462636, 05528172, 05593965, 05659757, 05725549, 05791085, 05856877, +05857133, 05922669, 05988462, 06054254, 06119790, 06185582, 06251374, 06317166, +06382703, 06382959, 06448751, 06514287, 06580079, 06645872, 06711664, 06777200, +06842992, 06843249, 06908785, 06974577, 07040369, 07105906, 07171698, 07237490, +07237747, 07303283, 07369075, 07434867, 07500660, 07566196, 07631989, 07632245, +07697781, 07763574, 07829366, 07895158, 07960695, 07960951, 08026743, 08092535, +08158072, 08223864, 08289656, 08355192, 08420984, 08486776, 08552568, 08618104, +08683896, 08749688, 08749944, 08815480, 08881272, 08947064, 09012856, 09078392, +09144184, 09209976, 09275768, 09341304, 09407095, 09472887, 09538679, 09604215, +09670007, 09735799, 09801591, 09867127, 09932918, 09998710, 10064502, 10130294, +10195830, 10261622, 10327413, 10393205, 10458741, 10524533, 10590324, 10656116, +10721908, 10787444, 10853235, 10919027, 10984819, 11050611, 11116146, 11181938, +11247730, 11313521, 11379313, 11444849, 11510640, 11576432, 11642224, 11708015, +11773551, 11839343, 11905134, 11970926, 12036717, 12102509, 12168045, 12233836, +12299628, 12365419, 12431211, 12497002, 12562538, 12693865, 12759657, 12825448, +12891240, 12957031, 13022567, 13088358, 13154149, 13219941, 13285732, 13351524, +13417315, 13483106, 13548642, 13614433, 13680224, 13811552, 13877343, 13943134, +14008926, 14074717, 14140252, 14206043, 14271834, 14337626, 14403417, 14469208, +14600535, 14666326, 14732117, 14797908, 14863443, 14929234, 14995025, 15060816, +15126607, 15257934, 15323725, 15389516, 15455307, 15521098, 15586888, 15652679, +15718470, 15849796, 15915587, 15981122, 16046912, 16112703, 16178493, 16309819, +16375610, 16441400, 16507190, 16638516, 16638771, 16639284, 16639542, 16639799, diff --git a/dev/colormap/analyzed2/cividis.png b/dev/colormap/analyzed2/cividis.png new file mode 100644 index 0000000..7a4d7ff Binary files /dev/null and b/dev/colormap/analyzed2/cividis.png differ diff --git a/dev/colormap/analyzed2/gray.csv b/dev/colormap/analyzed2/gray.csv new file mode 100644 index 0000000..712fe05 --- /dev/null +++ b/dev/colormap/analyzed2/gray.csv @@ -0,0 +1,33 @@ +# colormap gray +00000000, 00065793, 00131586, 00197379, 00263172, 00328965, 00394758, 00460551, +00526344, 00592137, 00657930, 00723723, 00789516, 00855309, 00921102, 00986895, +01052688, 01118481, 01184274, 01250067, 01315860, 01381653, 01447446, 01513239, +01579032, 01644825, 01710618, 01776411, 01842204, 01907997, 01973790, 02039583, +02105376, 02105376, 02236962, 02302755, 02368548, 02368548, 02500134, 02565927, +02631720, 02631720, 02763306, 02829099, 02894892, 02894892, 03026478, 03092271, +03158064, 03158064, 03289650, 03355443, 03421236, 03421236, 03552822, 03618615, +03684408, 03684408, 03815994, 03881787, 03947580, 03947580, 04079166, 04144959, +04210752, 04276545, 04276545, 04408131, 04473924, 04539717, 04605510, 04671303, +04737096, 04802889, 04802889, 04934475, 05000268, 05066061, 05131854, 05197647, +05263440, 05329233, 05329233, 05460819, 05526612, 05592405, 05658198, 05723991, +05789784, 05855577, 05855577, 05987163, 06052956, 06118749, 06184542, 06250335, +06316128, 06381921, 06381921, 06513507, 06579300, 06645093, 06710886, 06776679, +06842472, 06908265, 06908265, 07039851, 07105644, 07171437, 07237230, 07303023, +07368816, 07434609, 07434609, 07566195, 07631988, 07697781, 07763574, 07829367, +07895160, 07960953, 07960953, 08092539, 08158332, 08224125, 08289918, 08355711, +08421504, 08487297, 08553090, 08618883, 08618883, 08750469, 08816262, 08882055, +08947848, 09013641, 09079434, 09145227, 09211020, 09276813, 09342606, 09408399, +09474192, 09539985, 09605778, 09671571, 09671571, 09803157, 09868950, 09934743, +10000536, 10066329, 10132122, 10197915, 10263708, 10329501, 10395294, 10461087, +10526880, 10592673, 10658466, 10724259, 10724259, 10855845, 10921638, 10987431, +11053224, 11119017, 11184810, 11250603, 11316396, 11382189, 11447982, 11513775, +11579568, 11645361, 11711154, 11776947, 11776947, 11908533, 11974326, 12040119, +12105912, 12171705, 12237498, 12303291, 12369084, 12434877, 12500670, 12566463, +12632256, 12698049, 12763842, 12829635, 12829635, 12961221, 13027014, 13092807, +13158600, 13224393, 13290186, 13355979, 13421772, 13487565, 13553358, 13619151, +13684944, 13750737, 13816530, 13882323, 13882323, 14013909, 14079702, 14145495, +14211288, 14277081, 14342874, 14408667, 14474460, 14540253, 14606046, 14671839, +14737632, 14803425, 14869218, 14935011, 14935011, 15066597, 15132390, 15198183, +15263976, 15329769, 15395562, 15461355, 15527148, 15592941, 15658734, 15724527, +15790320, 15856113, 15921906, 15987699, 15987699, 16119285, 16185078, 16250871, +16316664, 16382457, 16448250, 16514043, 16579836, 16645629, 16711422, 16777215, diff --git a/dev/colormap/analyzed2/gray.png b/dev/colormap/analyzed2/gray.png new file mode 100644 index 0000000..e363919 Binary files /dev/null and b/dev/colormap/analyzed2/gray.png differ diff --git a/dev/colormap/analyzed2/gray_r.csv b/dev/colormap/analyzed2/gray_r.csv new file mode 100644 index 0000000..132e8f3 --- /dev/null +++ b/dev/colormap/analyzed2/gray_r.csv @@ -0,0 +1,33 @@ +# colormap gray_r +16777215, 16711422, 16645629, 16579836, 16514043, 16448250, 16382457, 16316664, +16250871, 16185078, 16119285, 16053492, 15987699, 15921906, 15856113, 15790320, +15724527, 15658734, 15592941, 15527148, 15461355, 15395562, 15329769, 15263976, +15198183, 15132390, 15066597, 15000804, 14935011, 14869218, 14803425, 14737632, +14671839, 14606046, 14540253, 14474460, 14408667, 14342874, 14277081, 14211288, +14145495, 14079702, 14013909, 13882323, 13882323, 13816530, 13750737, 13684944, +13619151, 13553358, 13487565, 13421772, 13355979, 13290186, 13224393, 13158600, +13092807, 13027014, 12961221, 12829635, 12829635, 12763842, 12698049, 12632256, +12566463, 12500670, 12434877, 12369084, 12303291, 12237498, 12171705, 12105912, +12040119, 11974326, 11908533, 11776947, 11776947, 11711154, 11645361, 11579568, +11513775, 11447982, 11382189, 11316396, 11250603, 11184810, 11119017, 11053224, +10987431, 10921638, 10855845, 10724259, 10724259, 10658466, 10592673, 10526880, +10461087, 10395294, 10329501, 10263708, 10197915, 10132122, 10066329, 10000536, +09934743, 09868950, 09803157, 09671571, 09671571, 09605778, 09539985, 09474192, +09408399, 09342606, 09276813, 09211020, 09145227, 09079434, 09013641, 08947848, +08882055, 08816262, 08750469, 08618883, 08618883, 08553090, 08487297, 08421504, +08355711, 08289918, 08224125, 08158332, 08092539, 07960953, 07960953, 07895160, +07829367, 07763574, 07697781, 07631988, 07500402, 07434609, 07434609, 07368816, +07303023, 07237230, 07171437, 07105644, 07039851, 06908265, 06908265, 06842472, +06776679, 06710886, 06645093, 06579300, 06447714, 06381921, 06381921, 06316128, +06250335, 06184542, 06118749, 06052956, 05987163, 05855577, 05855577, 05789784, +05723991, 05658198, 05592405, 05526612, 05395026, 05329233, 05329233, 05263440, +05197647, 05131854, 05066061, 05000268, 04934475, 04802889, 04802889, 04737096, +04671303, 04605510, 04539717, 04473924, 04342338, 04276545, 04276545, 04210752, +04144959, 04079166, 04013373, 03947580, 03881787, 03750201, 03684408, 03684408, +03618615, 03552822, 03487029, 03421236, 03289650, 03223857, 03158064, 03158064, +03092271, 03026478, 02960685, 02894892, 02829099, 02697513, 02631720, 02631720, +02565927, 02500134, 02434341, 02368548, 02236962, 02171169, 02105376, 02105376, +02039583, 01973790, 01907997, 01842204, 01776411, 01644825, 01579032, 01579032, +01513239, 01447446, 01381653, 01315860, 01184274, 01118481, 01052688, 01052688, +00986895, 00921102, 00855309, 00789516, 00723723, 00592137, 00526344, 00526344, +00460551, 00394758, 00328965, 00263172, 00131586, 00065793, 00000000, 00000000, diff --git a/dev/colormap/analyzed2/gray_r.png b/dev/colormap/analyzed2/gray_r.png new file mode 100644 index 0000000..dc67637 Binary files /dev/null and b/dev/colormap/analyzed2/gray_r.png differ diff --git a/dev/colormap/analyzed2/greens.csv b/dev/colormap/analyzed2/greens.csv new file mode 100644 index 0000000..5bb94c8 --- /dev/null +++ b/dev/colormap/analyzed2/greens.csv @@ -0,0 +1,33 @@ +# QRSS VD greens +00017961, 00018217, 00017961, 00018217, 00018474, 00018730, 00018987, 00019243, +00019500, 00019756, 00020012, 00020269, 00020525, 00021038, 00021294, 00021551, +00021807, 00022064, 00022320, 00022576, 00022833, 00023089, 00023346, 00023858, +00024115, 00024371, 00024627, 00024884, 00025140, 00025397, 00025653, 00025910, +00026166, 00026679, 00092215, 00158007, 00223800, 00289592, 00355384, 00421177, +00486969, 00552762, 00618554, 00749626, 00815419, 00881211, 00947003, 01012796, +01078588, 01144381, 01210173, 01275709, 01407038, 01472830, 01538622, 01604415, +01670207, 01736000, 01801792, 01867328, 01933121, 01998913, 02064705, 02196034, +02261826, 02327619, 02393412, 02459204, 02524997, 02591046, 02591303, 02657096, +02722888, 02788937, 02854730, 02920523, 02986316, 03052108, 03118157, 03183950, +03249743, 03315536, 03381585, 03447377, 03513170, 03578963, 03645012, 03645269, +03711061, 03776854, 03842647, 03908696, 03974489, 04040282, 04106074, 04172123, +04237916, 04303709, 04435038, 04566367, 04631903, 04763232, 04894561, 05025890, +05091683, 05223012, 05354085, 05419878, 05551206, 05682535, 05813864, 05879657, +06010986, 06142059, 06207852, 06339181, 06470510, 06601838, 06667631, 06798704, +06930033, 07061362, 07127155, 07258484, 07389813, 07455605, 07586678, 07718007, +07849336, 07915129, 08046457, 08177530, 08243323, 08374651, 08505724, 08571517, +08702845, 08834174, 08899711, 09031039, 09162368, 09228161, 09359233, 09490562, +09556355, 09687427, 09818756, 09884549, 10015877, 10146950, 10212743, 10344071, +10475144, 10540937, 10672265, 10803594, 10869131, 11000459, 11131788, 11197581, +11328653, 11394446, 11525519, 11591311, 11722640, 11788177, 11853969, 11985042, +12050835, 12116627, 12247700, 12313493, 12444565, 12510358, 12641686, 12707223, +12773016, 12838552, 12969881, 13101210, 13166746, 13232539, 13298076, 13429404, +13495197, 13560734, 13692062, 13823135, 13888928, 13954720, 14020257, 14151586, +14217122, 14282915, 14348452, 14414244, 14479781, 14545318, 14611110, 14676647, +14742184, 14807977, 14873513, 14939306, 15004843, 15070379, 15070636, 15136173, +15201709, 15267502, 15333039, 15398831, 15464368, 15529905, 15595698, 15661234, +15726771, 15792564, 15858100, 15923893, 15989430, 16054966, 16120759, 16120760, +16186297, 16252090, 16252091, 16252093, 16252094, 16317632, 16317633, 16317634, +16317636, 16383173, 16383175, 16383432, 16383433, 16448971, 16448972, 16448973, +16448975, 16514512, 16514514, 16514515, 16514516, 16580054, 16580311, 16580312, +16580314, 16645851, 16645853, 16645854, 16645855, 16711393, 16711395, 16777188, diff --git a/dev/colormap/analyzed2/greens.png b/dev/colormap/analyzed2/greens.png new file mode 100644 index 0000000..945d771 Binary files /dev/null and b/dev/colormap/analyzed2/greens.png differ diff --git a/dev/colormap/analyzed2/inferno.csv b/dev/colormap/analyzed2/inferno.csv new file mode 100644 index 0000000..6022573 --- /dev/null +++ b/dev/colormap/analyzed2/inferno.csv @@ -0,0 +1,33 @@ +# colormap inferno +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/dev/colormap/analyzed2/inferno.png b/dev/colormap/analyzed2/inferno.png new file mode 100644 index 0000000..865690b Binary files /dev/null and b/dev/colormap/analyzed2/inferno.png differ diff --git a/dev/colormap/analyzed2/jet.csv b/dev/colormap/analyzed2/jet.csv new file mode 100644 index 0000000..e8774cc --- /dev/null +++ b/dev/colormap/analyzed2/jet.csv @@ -0,0 +1,33 @@ +# colormap jet +00000127, 00000132, 00000136, 00000141, 00000145, 00000150, 00000154, 00000159, +00000163, 00000168, 00000172, 00000177, 00000182, 00000186, 00000191, 00000195, +00000200, 00000204, 00000209, 00000213, 00000218, 00000222, 00000227, 00000232, +00000236, 00000241, 00000245, 00000250, 00000254, 00000255, 00000255, 00000255, +00000255, 00001279, 00002303, 00003327, 00004351, 00005375, 00006399, 00007423, +00008447, 00009471, 00010495, 00011519, 00012543, 00013567, 00014591, 00015615, +00016639, 00017663, 00018687, 00019711, 00020735, 00021759, 00022783, 00023807, +00024831, 00025855, 00026879, 00027903, 00028927, 00029951, 00030975, 00031999, +00033023, 00034047, 00035071, 00036095, 00037119, 00038143, 00039167, 00040191, +00041215, 00042239, 00043263, 00044287, 00045311, 00046335, 00047359, 00048383, +00049407, 00050431, 00051455, 00052479, 00053503, 00054527, 00055551, 00056574, +00057594, 00058615, 00190708, 00388337, 00585965, 00849130, 01046759, 01244388, +01441761, 01638365, 01900506, 02097111, 02293716, 02490320, 02752461, 02949066, +03145671, 03342275, 03604416, 03801021, 03997626, 04194231, 04390835, 04652976, +04849581, 05046186, 05242790, 05504931, 05701536, 05898141, 06094746, 06291350, +06553491, 06750096, 06946701, 07143305, 07405446, 07602051, 07798656, 07995261, +08191865, 08454006, 08650611, 08847216, 09043820, 09305961, 09502566, 09699171, +09895775, 10157916, 10354521, 10551126, 10747731, 10944335, 11206476, 11403081, +11599686, 11796290, 12058431, 12255036, 12451641, 12648246, 12844850, 13106991, +13303596, 13500201, 13696805, 13958946, 14155551, 14352156, 14548760, 14745365, +15007506, 15204111, 15400716, 15597320, 15858693, 16054274, 16249856, 16445440, +16706816, 16771328, 16770304, 16769536, 16768512, 16767488, 16766720, 16765696, +16764672, 16763648, 16762880, 16761856, 16760832, 16760064, 16759040, 16758016, +16756992, 16756224, 16755200, 16754176, 16753408, 16752384, 16751360, 16750592, +16749568, 16748544, 16747520, 16746752, 16745728, 16744704, 16743936, 16742912, +16741888, 16741120, 16740096, 16739072, 16738048, 16737280, 16736256, 16735232, +16734464, 16733440, 16732416, 16731392, 16730624, 16729600, 16728576, 16727808, +16726784, 16725760, 16724992, 16723968, 16722944, 16721920, 16721152, 16720128, +16719104, 16718336, 16717312, 16650752, 16387840, 16059136, 15795968, 15467264, +15204352, 14876672, 14548992, 14286848, 13959168, 13697024, 13369344, 13107200, +12779520, 12517376, 12189696, 11927552, 11599872, 11272192, 11010048, 10682368, +10420224, 10092544, 09830400, 09502720, 09240576, 08912896, 08650752, 08323072, diff --git a/dev/colormap/analyzed2/jet.png b/dev/colormap/analyzed2/jet.png new file mode 100644 index 0000000..d9e5773 Binary files /dev/null and b/dev/colormap/analyzed2/jet.png differ diff --git a/dev/colormap/analyzed2/magma.csv b/dev/colormap/analyzed2/magma.csv new file mode 100644 index 0000000..3921fae --- /dev/null +++ b/dev/colormap/analyzed2/magma.csv @@ -0,0 +1,33 @@ +# colormap magma +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/dev/colormap/analyzed2/magma.png b/dev/colormap/analyzed2/magma.png new file mode 100644 index 0000000..73ee8c7 Binary files /dev/null and b/dev/colormap/analyzed2/magma.png differ diff --git a/dev/colormap/analyzed2/plasma.csv b/dev/colormap/analyzed2/plasma.csv new file mode 100644 index 0000000..ec9ea8e --- /dev/null +++ b/dev/colormap/analyzed2/plasma.csv @@ -0,0 +1,33 @@ +# colormap plasma +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/dev/colormap/analyzed2/plasma.png b/dev/colormap/analyzed2/plasma.png new file mode 100644 index 0000000..d078fd2 Binary files /dev/null and b/dev/colormap/analyzed2/plasma.png differ diff --git a/dev/colormap/analyzed2/turbo.csv b/dev/colormap/analyzed2/turbo.csv new file mode 100644 index 0000000..f1d7eb5 --- /dev/null +++ b/dev/colormap/analyzed2/turbo.csv @@ -0,0 +1,33 @@ +# colormap turbo +03150395, 03216706, 03283018, 03414865, 03481176, 03547487, 03613541, 03679852, +03746162, 03812473, 03878783, 03945093, 03945867, 04011921, 04078230, 04144540, +04210849, 04211622, 04277675, 04278448, 04344757, 04411066, 04411582, 04412354, +04478663, 04479179, 04545486, 04546258, 04546774, 04547545, 04613853, 04614368, +04615139, 04615654, 04616424, 04617195, 04617709, 04618480, 04618994, 04619764, +04620278, 04621048, 04556025, 04556795, 04557308, 04492541, 04427517, 04362750, +04297726, 04232958, 04167934, 04103166, 03972605, 03907836, 03777276, 03712507, +03582201, 03451640, 03386614, 03256309, 03125747, 02995441, 02864879, 02800109, +02669547, 02538985, 02474214, 02343652, 02213089, 02148063, 02017756, 01952730, +01887703, 01822676, 01757650, 01692623, 01627596, 01628106, 01563079, 01563332, +01563842, 01564351, 01630397, 01630650, 01696696, 01762486, 01828276, 01959857, +02025647, 02156972, 02288553, 02419878, 02616739, 02748064, 02944925, 03141786, +03338647, 03535764, 03732625, 03929229, 04191626, 04388487, 04650883, 04913280, +05110140, 05372281, 05634678, 05897074, 06159215, 06421612, 06683752, 06946149, +07208290, 07470431, 07667292, 07929433, 08191574, 08453715, 08715856, 08912461, +09174603, 09371208, 09633350, 09829956, 10026562, 10222912, 10419518, 10615869, +10812475, 10943290, 11139897, 11336247, 11467063, 11663414, 11794485, 11990837, +12186932, 12317748, 12514100, 12644915, 12841267, 12971827, 13168179, 13298995, +13495092, 13625908, 13756468, 13952821, 14083381, 14213941, 14344758, 14540854, +14671414, 14801975, 14932535, 15063096, 15193912, 15258936, 15389497, 15520057, +15585081, 15715641, 15780666, 15910970, 15975994, 16041018, 16171578, 16236601, +16301625, 16366649, 16366136, 16430903, 16495927, 16495414, 16560181, 16559668, +16624435, 16623922, 16623153, 16622384, 16687407, 16686638, 16685869, 16685100, +16618795, 16618025, 16617256, 16616487, 16550182, 16549412, 16483107, 16482338, +16416032, 16415263, 16348958, 16282652, 16216347, 16215578, 16149272, 16082967, +16016662, 15950613, 15884308, 15818003, 15686161, 15620112, 15553807, 15487502, +15355917, 15289613, 15223564, 15091979, 15025674, 14894090, 14828041, 14696456, +14564872, 14498823, 14367239, 14235654, 14104070, 14038021, 13906437, 13774853, +13643524, 13511940, 13314819, 13183235, 13051907, 12920322, 12788738, 12591874, +12460290, 12263169, 12131841, 11934721, 11803393, 11606273, 11409409, 11277825, +11080961, 10884097, 10686977, 10490113, 10293249, 10096129, 09899265, 09702401, +09505537, 09308673, 09111809, 08849409, 08652545, 08455682, 08193282, 07996418, diff --git a/dev/colormap/analyzed2/turbo.png b/dev/colormap/analyzed2/turbo.png new file mode 100644 index 0000000..95d2802 Binary files /dev/null and b/dev/colormap/analyzed2/turbo.png differ diff --git a/dev/colormap/analyzed2/viridis.csv b/dev/colormap/analyzed2/viridis.csv new file mode 100644 index 0000000..5342038 --- /dev/null +++ b/dev/colormap/analyzed2/viridis.csv @@ -0,0 +1,33 @@ +# colormap viridis +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/dev/colormap/analyzed2/viridis.png b/dev/colormap/analyzed2/viridis.png new file mode 100644 index 0000000..9fdd0d0 Binary files /dev/null and b/dev/colormap/analyzed2/viridis.png differ diff --git a/dev/colormap/cmap2cs.py b/dev/colormap/cmap2cs.py new file mode 100644 index 0000000..346017c --- /dev/null +++ b/dev/colormap/cmap2cs.py @@ -0,0 +1,104 @@ +""" +This script converts matplotlib colormaps to a format C# can use. +""" + +import matplotlib.pyplot as plt +import numpy as np + +TEMPLATE = """using System; + +namespace Spectrogram.Colormaps +{ + class CLASSNAME : IColormap + { + public (byte r, byte g, byte b) GetRGB(byte value) + { + byte[] bytes = BitConverter.GetBytes(rgb[value]); + return (bytes[2], bytes[1], bytes[0]); + } + + // RGB values are derived from the CLASSNAME colormap in Matplotlib 3.2.1 (https://matplotlib.org) + private readonly int[] rgb = + { + VALUES + }; + } +} +""" + + +def plotCmap(rgbValues, title="colormap"): + assert isinstance(rgbValues, np.ndarray) + assert rgbValues.shape == (256, 3) + + plt.figure(figsize=(6, 4)) + plt.grid(alpha=.2, ls='--') + plt.plot(rgbValues[:, 0], 'r-') + plt.plot(rgbValues[:, 1], 'g-') + plt.plot(rgbValues[:, 2], 'b-') + plt.plot(np.sum(rgbValues, 1)/3, 'k:') + plt.ylabel("Intensity by Color") + plt.xlabel("Signal Intensity") + plt.title(title) + plt.show() + + +def cmapToRGB(cmapName="jet"): + cmap = plt.get_cmap(cmapName) + rgbValues = np.zeros((256, 3)) + for i in range(256): + r, g, b, a = cmap(i/255) + rgbValues[i, 0] = int(r*255) + rgbValues[i, 1] = int(g*255) + rgbValues[i, 2] = int(b*255) + return rgbValues + + +def rgbToInt(rgbValues): + assert isinstance(rgbValues, np.ndarray) + assert rgbValues.shape == (256, 3) + ints = np.zeros(256, dtype=int) + for i in range(256): + r, g, b = rgbValues[i] + ints[i] = int(r * 2**16 + g * 2**8 + b) + return ints + + +def isStepped(cmapName): + cmap = plt.get_cmap(cmapName) + return cmap(0/255) == cmap(1/255) + + +def makeCsClass(cmapName, intValues): + className = cmapName.capitalize() + className = className.replace("_r", "Reversed") + txt = TEMPLATE.replace("CLASSNAME", className) + + valBlock = "" + for i in range(256): + valBlock += f"{intValues[i]:08d}, " + if i % 8 == 7 and i < 255: + valBlock += "\n " + txt = txt.replace("VALUES", valBlock) + + with open(f"src/Spectrogram/Colormaps/{className}.cs", 'w') as f: + f.write(txt) + + return f"public static Colormap {className} => new Colormap(new Colormaps.{className}());" + + +if __name__ == "__main__": + + statics = [] + for cmapName in plt.colormaps(): + if "_" in cmapName.replace("_r", ""): + continue + if isStepped(cmapName): + continue + print(f"analyzing {cmapName}...") + rgbValues = cmapToRGB(cmapName) + intValues = rgbToInt(rgbValues) + statics.append(makeCsClass(cmapName, intValues)) + statics.sort() + print("\n".join(statics)) + print("DONE") diff --git a/dev/colormap/greens.py b/dev/colormap/greens.py new file mode 100644 index 0000000..b49438c --- /dev/null +++ b/dev/colormap/greens.py @@ -0,0 +1,37 @@ +# from QRSS VD +import matplotlib.pyplot as plt +import numpy as np +colormap_green = [(255, 255, 228), (254, 254, 227), (254, 254, 225), (253, 254, 223), (253, 254, 222), (253, 254, 221), (253, 254, 219), (252, 254, 218), (252, 254, 216), (252, 254, 215), (252, 253, 214), (251, 253, 212), (251, 253, 211), (251, 253, 210), (251, 253, 208), (250, 253, 207), (250, 253, 205), (250, 253, 204), (250, 253, 203), (249, 253, 201), (249, 253, 200), (249, 252, 199), (249, 252, 197), (248, 252, 196), (248, 252, 194), (248, 252, 193), (248, 252, 192), (247, 252, 190), (247, 252, 189), (247, 252, 187), (247, 252, 186), (246, 251, 185), (245, 251, 184), (245, 251, 183), (244, 250, 182), (243, 250, 182), (242, 250, 181), (241, 249, 180), (240, 249, 180), (239, 248, 179), (238, 248, 178), (237, 248, 178), (236, 247, 177), (235, 247, 176), (234, 247, 175), (233, 246, 175), (232, 246, 174), (231, 245, 173), (230, 245, 173), (229, 245, 172), (229, 244, 171), (228, 244, 171), (227, 244, 170), (226, 243, 169), (225, 243, 169), (224, 242, 168), (223, 242, 167), (222, 242, 166), (221, 241, 166), (220, 241, 165), (219, 241, 164), (218, 240, 164), (217, 240, 163), (216, 239, 162), (215, 239, 162), (213, 238, 161), (212, 238, 160), (211, 237, 160), (210, 236, 159), (208, 236, 158), (206, 235, 158), (205, 235, 157), (204, 234, 156), (202, 233, 156), (201, 233, 155), (200, 232, 154), (199, 232, 154), (197, 231, 153), (195, 230, 152), (194, 230, 152), (193, 229, 151), (192, 229, 150), (190, 228, 150), (189, 227, 149), (187, 227, 149), (186, 226, 148), (184, 226, 147), (183, 225, 147), (182, 224, 146), (180, 224, 145), (179, 223, 145), (178, 223, 144), (176, 222, 143), (175, 221, 143), (173, 221, 142), (172, 220, 141), (170, 220, 141), (169, 219, 140), (167, 218, 139), (165, 217, 139), (164, 217, 138), (162, 216, 137), (160, 215, 137), (159, 214, 136), (157, 214, 135), (155, 213, 135), (154, 212, 134), (152, 212, 133), (150, 211, 133), (149, 210, 132), (147, 209, 131), (145, 209, 131), (144, 208, 130), (142, 207, 129), (140, 207, 129), (139, 206, 128), (137, 205, 127), (135, 204, 127), (134, 204, 126), + (132, 203, 125), (130, 202, 125), (129, 201, 124), (127, 201, 123), (125, 200, 123), (124, 199, 122), (122, 199, 121), (120, 198, 121), (119, 197, 120), (117, 196, 119), (115, 195, 118), (113, 195, 117), (112, 194, 117), (110, 193, 116), (108, 192, 115), (107, 191, 114), (105, 190, 113), (103, 189, 112), (101, 189, 111), (100, 188, 110), (98, 187, 110), (96, 186, 109), (94, 185, 108), (93, 184, 107), (91, 184, 106), (89, 183, 105), (88, 182, 104), (86, 181, 103), (84, 180, 102), (82, 179, 102), (81, 178, 101), (79, 178, 100), (77, 177, 99), (76, 176, 98), (74, 175, 97), (72, 174, 96), (70, 173, 95), (69, 173, 95), (67, 172, 94), (65, 171, 93), (64, 170, 92), (63, 169, 91), (62, 167, 90), (61, 166, 90), (60, 165, 89), (59, 164, 88), (58, 162, 87), (57, 161, 86), (56, 160, 85), (55, 159, 85), (55, 158, 84), (54, 156, 83), (53, 155, 82), (52, 154, 81), (51, 153, 81), (50, 151, 80), (49, 150, 79), (48, 149, 78), (47, 148, 77), (46, 146, 76), (45, 145, 76), (44, 144, 75), (43, 143, 74), (42, 142, 73), (41, 140, 72), (40, 139, 72), (39, 138, 71), (39, 137, 70), (38, 135, 69), (37, 134, 68), (36, 133, 68), (35, 132, 67), (34, 131, 66), (33, 130, 66), (31, 129, 65), (30, 128, 65), (29, 127, 65), (28, 126, 64), (27, 126, 64), (26, 125, 64), (25, 124, 63), (24, 123, 63), (23, 122, 62), (22, 121, 62), (21, 120, 62), (19, 119, 61), (18, 119, 61), (17, 118, 61), (16, 117, 60), (15, 116, 60), (14, 115, 59), (13, 114, 59), (12, 113, 59), (11, 112, 58), (9, 112, 58), (8, 111, 58), (7, 110, 57), (6, 109, 57), (5, 108, 56), (4, 107, 56), (3, 106, 56), (2, 105, 55), (1, 104, 55), (0, 104, 55), (0, 102, 54), (0, 101, 54), (0, 100, 53), (0, 99, 53), (0, 98, 52), (0, 97, 52), (0, 96, 51), (0, 95, 51), (0, 94, 51), (0, 93, 50), (0, 91, 50), (0, 90, 49), (0, 89, 49), (0, 88, 48), (0, 87, 48), (0, 86, 48), (0, 85, 47), (0, 84, 47), (0, 83, 46), (0, 82, 46), (0, 80, 45), (0, 79, 45), (0, 78, 44), (0, 77, 44), (0, 76, 44), (0, 75, 43), (0, 74, 43), (0, 73, 42), (0, 72, 42), (0, 71, 41), (0, 70, 41), (0, 71, 41), (0, 70, 41)] +colormap_green.reverse() +colormap_blue = [(255, 247, 251), (254, 246, 250), (253, 245, 250), (252, 244, 249), (252, 244, 249), (252, 244, 249), (250, 243, 249), (250, 242, 248), (249, 242, 248), (249, 241, 248), (249, 241, 248), (247, 240, 247), (247, 240, 247), (246, 239, 247), (246, 239, 246), (246, 239, 246), (244, 238, 246), (244, 237, 245), (243, 237, 245), (243, 236, 245), (242, 236, 245), (241, 235, 244), (241, 235, 244), (240, 234, 244), (240, 234, 243), (239, 233, 243), (238, 233, 243), (238, 232, 243), (237, 232, 242), (237, 231, 242), (236, 231, 242), (235, 230, 241), (235, 230, 241), (234, 229, 241), (233, 228, 240), (232, 228, 240), (231, 227, 240), (230, 226, 239), (229, 226, 239), (228, 225, 238), (227, 224, 238), (227, 224, 238), (226, 223, 237), (225, 222, 237), (224, 221, 237), (223, 221, 236), (222, 220, 236), (221, 219, 235), (220, 219, 235), (220, 218, 235), (219, 217, 234), (218, 217, 234), (217, 216, 234), (216, 215, 233), (215, 215, 233), (214, 214, 232), (213, 213, 232), (213, 212, 232), (212, 212, 231), (211, 211, 231), (210, 210, 231), (209, 210, 230), (208, 209, 230), (207, 208, 229), (206, 208, 229), (205, 207, 229), (204, 206, 228), (202, 206, 228), (201, 205, 228), (199, 205, 227), (198, 204, 227), (197, 203, 227), (196, 203, 226), (194, 202, 226), (193, 201, 226), (191, 201, 225), (190, 200, 225), (189, 200, 225), (187, 199, 224), (186, 198, 224), (185, 198, 224), (184, 197, 223), (182, 196, 223), (181, 196, 223), (180, 195, 222), (178, 195, 222), (177, 194, 221), (176, 193, 221), (174, 193, 221), (173, 192, 220), (172, 191, 220), (170, 191, 220), (169, 190, 219), (168, 190, 219), (166, 189, 219), (165, 188, 218), (163, 188, 218), (162, 187, 218), (160, 186, 217), (159, 186, 217), (157, 185, 216), (156, 185, 216), (154, 184, 216), (152, 183, 215), (151, 183, 215), (149, 182, 215), (148, 181, 214), (146, 181, 214), (145, 180, 213), (143, 179, 213), (141, 179, 213), (140, 178, 212), (138, 178, 212), (137, 177, 212), (135, 176, 211), (134, 176, 211), (132, 175, 210), (130, 174, 210), (129, 174, 210), (127, 173, 209), + (126, 173, 209), (124, 172, 209), (123, 171, 208), (121, 171, 208), (119, 170, 207), (118, 169, 207), (116, 169, 207), (115, 168, 206), (113, 167, 206), (111, 167, 205), (109, 166, 205), (107, 165, 204), (105, 164, 204), (103, 163, 203), (101, 163, 203), (99, 162, 203), (97, 161, 202), (95, 160, 202), (93, 159, 201), (91, 159, 201), (89, 158, 200), (87, 157, 200), (85, 156, 199), (83, 156, 199), (81, 155, 198), (80, 154, 198), (78, 153, 197), (76, 152, 197), (74, 152, 196), (72, 151, 196), (70, 150, 195), (68, 149, 195), (66, 149, 195), (64, 148, 194), (62, 147, 194), (60, 146, 193), (58, 145, 193), (56, 145, 192), (54, 144, 192), (53, 143, 191), (51, 142, 191), (49, 141, 190), (48, 140, 190), (46, 139, 189), (45, 138, 189), (43, 137, 188), (42, 136, 188), (40, 135, 187), (39, 134, 187), (37, 133, 186), (36, 132, 186), (34, 131, 185), (33, 130, 185), (31, 129, 184), (29, 128, 184), (28, 127, 183), (26, 126, 183), (25, 125, 182), (23, 124, 182), (22, 123, 181), (20, 122, 181), (19, 121, 180), (17, 120, 180), (16, 119, 179), (14, 118, 179), (13, 117, 178), (11, 116, 178), (9, 115, 177), (8, 114, 177), (6, 113, 176), (5, 112, 176), (4, 111, 175), (4, 110, 174), (4, 110, 172), (4, 109, 171), (4, 108, 170), (4, 108, 169), (4, 107, 168), (4, 106, 167), (4, 105, 166), (4, 105, 165), (4, 104, 164), (4, 103, 163), (4, 103, 161), (4, 102, 160), (4, 101, 159), (4, 101, 158), (4, 100, 157), (4, 99, 156), (4, 99, 155), (4, 98, 154), (4, 97, 153), (4, 96, 152), (4, 96, 150), (4, 95, 149), (4, 94, 148), (4, 94, 147), (4, 93, 146), (4, 92, 145), (4, 92, 144), (4, 91, 143), (4, 90, 142), (4, 90, 140), (3, 89, 138), (3, 87, 137), (3, 86, 135), (3, 85, 133), (3, 84, 132), (3, 83, 130), (3, 82, 128), (3, 81, 127), (3, 80, 125), (3, 79, 123), (3, 78, 122), (3, 77, 120), (3, 76, 118), (3, 75, 117), (3, 74, 115), (3, 73, 113), (2, 71, 112), (2, 70, 110), (2, 69, 108), (2, 68, 107), (2, 67, 105), (2, 66, 103), (2, 65, 102), (2, 64, 100), (2, 63, 98), (2, 62, 97), (2, 61, 95), (2, 60, 93), (2, 59, 92), (2, 58, 90), (2, 57, 89), (2, 58, 90), (2, 57, 89)] +colormap_blue.reverse() + +if __name__ == "__main__": + rgbValues = np.zeros((256, 4)) + txt = "# QRSS VD blues" + for i, vals in enumerate(colormap_blue): + rgbValues[i, 0] = vals[0] + rgbValues[i, 1] = vals[1] + rgbValues[i, 2] = vals[2] + rgbValues[i, 3] = np.sum(rgbValues[i]) + + val = vals[0] << 16 | vals[1] << 8 | vals[2] + txt += f"{val:08d}, " + if i % 8 == 7: + txt += "\n" + + with open("analyzed2/blues.csv", 'w') as f: + f.write(txt) + + plt.figure(figsize=(6, 4)) + plt.grid(alpha=.2, ls='--') + plt.plot(rgbValues[:, 0], 'r-') + plt.plot(rgbValues[:, 1], 'g-') + plt.plot(rgbValues[:, 2], 'b-') + plt.plot(rgbValues[:, 3]/3, 'k:') + plt.ylabel("Intensity by Color") + plt.xlabel("Signal Intensity") + plt.title("blues colormap") + plt.show() diff --git a/dev/colormap/lopora-brightness.png b/dev/colormap/lopora-brightness.png new file mode 100644 index 0000000..2a14bfd Binary files /dev/null and b/dev/colormap/lopora-brightness.png differ diff --git a/dev/colormap/lopora-display.png b/dev/colormap/lopora-display.png new file mode 100644 index 0000000..efd6840 Binary files /dev/null and b/dev/colormap/lopora-display.png differ diff --git a/dev/colormap/lopora.py b/dev/colormap/lopora.py new file mode 100644 index 0000000..e2d087f --- /dev/null +++ b/dev/colormap/lopora.py @@ -0,0 +1,57 @@ +""" +Attempt to mimic LOPORA colormap from +https://github.com/swharden/Lopora/blob/20afe72416579f8b7d3c8861532c71a95b904066/src/LOPORA-v5a.py#L828-L872 +""" +import numpy +import matplotlib.pyplot as plt + + +def doit(intensityCompression=2, pointCount=256): + + Rs = numpy.ones(pointCount) + Gs = numpy.ones(pointCount) + Bs = numpy.ones(pointCount) + + for n in range(pointCount): + + frac = n / len(Rs) + v = frac ** (1.0 / (1.0 + float(intensityCompression) / 2.0)) + + R = int(v * v * 255 + 0.5) + Rs[n] = int(min(max(R, 0), 255)) + + G = int(v * 255 + 0.5) + Gs[n] = int(min(max(G, 0), 255)) + + B = int(255 * numpy.sqrt(v) + 0.5) + Bs[n] = int(min(max(B, 0), 255)) + + return [Rs, Gs, Bs] + + +def rgbToInt32(r, g, b): + return int(r * 2**16 + g * 2**8 + b) + + +if __name__ == "__main__": + + [r, g, b] = doit() + + for i in range(256): + int32 = rgbToInt32(r[i], g[i], b[i]) + print(f"{int32:010d}, ", end='') + if i % 8 == 7: + print() + + # plt.plot(r, 'r-', alpha=.5) + # plt.plot(g, 'g-', alpha=.5) + # plt.plot(b, 'b-', alpha=.5) + + # plt.grid(alpha=.2, ls='--') + # plt.title("Lopora Style Colormap") + # plt.xlabel("input value") + # plt.ylabel("color intensity") + # plt.tight_layout() + # plt.show() + + print("DONE") diff --git a/dev/colormap/mp.py b/dev/colormap/mp.py new file mode 100644 index 0000000..e299b54 --- /dev/null +++ b/dev/colormap/mp.py @@ -0,0 +1,86 @@ +import matplotlib.pyplot as plt +import pyperclip +import numpy as np +import turbo + +def cmapToCode(cmapName="inferno"): + cmap = plt.get_cmap(cmapName) + + out = f"""\n // {cmapName}\n""" + out += """ public readonly byte[,] RGB =\n {\n""" + for i in range(256): + r, g, b, a = cmap(i/255.0) + out += " {%d, %d, %d},\n" % (r*256, g*256, b*256) + out += " };\n" + + print(out) + pyperclip.copy(out) + print(f"Colormap {cmapName} copied to clipboard.") + + +def rgbToInt32(r, g, b): + return int(r * 2**16 + g * 2**8 + b) + + +def cmapToInt32s(cmapName="viridis"): + cmap = plt.get_cmap(cmapName) + for i in range(256): + r, g, b, a = cmap(i/255.0) + int32 = rgbToInt32(r*255, g*255, b*255) + print(f"{int32:010d}, ", end='') + if i % 8 == 7: + print() + + +def plotCmapCurves(cmapName="viridis"): + cmap = plt.get_cmap(cmapName) + rgbValues = np.zeros((256, 4)) + for i in range(256): + r, g, b, a = cmap(i/255.0) + rgbValues[i, 0] = r*255 + rgbValues[i, 1] = g*255 + rgbValues[i, 2] = b*255 + rgbValues[i, 3] = np.sum(rgbValues[i]) + + plt.figure(figsize=(6, 4)) + plt.grid(alpha=.2, ls='--') + plt.plot(rgbValues[:, 0], 'r-') + plt.plot(rgbValues[:, 1], 'g-') + plt.plot(rgbValues[:, 2], 'b-') + plt.plot(rgbValues[:, 3]/3, 'k:') + plt.ylabel("Intensity by Color") + plt.xlabel("Signal Intensity") + plt.title(f"{cmapName} colormap") + + +def colormapToIntegerText(cmapName="viridis"): + txt = f"# colormap {cmapName}\n" + + cmap = plt.get_cmap(cmapName) + for i in range(256): + r, g, b, a = cmap(i/255.0) + r = int(r*255) + g = int(g*255) + b = int(b*255) + val = r << 16 | g << 8 | b + txt += f"{val:08d}, " + if i % 8 == 7: + txt += "\n" + + with open(f"analyzed2/{cmapName}.csv", 'w') as f: + f.write(txt) + + +if __name__ == "__main__": + + cmapNames = ["cividis", "gray", "gray_r", "inferno", + "jet", "magma", "plasma", "viridis", "turbo"] + + for cmapName in cmapNames: + print(f"analyzing {cmapName}...") + colormapToIntegerText(cmapName) + plotCmapCurves(cmapName) + plt.savefig(f"analyzed2/{cmapName}.png") + plt.close() + +print("DONE") diff --git a/dev/LUT generation/qrssvd.py b/dev/colormap/qrssvd.py similarity index 100% rename from dev/LUT generation/qrssvd.py rename to dev/colormap/qrssvd.py diff --git a/dev/colormap/turbo.py b/dev/colormap/turbo.py new file mode 100644 index 0000000..bf564df --- /dev/null +++ b/dev/colormap/turbo.py @@ -0,0 +1,293 @@ + +# This script registers the "turbo" colormap to matplotlib, and the reversed version as "turbo_r" +# Reference: https://ai.googleblog.com/2019/08/turbo-improved-rainbow-colormap-for.html + +import numpy as np +import matplotlib.pyplot as plt + +turbo_colormap_data = np.array( + [[0.18995,0.07176,0.23217], + [0.19483,0.08339,0.26149], + [0.19956,0.09498,0.29024], + [0.20415,0.10652,0.31844], + [0.20860,0.11802,0.34607], + [0.21291,0.12947,0.37314], + [0.21708,0.14087,0.39964], + [0.22111,0.15223,0.42558], + [0.22500,0.16354,0.45096], + [0.22875,0.17481,0.47578], + [0.23236,0.18603,0.50004], + [0.23582,0.19720,0.52373], + [0.23915,0.20833,0.54686], + [0.24234,0.21941,0.56942], + [0.24539,0.23044,0.59142], + [0.24830,0.24143,0.61286], + [0.25107,0.25237,0.63374], + [0.25369,0.26327,0.65406], + [0.25618,0.27412,0.67381], + [0.25853,0.28492,0.69300], + [0.26074,0.29568,0.71162], + [0.26280,0.30639,0.72968], + [0.26473,0.31706,0.74718], + [0.26652,0.32768,0.76412], + [0.26816,0.33825,0.78050], + [0.26967,0.34878,0.79631], + [0.27103,0.35926,0.81156], + [0.27226,0.36970,0.82624], + [0.27334,0.38008,0.84037], + [0.27429,0.39043,0.85393], + [0.27509,0.40072,0.86692], + [0.27576,0.41097,0.87936], + [0.27628,0.42118,0.89123], + [0.27667,0.43134,0.90254], + [0.27691,0.44145,0.91328], + [0.27701,0.45152,0.92347], + [0.27698,0.46153,0.93309], + [0.27680,0.47151,0.94214], + [0.27648,0.48144,0.95064], + [0.27603,0.49132,0.95857], + [0.27543,0.50115,0.96594], + [0.27469,0.51094,0.97275], + [0.27381,0.52069,0.97899], + [0.27273,0.53040,0.98461], + [0.27106,0.54015,0.98930], + [0.26878,0.54995,0.99303], + [0.26592,0.55979,0.99583], + [0.26252,0.56967,0.99773], + [0.25862,0.57958,0.99876], + [0.25425,0.58950,0.99896], + [0.24946,0.59943,0.99835], + [0.24427,0.60937,0.99697], + [0.23874,0.61931,0.99485], + [0.23288,0.62923,0.99202], + [0.22676,0.63913,0.98851], + [0.22039,0.64901,0.98436], + [0.21382,0.65886,0.97959], + [0.20708,0.66866,0.97423], + [0.20021,0.67842,0.96833], + [0.19326,0.68812,0.96190], + [0.18625,0.69775,0.95498], + [0.17923,0.70732,0.94761], + [0.17223,0.71680,0.93981], + [0.16529,0.72620,0.93161], + [0.15844,0.73551,0.92305], + [0.15173,0.74472,0.91416], + [0.14519,0.75381,0.90496], + [0.13886,0.76279,0.89550], + [0.13278,0.77165,0.88580], + [0.12698,0.78037,0.87590], + [0.12151,0.78896,0.86581], + [0.11639,0.79740,0.85559], + [0.11167,0.80569,0.84525], + [0.10738,0.81381,0.83484], + [0.10357,0.82177,0.82437], + [0.10026,0.82955,0.81389], + [0.09750,0.83714,0.80342], + [0.09532,0.84455,0.79299], + [0.09377,0.85175,0.78264], + [0.09287,0.85875,0.77240], + [0.09267,0.86554,0.76230], + [0.09320,0.87211,0.75237], + [0.09451,0.87844,0.74265], + [0.09662,0.88454,0.73316], + [0.09958,0.89040,0.72393], + [0.10342,0.89600,0.71500], + [0.10815,0.90142,0.70599], + [0.11374,0.90673,0.69651], + [0.12014,0.91193,0.68660], + [0.12733,0.91701,0.67627], + [0.13526,0.92197,0.66556], + [0.14391,0.92680,0.65448], + [0.15323,0.93151,0.64308], + [0.16319,0.93609,0.63137], + [0.17377,0.94053,0.61938], + [0.18491,0.94484,0.60713], + [0.19659,0.94901,0.59466], + [0.20877,0.95304,0.58199], + [0.22142,0.95692,0.56914], + [0.23449,0.96065,0.55614], + [0.24797,0.96423,0.54303], + [0.26180,0.96765,0.52981], + [0.27597,0.97092,0.51653], + [0.29042,0.97403,0.50321], + [0.30513,0.97697,0.48987], + [0.32006,0.97974,0.47654], + [0.33517,0.98234,0.46325], + [0.35043,0.98477,0.45002], + [0.36581,0.98702,0.43688], + [0.38127,0.98909,0.42386], + [0.39678,0.99098,0.41098], + [0.41229,0.99268,0.39826], + [0.42778,0.99419,0.38575], + [0.44321,0.99551,0.37345], + [0.45854,0.99663,0.36140], + [0.47375,0.99755,0.34963], + [0.48879,0.99828,0.33816], + [0.50362,0.99879,0.32701], + [0.51822,0.99910,0.31622], + [0.53255,0.99919,0.30581], + [0.54658,0.99907,0.29581], + [0.56026,0.99873,0.28623], + [0.57357,0.99817,0.27712], + [0.58646,0.99739,0.26849], + [0.59891,0.99638,0.26038], + [0.61088,0.99514,0.25280], + [0.62233,0.99366,0.24579], + [0.63323,0.99195,0.23937], + [0.64362,0.98999,0.23356], + [0.65394,0.98775,0.22835], + [0.66428,0.98524,0.22370], + [0.67462,0.98246,0.21960], + [0.68494,0.97941,0.21602], + [0.69525,0.97610,0.21294], + [0.70553,0.97255,0.21032], + [0.71577,0.96875,0.20815], + [0.72596,0.96470,0.20640], + [0.73610,0.96043,0.20504], + [0.74617,0.95593,0.20406], + [0.75617,0.95121,0.20343], + [0.76608,0.94627,0.20311], + [0.77591,0.94113,0.20310], + [0.78563,0.93579,0.20336], + [0.79524,0.93025,0.20386], + [0.80473,0.92452,0.20459], + [0.81410,0.91861,0.20552], + [0.82333,0.91253,0.20663], + [0.83241,0.90627,0.20788], + [0.84133,0.89986,0.20926], + [0.85010,0.89328,0.21074], + [0.85868,0.88655,0.21230], + [0.86709,0.87968,0.21391], + [0.87530,0.87267,0.21555], + [0.88331,0.86553,0.21719], + [0.89112,0.85826,0.21880], + [0.89870,0.85087,0.22038], + [0.90605,0.84337,0.22188], + [0.91317,0.83576,0.22328], + [0.92004,0.82806,0.22456], + [0.92666,0.82025,0.22570], + [0.93301,0.81236,0.22667], + [0.93909,0.80439,0.22744], + [0.94489,0.79634,0.22800], + [0.95039,0.78823,0.22831], + [0.95560,0.78005,0.22836], + [0.96049,0.77181,0.22811], + [0.96507,0.76352,0.22754], + [0.96931,0.75519,0.22663], + [0.97323,0.74682,0.22536], + [0.97679,0.73842,0.22369], + [0.98000,0.73000,0.22161], + [0.98289,0.72140,0.21918], + [0.98549,0.71250,0.21650], + [0.98781,0.70330,0.21358], + [0.98986,0.69382,0.21043], + [0.99163,0.68408,0.20706], + [0.99314,0.67408,0.20348], + [0.99438,0.66386,0.19971], + [0.99535,0.65341,0.19577], + [0.99607,0.64277,0.19165], + [0.99654,0.63193,0.18738], + [0.99675,0.62093,0.18297], + [0.99672,0.60977,0.17842], + [0.99644,0.59846,0.17376], + [0.99593,0.58703,0.16899], + [0.99517,0.57549,0.16412], + [0.99419,0.56386,0.15918], + [0.99297,0.55214,0.15417], + [0.99153,0.54036,0.14910], + [0.98987,0.52854,0.14398], + [0.98799,0.51667,0.13883], + [0.98590,0.50479,0.13367], + [0.98360,0.49291,0.12849], + [0.98108,0.48104,0.12332], + [0.97837,0.46920,0.11817], + [0.97545,0.45740,0.11305], + [0.97234,0.44565,0.10797], + [0.96904,0.43399,0.10294], + [0.96555,0.42241,0.09798], + [0.96187,0.41093,0.09310], + [0.95801,0.39958,0.08831], + [0.95398,0.38836,0.08362], + [0.94977,0.37729,0.07905], + [0.94538,0.36638,0.07461], + [0.94084,0.35566,0.07031], + [0.93612,0.34513,0.06616], + [0.93125,0.33482,0.06218], + [0.92623,0.32473,0.05837], + [0.92105,0.31489,0.05475], + [0.91572,0.30530,0.05134], + [0.91024,0.29599,0.04814], + [0.90463,0.28696,0.04516], + [0.89888,0.27824,0.04243], + [0.89298,0.26981,0.03993], + [0.88691,0.26152,0.03753], + [0.88066,0.25334,0.03521], + [0.87422,0.24526,0.03297], + [0.86760,0.23730,0.03082], + [0.86079,0.22945,0.02875], + [0.85380,0.22170,0.02677], + [0.84662,0.21407,0.02487], + [0.83926,0.20654,0.02305], + [0.83172,0.19912,0.02131], + [0.82399,0.19182,0.01966], + [0.81608,0.18462,0.01809], + [0.80799,0.17753,0.01660], + [0.79971,0.17055,0.01520], + [0.79125,0.16368,0.01387], + [0.78260,0.15693,0.01264], + [0.77377,0.15028,0.01148], + [0.76476,0.14374,0.01041], + [0.75556,0.13731,0.00942], + [0.74617,0.13098,0.00851], + [0.73661,0.12477,0.00769], + [0.72686,0.11867,0.00695], + [0.71692,0.11268,0.00629], + [0.70680,0.10680,0.00571], + [0.69650,0.10102,0.00522], + [0.68602,0.09536,0.00481], + [0.67535,0.08980,0.00449], + [0.66449,0.08436,0.00424], + [0.65345,0.07902,0.00408], + [0.64223,0.07380,0.00401], + [0.63082,0.06868,0.00401], + [0.61923,0.06367,0.00410], + [0.60746,0.05878,0.00427], + [0.59550,0.05399,0.00453], + [0.58336,0.04931,0.00486], + [0.57103,0.04474,0.00529], + [0.55852,0.04028,0.00579], + [0.54583,0.03593,0.00638], + [0.53295,0.03169,0.00705], + [0.51989,0.02756,0.00780], + [0.50664,0.02354,0.00863], + [0.49321,0.01963,0.00955], + [0.47960,0.01583,0.01055]]) + + + + +def RGBToPyCmap(rgbdata): + nsteps = rgbdata.shape[0] + stepaxis = np.linspace(0, 1, nsteps) + + rdata=[]; gdata=[]; bdata=[] + for istep in range(nsteps): + r = rgbdata[istep,0] + g = rgbdata[istep,1] + b = rgbdata[istep,2] + rdata.append((stepaxis[istep], r, r)) + gdata.append((stepaxis[istep], g, g)) + bdata.append((stepaxis[istep], b, b)) + + mpl_data = {'red': rdata, + 'green': gdata, + 'blue': bdata} + + return mpl_data + + +mpl_data = RGBToPyCmap(turbo_colormap_data) +plt.register_cmap(name='turbo', data=mpl_data, lut=turbo_colormap_data.shape[0]) + +mpl_data_r = RGBToPyCmap(turbo_colormap_data[::-1,:]) +plt.register_cmap(name='turbo_r', data=mpl_data_r, lut=turbo_colormap_data.shape[0]) \ No newline at end of file diff --git a/dev/compiled-demos/SpectrogramDemo.zip b/dev/compiled-demos/SpectrogramDemo.zip deleted file mode 100644 index 85fe561..0000000 Binary files a/dev/compiled-demos/SpectrogramDemo.zip and /dev/null differ diff --git a/dev/graphics/hal-Argo.png b/dev/graphics/hal-Argo.png new file mode 100644 index 0000000..6ebfaa9 Binary files /dev/null and b/dev/graphics/hal-Argo.png differ diff --git a/dev/graphics/hal-Blues.png b/dev/graphics/hal-Blues.png new file mode 100644 index 0000000..af9c929 Binary files /dev/null and b/dev/graphics/hal-Blues.png differ diff --git a/dev/graphics/hal-Grayscale.png b/dev/graphics/hal-Grayscale.png new file mode 100644 index 0000000..63927e6 Binary files /dev/null and b/dev/graphics/hal-Grayscale.png differ diff --git a/dev/graphics/hal-GrayscaleR.png b/dev/graphics/hal-GrayscaleR.png new file mode 100644 index 0000000..0770ffe Binary files /dev/null and b/dev/graphics/hal-GrayscaleR.png differ diff --git a/dev/graphics/hal-Greens.png b/dev/graphics/hal-Greens.png new file mode 100644 index 0000000..a15a81d Binary files /dev/null and b/dev/graphics/hal-Greens.png differ diff --git a/dev/graphics/hal-Inferno.png b/dev/graphics/hal-Inferno.png new file mode 100644 index 0000000..643ba8b Binary files /dev/null and b/dev/graphics/hal-Inferno.png differ diff --git a/dev/graphics/hal-Lopora.png b/dev/graphics/hal-Lopora.png new file mode 100644 index 0000000..d79fd74 Binary files /dev/null and b/dev/graphics/hal-Lopora.png differ diff --git a/dev/graphics/hal-Magma.png b/dev/graphics/hal-Magma.png new file mode 100644 index 0000000..8dad373 Binary files /dev/null and b/dev/graphics/hal-Magma.png differ diff --git a/dev/graphics/hal-Plasma.png b/dev/graphics/hal-Plasma.png new file mode 100644 index 0000000..8947860 Binary files /dev/null and b/dev/graphics/hal-Plasma.png differ diff --git a/dev/graphics/hal-Turbo.png b/dev/graphics/hal-Turbo.png new file mode 100644 index 0000000..3382989 Binary files /dev/null and b/dev/graphics/hal-Turbo.png differ diff --git a/dev/graphics/hal-Viridis.png b/dev/graphics/hal-Viridis.png new file mode 100644 index 0000000..ef34eb3 Binary files /dev/null and b/dev/graphics/hal-Viridis.png differ diff --git a/dev/graphics/hal-spectrogram.png b/dev/graphics/hal-spectrogram.png new file mode 100644 index 0000000..fbdb2d8 Binary files /dev/null and b/dev/graphics/hal-spectrogram.png differ diff --git a/dev/graphics/hal.png b/dev/graphics/hal.png new file mode 100644 index 0000000..80dc981 Binary files /dev/null and b/dev/graphics/hal.png differ diff --git a/dev/graphics/halMel-LinearCropped.png b/dev/graphics/halMel-LinearCropped.png new file mode 100644 index 0000000..9bf9e47 Binary files /dev/null and b/dev/graphics/halMel-LinearCropped.png differ diff --git a/dev/graphics/halMel-MelScale.png b/dev/graphics/halMel-MelScale.png new file mode 100644 index 0000000..432a64c Binary files /dev/null and b/dev/graphics/halMel-MelScale.png differ diff --git a/dev/graphics/spectrogram-song.png b/dev/graphics/spectrogram-song.png new file mode 100644 index 0000000..9486ae5 Binary files /dev/null and b/dev/graphics/spectrogram-song.png differ diff --git a/dev/graphics/theory-of-operation.png b/dev/graphics/theory-of-operation.png deleted file mode 100644 index 751a41d..0000000 Binary files a/dev/graphics/theory-of-operation.png and /dev/null differ diff --git a/dev/graphics/theory-of-operation.svg b/dev/graphics/theory-of-operation.svg deleted file mode 100644 index 2431a8a..0000000 --- a/dev/graphics/theory-of-operation.svg +++ /dev/null @@ -1,746 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - New data is added with:AddExtend()AddCircular()AddScroll() - Time-series data is stored in signalData is processed as it is added - - - The first fftSize points are copiedinto segment for analysis.It is then multiplied by a window function. - - - - X - - - - - The FFT is calculated and the output is stored in fftsffts contains arrays with fftSize/2 points.Arrays contain power from 0 to 0.5/bitrate Hz.Frequency resolution is bitrate/fftSize Hz. - - - Theory of operation - Spectrogram - The first step points are deleted from signal, shifting the next analysis forward. This procedure repeats untilsignal.Length < fftSize - - GetBitmap() converts ffts to an image. - - - - - - - - - List<float[]> ffts - FFT - - float[] fft - float[] segment - List<float> signal - http://www.github.com/swharden/Spectrogram - - diff --git a/data/screenshot7.gif b/dev/microphone-spectrogram.gif similarity index 60% rename from data/screenshot7.gif rename to dev/microphone-spectrogram.gif index acb3e9a..2180394 100644 Binary files a/data/screenshot7.gif and b/dev/microphone-spectrogram.gif differ diff --git a/dev/python/readwav.py b/dev/python/readwav.py new file mode 100644 index 0000000..7515c8d --- /dev/null +++ b/dev/python/readwav.py @@ -0,0 +1,15 @@ +""" +sample rate: 44100 +values: 166671 +value 12345: 4435 +""" +from scipy.io import wavfile +import pathlib +PATH_HERE = pathlib.Path(__file__).parent +PATH_DATA = PATH_HERE.joinpath("../../data") + +if __name__ == "__main__": + for wavFilePath in PATH_DATA.glob("*.wav"): + wavFilePath = PATH_DATA.joinpath(wavFilePath) + samplerate, data = wavfile.read(wavFilePath) + print(f"{wavFilePath.name}, {samplerate}, {len(data)}") diff --git a/dev/FFTcalc/App.config b/dev/sff/SffViewer/App.config similarity index 100% rename from dev/FFTcalc/App.config rename to dev/sff/SffViewer/App.config diff --git a/dev/sff/SffViewer/Form1.Designer.cs b/dev/sff/SffViewer/Form1.Designer.cs new file mode 100644 index 0000000..45413c5 --- /dev/null +++ b/dev/sff/SffViewer/Form1.Designer.cs @@ -0,0 +1,177 @@ +namespace SffViewer +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.lblFileName = new System.Windows.Forms.Label(); + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.cbColormap = new System.Windows.Forms.ComboBox(); + this.tbBrightness = new System.Windows.Forms.TrackBar(); + this.label2 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.cbDecibels = new System.Windows.Forms.CheckBox(); + this.cbStretch = new System.Windows.Forms.CheckBox(); + this.label3 = new System.Windows.Forms.Label(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.tbBrightness)).BeginInit(); + this.SuspendLayout(); + // + // lblFileName + // + this.lblFileName.AutoSize = true; + this.lblFileName.Location = new System.Drawing.Point(12, 9); + this.lblFileName.Name = "lblFileName"; + this.lblFileName.Size = new System.Drawing.Size(226, 13); + this.lblFileName.TabIndex = 0; + this.lblFileName.Text = "Load a SFF file by dragging it onto this window"; + // + // pictureBox1 + // + this.pictureBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.pictureBox1.BackColor = System.Drawing.SystemColors.Control; + this.pictureBox1.Location = new System.Drawing.Point(12, 96); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(544, 279); + this.pictureBox1.TabIndex = 1; + this.pictureBox1.TabStop = false; + // + // cbColormap + // + this.cbColormap.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbColormap.FormattingEnabled = true; + this.cbColormap.Location = new System.Drawing.Point(15, 56); + this.cbColormap.Name = "cbColormap"; + this.cbColormap.Size = new System.Drawing.Size(121, 21); + this.cbColormap.TabIndex = 2; + this.cbColormap.SelectedIndexChanged += new System.EventHandler(this.cbColormap_SelectedIndexChanged); + // + // tbBrightness + // + this.tbBrightness.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbBrightness.AutoSize = false; + this.tbBrightness.Location = new System.Drawing.Point(274, 56); + this.tbBrightness.Maximum = 50; + this.tbBrightness.Name = "tbBrightness"; + this.tbBrightness.Size = new System.Drawing.Size(282, 34); + this.tbBrightness.TabIndex = 3; + this.tbBrightness.Value = 5; + this.tbBrightness.Scroll += new System.EventHandler(this.tbBrightness_Scroll); + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(12, 40); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(51, 13); + this.label2.TabIndex = 4; + this.label2.Text = "Colormap"; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(271, 40); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(56, 13); + this.label4.TabIndex = 6; + this.label4.Text = "Brightness"; + // + // cbDecibels + // + this.cbDecibels.AutoSize = true; + this.cbDecibels.Location = new System.Drawing.Point(142, 59); + this.cbDecibels.Name = "cbDecibels"; + this.cbDecibels.Size = new System.Drawing.Size(62, 17); + this.cbDecibels.TabIndex = 7; + this.cbDecibels.Text = "log (dB)"; + this.cbDecibels.UseVisualStyleBackColor = true; + this.cbDecibels.CheckedChanged += new System.EventHandler(this.cbDecibels_CheckedChanged); + // + // cbStretch + // + this.cbStretch.AutoSize = true; + this.cbStretch.Location = new System.Drawing.Point(210, 59); + this.cbStretch.Name = "cbStretch"; + this.cbStretch.Size = new System.Drawing.Size(58, 17); + this.cbStretch.TabIndex = 8; + this.cbStretch.Text = "stretch"; + this.cbStretch.UseVisualStyleBackColor = true; + this.cbStretch.CheckedChanged += new System.EventHandler(this.cbStretch_CheckedChanged); + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(139, 40); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(77, 13); + this.label3.TabIndex = 9; + this.label3.Text = "Image Settings"; + // + // Form1 + // + this.AllowDrop = true; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScroll = true; + this.ClientSize = new System.Drawing.Size(568, 387); + this.Controls.Add(this.label3); + this.Controls.Add(this.cbStretch); + this.Controls.Add(this.cbDecibels); + this.Controls.Add(this.label4); + this.Controls.Add(this.label2); + this.Controls.Add(this.tbBrightness); + this.Controls.Add(this.cbColormap); + this.Controls.Add(this.pictureBox1); + this.Controls.Add(this.lblFileName); + this.Name = "Form1"; + this.Text = "SFF Viewer"; + this.DragDrop += new System.Windows.Forms.DragEventHandler(this.Form1_DragDrop); + this.DragEnter += new System.Windows.Forms.DragEventHandler(this.Form1_DragEnter); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.tbBrightness)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label lblFileName; + private System.Windows.Forms.PictureBox pictureBox1; + private System.Windows.Forms.ComboBox cbColormap; + private System.Windows.Forms.TrackBar tbBrightness; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.CheckBox cbDecibels; + private System.Windows.Forms.CheckBox cbStretch; + private System.Windows.Forms.Label label3; + } +} + diff --git a/dev/sff/SffViewer/Form1.cs b/dev/sff/SffViewer/Form1.cs new file mode 100644 index 0000000..a795ae6 --- /dev/null +++ b/dev/sff/SffViewer/Form1.cs @@ -0,0 +1,89 @@ +using Spectrogram; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace SffViewer +{ + public partial class Form1 : Form + { + Colormap[] colormaps = Colormap.GetColormaps(); + public Form1() + { + InitializeComponent(); + cbColormap.Items.AddRange(colormaps.Select(x => x.Name).ToArray()); + cbColormap.SelectedIndex = cbColormap.Items.IndexOf("Viridis"); + + string startupSffFile = "../../../hal.sff"; + if (System.IO.File.Exists(startupSffFile)) + LoadSFF(startupSffFile); + } + + private void Form1_Load(object sender, EventArgs e) + { + } + + SFF sff; + private void LoadSFF(string filePath) + { + sff = new SFF(filePath); + Redraw(); + } + + private void Redraw() + { + if (sff is null) + return; + Colormap cmap = colormaps[cbColormap.SelectedIndex]; + Bitmap bmp = Spectrogram.Image.GetBitmap(sff.Ffts, cmap, tbBrightness.Value * .2, cbDecibels.Checked); + pictureBox1.Image?.Dispose(); + pictureBox1.Image = bmp; + } + + private void Form1_DragEnter(object sender, DragEventArgs e) + { + if (e.Data.GetDataPresent(DataFormats.FileDrop)) + e.Effect = DragDropEffects.Copy; + } + + private void Form1_DragDrop(object sender, DragEventArgs e) + { + string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop); + foreach (string path in paths) + { + if (path.EndsWith(".sff", StringComparison.OrdinalIgnoreCase)) + { + lblFileName.Text = System.IO.Path.GetFileName(path); + LoadSFF(path); + return; + } + } + } + + private void cbColormap_SelectedIndexChanged(object sender, EventArgs e) + { + Redraw(); + } + + private void cbDecibels_CheckedChanged(object sender, EventArgs e) + { + Redraw(); + } + + private void tbBrightness_Scroll(object sender, EventArgs e) + { + Redraw(); + } + + private void cbStretch_CheckedChanged(object sender, EventArgs e) + { + pictureBox1.SizeMode = cbStretch.Checked ? PictureBoxSizeMode.Zoom : PictureBoxSizeMode.Normal; + } + } +} diff --git a/dev/FFTcalc/Form1.resx b/dev/sff/SffViewer/Form1.resx similarity index 100% rename from dev/FFTcalc/Form1.resx rename to dev/sff/SffViewer/Form1.resx diff --git a/dev/FFTcalc/Program.cs b/dev/sff/SffViewer/Program.cs similarity index 96% rename from dev/FFTcalc/Program.cs rename to dev/sff/SffViewer/Program.cs index 4907f18..d24dc29 100644 --- a/dev/FFTcalc/Program.cs +++ b/dev/sff/SffViewer/Program.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using System.Windows.Forms; -namespace FFTcalc +namespace SffViewer { static class Program { diff --git a/dev/FFTcalc/Properties/AssemblyInfo.cs b/dev/sff/SffViewer/Properties/AssemblyInfo.cs similarity index 90% rename from dev/FFTcalc/Properties/AssemblyInfo.cs rename to dev/sff/SffViewer/Properties/AssemblyInfo.cs index ba395cf..3246c19 100644 --- a/dev/FFTcalc/Properties/AssemblyInfo.cs +++ b/dev/sff/SffViewer/Properties/AssemblyInfo.cs @@ -5,11 +5,11 @@ // 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("FFTcalc")] +[assembly: AssemblyTitle("SffViewer")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FFTcalc")] +[assembly: AssemblyProduct("SffViewer")] [assembly: AssemblyCopyright("Copyright © 2020")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -20,7 +20,7 @@ [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("6c742189-d1c3-46ce-84eb-eb5cec9312f0")] +[assembly: Guid("9478208d-60c7-4f6a-b2e4-6325d38139da")] // Version information for an assembly consists of the following four values: // diff --git a/dev/FFTcalc/Properties/Resources.Designer.cs b/dev/sff/SffViewer/Properties/Resources.Designer.cs similarity index 94% rename from dev/FFTcalc/Properties/Resources.Designer.cs rename to dev/sff/SffViewer/Properties/Resources.Designer.cs index efd5027..7809b09 100644 --- a/dev/FFTcalc/Properties/Resources.Designer.cs +++ b/dev/sff/SffViewer/Properties/Resources.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace FFTcalc.Properties +namespace SffViewer.Properties { @@ -44,7 +44,7 @@ internal Resources() { if ((resourceMan == null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FFTcalc.Properties.Resources", typeof(Resources).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SffViewer.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; diff --git a/dev/FFTcalc/Properties/Resources.resx b/dev/sff/SffViewer/Properties/Resources.resx similarity index 100% rename from dev/FFTcalc/Properties/Resources.resx rename to dev/sff/SffViewer/Properties/Resources.resx diff --git a/dev/FFTcalc/Properties/Settings.Designer.cs b/dev/sff/SffViewer/Properties/Settings.Designer.cs similarity index 97% rename from dev/FFTcalc/Properties/Settings.Designer.cs rename to dev/sff/SffViewer/Properties/Settings.Designer.cs index 5719da2..2afa7a6 100644 --- a/dev/FFTcalc/Properties/Settings.Designer.cs +++ b/dev/sff/SffViewer/Properties/Settings.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace FFTcalc.Properties +namespace SffViewer.Properties { diff --git a/dev/FFTcalc/Properties/Settings.settings b/dev/sff/SffViewer/Properties/Settings.settings similarity index 100% rename from dev/FFTcalc/Properties/Settings.settings rename to dev/sff/SffViewer/Properties/Settings.settings diff --git a/dev/sff/SffViewer/SffViewer.csproj b/dev/sff/SffViewer/SffViewer.csproj new file mode 100644 index 0000000..58946c2 --- /dev/null +++ b/dev/sff/SffViewer/SffViewer.csproj @@ -0,0 +1,16 @@ + + + net5.0-windows + WinExe + false + true + true + + + + + + + + + \ No newline at end of file diff --git a/dev/sff/SffViewer/packages.config b/dev/sff/SffViewer/packages.config new file mode 100644 index 0000000..ed03785 --- /dev/null +++ b/dev/sff/SffViewer/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/dev/sff/SffViewer/screenshot.png b/dev/sff/SffViewer/screenshot.png new file mode 100644 index 0000000..a89ea47 Binary files /dev/null and b/dev/sff/SffViewer/screenshot.png differ diff --git a/dev/sff/hal.png b/dev/sff/hal.png new file mode 100644 index 0000000..0ae9a2e Binary files /dev/null and b/dev/sff/hal.png differ diff --git a/dev/sff/hal.sff b/dev/sff/hal.sff new file mode 100644 index 0000000..85557c2 Binary files /dev/null and b/dev/sff/hal.sff differ diff --git a/dev/sff/halMel.png b/dev/sff/halMel.png new file mode 100644 index 0000000..5bed878 Binary files /dev/null and b/dev/sff/halMel.png differ diff --git a/dev/sff/halMel.sff b/dev/sff/halMel.sff new file mode 100644 index 0000000..a23b726 Binary files /dev/null and b/dev/sff/halMel.sff differ diff --git a/dev/sff/python/hal.sff.png b/dev/sff/python/hal.sff.png new file mode 100644 index 0000000..0ae9a2e Binary files /dev/null and b/dev/sff/python/hal.sff.png differ diff --git a/dev/sff/python/halMel.sff.png b/dev/sff/python/halMel.sff.png new file mode 100644 index 0000000..d609420 Binary files /dev/null and b/dev/sff/python/halMel.sff.png differ diff --git a/dev/sff/python/sffDemo.py b/dev/sff/python/sffDemo.py new file mode 100644 index 0000000..9f901a7 --- /dev/null +++ b/dev/sff/python/sffDemo.py @@ -0,0 +1,31 @@ +import sffLib +import os +import matplotlib.pyplot as plt +import numpy as np +np.set_printoptions(precision=3) + + +def plotSFF(filePath, show=False): + + # access SFF details and data like this + sf = sffLib.SpectrogramFile(filePath) + print(sf.getDescription()) + print(sf.values) + + # rotate values for display as a pseudocolor mesh + rotatedValues = np.rot90(sf.values, 1)[::-1] + + # plot the spectrogram + plt.figure() + plt.pcolormesh(rotatedValues) + plt.colorbar() + plt.title(f"{os.path.basename(filePath)} Spectrogram") + plt.savefig(os.path.basename(filePath)+".png") + if show: + plt.show() + plt.close() + + +if __name__ == "__main__": + plotSFF("../hal.sff", True) + plotSFF("../halMel.sff", True) diff --git a/dev/sff/python/sffLib.py b/dev/sff/python/sffLib.py new file mode 100644 index 0000000..d4b9775 --- /dev/null +++ b/dev/sff/python/sffLib.py @@ -0,0 +1,127 @@ +import os +import numpy as np +import struct +import datetime +import time +import math + + +class SpectrogramFile: + + def __init__(self, filePath): + + timeStart = time.perf_counter() + + print(f"Spectrogram from file: {os.path.basename(filePath)}") + self.filePath = os.path.abspath(filePath) + + with open(filePath, 'rb') as f: + filebytes = f.read() + + # validate file format + magicNumber = struct.unpack(" - - - - - \ No newline at end of file diff --git a/src/AudioMonitor/AudioMonitor.csproj b/src/AudioMonitor/AudioMonitor.csproj deleted file mode 100644 index 8bd4876..0000000 --- a/src/AudioMonitor/AudioMonitor.csproj +++ /dev/null @@ -1,93 +0,0 @@ - - - - - Debug - AnyCPU - {806D56D1-077A-4E8C-A742-2AEED037AF17} - WinExe - AudioMonitor - AudioMonitor - v4.5 - 512 - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\NAudio.1.9.0\lib\net35\NAudio.dll - - - - - - - - - - - - - - - - Form - - - Form1.cs - - - - - - Form1.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - - - - - - {8717f4ce-4497-4eaa-b95d-0f7a04fb397d} - Spectrogram - - - - \ No newline at end of file diff --git a/src/AudioMonitor/Form1.Designer.cs b/src/AudioMonitor/Form1.Designer.cs deleted file mode 100644 index 2341612..0000000 --- a/src/AudioMonitor/Form1.Designer.cs +++ /dev/null @@ -1,294 +0,0 @@ -namespace AudioMonitor -{ - partial class Form1 - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.components = new System.ComponentModel.Container(); - this.groupBox1 = new System.Windows.Forms.GroupBox(); - this.btnSetMicrophone = new System.Windows.Forms.Button(); - this.cbMicrophones = new System.Windows.Forms.ComboBox(); - this.statusStrip1 = new System.Windows.Forms.StatusStrip(); - this.lblStatus = new System.Windows.Forms.ToolStripStatusLabel(); - this.pictureBox1 = new System.Windows.Forms.PictureBox(); - this.timer1 = new System.Windows.Forms.Timer(this.components); - this.groupBox2 = new System.Windows.Forms.GroupBox(); - this.cbDisplay = new System.Windows.Forms.ComboBox(); - this.groupBox3 = new System.Windows.Forms.GroupBox(); - this.cbDecibels = new System.Windows.Forms.CheckBox(); - this.tbIntensity = new System.Windows.Forms.TrackBar(); - this.nudIntensity = new System.Windows.Forms.NumericUpDown(); - this.groupBox4 = new System.Windows.Forms.GroupBox(); - this.cbColormap = new System.Windows.Forms.ComboBox(); - this.groupBox5 = new System.Windows.Forms.GroupBox(); - this.cbTicks = new System.Windows.Forms.CheckBox(); - this.groupBox1.SuspendLayout(); - this.statusStrip1.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); - this.groupBox2.SuspendLayout(); - this.groupBox3.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.tbIntensity)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.nudIntensity)).BeginInit(); - this.groupBox4.SuspendLayout(); - this.groupBox5.SuspendLayout(); - this.SuspendLayout(); - // - // groupBox1 - // - this.groupBox1.Controls.Add(this.btnSetMicrophone); - this.groupBox1.Controls.Add(this.cbMicrophones); - this.groupBox1.Location = new System.Drawing.Point(12, 12); - this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(200, 47); - this.groupBox1.TabIndex = 1; - this.groupBox1.TabStop = false; - this.groupBox1.Text = "Microphone"; - // - // btnSetMicrophone - // - this.btnSetMicrophone.Location = new System.Drawing.Point(133, 18); - this.btnSetMicrophone.Name = "btnSetMicrophone"; - this.btnSetMicrophone.Size = new System.Drawing.Size(61, 23); - this.btnSetMicrophone.TabIndex = 1; - this.btnSetMicrophone.Text = "open"; - this.btnSetMicrophone.UseVisualStyleBackColor = true; - this.btnSetMicrophone.Click += new System.EventHandler(this.BtnSetMicrophone_Click); - // - // cbMicrophones - // - this.cbMicrophones.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cbMicrophones.FormattingEnabled = true; - this.cbMicrophones.Location = new System.Drawing.Point(6, 19); - this.cbMicrophones.Name = "cbMicrophones"; - this.cbMicrophones.Size = new System.Drawing.Size(121, 21); - this.cbMicrophones.TabIndex = 0; - // - // statusStrip1 - // - this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.lblStatus}); - this.statusStrip1.Location = new System.Drawing.Point(0, 476); - this.statusStrip1.Name = "statusStrip1"; - this.statusStrip1.Size = new System.Drawing.Size(1045, 22); - this.statusStrip1.TabIndex = 2; - this.statusStrip1.Text = "statusStrip1"; - // - // lblStatus - // - this.lblStatus.Name = "lblStatus"; - this.lblStatus.Size = new System.Drawing.Size(118, 17); - this.lblStatus.Text = "toolStripStatusLabel1"; - // - // pictureBox1 - // - this.pictureBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.pictureBox1.BackColor = System.Drawing.SystemColors.ControlDark; - this.pictureBox1.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch; - this.pictureBox1.Location = new System.Drawing.Point(12, 65); - this.pictureBox1.Name = "pictureBox1"; - this.pictureBox1.Size = new System.Drawing.Size(1021, 408); - this.pictureBox1.TabIndex = 3; - this.pictureBox1.TabStop = false; - this.pictureBox1.Click += new System.EventHandler(this.PictureBox1_Click); - // - // timer1 - // - this.timer1.Enabled = true; - this.timer1.Interval = 10; - this.timer1.Tick += new System.EventHandler(this.Timer1_Tick); - // - // groupBox2 - // - this.groupBox2.Controls.Add(this.cbDisplay); - this.groupBox2.Location = new System.Drawing.Point(218, 12); - this.groupBox2.Name = "groupBox2"; - this.groupBox2.Size = new System.Drawing.Size(139, 47); - this.groupBox2.TabIndex = 5; - this.groupBox2.TabStop = false; - this.groupBox2.Text = "Display"; - // - // cbDisplay - // - this.cbDisplay.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.cbDisplay.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cbDisplay.FormattingEnabled = true; - this.cbDisplay.Location = new System.Drawing.Point(6, 19); - this.cbDisplay.Name = "cbDisplay"; - this.cbDisplay.Size = new System.Drawing.Size(127, 21); - this.cbDisplay.TabIndex = 0; - this.cbDisplay.SelectedIndexChanged += new System.EventHandler(this.CbDisplay_SelectedIndexChanged); - // - // groupBox3 - // - this.groupBox3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.groupBox3.Controls.Add(this.cbDecibels); - this.groupBox3.Controls.Add(this.tbIntensity); - this.groupBox3.Controls.Add(this.nudIntensity); - this.groupBox3.Location = new System.Drawing.Point(572, 12); - this.groupBox3.Name = "groupBox3"; - this.groupBox3.Size = new System.Drawing.Size(461, 47); - this.groupBox3.TabIndex = 6; - this.groupBox3.TabStop = false; - this.groupBox3.Text = "Intensity"; - // - // cbDecibels - // - this.cbDecibels.AutoSize = true; - this.cbDecibels.Location = new System.Drawing.Point(79, 22); - this.cbDecibels.Name = "cbDecibels"; - this.cbDecibels.Size = new System.Drawing.Size(65, 17); - this.cbDecibels.TabIndex = 2; - this.cbDecibels.Text = "decibels"; - this.cbDecibels.UseVisualStyleBackColor = true; - // - // tbIntensity - // - this.tbIntensity.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.tbIntensity.AutoSize = false; - this.tbIntensity.LargeChange = 1; - this.tbIntensity.Location = new System.Drawing.Point(150, 11); - this.tbIntensity.Maximum = 50; - this.tbIntensity.Name = "tbIntensity"; - this.tbIntensity.Size = new System.Drawing.Size(305, 30); - this.tbIntensity.TabIndex = 1; - this.tbIntensity.Value = 10; - this.tbIntensity.Scroll += new System.EventHandler(this.TbIntensity_Scroll); - // - // nudIntensity - // - this.nudIntensity.DecimalPlaces = 1; - this.nudIntensity.Location = new System.Drawing.Point(6, 19); - this.nudIntensity.Name = "nudIntensity"; - this.nudIntensity.Size = new System.Drawing.Size(67, 20); - this.nudIntensity.TabIndex = 0; - this.nudIntensity.Value = new decimal(new int[] { - 10, - 0, - 0, - 0}); - // - // groupBox4 - // - this.groupBox4.Controls.Add(this.cbColormap); - this.groupBox4.Location = new System.Drawing.Point(363, 12); - this.groupBox4.Name = "groupBox4"; - this.groupBox4.Size = new System.Drawing.Size(139, 47); - this.groupBox4.TabIndex = 7; - this.groupBox4.TabStop = false; - this.groupBox4.Text = "Colormap"; - // - // cbColormap - // - this.cbColormap.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.cbColormap.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cbColormap.FormattingEnabled = true; - this.cbColormap.Location = new System.Drawing.Point(6, 19); - this.cbColormap.Name = "cbColormap"; - this.cbColormap.Size = new System.Drawing.Size(127, 21); - this.cbColormap.TabIndex = 0; - // - // groupBox5 - // - this.groupBox5.Controls.Add(this.cbTicks); - this.groupBox5.Location = new System.Drawing.Point(508, 12); - this.groupBox5.Name = "groupBox5"; - this.groupBox5.Size = new System.Drawing.Size(58, 47); - this.groupBox5.TabIndex = 8; - this.groupBox5.TabStop = false; - this.groupBox5.Text = "Misc"; - // - // cbTicks - // - this.cbTicks.AutoSize = true; - this.cbTicks.Location = new System.Drawing.Point(6, 21); - this.cbTicks.Name = "cbTicks"; - this.cbTicks.Size = new System.Drawing.Size(48, 17); - this.cbTicks.TabIndex = 3; - this.cbTicks.Text = "ticks"; - this.cbTicks.UseVisualStyleBackColor = true; - // - // Form1 - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; - this.ClientSize = new System.Drawing.Size(1045, 498); - this.Controls.Add(this.groupBox5); - this.Controls.Add(this.groupBox4); - this.Controls.Add(this.groupBox3); - this.Controls.Add(this.groupBox2); - this.Controls.Add(this.pictureBox1); - this.Controls.Add(this.statusStrip1); - this.Controls.Add(this.groupBox1); - this.Name = "Form1"; - this.Text = "Audio Monitor"; - this.Load += new System.EventHandler(this.Form1_Load); - this.groupBox1.ResumeLayout(false); - this.statusStrip1.ResumeLayout(false); - this.statusStrip1.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); - this.groupBox2.ResumeLayout(false); - this.groupBox3.ResumeLayout(false); - this.groupBox3.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.tbIntensity)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.nudIntensity)).EndInit(); - this.groupBox4.ResumeLayout(false); - this.groupBox5.ResumeLayout(false); - this.groupBox5.PerformLayout(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.GroupBox groupBox1; - private System.Windows.Forms.Button btnSetMicrophone; - private System.Windows.Forms.ComboBox cbMicrophones; - private System.Windows.Forms.StatusStrip statusStrip1; - private System.Windows.Forms.ToolStripStatusLabel lblStatus; - private System.Windows.Forms.PictureBox pictureBox1; - private System.Windows.Forms.Timer timer1; - private System.Windows.Forms.GroupBox groupBox2; - private System.Windows.Forms.ComboBox cbDisplay; - private System.Windows.Forms.GroupBox groupBox3; - private System.Windows.Forms.NumericUpDown nudIntensity; - private System.Windows.Forms.TrackBar tbIntensity; - private System.Windows.Forms.CheckBox cbDecibels; - private System.Windows.Forms.GroupBox groupBox4; - private System.Windows.Forms.ComboBox cbColormap; - private System.Windows.Forms.GroupBox groupBox5; - private System.Windows.Forms.CheckBox cbTicks; - } -} - diff --git a/src/AudioMonitor/Form1.cs b/src/AudioMonitor/Form1.cs deleted file mode 100644 index eb546dc..0000000 --- a/src/AudioMonitor/Form1.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; - -namespace AudioMonitor -{ - public partial class Form1 : Form - { - private NAudio.Wave.WaveInEvent wvin; - private Spectrogram.Spectrogram spec; - - public Form1() - { - InitializeComponent(); - - cbMicrophones.Items.AddRange(Listener.GetInputDevices()); - if (cbMicrophones.Items.Count > 0) - cbMicrophones.SelectedItem = cbMicrophones.Items[0]; - - cbDisplay.Items.Add("horizontal repeat"); - cbDisplay.Items.Add("waterfall"); - cbDisplay.SelectedItem = cbDisplay.Items[0]; - - string[] colormapNames = Enum.GetNames(typeof(Spectrogram.Colormap)); - Array.Sort(colormapNames); - cbColormap.Items.AddRange(colormapNames); - cbColormap.SelectedItem = cbColormap.Items[cbColormap.Items.Count - 1]; - } - - private void Form1_Load(object sender, EventArgs e) - { - BtnSetMicrophone_Click(null, null); - } - - private void BtnSetMicrophone_Click(object sender, EventArgs e) - { - if (btnSetMicrophone.Text == "open") - { - AudioMonitorInitialize(DeviceIndex: cbMicrophones.SelectedIndex); - btnSetMicrophone.Text = "close"; - } - else - { - btnSetMicrophone.Text = "open"; - wvin.StopRecording(); - wvin = null; - } - } - - private void OnDataAvailable(object sender, NAudio.Wave.WaveInEventArgs args) - { - int bytesPerSample = wvin.WaveFormat.BitsPerSample / 8; - float[] buffer = new float[args.BytesRecorded / bytesPerSample]; - for (int i = 0; i < buffer.Length; i++) - buffer[i] = BitConverter.ToInt16(args.Buffer, i * bytesPerSample); - try - { - if (waterfall) - spec.AddScroll(buffer, fixedSize: pictureBox1.Height); - else - spec.AddCircular(buffer, fixedSize: pictureBox1.Width); - renderNeeded = true; - } - catch (Exception ex) - { - Console.WriteLine("EXCEPTION: " + ex); - } - } - - private void AudioMonitorInitialize( - int DeviceIndex = 0, - int sampleRate = 8000, - int bitRate = 16, - int channels = 1, - int bufferMilliseconds = 10, - int fftSize = 1024, - int step = 250 - ) - { - spec = new Spectrogram.Spectrogram(sampleRate, fftSize, step); - - wvin = new NAudio.Wave.WaveInEvent(); - wvin.DeviceNumber = DeviceIndex; - wvin.WaveFormat = new NAudio.Wave.WaveFormat(sampleRate, bitRate, channels); - wvin.DataAvailable += OnDataAvailable; - wvin.BufferMilliseconds = bufferMilliseconds; - wvin.StartRecording(); - } - - bool renderNeeded = false; - bool busyRendering = false; - private void Timer1_Tick(object sender, EventArgs e) - { - if (!renderNeeded) - return; - - if ((spec == null) || (spec.fftList.Count == 0)) - return; - - if (busyRendering) - return; - else - busyRendering = true; - - Spectrogram.Colormap colormap = (Spectrogram.Colormap)Enum.Parse(typeof(Spectrogram.Colormap), cbColormap.Text); - - pictureBox1.BackgroundImage = spec.GetBitmap( - intensity: (float)nudIntensity.Value, - decibels: cbDecibels.Checked, - vertical: waterfall, - colormap: colormap, - showTicks: cbTicks.Checked, - highlightLatestColumn: (cbDisplay.Text != "waterfall"), - freqHigh: 4000, - tickSpacingHz: 250, - tickSpacingSec: 1 - ); - lblStatus.Text = $"spectrogram contains {spec.fftList.Count} FFT samples | last render: {spec.GetLastRenderTime()} ms"; - renderNeeded = false; - busyRendering = false; - - } - - private void TbIntensity_Scroll(object sender, EventArgs e) - { - nudIntensity.Value = tbIntensity.Value; - } - - private void PictureBox1_Click(object sender, EventArgs e) - { - MessageBox.Show(spec.GetFftInfo(), "Configuration Details"); - } - - public bool waterfall = false; - private void CbDisplay_SelectedIndexChanged(object sender, EventArgs e) - { - waterfall = (cbDisplay.Text == "waterfall"); - } - } -} diff --git a/src/AudioMonitor/Listener.cs b/src/AudioMonitor/Listener.cs deleted file mode 100644 index 4cdc1c1..0000000 --- a/src/AudioMonitor/Listener.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace AudioMonitor -{ - class Listener - { - public static string[] GetInputDevices() - { - string[] devices = new string[NAudio.Wave.WaveIn.DeviceCount]; - for (int i = 0; i < devices.Length; i++) - devices[i] = NAudio.Wave.WaveIn.GetCapabilities(i).ProductName; - return devices; - } - } - -} diff --git a/src/AudioMonitor/Properties/AssemblyInfo.cs b/src/AudioMonitor/Properties/AssemblyInfo.cs deleted file mode 100644 index 122c94c..0000000 --- a/src/AudioMonitor/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("AudioMonitor")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("AudioMonitor")] -[assembly: AssemblyCopyright("Copyright © 2019")] -[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("806d56d1-077a-4e8c-a742-2aeed037af17")] - -// 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/AudioMonitor/Properties/Resources.Designer.cs b/src/AudioMonitor/Properties/Resources.Designer.cs deleted file mode 100644 index 3763ce3..0000000 --- a/src/AudioMonitor/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 AudioMonitor.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("AudioMonitor.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/AudioMonitor/Properties/Resources.resx b/src/AudioMonitor/Properties/Resources.resx deleted file mode 100644 index af7dbeb..0000000 --- a/src/AudioMonitor/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/AudioMonitor/Properties/Settings.Designer.cs b/src/AudioMonitor/Properties/Settings.Designer.cs deleted file mode 100644 index 7b04022..0000000 --- a/src/AudioMonitor/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 AudioMonitor.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/AudioMonitor/Properties/Settings.settings b/src/AudioMonitor/Properties/Settings.settings deleted file mode 100644 index 3964565..0000000 --- a/src/AudioMonitor/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/AudioMonitor/packages.config b/src/AudioMonitor/packages.config deleted file mode 100644 index abd5fbc..0000000 --- a/src/AudioMonitor/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/ConsoleDemo/App.config b/src/ConsoleDemo/App.config deleted file mode 100644 index 8e15646..0000000 --- a/src/ConsoleDemo/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/ConsoleDemo/ConsoleDemo.csproj b/src/ConsoleDemo/ConsoleDemo.csproj deleted file mode 100644 index 26e20af..0000000 --- a/src/ConsoleDemo/ConsoleDemo.csproj +++ /dev/null @@ -1,63 +0,0 @@ - - - - - Debug - AnyCPU - {DA5009A8-9036-4BDA-B591-3244DEB759E9} - WinExe - ConsoleDemo - ConsoleDemo - v4.5 - 512 - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - {8717f4ce-4497-4eaa-b95d-0f7a04fb397d} - Spectrogram - - - - \ No newline at end of file diff --git a/src/ConsoleDemo/Program.cs b/src/ConsoleDemo/Program.cs deleted file mode 100644 index 0b417eb..0000000 --- a/src/ConsoleDemo/Program.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ConsoleDemo -{ - class Program - { - static void Main(string[] args) - { - DemoHal(); - //DemoMozart(); - //DemoQRSS(); - } - - static void DemoMozart() - { - var spec = new Spectrogram.Spectrogram(sampleRate: 8000, fftSize: 2048, step: 700); - float[] values = Spectrogram.Tools.ReadWav("mozart.wav"); - spec.AddExtend(values); - Bitmap bmp = spec.GetBitmap(intensity: 2, freqHigh: 2500); - spec.SaveBitmap(bmp, "mozart.jpg"); - } - - static void DemoQRSS() - { - var spec = new Spectrogram.Spectrogram(sampleRate: 8000, fftSize: 16384, step: 8000); - float[] values = Spectrogram.Tools.ReadMp3("qrss-w4hbk.mp3"); - spec.AddExtend(values); - Bitmap bmp = spec.GetBitmap(intensity: 1.5, freqLow: 1100, freqHigh: 1500, - showTicks: true, tickSpacingHz: 50, tickSpacingSec: 60); - spec.SaveBitmap(bmp, "qrss.png"); - } - - static void DemoHal() - { - var spec = new Spectrogram.Spectrogram(sampleRate: 15000, fftSize: 4096, step: 400); - float[] values = Spectrogram.Tools.ReadMp3("cant-do-that.mp3"); - spec.AddExtend(values); - Bitmap bmp; - - bmp = spec.GetBitmap(intensity: .2, freqHigh: 1000); - spec.SaveBitmap(bmp, "cant-do-that.jpg"); - - bmp = spec.GetBitmap(intensity: .2, freqHigh: 1000, - colormap: Spectrogram.Colormap.grayscale); - spec.SaveBitmap(bmp, "cant-do-that-grayscale.jpg"); - - bmp = spec.GetBitmap(intensity: .2, freqHigh: 1000, - colormap: Spectrogram.Colormap.grayscaleInverted); - spec.SaveBitmap(bmp, "cant-do-that-grayscale-inverted.jpg"); - - bmp = spec.GetBitmap(intensity: .2, freqHigh: 1000, - colormap: Spectrogram.Colormap.vdGreen); - spec.SaveBitmap(bmp, "cant-do-that-green.jpg"); - } - } -} diff --git a/src/ConsoleDemo/Properties/AssemblyInfo.cs b/src/ConsoleDemo/Properties/AssemblyInfo.cs deleted file mode 100644 index 290544e..0000000 --- a/src/ConsoleDemo/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("ConsoleDemo")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ConsoleDemo")] -[assembly: AssemblyCopyright("Copyright © 2019")] -[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("da5009a8-9036-4bda-b591-3244deb759e9")] - -// 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/FormMicrophone.Designer.cs b/src/Spectrogram.MicrophoneDemo/FormMicrophone.Designer.cs new file mode 100644 index 0000000..156e925 --- /dev/null +++ b/src/Spectrogram.MicrophoneDemo/FormMicrophone.Designer.cs @@ -0,0 +1,320 @@ +namespace Spectrogram.MicrophoneDemo +{ + partial class FormMicrophone + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.cbDevice = new System.Windows.Forms.ComboBox(); + this.label1 = new System.Windows.Forms.Label(); + this.pbSpectrogram = new System.Windows.Forms.PictureBox(); + this.statusStrip1 = new System.Windows.Forms.StatusStrip(); + this.lblStatus1 = new System.Windows.Forms.ToolStripStatusLabel(); + this.lblStatus2 = new System.Windows.Forms.ToolStripStatusLabel(); + this.lblStatus3 = new System.Windows.Forms.ToolStripStatusLabel(); + this.pbAmplitude = new System.Windows.Forms.ToolStripProgressBar(); + this.timer1 = new System.Windows.Forms.Timer(this.components); + this.label2 = new System.Windows.Forms.Label(); + this.tbBrightness = new System.Windows.Forms.TrackBar(); + this.cbDecibels = new System.Windows.Forms.CheckBox(); + this.cbRoll = new System.Windows.Forms.CheckBox(); + this.panel1 = new System.Windows.Forms.Panel(); + this.pbScaleVert = new System.Windows.Forms.PictureBox(); + this.cbFftSize = new System.Windows.Forms.ComboBox(); + this.label3 = new System.Windows.Forms.Label(); + this.cbColormap = new System.Windows.Forms.ComboBox(); + this.label4 = new System.Windows.Forms.Label(); + this.btnResetRoll = new System.Windows.Forms.Button(); + this.lblStatus4 = new System.Windows.Forms.ToolStripStatusLabel(); + ((System.ComponentModel.ISupportInitialize)(this.pbSpectrogram)).BeginInit(); + this.statusStrip1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.tbBrightness)).BeginInit(); + this.panel1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pbScaleVert)).BeginInit(); + this.SuspendLayout(); + // + // cbDevice + // + this.cbDevice.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbDevice.FormattingEnabled = true; + this.cbDevice.Location = new System.Drawing.Point(12, 25); + this.cbDevice.Name = "cbDevice"; + this.cbDevice.Size = new System.Drawing.Size(121, 21); + this.cbDevice.TabIndex = 0; + this.cbDevice.SelectedIndexChanged += new System.EventHandler(this.cbDevice_SelectedIndexChanged); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(9, 9); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(41, 13); + this.label1.TabIndex = 1; + this.label1.Text = "Device"; + // + // pbSpectrogram + // + this.pbSpectrogram.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.pbSpectrogram.BackColor = System.Drawing.Color.Black; + this.pbSpectrogram.Location = new System.Drawing.Point(0, 0); + this.pbSpectrogram.Name = "pbSpectrogram"; + this.pbSpectrogram.Size = new System.Drawing.Size(901, 512); + this.pbSpectrogram.TabIndex = 2; + this.pbSpectrogram.TabStop = false; + // + // statusStrip1 + // + this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.lblStatus1, + this.lblStatus2, + this.lblStatus3, + this.lblStatus4, + this.pbAmplitude}); + this.statusStrip1.Location = new System.Drawing.Point(0, 567); + this.statusStrip1.Name = "statusStrip1"; + this.statusStrip1.Size = new System.Drawing.Size(1032, 22); + this.statusStrip1.TabIndex = 3; + this.statusStrip1.Text = "statusStrip1"; + // + // lblStatus1 + // + this.lblStatus1.AutoSize = false; + this.lblStatus1.Name = "lblStatus1"; + this.lblStatus1.Size = new System.Drawing.Size(150, 17); + this.lblStatus1.Text = "toolStripStatusLabel1"; + this.lblStatus1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // lblStatus2 + // + this.lblStatus2.AutoSize = false; + this.lblStatus2.Name = "lblStatus2"; + this.lblStatus2.Size = new System.Drawing.Size(150, 17); + this.lblStatus2.Text = "toolStripStatusLabel1"; + this.lblStatus2.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // lblStatus3 + // + this.lblStatus3.AutoSize = false; + this.lblStatus3.Name = "lblStatus3"; + this.lblStatus3.Size = new System.Drawing.Size(150, 17); + this.lblStatus3.Text = "toolStripStatusLabel1"; + this.lblStatus3.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // pbAmplitude + // + this.pbAmplitude.Name = "pbAmplitude"; + this.pbAmplitude.Size = new System.Drawing.Size(200, 16); + this.pbAmplitude.Step = 1; + this.pbAmplitude.Style = System.Windows.Forms.ProgressBarStyle.Continuous; + this.pbAmplitude.Value = 25; + // + // timer1 + // + this.timer1.Enabled = true; + this.timer1.Interval = 1; + this.timer1.Tick += new System.EventHandler(this.timer1_Tick); + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(390, 9); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(56, 13); + this.label2.TabIndex = 5; + this.label2.Text = "Brightness"; + // + // tbBrightness + // + this.tbBrightness.Location = new System.Drawing.Point(393, 24); + this.tbBrightness.Maximum = 100; + this.tbBrightness.Name = "tbBrightness"; + this.tbBrightness.Size = new System.Drawing.Size(220, 45); + this.tbBrightness.TabIndex = 6; + this.tbBrightness.TickFrequency = 2; + this.tbBrightness.Value = 5; + // + // cbDecibels + // + this.cbDecibels.AutoSize = true; + this.cbDecibels.Location = new System.Drawing.Point(620, 28); + this.cbDecibels.Name = "cbDecibels"; + this.cbDecibels.Size = new System.Drawing.Size(39, 17); + this.cbDecibels.TabIndex = 7; + this.cbDecibels.Text = "dB"; + this.cbDecibels.UseVisualStyleBackColor = true; + // + // cbRoll + // + this.cbRoll.AutoSize = true; + this.cbRoll.Location = new System.Drawing.Point(665, 28); + this.cbRoll.Name = "cbRoll"; + this.cbRoll.Size = new System.Drawing.Size(44, 17); + this.cbRoll.TabIndex = 8; + this.cbRoll.Text = "Roll"; + this.cbRoll.UseVisualStyleBackColor = true; + this.cbRoll.CheckedChanged += new System.EventHandler(this.cbRoll_CheckedChanged); + // + // panel1 + // + this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.panel1.AutoScroll = true; + this.panel1.BackColor = System.Drawing.SystemColors.ControlDark; + this.panel1.Controls.Add(this.pbScaleVert); + this.panel1.Controls.Add(this.pbSpectrogram); + this.panel1.Location = new System.Drawing.Point(12, 52); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(1008, 512); + this.panel1.TabIndex = 10; + // + // pbScaleVert + // + this.pbScaleVert.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.pbScaleVert.BackColor = System.Drawing.Color.Navy; + this.pbScaleVert.Location = new System.Drawing.Point(901, 0); + this.pbScaleVert.Name = "pbScaleVert"; + this.pbScaleVert.Size = new System.Drawing.Size(107, 512); + this.pbScaleVert.TabIndex = 3; + this.pbScaleVert.TabStop = false; + // + // cbFftSize + // + this.cbFftSize.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbFftSize.FormattingEnabled = true; + this.cbFftSize.Location = new System.Drawing.Point(139, 25); + this.cbFftSize.Name = "cbFftSize"; + this.cbFftSize.Size = new System.Drawing.Size(121, 21); + this.cbFftSize.TabIndex = 11; + this.cbFftSize.SelectedIndexChanged += new System.EventHandler(this.cbFftSize_SelectedIndexChanged); + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(136, 9); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(49, 13); + this.label3.TabIndex = 12; + this.label3.Text = "FFT Size"; + // + // cbColormap + // + this.cbColormap.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbColormap.FormattingEnabled = true; + this.cbColormap.Location = new System.Drawing.Point(266, 25); + this.cbColormap.Name = "cbColormap"; + this.cbColormap.Size = new System.Drawing.Size(121, 21); + this.cbColormap.TabIndex = 13; + this.cbColormap.SelectedIndexChanged += new System.EventHandler(this.cbColormap_SelectedIndexChanged); + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(263, 9); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(51, 13); + this.label4.TabIndex = 14; + this.label4.Text = "Colormap"; + // + // btnResetRoll + // + this.btnResetRoll.Enabled = false; + this.btnResetRoll.Location = new System.Drawing.Point(715, 24); + this.btnResetRoll.Name = "btnResetRoll"; + this.btnResetRoll.Size = new System.Drawing.Size(75, 23); + this.btnResetRoll.TabIndex = 15; + this.btnResetRoll.Text = "Reset Roll"; + this.btnResetRoll.UseVisualStyleBackColor = true; + this.btnResetRoll.Click += new System.EventHandler(this.btnResetRoll_Click); + // + // lblStatus4 + // + this.lblStatus4.Name = "lblStatus4"; + this.lblStatus4.Size = new System.Drawing.Size(365, 17); + this.lblStatus4.Spring = true; + this.lblStatus4.Text = "toolStripStatusLabel1"; + this.lblStatus4.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // FormMicrophone + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1032, 589); + this.Controls.Add(this.btnResetRoll); + this.Controls.Add(this.label4); + this.Controls.Add(this.cbColormap); + this.Controls.Add(this.label3); + this.Controls.Add(this.cbFftSize); + this.Controls.Add(this.panel1); + this.Controls.Add(this.cbRoll); + this.Controls.Add(this.cbDecibels); + this.Controls.Add(this.label2); + this.Controls.Add(this.statusStrip1); + this.Controls.Add(this.tbBrightness); + this.Controls.Add(this.label1); + this.Controls.Add(this.cbDevice); + this.Name = "FormMicrophone"; + this.Text = "Spectrogram Microphone Demo"; + this.Load += new System.EventHandler(this.Form1_Load); + ((System.ComponentModel.ISupportInitialize)(this.pbSpectrogram)).EndInit(); + this.statusStrip1.ResumeLayout(false); + this.statusStrip1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.tbBrightness)).EndInit(); + this.panel1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.pbScaleVert)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.ComboBox cbDevice; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.PictureBox pbSpectrogram; + private System.Windows.Forms.StatusStrip statusStrip1; + private System.Windows.Forms.ToolStripStatusLabel lblStatus1; + private System.Windows.Forms.ToolStripStatusLabel lblStatus2; + private System.Windows.Forms.ToolStripProgressBar pbAmplitude; + private System.Windows.Forms.Timer timer1; + private System.Windows.Forms.ToolStripStatusLabel lblStatus3; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TrackBar tbBrightness; + private System.Windows.Forms.CheckBox cbDecibels; + private System.Windows.Forms.CheckBox cbRoll; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.ComboBox cbFftSize; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.PictureBox pbScaleVert; + private System.Windows.Forms.ComboBox cbColormap; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Button btnResetRoll; + private System.Windows.Forms.ToolStripStatusLabel lblStatus4; + } +} + diff --git a/src/Spectrogram.MicrophoneDemo/FormMicrophone.cs b/src/Spectrogram.MicrophoneDemo/FormMicrophone.cs new file mode 100644 index 0000000..975c5b1 --- /dev/null +++ b/src/Spectrogram.MicrophoneDemo/FormMicrophone.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using SkiaSharp.Views.Desktop; + +namespace Spectrogram.MicrophoneDemo +{ + public partial class FormMicrophone : Form + { + Colormap[] cmaps; + + public FormMicrophone() + { + InitializeComponent(); + + if (NAudio.Wave.WaveIn.DeviceCount == 0) + { + MessageBox.Show("No audio input devices found.\n\nThis program will now exit.", + "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); + Close(); + } + else + { + cbDevice.Items.Clear(); + for (int i = 0; i < NAudio.Wave.WaveIn.DeviceCount; i++) + cbDevice.Items.Add(NAudio.Wave.WaveIn.GetCapabilities(i).ProductName); + cbDevice.SelectedIndex = 0; + } + + for (int i = 9; i < 16; i++) + cbFftSize.Items.Add($"2^{i} ({1 << i:N0})"); + cbFftSize.SelectedIndex = 1; + + cmaps = Colormap.GetColormaps(); + foreach (Colormap cmap in cmaps) + cbColormap.Items.Add(cmap.Name); + cbColormap.SelectedIndex = cbColormap.Items.IndexOf("Viridis"); + } + + private void Form1_Load(object sender, EventArgs e) { } + private void cbDevice_SelectedIndexChanged(object sender, EventArgs e) => StartListening(); + private void cbFftSize_SelectedIndexChanged(object sender, EventArgs e) => StartListening(); + + private SpectrogramGenerator spec; + private Listener listener; + private void StartListening() + { + int sampleRate = 6000; + int fftSize = 1 << (9 + cbFftSize.SelectedIndex); + int stepSize = fftSize / 20; + + pbSpectrogram.Image?.Dispose(); + pbSpectrogram.Image = null; + listener?.Dispose(); + listener = new Listener(cbDevice.SelectedIndex, sampleRate); + spec = new SpectrogramGenerator(sampleRate, fftSize, stepSize); + pbSpectrogram.Height = spec.Height; + + pbScaleVert.Image?.Dispose(); + pbScaleVert.Image = spec.GetVerticalScale(pbScaleVert.Width).ToBitmap(); + pbScaleVert.Height = spec.Height; + } + + private void timer1_Tick(object sender, EventArgs e) + { + double[] newAudio = listener.GetNewAudio(); + spec.Add(newAudio, process: false); + + double multiplier = tbBrightness.Value / 20.0; + + if (spec.FftsToProcess > 0) + { + Stopwatch sw = Stopwatch.StartNew(); + spec.Process(); + spec.SetFixedWidth(pbSpectrogram.Width); + Bitmap bmpSpec = new Bitmap(spec.Width, spec.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb); + using (var bmpSpecIndexed = spec.GetBitmap(multiplier, cbDecibels.Checked, roll: cbRoll.Checked)) + using (var gfx = Graphics.FromImage(bmpSpec)) + using (var pen = new Pen(Color.White)) + { + gfx.DrawImage(bmpSpecIndexed.ToBitmap(), 0, 0); + if (cbRoll.Checked) + { + gfx.DrawLine(pen, spec.NextColumnIndex, 0, spec.NextColumnIndex, pbSpectrogram.Height); + } + } + sw.Stop(); + pbSpectrogram.Image?.Dispose(); + pbSpectrogram.Image = bmpSpec; + lblStatus3.Text = $"Render time: {sw.ElapsedMilliseconds:D2} ms"; + lblStatus4.Text = $"Peak (Hz): {spec.GetPeak().freqHz:N0}"; + } + + lblStatus1.Text = $"Time: {listener.TotalTimeSec:N3} sec"; + lblStatus2.Text = $"FFTs processed: {spec.FftsProcessed:N0}"; + pbAmplitude.Value = (int)(listener.AmplitudeFrac * pbAmplitude.Maximum); + } + + private void cbColormap_SelectedIndexChanged(object sender, EventArgs e) + { + spec.Colormap = cmaps[cbColormap.SelectedIndex]; + } + + private void btnResetRoll_Click(object sender, EventArgs e) + { + spec.RollReset(); + } + + private void cbRoll_CheckedChanged(object sender, EventArgs e) + { + btnResetRoll.Enabled = cbRoll.Checked; + } + } +} diff --git a/src/AudioMonitor/Form1.resx b/src/Spectrogram.MicrophoneDemo/FormMicrophone.resx similarity index 100% rename from src/AudioMonitor/Form1.resx rename to src/Spectrogram.MicrophoneDemo/FormMicrophone.resx diff --git a/src/Spectrogram.MicrophoneDemo/Listener.cs b/src/Spectrogram.MicrophoneDemo/Listener.cs new file mode 100644 index 0000000..6ac56b9 --- /dev/null +++ b/src/Spectrogram.MicrophoneDemo/Listener.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Spectrogram.MicrophoneDemo +{ + public class Listener : IDisposable + { + public readonly int SampleRate; + private readonly NAudio.Wave.WaveInEvent wvin; + public double AmplitudeFrac { get; private set; } + public double TotalSamples { get; private set; } + public double TotalTimeSec { get { return (double)TotalSamples / SampleRate; } } + private readonly List audio = new List(); + public int SamplesInMemory { get { return audio.Count; } } + + public Listener(int deviceIndex, int sampleRate) + { + SampleRate = sampleRate; + wvin = new NAudio.Wave.WaveInEvent + { + DeviceNumber = deviceIndex, + WaveFormat = new NAudio.Wave.WaveFormat(sampleRate, bits: 16, channels: 1), + BufferMilliseconds = 20 + }; + wvin.DataAvailable += OnNewAudioData; + wvin.StartRecording(); + } + + public void Dispose() + { + wvin?.StopRecording(); + wvin?.Dispose(); + } + + private void OnNewAudioData(object sender, NAudio.Wave.WaveInEventArgs args) + { + int bytesPerSample = wvin.WaveFormat.BitsPerSample / 8; + int newSampleCount = args.BytesRecorded / bytesPerSample; + double[] buffer = new double[newSampleCount]; + double peak = 0; + for (int i = 0; i < newSampleCount; i++) + { + buffer[i] = BitConverter.ToInt16(args.Buffer, i * bytesPerSample); + peak = Math.Max(peak, buffer[i]); + } + lock (audio) + { + audio.AddRange(buffer); + } + AmplitudeFrac = peak / (1 << 15); + TotalSamples += newSampleCount; + } + + public double[] GetNewAudio() + { + lock (audio) + { + double[] values = new double[audio.Count]; + for (int i = 0; i < values.Length; i++) + values[i] = audio[i]; + audio.RemoveRange(0, values.Length); + return values; + } + } + } +} diff --git a/src/AudioMonitor/Program.cs b/src/Spectrogram.MicrophoneDemo/Program.cs similarity index 51% rename from src/AudioMonitor/Program.cs rename to src/Spectrogram.MicrophoneDemo/Program.cs index c3e4b01..821c47b 100644 --- a/src/AudioMonitor/Program.cs +++ b/src/Spectrogram.MicrophoneDemo/Program.cs @@ -1,22 +1,17 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Runtime.Versioning; using System.Windows.Forms; -namespace AudioMonitor +namespace Spectrogram.MicrophoneDemo { static class Program { - /// - /// The main entry point for the application. - /// [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new Form1()); + Application.Run(new FormMicrophone()); } } } diff --git a/src/Spectrogram.MicrophoneDemo/Spectrogram.Demo.csproj b/src/Spectrogram.MicrophoneDemo/Spectrogram.Demo.csproj new file mode 100644 index 0000000..e58c01a --- /dev/null +++ b/src/Spectrogram.MicrophoneDemo/Spectrogram.Demo.csproj @@ -0,0 +1,15 @@ + + + net8.0-windows + WinExe + true + true + NU1701 + + + + + + + + \ No newline at end of file diff --git a/src/Spectrogram.Tests/AddTests.cs b/src/Spectrogram.Tests/AddTests.cs new file mode 100644 index 0000000..396e879 --- /dev/null +++ b/src/Spectrogram.Tests/AddTests.cs @@ -0,0 +1,19 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Spectrogram.Tests +{ + internal class AddTests + { + [Test] + public void Test_No_Data() + { + SpectrogramGenerator sg = new(44100, 2048, 1000); + Assert.Throws(() => sg.GetBitmap()); + } + } +} diff --git a/src/Spectrogram.Tests/AudioFile.cs b/src/Spectrogram.Tests/AudioFile.cs new file mode 100644 index 0000000..99875c6 --- /dev/null +++ b/src/Spectrogram.Tests/AudioFile.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Spectrogram.Tests +{ + public static class AudioFile + { + /// + /// Use NAudio to read the contents of a WAV file. + /// + public static (double[] audio, int sampleRate) ReadWAV(string filePath, double multiplier = 16_000) + { + using var afr = new NAudio.Wave.AudioFileReader(filePath); + int sampleRate = afr.WaveFormat.SampleRate; + int bytesPerSample = afr.WaveFormat.BitsPerSample / 8; + int sampleCount = (int)afr.Length / bytesPerSample; + int channelCount = afr.WaveFormat.Channels; + var audio = new List(sampleCount); + var buffer = new float[sampleRate * channelCount]; + int samplesRead = 0; + while ((samplesRead = afr.Read(buffer, 0, buffer.Length)) > 0) + audio.AddRange(buffer.Take(samplesRead).Select(x => x * multiplier)); + return (audio.ToArray(), sampleRate); + } + + /// + /// Use MP3Sharp to read the contents of an MP3 file. + /// + public static double[] ReadMP3(string filePath, int bufferSize = 4096) + { + List audio = new List(); + MP3Sharp.MP3Stream stream = new MP3Sharp.MP3Stream(filePath); + byte[] buffer = new byte[bufferSize]; + int bytesReturned = 1; + while (bytesReturned > 0) + { + bytesReturned = stream.Read(buffer, 0, bufferSize); + for (int i = 0; i < bytesReturned / 2 - 1; i += 2) + audio.Add(BitConverter.ToInt16(buffer, i * 2)); + } + stream.Close(); + return audio.ToArray(); + } + } +} diff --git a/src/Spectrogram.Tests/AudioFileTests.cs b/src/Spectrogram.Tests/AudioFileTests.cs new file mode 100644 index 0000000..ef99c25 --- /dev/null +++ b/src/Spectrogram.Tests/AudioFileTests.cs @@ -0,0 +1,25 @@ +using FluentAssertions; +using NUnit.Framework; + +namespace Spectrogram.Tests +{ + class AudioFileTests + { + /// + /// Compare values read from the WAV reader against those read by Python's SciPy module (see script in /dev folder) + /// + [TestCase("cant-do-that-44100.wav", 44_100, 166_671, 1)] + [TestCase("03-02-03-01-02-01-19.wav", 48_000, 214_615, 1)] + [TestCase("qrss-10min.wav", 6_000, 3_600_000, 1)] + [TestCase("cant-do-that-11025-stereo.wav", 11_025, 41668, 2)] + [TestCase("asehgal-original.wav", 40_000, 1_600_000, 1)] + public void Test_AudioFile_LengthAndSampleRate(string filename, int knownRate, int knownLength, int channels) + { + string filePath = $"../../../../../data/{filename}"; + (double[] audio, int sampleRate) = AudioFile.ReadWAV(filePath); + + sampleRate.Should().Be(knownRate); + (audio.Length / channels).Should().Be(knownLength); + } + } +} diff --git a/src/Spectrogram.Tests/ImageTests.cs b/src/Spectrogram.Tests/ImageTests.cs new file mode 100644 index 0000000..c0fe5ab --- /dev/null +++ b/src/Spectrogram.Tests/ImageTests.cs @@ -0,0 +1,22 @@ +using NUnit.Framework; +using SkiaSharp; + +namespace Spectrogram.Tests; + +internal class ImageTests +{ + [Test] + public void Test_Image_Rotations() + { + string filePath = $"../../../../../data/cant-do-that-44100.wav"; + (double[] audio, int sampleRate) = AudioFile.ReadWAV(filePath); + SpectrogramGenerator sg = new(sampleRate, 4096, 500, maxFreq: 3000); + sg.Add(audio); + + SKBitmap bmp1 = sg.GetBitmap(rotate: false); + bmp1.SaveTo("test-image-original.png", SKEncodedImageFormat.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 new file mode 100644 index 0000000..be6b6f3 --- /dev/null +++ b/src/Spectrogram.Tests/Mel.cs @@ -0,0 +1,121 @@ +using NUnit.Framework; +using System; +using SkiaSharp; + +namespace Spectrogram.Tests +{ + class Mel + { + [Test] + public void Test_MelSpectrogram_MelScale() + { + (double[] audio, int sampleRate) = AudioFile.ReadWAV("../../../../../data/cant-do-that-44100.wav"); + int fftSize = 4096; + var sg = new SpectrogramGenerator(sampleRate, fftSize, stepSize: 500); + sg.Add(audio); + + // 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); + + // Disegna bmpRaw su bmpCropped usando SKCanvas + using (var canvas = new SKCanvas(bmpCropped)) + { + 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); + } + } + } + + [Test] + public void Test_Mel_Graph() + { + int specPoints = 4096; + double maxFreq = 50_000; + double maxMel = 2595 * Math.Log10(1 + maxFreq / 700); + + Random rand = new Random(1); + double[] freq = ScottPlot.Generate.Consecutive(specPoints, maxFreq / specPoints); + double[] power = ScottPlot.Generate.RandomWalk(specPoints, .02, .5); + + var plt1 = new ScottPlot.Plot(); + plt1.Add.ScatterLine(freq, power); + + int filterSize = 25; + + // generate scales + double[] pointsLinear = new double[filterSize + 1]; + double[] pointsMel = new double[filterSize + 1]; + for (int i = 0; i < filterSize + 1; i++) + { + double thisFreq = maxFreq * i / filterSize; + double thisMel = maxMel * i / filterSize; + pointsLinear[i] = thisFreq; + pointsMel[i] = 700 * (Math.Pow(10, thisMel / 2595d) - 1); + } + + // draw rectangles + double[] binStartFreqs = pointsMel; + for (int binIndex = 0; binIndex < binStartFreqs.Length - 2; binIndex++) + { + double freqLow = binStartFreqs[binIndex]; + double freqCenter = binStartFreqs[binIndex + 1]; + double freqHigh = binStartFreqs[binIndex + 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); + int indexSpan = indexHigh - indexLow; + Console.WriteLine($"bin {binIndex}: [{freqLow} Hz - {freqHigh} Hz] = [{indexLow}:{indexHigh}]"); + + double binValue = 0; + double binScaleSum = 0; + for (int i = 0; i < indexSpan; i++) + { + double frac = (double)i / indexSpan; + frac = (frac < .5) ? frac * 2 : 1 - frac; + binScaleSum += frac; + binValue += power[indexLow + i] * frac; + } + binValue /= binScaleSum; + plt1.Add.Marker(freqCenter, binValue, ScottPlot.MarkerShape.FilledCircle, 10, sctr.Color); + } + + plt1.SavePng("mel1.png", 800, 300); + } + + [Test] + public void Test_SaveEmpty_Throws() + { + (double[] audio, int sampleRate) = AudioFile.ReadWAV("../../../../../data/cant-do-that-44100.wav"); + int fftSize = 4096; + var spec = new SpectrogramGenerator(sampleRate, fftSize, stepSize: 500); + //spec.Add(audio); + Assert.Throws(() => { spec.SaveImage("empty.png"); }); + } + } +} diff --git a/src/Spectrogram.Tests/Quickstart.cs b/src/Spectrogram.Tests/Quickstart.cs new file mode 100644 index 0000000..ec98436 --- /dev/null +++ b/src/Spectrogram.Tests/Quickstart.cs @@ -0,0 +1,55 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Spectrogram.Tests +{ + public class Quickstart + { + [Test] + public void Test_Quickstart_Hal() + { + (double[] audio, int sampleRate) = AudioFile.ReadWAV("../../../../../data/cant-do-that-44100.wav"); + var sg = new SpectrogramGenerator(sampleRate, fftSize: 4096, stepSize: 500, maxFreq: 3000); + sg.Add(audio); + sg.SaveImage("../../../../../dev/graphics/hal.png"); + + Console.WriteLine(sg); + } + + [Test] + public void Test_Readme_HeaderImage() + { + (double[] audio, int sampleRate) = AudioFile.ReadWAV("../../../../../data/cant-do-that-44100.wav"); + int fftSize = 2048; + var spec = new SpectrogramGenerator(sampleRate, fftSize, stepSize: 400, maxFreq: 6000); + spec.Add(audio); + spec.SaveImage("../../../../../dev/graphics/hal-spectrogram.png", intensity: 10, dB: true, dBScale: .05); + + Console.WriteLine(spec); + } + + [Test] + public void Test_Quickstart_Handel() + { + double[] audio = AudioFile.ReadMP3("../../../../../data/Handel - Air and Variations.mp3"); + int sampleRate = 44100; + + int fftSize = 16384; + int targetWidthPx = 3000; + int stepSize = audio.Length / targetWidthPx; + + var sg = new SpectrogramGenerator(sampleRate, fftSize, stepSize, maxFreq: 2200); + sg.Add(audio); + sg.SaveImage("../../../../../dev/graphics/spectrogram-song.png", intensity: 5, dB: true); + + Console.WriteLine(sg); + /* + Spectrogram (2993, 817) + Vertical (817 px): 0 - 2,199 Hz, FFT size: 16,384 samples, 2.69 Hz/px + Horizontal (2993 px): 2.96 min, window: 0.37 sec, step: 0.06 sec, overlap: 84% + */ + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..83fa10d --- /dev/null +++ b/src/Spectrogram.Tests/Spectrogram.Tests.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + false + + + + + + + + + + + + + + + + + diff --git a/src/Spectrogram.Tests/TestAGC.cs b/src/Spectrogram.Tests/TestAGC.cs new file mode 100644 index 0000000..70cb9e8 --- /dev/null +++ b/src/Spectrogram.Tests/TestAGC.cs @@ -0,0 +1,123 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Spectrogram.Tests +{ + class TestAGC + { + [Test] + public void Test_AGC_off() + { + string wavFilePath = "../../../../../data/qrss-10min.wav"; + (double[] audio, int sampleRate) = AudioFile.ReadWAV(wavFilePath); + + int fftSize = 8192; + var spec = new SpectrogramGenerator(sampleRate, fftSize, stepSize: 2000, maxFreq: 3000); + spec.Add(audio); + spec.SaveImage("qrss-agc-off.png", intensity: 3); + } + + [Test] + public void Test_AGC_normToNoiseFloor() + { + // strategy here is to normalize to the magnitude of the quietest 20% of frequencies + + string wavFilePath = "../../../../../data/qrss-10min.wav"; + (double[] audio, int sampleRate) = AudioFile.ReadWAV(wavFilePath); + + int fftSize = 8192; + var spec = new SpectrogramGenerator(sampleRate, fftSize, stepSize: 2000, maxFreq: 3000); + spec.Add(audio); + + var ffts = spec.GetFFTs(); + double normalIntensity = 2; + for (int i = 0; i < ffts.Count; i++) + { + double[] sorted = new double[ffts[i].Length]; + ffts[i].CopyTo(sorted, 0); + Array.Sort(sorted); + + double percentile = 0.25; + int percentileIndex = (int)(percentile * ffts[0].Length); + double floorValue = sorted[percentileIndex]; + + for (int y = 0; y < ffts[i].Length; y++) + { + ffts[i][y] = ffts[i][y] / floorValue * normalIntensity; + } + + Console.WriteLine(floorValue); + } + + spec.SaveImage("qrss-agc-norm-floor.png", intensity: 3); + } + + [Test] + public void Test_AGC_normWindow() + { + // strategy here is to create a weighted moving window mean and normalize to that + + string wavFilePath = "../../../../../data/qrss-10min.wav"; + (double[] audio, int sampleRate) = AudioFile.ReadWAV(wavFilePath); + + int fftSize = 8192; + var spec = new SpectrogramGenerator(sampleRate, fftSize, stepSize: 2000, maxFreq: 3000); + spec.Add(audio); + + var ffts = spec.GetFFTs(); + for (int i = 0; i < ffts.Count; i++) + ffts[i] = SubtractMovingWindowFloor(ffts[i]); + + spec.SaveImage("qrss-agc-norm-window.png", intensity: 3); + } + + private double[] SubtractMovingWindow(double[] input, int windowSizePx = 100) + { + // return a copy of the input array with the moving window subtracted + + var hanningWindow = new FftSharp.Windows.Hanning(); + double[] window = hanningWindow.Create(windowSizePx); + double windowSum = window.Sum(); + + double[] windowed = new double[input.Length]; + double[] normalized = new double[input.Length]; + + for (int i = 0; i < input.Length - window.Length; i++) + { + double windowedInputSum = 0; + for (int j = 0; j < window.Length; j++) + windowedInputSum += input[i + j] * window[j]; + windowed[i + window.Length / 2] = windowedInputSum / windowSum; + } + + for (int i = 0; i < input.Length; i++) + normalized[i] = Math.Max(input[i] - windowed[i], 0); + + return normalized; + } + + private double[] SubtractMovingWindowFloor(double[] input, int windowSizePx = 20, double percentile = .2) + { + // return a copy of the input with the noise floor subtracted + // where the noise floor is calculated from a moving window + // this is good but very slow + + double[] normalized = new double[input.Length]; + int floorIndex = (int)(percentile * windowSizePx); + + double[] segment = new double[windowSizePx]; + for (int i = 0; i < input.Length - windowSizePx; i++) + { + for (int j = 0; j < windowSizePx; j++) + segment[j] = input[i + j]; + Array.Sort(segment); + normalized[i] = Math.Max(input[i] - segment[floorIndex], 0); + } + + return normalized; + } + } +} diff --git a/src/Spectrogram.sln b/src/Spectrogram.sln index 43e0891..215c96f 100644 --- a/src/Spectrogram.sln +++ b/src/Spectrogram.sln @@ -1,17 +1,13 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29209.62 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31815.197 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleDemo", "ConsoleDemo\ConsoleDemo.csproj", "{DA5009A8-9036-4BDA-B591-3244DEB759E9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectrogram", "Spectrogram\Spectrogram.csproj", "{6FF83EDD-E18A-4EDD-8D53-D2281515AC47}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectrogram", "Spectrogram\Spectrogram.csproj", "{8717F4CE-4497-4EAA-B95D-0F7A04FB397D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectrogram.Demo", "Spectrogram.MicrophoneDemo\Spectrogram.Demo.csproj", "{D51ABC6A-53F4-4620-88A1-14EA1D779538}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AudioMonitor", "AudioMonitor\AudioMonitor.csproj", "{806D56D1-077A-4E8C-A742-2AEED037AF17}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Demos", "Demos", "{9B6B9DCD-7360-4E63-A39C-4084F5CFF869}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WaterfallDemo", "WaterfallDemo\WaterfallDemo.csproj", "{3264BE41-10B0-4121-B837-D7B134BD1A9B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectrogram.Tests", "Spectrogram.Tests\Spectrogram.Tests.csproj", "{E7482801-78C7-41FD-88D1-72A7ED3EFC9D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -19,32 +15,23 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {DA5009A8-9036-4BDA-B591-3244DEB759E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DA5009A8-9036-4BDA-B591-3244DEB759E9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DA5009A8-9036-4BDA-B591-3244DEB759E9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DA5009A8-9036-4BDA-B591-3244DEB759E9}.Release|Any CPU.Build.0 = Release|Any CPU - {8717F4CE-4497-4EAA-B95D-0F7A04FB397D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8717F4CE-4497-4EAA-B95D-0F7A04FB397D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8717F4CE-4497-4EAA-B95D-0F7A04FB397D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8717F4CE-4497-4EAA-B95D-0F7A04FB397D}.Release|Any CPU.Build.0 = Release|Any CPU - {806D56D1-077A-4E8C-A742-2AEED037AF17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {806D56D1-077A-4E8C-A742-2AEED037AF17}.Debug|Any CPU.Build.0 = Debug|Any CPU - {806D56D1-077A-4E8C-A742-2AEED037AF17}.Release|Any CPU.ActiveCfg = Release|Any CPU - {806D56D1-077A-4E8C-A742-2AEED037AF17}.Release|Any CPU.Build.0 = Release|Any CPU - {3264BE41-10B0-4121-B837-D7B134BD1A9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3264BE41-10B0-4121-B837-D7B134BD1A9B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3264BE41-10B0-4121-B837-D7B134BD1A9B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3264BE41-10B0-4121-B837-D7B134BD1A9B}.Release|Any CPU.Build.0 = Release|Any CPU + {6FF83EDD-E18A-4EDD-8D53-D2281515AC47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6FF83EDD-E18A-4EDD-8D53-D2281515AC47}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6FF83EDD-E18A-4EDD-8D53-D2281515AC47}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6FF83EDD-E18A-4EDD-8D53-D2281515AC47}.Release|Any CPU.Build.0 = Release|Any CPU + {D51ABC6A-53F4-4620-88A1-14EA1D779538}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D51ABC6A-53F4-4620-88A1-14EA1D779538}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D51ABC6A-53F4-4620-88A1-14EA1D779538}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D51ABC6A-53F4-4620-88A1-14EA1D779538}.Release|Any CPU.Build.0 = Release|Any CPU + {E7482801-78C7-41FD-88D1-72A7ED3EFC9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E7482801-78C7-41FD-88D1-72A7ED3EFC9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E7482801-78C7-41FD-88D1-72A7ED3EFC9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E7482801-78C7-41FD-88D1-72A7ED3EFC9D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {DA5009A8-9036-4BDA-B591-3244DEB759E9} = {9B6B9DCD-7360-4E63-A39C-4084F5CFF869} - {806D56D1-077A-4E8C-A742-2AEED037AF17} = {9B6B9DCD-7360-4E63-A39C-4084F5CFF869} - {3264BE41-10B0-4121-B837-D7B134BD1A9B} = {9B6B9DCD-7360-4E63-A39C-4084F5CFF869} - EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {7D18DDFF-2D40-4E8A-9EA7-3F7B658062D3} + SolutionGuid = {027B0366-F1D7-426F-B064-71E97E3248C1} EndGlobalSection EndGlobal diff --git a/src/Spectrogram/Annotations.cs b/src/Spectrogram/Annotations.cs deleted file mode 100644 index 1946405..0000000 --- a/src/Spectrogram/Annotations.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Spectrogram.Settings; -using System; -using System.Drawing; - -namespace Spectrogram -{ - public class Annotations - { - public static void drawTicks(Bitmap bmp, FftSettings fftSettings, DisplaySettings displaySettings) - { - Graphics gfx = Graphics.FromImage(bmp); - - double tickSpacingSec = displaySettings.tickSpacingSec; - double tickSpacingHz = displaySettings.tickSpacingHz; - - double horizontalTickSpacing = tickSpacingSec * fftSettings.sampleRate / fftSettings.step; - - double firstFreqTick = fftSettings.FrequencyFromIndex(displaySettings.pixelLower) + tickSpacingHz; - double lastFreqTick = fftSettings.FrequencyFromIndex(displaySettings.pixelUpper) - tickSpacingHz; - - for (double frequency = 0; frequency < fftSettings.maxFreq; frequency += tickSpacingHz) - { - if ((frequency < firstFreqTick) || (frequency > lastFreqTick)) - continue; - int yPosition = bmp.Height - (fftSettings.IndexFromFrequency(frequency) - displaySettings.pixelLower); - Point p1 = new Point(bmp.Width - displaySettings.tickSize, yPosition); - Point p2 = new Point(bmp.Width, yPosition); - DrawLineWithShadow(gfx, p1, p2); - DrawTextWithShadow(gfx, Math.Round(frequency).ToString(), p1, displaySettings.tickFont, displaySettings.sfTicksRight); - } - - for (double xPx = 0; xPx < bmp.Width; xPx += horizontalTickSpacing) - { - Point p1 = new Point((int)xPx, bmp.Height); - Point p2 = new Point((int)xPx, bmp.Height - displaySettings.tickSize); - DrawLineWithShadow(gfx, p1, p2); - } - } - - static void DrawTextWithShadow(Graphics gfx, string s, Point pt, Font fnt, StringFormat sf) - { - for (int dX = -1; dX < 2; dX++) - for (int dY = -1; dY < 2; dY++) - gfx.DrawString(s, fnt, Brushes.Black, pt.X + dX, pt.Y + dY, sf); - - gfx.DrawString(s, fnt, Brushes.White, pt, sf); - } - - static void DrawLineWithShadow(Graphics gfx, Point p1, Point p2) - { - Pen penShadow = new Pen(Brushes.Black, 3); - Pen penTick = new Pen(Brushes.White, 1); - penShadow.EndCap = System.Drawing.Drawing2D.LineCap.Round; - penTick.EndCap = System.Drawing.Drawing2D.LineCap.Round; - gfx.DrawLine(penShadow, p1, p2); - gfx.DrawLine(penTick, p1, p2); - } - } -} diff --git a/src/Spectrogram/Benchmark.cs b/src/Spectrogram/Benchmark.cs deleted file mode 100644 index b0bda36..0000000 --- a/src/Spectrogram/Benchmark.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Diagnostics; - -namespace Spectrogram -{ - public class Benchmark : IDisposable - { - Stopwatch stopwatch; - bool silent; - - public Benchmark(bool silent = false) - { - stopwatch = Stopwatch.StartNew(); - this.silent = silent; - } - - public double elapsedMilliseconds - { - get - { - return stopwatch.ElapsedTicks * 1000.0 / Stopwatch.Frequency; - } - } - - public void Dispose() - { - stopwatch.Stop(); - if (!silent) - Console.WriteLine(string.Format("completed in {0:0.00} ms", elapsedMilliseconds)); - } - } -} diff --git a/src/Spectrogram/Colormap.cs b/src/Spectrogram/Colormap.cs index 09f04fc..1553712 100644 --- a/src/Spectrogram/Colormap.cs +++ b/src/Spectrogram/Colormap.cs @@ -1,9 +1,91 @@ using System; -using System.Collections.Generic; -using System.Drawing; -using System.Text; +using System.Linq; +using SkiaSharp; -namespace Spectrogram +namespace Spectrogram; + +public class Colormap(ScottPlot.IColormap colormap) { - public enum Colormap { grayscale, grayscaleInverted, viridis, vdGreen, vdBlue } + 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) + { + foreach (Colormap cmap in GetColormaps()) + if (string.Equals(cmap.Name, colormapName, StringComparison.InvariantCultureIgnoreCase)) + return cmap; + + throw new ArgumentException($"Colormap does not exist: {colormapName}"); + } + + public (byte r, byte g, byte b) GetRGB(byte value) => GetRGB(value / 255.0); + + public (byte r, byte g, byte b) GetRGB(double fraction) + { + ScottPlot.Color color = _Colormap.GetColor(fraction); + return (color.R, color.G, color.B); + } + + public int GetInt32(byte value) + { + var (r, g, b) = GetRGB(value); + return 255 << 24 | r << 16 | g << 8 | b; + } + + public int GetInt32(double fraction) + { + var (r, g, b) = GetRGB(fraction); + return 255 << 24 | r << 16 | g << 8 | b; + } + + public SKColor GetColor(byte value) + { + var color = GetInt32(value); + return new SKColor((uint)color); + } + + public SKColor GetColor(double fraction) + { + var color = GetInt32(fraction); + return new SKColor((uint)color); + } + + 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(); + + using SKPaint paint = new SKPaint(); + + byte[] A = new byte[256]; + byte[] R = new byte[256]; + byte[] G = new byte[256]; + byte[] B = new byte[256]; + + for (int i = 0; i < 256; i++) + { + 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); + + canvas.DrawBitmap(bmp, 0, 0, paint); + return newBitmap; + } } diff --git a/src/Spectrogram/Colormaps/Colormap.cs b/src/Spectrogram/Colormaps/Colormap.cs deleted file mode 100644 index 86e7c57..0000000 --- a/src/Spectrogram/Colormaps/Colormap.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Text; - -namespace Spectrogram.Colormaps -{ - public abstract class Colormap - { - public abstract string GetName(); - public abstract void Apply(Bitmap bmp); - } -} diff --git a/src/Spectrogram/Colormaps/Grayscale.cs b/src/Spectrogram/Colormaps/Grayscale.cs deleted file mode 100644 index 60e720a..0000000 --- a/src/Spectrogram/Colormaps/Grayscale.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; -using System.Text; - -namespace Spectrogram.Colormaps -{ - class Grayscale : Colormap - { - public override string GetName() - { - return "Grayscale"; - } - - public override void Apply(Bitmap bmp) - { - ColorPalette pal = bmp.Palette; - - for (int i=0; i<256; i++) - pal.Entries[i] = Color.FromArgb(255, i, i, i); - - bmp.Palette = pal; - } - - } -} diff --git a/src/Spectrogram/Colormaps/GrayscaleInverted.cs b/src/Spectrogram/Colormaps/GrayscaleInverted.cs deleted file mode 100644 index b136883..0000000 --- a/src/Spectrogram/Colormaps/GrayscaleInverted.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Drawing; -using System.Drawing.Imaging; - -namespace Spectrogram.Colormaps -{ - class GrayscaleInverted : Colormap - { - public override string GetName() - { - return "Grayscale Inverted"; - } - - public override void Apply(Bitmap bmp) - { - ColorPalette pal = bmp.Palette; - - for (int i = 0; i < 256; i++) - pal.Entries[i] = Color.FromArgb(255, 255 - i, 255 - i, 255 - i); - - bmp.Palette = pal; - } - - } -} diff --git a/src/Spectrogram/Colormaps/Jet.cs b/src/Spectrogram/Colormaps/Jet.cs deleted file mode 100644 index ee3a166..0000000 --- a/src/Spectrogram/Colormaps/Jet.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; -using System.Text; - -namespace Spectrogram.Colormaps -{ - class Jet : Colormap - { - public override string GetName() - { - return "Grayscale"; - } - - public override void Apply(Bitmap bmp) - { - ColorPalette pal = bmp.Palette; - - bmp.Palette = pal; - } - } -} diff --git a/src/Spectrogram/Colormaps/VdBlues.cs b/src/Spectrogram/Colormaps/VdBlues.cs deleted file mode 100644 index 1d95eb5..0000000 --- a/src/Spectrogram/Colormaps/VdBlues.cs +++ /dev/null @@ -1,281 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; -using System.Text; - -namespace Spectrogram.Colormaps -{ - class VdBlues : Colormap - { - public override string GetName() - { - return "QRSS-VD Blues"; - } - - public override void Apply(Bitmap bmp) - { - ColorPalette pal = bmp.Palette; - - pal.Entries[0] = Color.FromArgb(255, 2, 57, 89); - pal.Entries[1] = Color.FromArgb(255, 2, 58, 90); - pal.Entries[2] = Color.FromArgb(255, 2, 57, 89); - pal.Entries[3] = Color.FromArgb(255, 2, 58, 90); - pal.Entries[4] = Color.FromArgb(255, 2, 59, 92); - pal.Entries[5] = Color.FromArgb(255, 2, 60, 93); - pal.Entries[6] = Color.FromArgb(255, 2, 61, 95); - pal.Entries[7] = Color.FromArgb(255, 2, 62, 97); - pal.Entries[8] = Color.FromArgb(255, 2, 63, 98); - pal.Entries[9] = Color.FromArgb(255, 2, 64, 100); - pal.Entries[10] = Color.FromArgb(255, 2, 65, 102); - pal.Entries[11] = Color.FromArgb(255, 2, 66, 103); - pal.Entries[12] = Color.FromArgb(255, 2, 67, 105); - pal.Entries[13] = Color.FromArgb(255, 2, 68, 107); - pal.Entries[14] = Color.FromArgb(255, 2, 69, 108); - pal.Entries[15] = Color.FromArgb(255, 2, 70, 110); - pal.Entries[16] = Color.FromArgb(255, 2, 71, 112); - pal.Entries[17] = Color.FromArgb(255, 3, 73, 113); - pal.Entries[18] = Color.FromArgb(255, 3, 74, 115); - pal.Entries[19] = Color.FromArgb(255, 3, 75, 117); - pal.Entries[20] = Color.FromArgb(255, 3, 76, 118); - pal.Entries[21] = Color.FromArgb(255, 3, 77, 120); - pal.Entries[22] = Color.FromArgb(255, 3, 78, 122); - pal.Entries[23] = Color.FromArgb(255, 3, 79, 123); - pal.Entries[24] = Color.FromArgb(255, 3, 80, 125); - pal.Entries[25] = Color.FromArgb(255, 3, 81, 127); - pal.Entries[26] = Color.FromArgb(255, 3, 82, 128); - pal.Entries[27] = Color.FromArgb(255, 3, 83, 130); - pal.Entries[28] = Color.FromArgb(255, 3, 84, 132); - pal.Entries[29] = Color.FromArgb(255, 3, 85, 133); - pal.Entries[30] = Color.FromArgb(255, 3, 86, 135); - pal.Entries[31] = Color.FromArgb(255, 3, 87, 137); - pal.Entries[32] = Color.FromArgb(255, 3, 89, 138); - pal.Entries[33] = Color.FromArgb(255, 4, 90, 140); - pal.Entries[34] = Color.FromArgb(255, 4, 90, 142); - pal.Entries[35] = Color.FromArgb(255, 4, 91, 143); - pal.Entries[36] = Color.FromArgb(255, 4, 92, 144); - pal.Entries[37] = Color.FromArgb(255, 4, 92, 145); - pal.Entries[38] = Color.FromArgb(255, 4, 93, 146); - pal.Entries[39] = Color.FromArgb(255, 4, 94, 147); - pal.Entries[40] = Color.FromArgb(255, 4, 94, 148); - pal.Entries[41] = Color.FromArgb(255, 4, 95, 149); - pal.Entries[42] = Color.FromArgb(255, 4, 96, 150); - pal.Entries[43] = Color.FromArgb(255, 4, 96, 152); - pal.Entries[44] = Color.FromArgb(255, 4, 97, 153); - pal.Entries[45] = Color.FromArgb(255, 4, 98, 154); - pal.Entries[46] = Color.FromArgb(255, 4, 99, 155); - pal.Entries[47] = Color.FromArgb(255, 4, 99, 156); - pal.Entries[48] = Color.FromArgb(255, 4, 100, 157); - pal.Entries[49] = Color.FromArgb(255, 4, 101, 158); - pal.Entries[50] = Color.FromArgb(255, 4, 101, 159); - pal.Entries[51] = Color.FromArgb(255, 4, 102, 160); - pal.Entries[52] = Color.FromArgb(255, 4, 103, 161); - pal.Entries[53] = Color.FromArgb(255, 4, 103, 163); - pal.Entries[54] = Color.FromArgb(255, 4, 104, 164); - pal.Entries[55] = Color.FromArgb(255, 4, 105, 165); - pal.Entries[56] = Color.FromArgb(255, 4, 105, 166); - pal.Entries[57] = Color.FromArgb(255, 4, 106, 167); - pal.Entries[58] = Color.FromArgb(255, 4, 107, 168); - pal.Entries[59] = Color.FromArgb(255, 4, 108, 169); - pal.Entries[60] = Color.FromArgb(255, 4, 108, 170); - pal.Entries[61] = Color.FromArgb(255, 4, 109, 171); - pal.Entries[62] = Color.FromArgb(255, 4, 110, 172); - pal.Entries[63] = Color.FromArgb(255, 4, 110, 174); - pal.Entries[64] = Color.FromArgb(255, 4, 111, 175); - pal.Entries[65] = Color.FromArgb(255, 5, 112, 176); - pal.Entries[66] = Color.FromArgb(255, 6, 113, 176); - pal.Entries[67] = Color.FromArgb(255, 8, 114, 177); - pal.Entries[68] = Color.FromArgb(255, 9, 115, 177); - pal.Entries[69] = Color.FromArgb(255, 11, 116, 178); - pal.Entries[70] = Color.FromArgb(255, 13, 117, 178); - pal.Entries[71] = Color.FromArgb(255, 14, 118, 179); - pal.Entries[72] = Color.FromArgb(255, 16, 119, 179); - pal.Entries[73] = Color.FromArgb(255, 17, 120, 180); - pal.Entries[74] = Color.FromArgb(255, 19, 121, 180); - pal.Entries[75] = Color.FromArgb(255, 20, 122, 181); - pal.Entries[76] = Color.FromArgb(255, 22, 123, 181); - pal.Entries[77] = Color.FromArgb(255, 23, 124, 182); - pal.Entries[78] = Color.FromArgb(255, 25, 125, 182); - pal.Entries[79] = Color.FromArgb(255, 26, 126, 183); - pal.Entries[80] = Color.FromArgb(255, 28, 127, 183); - pal.Entries[81] = Color.FromArgb(255, 29, 128, 184); - pal.Entries[82] = Color.FromArgb(255, 31, 129, 184); - pal.Entries[83] = Color.FromArgb(255, 33, 130, 185); - pal.Entries[84] = Color.FromArgb(255, 34, 131, 185); - pal.Entries[85] = Color.FromArgb(255, 36, 132, 186); - pal.Entries[86] = Color.FromArgb(255, 37, 133, 186); - pal.Entries[87] = Color.FromArgb(255, 39, 134, 187); - pal.Entries[88] = Color.FromArgb(255, 40, 135, 187); - pal.Entries[89] = Color.FromArgb(255, 42, 136, 188); - pal.Entries[90] = Color.FromArgb(255, 43, 137, 188); - pal.Entries[91] = Color.FromArgb(255, 45, 138, 189); - pal.Entries[92] = Color.FromArgb(255, 46, 139, 189); - pal.Entries[93] = Color.FromArgb(255, 48, 140, 190); - pal.Entries[94] = Color.FromArgb(255, 49, 141, 190); - pal.Entries[95] = Color.FromArgb(255, 51, 142, 191); - pal.Entries[96] = Color.FromArgb(255, 53, 143, 191); - pal.Entries[97] = Color.FromArgb(255, 54, 144, 192); - pal.Entries[98] = Color.FromArgb(255, 56, 145, 192); - pal.Entries[99] = Color.FromArgb(255, 58, 145, 193); - pal.Entries[100] = Color.FromArgb(255, 60, 146, 193); - pal.Entries[101] = Color.FromArgb(255, 62, 147, 194); - pal.Entries[102] = Color.FromArgb(255, 64, 148, 194); - pal.Entries[103] = Color.FromArgb(255, 66, 149, 195); - pal.Entries[104] = Color.FromArgb(255, 68, 149, 195); - pal.Entries[105] = Color.FromArgb(255, 70, 150, 195); - pal.Entries[106] = Color.FromArgb(255, 72, 151, 196); - pal.Entries[107] = Color.FromArgb(255, 74, 152, 196); - pal.Entries[108] = Color.FromArgb(255, 76, 152, 197); - pal.Entries[109] = Color.FromArgb(255, 78, 153, 197); - pal.Entries[110] = Color.FromArgb(255, 80, 154, 198); - pal.Entries[111] = Color.FromArgb(255, 81, 155, 198); - pal.Entries[112] = Color.FromArgb(255, 83, 156, 199); - pal.Entries[113] = Color.FromArgb(255, 85, 156, 199); - pal.Entries[114] = Color.FromArgb(255, 87, 157, 200); - pal.Entries[115] = Color.FromArgb(255, 89, 158, 200); - pal.Entries[116] = Color.FromArgb(255, 91, 159, 201); - pal.Entries[117] = Color.FromArgb(255, 93, 159, 201); - pal.Entries[118] = Color.FromArgb(255, 95, 160, 202); - pal.Entries[119] = Color.FromArgb(255, 97, 161, 202); - pal.Entries[120] = Color.FromArgb(255, 99, 162, 203); - pal.Entries[121] = Color.FromArgb(255, 101, 163, 203); - pal.Entries[122] = Color.FromArgb(255, 103, 163, 203); - pal.Entries[123] = Color.FromArgb(255, 105, 164, 204); - pal.Entries[124] = Color.FromArgb(255, 107, 165, 204); - pal.Entries[125] = Color.FromArgb(255, 109, 166, 205); - pal.Entries[126] = Color.FromArgb(255, 111, 167, 205); - pal.Entries[127] = Color.FromArgb(255, 113, 167, 206); - pal.Entries[128] = Color.FromArgb(255, 115, 168, 206); - pal.Entries[129] = Color.FromArgb(255, 116, 169, 207); - pal.Entries[130] = Color.FromArgb(255, 118, 169, 207); - pal.Entries[131] = Color.FromArgb(255, 119, 170, 207); - pal.Entries[132] = Color.FromArgb(255, 121, 171, 208); - pal.Entries[133] = Color.FromArgb(255, 123, 171, 208); - pal.Entries[134] = Color.FromArgb(255, 124, 172, 209); - pal.Entries[135] = Color.FromArgb(255, 126, 173, 209); - pal.Entries[136] = Color.FromArgb(255, 127, 173, 209); - pal.Entries[137] = Color.FromArgb(255, 129, 174, 210); - pal.Entries[138] = Color.FromArgb(255, 130, 174, 210); - pal.Entries[139] = Color.FromArgb(255, 132, 175, 210); - pal.Entries[140] = Color.FromArgb(255, 134, 176, 211); - pal.Entries[141] = Color.FromArgb(255, 135, 176, 211); - pal.Entries[142] = Color.FromArgb(255, 137, 177, 212); - pal.Entries[143] = Color.FromArgb(255, 138, 178, 212); - pal.Entries[144] = Color.FromArgb(255, 140, 178, 212); - pal.Entries[145] = Color.FromArgb(255, 141, 179, 213); - pal.Entries[146] = Color.FromArgb(255, 143, 179, 213); - pal.Entries[147] = Color.FromArgb(255, 145, 180, 213); - pal.Entries[148] = Color.FromArgb(255, 146, 181, 214); - pal.Entries[149] = Color.FromArgb(255, 148, 181, 214); - pal.Entries[150] = Color.FromArgb(255, 149, 182, 215); - pal.Entries[151] = Color.FromArgb(255, 151, 183, 215); - pal.Entries[152] = Color.FromArgb(255, 152, 183, 215); - pal.Entries[153] = Color.FromArgb(255, 154, 184, 216); - pal.Entries[154] = Color.FromArgb(255, 156, 185, 216); - pal.Entries[155] = Color.FromArgb(255, 157, 185, 216); - pal.Entries[156] = Color.FromArgb(255, 159, 186, 217); - pal.Entries[157] = Color.FromArgb(255, 160, 186, 217); - pal.Entries[158] = Color.FromArgb(255, 162, 187, 218); - pal.Entries[159] = Color.FromArgb(255, 163, 188, 218); - pal.Entries[160] = Color.FromArgb(255, 165, 188, 218); - pal.Entries[161] = Color.FromArgb(255, 166, 189, 219); - pal.Entries[162] = Color.FromArgb(255, 168, 190, 219); - pal.Entries[163] = Color.FromArgb(255, 169, 190, 219); - pal.Entries[164] = Color.FromArgb(255, 170, 191, 220); - pal.Entries[165] = Color.FromArgb(255, 172, 191, 220); - pal.Entries[166] = Color.FromArgb(255, 173, 192, 220); - pal.Entries[167] = Color.FromArgb(255, 174, 193, 221); - pal.Entries[168] = Color.FromArgb(255, 176, 193, 221); - pal.Entries[169] = Color.FromArgb(255, 177, 194, 221); - pal.Entries[170] = Color.FromArgb(255, 178, 195, 222); - pal.Entries[171] = Color.FromArgb(255, 180, 195, 222); - pal.Entries[172] = Color.FromArgb(255, 181, 196, 223); - pal.Entries[173] = Color.FromArgb(255, 182, 196, 223); - pal.Entries[174] = Color.FromArgb(255, 184, 197, 223); - pal.Entries[175] = Color.FromArgb(255, 185, 198, 224); - pal.Entries[176] = Color.FromArgb(255, 186, 198, 224); - pal.Entries[177] = Color.FromArgb(255, 187, 199, 224); - pal.Entries[178] = Color.FromArgb(255, 189, 200, 225); - pal.Entries[179] = Color.FromArgb(255, 190, 200, 225); - pal.Entries[180] = Color.FromArgb(255, 191, 201, 225); - pal.Entries[181] = Color.FromArgb(255, 193, 201, 226); - pal.Entries[182] = Color.FromArgb(255, 194, 202, 226); - pal.Entries[183] = Color.FromArgb(255, 196, 203, 226); - pal.Entries[184] = Color.FromArgb(255, 197, 203, 227); - pal.Entries[185] = Color.FromArgb(255, 198, 204, 227); - pal.Entries[186] = Color.FromArgb(255, 199, 205, 227); - pal.Entries[187] = Color.FromArgb(255, 201, 205, 228); - pal.Entries[188] = Color.FromArgb(255, 202, 206, 228); - pal.Entries[189] = Color.FromArgb(255, 204, 206, 228); - pal.Entries[190] = Color.FromArgb(255, 205, 207, 229); - pal.Entries[191] = Color.FromArgb(255, 206, 208, 229); - pal.Entries[192] = Color.FromArgb(255, 207, 208, 229); - pal.Entries[193] = Color.FromArgb(255, 208, 209, 230); - pal.Entries[194] = Color.FromArgb(255, 209, 210, 230); - pal.Entries[195] = Color.FromArgb(255, 210, 210, 231); - pal.Entries[196] = Color.FromArgb(255, 211, 211, 231); - pal.Entries[197] = Color.FromArgb(255, 212, 212, 231); - pal.Entries[198] = Color.FromArgb(255, 213, 212, 232); - pal.Entries[199] = Color.FromArgb(255, 213, 213, 232); - pal.Entries[200] = Color.FromArgb(255, 214, 214, 232); - pal.Entries[201] = Color.FromArgb(255, 215, 215, 233); - pal.Entries[202] = Color.FromArgb(255, 216, 215, 233); - pal.Entries[203] = Color.FromArgb(255, 217, 216, 234); - pal.Entries[204] = Color.FromArgb(255, 218, 217, 234); - pal.Entries[205] = Color.FromArgb(255, 219, 217, 234); - pal.Entries[206] = Color.FromArgb(255, 220, 218, 235); - pal.Entries[207] = Color.FromArgb(255, 220, 219, 235); - pal.Entries[208] = Color.FromArgb(255, 221, 219, 235); - pal.Entries[209] = Color.FromArgb(255, 222, 220, 236); - pal.Entries[210] = Color.FromArgb(255, 223, 221, 236); - pal.Entries[211] = Color.FromArgb(255, 224, 221, 237); - pal.Entries[212] = Color.FromArgb(255, 225, 222, 237); - pal.Entries[213] = Color.FromArgb(255, 226, 223, 237); - pal.Entries[214] = Color.FromArgb(255, 227, 224, 238); - pal.Entries[215] = Color.FromArgb(255, 227, 224, 238); - pal.Entries[216] = Color.FromArgb(255, 228, 225, 238); - pal.Entries[217] = Color.FromArgb(255, 229, 226, 239); - pal.Entries[218] = Color.FromArgb(255, 230, 226, 239); - pal.Entries[219] = Color.FromArgb(255, 231, 227, 240); - pal.Entries[220] = Color.FromArgb(255, 232, 228, 240); - pal.Entries[221] = Color.FromArgb(255, 233, 228, 240); - pal.Entries[222] = Color.FromArgb(255, 234, 229, 241); - pal.Entries[223] = Color.FromArgb(255, 235, 230, 241); - pal.Entries[224] = Color.FromArgb(255, 235, 230, 241); - pal.Entries[225] = Color.FromArgb(255, 236, 231, 242); - pal.Entries[226] = Color.FromArgb(255, 237, 231, 242); - pal.Entries[227] = Color.FromArgb(255, 237, 232, 242); - pal.Entries[228] = Color.FromArgb(255, 238, 232, 243); - pal.Entries[229] = Color.FromArgb(255, 238, 233, 243); - pal.Entries[230] = Color.FromArgb(255, 239, 233, 243); - pal.Entries[231] = Color.FromArgb(255, 240, 234, 243); - pal.Entries[232] = Color.FromArgb(255, 240, 234, 244); - pal.Entries[233] = Color.FromArgb(255, 241, 235, 244); - pal.Entries[234] = Color.FromArgb(255, 241, 235, 244); - pal.Entries[235] = Color.FromArgb(255, 242, 236, 245); - pal.Entries[236] = Color.FromArgb(255, 243, 236, 245); - pal.Entries[237] = Color.FromArgb(255, 243, 237, 245); - pal.Entries[238] = Color.FromArgb(255, 244, 237, 245); - pal.Entries[239] = Color.FromArgb(255, 244, 238, 246); - pal.Entries[240] = Color.FromArgb(255, 246, 239, 246); - pal.Entries[241] = Color.FromArgb(255, 246, 239, 246); - pal.Entries[242] = Color.FromArgb(255, 246, 239, 247); - pal.Entries[243] = Color.FromArgb(255, 247, 240, 247); - pal.Entries[244] = Color.FromArgb(255, 247, 240, 247); - pal.Entries[245] = Color.FromArgb(255, 249, 241, 248); - pal.Entries[246] = Color.FromArgb(255, 249, 241, 248); - pal.Entries[247] = Color.FromArgb(255, 249, 242, 248); - pal.Entries[248] = Color.FromArgb(255, 250, 242, 248); - pal.Entries[249] = Color.FromArgb(255, 250, 243, 249); - pal.Entries[250] = Color.FromArgb(255, 252, 244, 249); - pal.Entries[251] = Color.FromArgb(255, 252, 244, 249); - pal.Entries[252] = Color.FromArgb(255, 252, 244, 249); - pal.Entries[253] = Color.FromArgb(255, 253, 245, 250); - pal.Entries[254] = Color.FromArgb(255, 254, 246, 250); - pal.Entries[255] = Color.FromArgb(255, 255, 247, 251); - - bmp.Palette = pal; - } - - } -} diff --git a/src/Spectrogram/Colormaps/VdGreens.cs b/src/Spectrogram/Colormaps/VdGreens.cs deleted file mode 100644 index 3e501f8..0000000 --- a/src/Spectrogram/Colormaps/VdGreens.cs +++ /dev/null @@ -1,281 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; -using System.Text; - -namespace Spectrogram.Colormaps -{ - class VdGreens : Colormap - { - public override string GetName() - { - return "QRSS-VD Greens"; - } - - public override void Apply(Bitmap bmp) - { - ColorPalette pal = bmp.Palette; - - pal.Entries[0] = Color.FromArgb(255, 0, 70, 41); - pal.Entries[1] = Color.FromArgb(255, 0, 71, 41); - pal.Entries[2] = Color.FromArgb(255, 0, 70, 41); - pal.Entries[3] = Color.FromArgb(255, 0, 71, 41); - pal.Entries[4] = Color.FromArgb(255, 0, 72, 42); - pal.Entries[5] = Color.FromArgb(255, 0, 73, 42); - pal.Entries[6] = Color.FromArgb(255, 0, 74, 43); - pal.Entries[7] = Color.FromArgb(255, 0, 75, 43); - pal.Entries[8] = Color.FromArgb(255, 0, 76, 44); - pal.Entries[9] = Color.FromArgb(255, 0, 77, 44); - pal.Entries[10] = Color.FromArgb(255, 0, 78, 44); - pal.Entries[11] = Color.FromArgb(255, 0, 79, 45); - pal.Entries[12] = Color.FromArgb(255, 0, 80, 45); - pal.Entries[13] = Color.FromArgb(255, 0, 82, 46); - pal.Entries[14] = Color.FromArgb(255, 0, 83, 46); - pal.Entries[15] = Color.FromArgb(255, 0, 84, 47); - pal.Entries[16] = Color.FromArgb(255, 0, 85, 47); - pal.Entries[17] = Color.FromArgb(255, 0, 86, 48); - pal.Entries[18] = Color.FromArgb(255, 0, 87, 48); - pal.Entries[19] = Color.FromArgb(255, 0, 88, 48); - pal.Entries[20] = Color.FromArgb(255, 0, 89, 49); - pal.Entries[21] = Color.FromArgb(255, 0, 90, 49); - pal.Entries[22] = Color.FromArgb(255, 0, 91, 50); - pal.Entries[23] = Color.FromArgb(255, 0, 93, 50); - pal.Entries[24] = Color.FromArgb(255, 0, 94, 51); - pal.Entries[25] = Color.FromArgb(255, 0, 95, 51); - pal.Entries[26] = Color.FromArgb(255, 0, 96, 51); - pal.Entries[27] = Color.FromArgb(255, 0, 97, 52); - pal.Entries[28] = Color.FromArgb(255, 0, 98, 52); - pal.Entries[29] = Color.FromArgb(255, 0, 99, 53); - pal.Entries[30] = Color.FromArgb(255, 0, 100, 53); - pal.Entries[31] = Color.FromArgb(255, 0, 101, 54); - pal.Entries[32] = Color.FromArgb(255, 0, 102, 54); - pal.Entries[33] = Color.FromArgb(255, 0, 104, 55); - pal.Entries[34] = Color.FromArgb(255, 1, 104, 55); - pal.Entries[35] = Color.FromArgb(255, 2, 105, 55); - pal.Entries[36] = Color.FromArgb(255, 3, 106, 56); - pal.Entries[37] = Color.FromArgb(255, 4, 107, 56); - pal.Entries[38] = Color.FromArgb(255, 5, 108, 56); - pal.Entries[39] = Color.FromArgb(255, 6, 109, 57); - pal.Entries[40] = Color.FromArgb(255, 7, 110, 57); - pal.Entries[41] = Color.FromArgb(255, 8, 111, 58); - pal.Entries[42] = Color.FromArgb(255, 9, 112, 58); - pal.Entries[43] = Color.FromArgb(255, 11, 112, 58); - pal.Entries[44] = Color.FromArgb(255, 12, 113, 59); - pal.Entries[45] = Color.FromArgb(255, 13, 114, 59); - pal.Entries[46] = Color.FromArgb(255, 14, 115, 59); - pal.Entries[47] = Color.FromArgb(255, 15, 116, 60); - pal.Entries[48] = Color.FromArgb(255, 16, 117, 60); - pal.Entries[49] = Color.FromArgb(255, 17, 118, 61); - pal.Entries[50] = Color.FromArgb(255, 18, 119, 61); - pal.Entries[51] = Color.FromArgb(255, 19, 119, 61); - pal.Entries[52] = Color.FromArgb(255, 21, 120, 62); - pal.Entries[53] = Color.FromArgb(255, 22, 121, 62); - pal.Entries[54] = Color.FromArgb(255, 23, 122, 62); - pal.Entries[55] = Color.FromArgb(255, 24, 123, 63); - pal.Entries[56] = Color.FromArgb(255, 25, 124, 63); - pal.Entries[57] = Color.FromArgb(255, 26, 125, 64); - pal.Entries[58] = Color.FromArgb(255, 27, 126, 64); - pal.Entries[59] = Color.FromArgb(255, 28, 126, 64); - pal.Entries[60] = Color.FromArgb(255, 29, 127, 65); - pal.Entries[61] = Color.FromArgb(255, 30, 128, 65); - pal.Entries[62] = Color.FromArgb(255, 31, 129, 65); - pal.Entries[63] = Color.FromArgb(255, 33, 130, 66); - pal.Entries[64] = Color.FromArgb(255, 34, 131, 66); - pal.Entries[65] = Color.FromArgb(255, 35, 132, 67); - pal.Entries[66] = Color.FromArgb(255, 36, 133, 68); - pal.Entries[67] = Color.FromArgb(255, 37, 134, 68); - pal.Entries[68] = Color.FromArgb(255, 38, 135, 69); - pal.Entries[69] = Color.FromArgb(255, 39, 137, 70); - pal.Entries[70] = Color.FromArgb(255, 39, 138, 71); - pal.Entries[71] = Color.FromArgb(255, 40, 139, 72); - pal.Entries[72] = Color.FromArgb(255, 41, 140, 72); - pal.Entries[73] = Color.FromArgb(255, 42, 142, 73); - pal.Entries[74] = Color.FromArgb(255, 43, 143, 74); - pal.Entries[75] = Color.FromArgb(255, 44, 144, 75); - pal.Entries[76] = Color.FromArgb(255, 45, 145, 76); - pal.Entries[77] = Color.FromArgb(255, 46, 146, 76); - pal.Entries[78] = Color.FromArgb(255, 47, 148, 77); - pal.Entries[79] = Color.FromArgb(255, 48, 149, 78); - pal.Entries[80] = Color.FromArgb(255, 49, 150, 79); - pal.Entries[81] = Color.FromArgb(255, 50, 151, 80); - pal.Entries[82] = Color.FromArgb(255, 51, 153, 81); - pal.Entries[83] = Color.FromArgb(255, 52, 154, 81); - pal.Entries[84] = Color.FromArgb(255, 53, 155, 82); - pal.Entries[85] = Color.FromArgb(255, 54, 156, 83); - pal.Entries[86] = Color.FromArgb(255, 55, 158, 84); - pal.Entries[87] = Color.FromArgb(255, 55, 159, 85); - pal.Entries[88] = Color.FromArgb(255, 56, 160, 85); - pal.Entries[89] = Color.FromArgb(255, 57, 161, 86); - pal.Entries[90] = Color.FromArgb(255, 58, 162, 87); - pal.Entries[91] = Color.FromArgb(255, 59, 164, 88); - pal.Entries[92] = Color.FromArgb(255, 60, 165, 89); - pal.Entries[93] = Color.FromArgb(255, 61, 166, 90); - pal.Entries[94] = Color.FromArgb(255, 62, 167, 90); - pal.Entries[95] = Color.FromArgb(255, 63, 169, 91); - pal.Entries[96] = Color.FromArgb(255, 64, 170, 92); - pal.Entries[97] = Color.FromArgb(255, 65, 171, 93); - pal.Entries[98] = Color.FromArgb(255, 67, 172, 94); - pal.Entries[99] = Color.FromArgb(255, 69, 173, 95); - pal.Entries[100] = Color.FromArgb(255, 70, 173, 95); - pal.Entries[101] = Color.FromArgb(255, 72, 174, 96); - pal.Entries[102] = Color.FromArgb(255, 74, 175, 97); - pal.Entries[103] = Color.FromArgb(255, 76, 176, 98); - pal.Entries[104] = Color.FromArgb(255, 77, 177, 99); - pal.Entries[105] = Color.FromArgb(255, 79, 178, 100); - pal.Entries[106] = Color.FromArgb(255, 81, 178, 101); - pal.Entries[107] = Color.FromArgb(255, 82, 179, 102); - pal.Entries[108] = Color.FromArgb(255, 84, 180, 102); - pal.Entries[109] = Color.FromArgb(255, 86, 181, 103); - pal.Entries[110] = Color.FromArgb(255, 88, 182, 104); - pal.Entries[111] = Color.FromArgb(255, 89, 183, 105); - pal.Entries[112] = Color.FromArgb(255, 91, 184, 106); - pal.Entries[113] = Color.FromArgb(255, 93, 184, 107); - pal.Entries[114] = Color.FromArgb(255, 94, 185, 108); - pal.Entries[115] = Color.FromArgb(255, 96, 186, 109); - pal.Entries[116] = Color.FromArgb(255, 98, 187, 110); - pal.Entries[117] = Color.FromArgb(255, 100, 188, 110); - pal.Entries[118] = Color.FromArgb(255, 101, 189, 111); - pal.Entries[119] = Color.FromArgb(255, 103, 189, 112); - pal.Entries[120] = Color.FromArgb(255, 105, 190, 113); - pal.Entries[121] = Color.FromArgb(255, 107, 191, 114); - pal.Entries[122] = Color.FromArgb(255, 108, 192, 115); - pal.Entries[123] = Color.FromArgb(255, 110, 193, 116); - pal.Entries[124] = Color.FromArgb(255, 112, 194, 117); - pal.Entries[125] = Color.FromArgb(255, 113, 195, 117); - pal.Entries[126] = Color.FromArgb(255, 115, 195, 118); - pal.Entries[127] = Color.FromArgb(255, 117, 196, 119); - pal.Entries[128] = Color.FromArgb(255, 119, 197, 120); - pal.Entries[129] = Color.FromArgb(255, 120, 198, 121); - pal.Entries[130] = Color.FromArgb(255, 122, 199, 121); - pal.Entries[131] = Color.FromArgb(255, 124, 199, 122); - pal.Entries[132] = Color.FromArgb(255, 125, 200, 123); - pal.Entries[133] = Color.FromArgb(255, 127, 201, 123); - pal.Entries[134] = Color.FromArgb(255, 129, 201, 124); - pal.Entries[135] = Color.FromArgb(255, 130, 202, 125); - pal.Entries[136] = Color.FromArgb(255, 132, 203, 125); - pal.Entries[137] = Color.FromArgb(255, 134, 204, 126); - pal.Entries[138] = Color.FromArgb(255, 135, 204, 127); - pal.Entries[139] = Color.FromArgb(255, 137, 205, 127); - pal.Entries[140] = Color.FromArgb(255, 139, 206, 128); - pal.Entries[141] = Color.FromArgb(255, 140, 207, 129); - pal.Entries[142] = Color.FromArgb(255, 142, 207, 129); - pal.Entries[143] = Color.FromArgb(255, 144, 208, 130); - pal.Entries[144] = Color.FromArgb(255, 145, 209, 131); - pal.Entries[145] = Color.FromArgb(255, 147, 209, 131); - pal.Entries[146] = Color.FromArgb(255, 149, 210, 132); - pal.Entries[147] = Color.FromArgb(255, 150, 211, 133); - pal.Entries[148] = Color.FromArgb(255, 152, 212, 133); - pal.Entries[149] = Color.FromArgb(255, 154, 212, 134); - pal.Entries[150] = Color.FromArgb(255, 155, 213, 135); - pal.Entries[151] = Color.FromArgb(255, 157, 214, 135); - pal.Entries[152] = Color.FromArgb(255, 159, 214, 136); - pal.Entries[153] = Color.FromArgb(255, 160, 215, 137); - pal.Entries[154] = Color.FromArgb(255, 162, 216, 137); - pal.Entries[155] = Color.FromArgb(255, 164, 217, 138); - pal.Entries[156] = Color.FromArgb(255, 165, 217, 139); - pal.Entries[157] = Color.FromArgb(255, 167, 218, 139); - pal.Entries[158] = Color.FromArgb(255, 169, 219, 140); - pal.Entries[159] = Color.FromArgb(255, 170, 220, 141); - pal.Entries[160] = Color.FromArgb(255, 172, 220, 141); - pal.Entries[161] = Color.FromArgb(255, 173, 221, 142); - pal.Entries[162] = Color.FromArgb(255, 175, 221, 143); - pal.Entries[163] = Color.FromArgb(255, 176, 222, 143); - pal.Entries[164] = Color.FromArgb(255, 178, 223, 144); - pal.Entries[165] = Color.FromArgb(255, 179, 223, 145); - pal.Entries[166] = Color.FromArgb(255, 180, 224, 145); - pal.Entries[167] = Color.FromArgb(255, 182, 224, 146); - pal.Entries[168] = Color.FromArgb(255, 183, 225, 147); - pal.Entries[169] = Color.FromArgb(255, 184, 226, 147); - pal.Entries[170] = Color.FromArgb(255, 186, 226, 148); - pal.Entries[171] = Color.FromArgb(255, 187, 227, 149); - pal.Entries[172] = Color.FromArgb(255, 189, 227, 149); - pal.Entries[173] = Color.FromArgb(255, 190, 228, 150); - pal.Entries[174] = Color.FromArgb(255, 192, 229, 150); - pal.Entries[175] = Color.FromArgb(255, 193, 229, 151); - pal.Entries[176] = Color.FromArgb(255, 194, 230, 152); - pal.Entries[177] = Color.FromArgb(255, 195, 230, 152); - pal.Entries[178] = Color.FromArgb(255, 197, 231, 153); - pal.Entries[179] = Color.FromArgb(255, 199, 232, 154); - pal.Entries[180] = Color.FromArgb(255, 200, 232, 154); - pal.Entries[181] = Color.FromArgb(255, 201, 233, 155); - pal.Entries[182] = Color.FromArgb(255, 202, 233, 156); - pal.Entries[183] = Color.FromArgb(255, 204, 234, 156); - pal.Entries[184] = Color.FromArgb(255, 205, 235, 157); - pal.Entries[185] = Color.FromArgb(255, 206, 235, 158); - pal.Entries[186] = Color.FromArgb(255, 208, 236, 158); - pal.Entries[187] = Color.FromArgb(255, 210, 236, 159); - pal.Entries[188] = Color.FromArgb(255, 211, 237, 160); - pal.Entries[189] = Color.FromArgb(255, 212, 238, 160); - pal.Entries[190] = Color.FromArgb(255, 213, 238, 161); - pal.Entries[191] = Color.FromArgb(255, 215, 239, 162); - pal.Entries[192] = Color.FromArgb(255, 216, 239, 162); - pal.Entries[193] = Color.FromArgb(255, 217, 240, 163); - pal.Entries[194] = Color.FromArgb(255, 218, 240, 164); - pal.Entries[195] = Color.FromArgb(255, 219, 241, 164); - pal.Entries[196] = Color.FromArgb(255, 220, 241, 165); - pal.Entries[197] = Color.FromArgb(255, 221, 241, 166); - pal.Entries[198] = Color.FromArgb(255, 222, 242, 166); - pal.Entries[199] = Color.FromArgb(255, 223, 242, 167); - pal.Entries[200] = Color.FromArgb(255, 224, 242, 168); - pal.Entries[201] = Color.FromArgb(255, 225, 243, 169); - pal.Entries[202] = Color.FromArgb(255, 226, 243, 169); - pal.Entries[203] = Color.FromArgb(255, 227, 244, 170); - pal.Entries[204] = Color.FromArgb(255, 228, 244, 171); - pal.Entries[205] = Color.FromArgb(255, 229, 244, 171); - pal.Entries[206] = Color.FromArgb(255, 229, 245, 172); - pal.Entries[207] = Color.FromArgb(255, 230, 245, 173); - pal.Entries[208] = Color.FromArgb(255, 231, 245, 173); - pal.Entries[209] = Color.FromArgb(255, 232, 246, 174); - pal.Entries[210] = Color.FromArgb(255, 233, 246, 175); - pal.Entries[211] = Color.FromArgb(255, 234, 247, 175); - pal.Entries[212] = Color.FromArgb(255, 235, 247, 176); - pal.Entries[213] = Color.FromArgb(255, 236, 247, 177); - pal.Entries[214] = Color.FromArgb(255, 237, 248, 178); - pal.Entries[215] = Color.FromArgb(255, 238, 248, 178); - pal.Entries[216] = Color.FromArgb(255, 239, 248, 179); - pal.Entries[217] = Color.FromArgb(255, 240, 249, 180); - pal.Entries[218] = Color.FromArgb(255, 241, 249, 180); - pal.Entries[219] = Color.FromArgb(255, 242, 250, 181); - pal.Entries[220] = Color.FromArgb(255, 243, 250, 182); - pal.Entries[221] = Color.FromArgb(255, 244, 250, 182); - pal.Entries[222] = Color.FromArgb(255, 245, 251, 183); - pal.Entries[223] = Color.FromArgb(255, 245, 251, 184); - pal.Entries[224] = Color.FromArgb(255, 246, 251, 185); - pal.Entries[225] = Color.FromArgb(255, 247, 252, 186); - pal.Entries[226] = Color.FromArgb(255, 247, 252, 187); - pal.Entries[227] = Color.FromArgb(255, 247, 252, 189); - pal.Entries[228] = Color.FromArgb(255, 247, 252, 190); - pal.Entries[229] = Color.FromArgb(255, 248, 252, 192); - pal.Entries[230] = Color.FromArgb(255, 248, 252, 193); - pal.Entries[231] = Color.FromArgb(255, 248, 252, 194); - pal.Entries[232] = Color.FromArgb(255, 248, 252, 196); - pal.Entries[233] = Color.FromArgb(255, 249, 252, 197); - pal.Entries[234] = Color.FromArgb(255, 249, 252, 199); - pal.Entries[235] = Color.FromArgb(255, 249, 253, 200); - pal.Entries[236] = Color.FromArgb(255, 249, 253, 201); - pal.Entries[237] = Color.FromArgb(255, 250, 253, 203); - pal.Entries[238] = Color.FromArgb(255, 250, 253, 204); - pal.Entries[239] = Color.FromArgb(255, 250, 253, 205); - pal.Entries[240] = Color.FromArgb(255, 250, 253, 207); - pal.Entries[241] = Color.FromArgb(255, 251, 253, 208); - pal.Entries[242] = Color.FromArgb(255, 251, 253, 210); - pal.Entries[243] = Color.FromArgb(255, 251, 253, 211); - pal.Entries[244] = Color.FromArgb(255, 251, 253, 212); - pal.Entries[245] = Color.FromArgb(255, 252, 253, 214); - pal.Entries[246] = Color.FromArgb(255, 252, 254, 215); - pal.Entries[247] = Color.FromArgb(255, 252, 254, 216); - pal.Entries[248] = Color.FromArgb(255, 252, 254, 218); - pal.Entries[249] = Color.FromArgb(255, 253, 254, 219); - pal.Entries[250] = Color.FromArgb(255, 253, 254, 221); - pal.Entries[251] = Color.FromArgb(255, 253, 254, 222); - pal.Entries[252] = Color.FromArgb(255, 253, 254, 223); - pal.Entries[253] = Color.FromArgb(255, 254, 254, 225); - pal.Entries[254] = Color.FromArgb(255, 254, 254, 227); - pal.Entries[255] = Color.FromArgb(255, 255, 255, 228); - - bmp.Palette = pal; - } - - } -} diff --git a/src/Spectrogram/Colormaps/Viridis.cs b/src/Spectrogram/Colormaps/Viridis.cs deleted file mode 100644 index 5b18d56..0000000 --- a/src/Spectrogram/Colormaps/Viridis.cs +++ /dev/null @@ -1,279 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; -using System.Text; - -namespace Spectrogram.Colormaps -{ - class Viridis : Colormap - { - public override string GetName() - { - return "Grayscale"; - } - - public override void Apply(Bitmap bmp) - { - ColorPalette pal = bmp.Palette; - - pal.Entries[0] = Color.FromArgb(255, 68, 1, 84); - pal.Entries[1] = Color.FromArgb(255, 68, 2, 85); - pal.Entries[2] = Color.FromArgb(255, 68, 3, 87); - pal.Entries[3] = Color.FromArgb(255, 69, 5, 88); - pal.Entries[4] = Color.FromArgb(255, 69, 6, 90); - pal.Entries[5] = Color.FromArgb(255, 69, 8, 91); - pal.Entries[6] = Color.FromArgb(255, 70, 9, 92); - pal.Entries[7] = Color.FromArgb(255, 70, 11, 94); - pal.Entries[8] = Color.FromArgb(255, 70, 12, 95); - pal.Entries[9] = Color.FromArgb(255, 70, 14, 97); - pal.Entries[10] = Color.FromArgb(255, 71, 15, 98); - pal.Entries[11] = Color.FromArgb(255, 71, 17, 99); - pal.Entries[12] = Color.FromArgb(255, 71, 18, 101); - pal.Entries[13] = Color.FromArgb(255, 71, 20, 102); - pal.Entries[14] = Color.FromArgb(255, 71, 21, 103); - pal.Entries[15] = Color.FromArgb(255, 71, 22, 105); - pal.Entries[16] = Color.FromArgb(255, 71, 24, 106); - pal.Entries[17] = Color.FromArgb(255, 72, 25, 107); - pal.Entries[18] = Color.FromArgb(255, 72, 26, 108); - pal.Entries[19] = Color.FromArgb(255, 72, 28, 110); - pal.Entries[20] = Color.FromArgb(255, 72, 29, 111); - pal.Entries[21] = Color.FromArgb(255, 72, 30, 112); - pal.Entries[22] = Color.FromArgb(255, 72, 32, 113); - pal.Entries[23] = Color.FromArgb(255, 72, 33, 114); - pal.Entries[24] = Color.FromArgb(255, 72, 34, 115); - pal.Entries[25] = Color.FromArgb(255, 72, 35, 116); - pal.Entries[26] = Color.FromArgb(255, 71, 37, 117); - pal.Entries[27] = Color.FromArgb(255, 71, 38, 118); - pal.Entries[28] = Color.FromArgb(255, 71, 39, 119); - pal.Entries[29] = Color.FromArgb(255, 71, 40, 120); - pal.Entries[30] = Color.FromArgb(255, 71, 42, 121); - pal.Entries[31] = Color.FromArgb(255, 71, 43, 122); - pal.Entries[32] = Color.FromArgb(255, 71, 44, 123); - pal.Entries[33] = Color.FromArgb(255, 70, 45, 124); - pal.Entries[34] = Color.FromArgb(255, 70, 47, 124); - pal.Entries[35] = Color.FromArgb(255, 70, 48, 125); - pal.Entries[36] = Color.FromArgb(255, 70, 49, 126); - pal.Entries[37] = Color.FromArgb(255, 69, 50, 127); - pal.Entries[38] = Color.FromArgb(255, 69, 52, 127); - pal.Entries[39] = Color.FromArgb(255, 69, 53, 128); - pal.Entries[40] = Color.FromArgb(255, 69, 54, 129); - pal.Entries[41] = Color.FromArgb(255, 68, 55, 129); - pal.Entries[42] = Color.FromArgb(255, 68, 57, 130); - pal.Entries[43] = Color.FromArgb(255, 67, 58, 131); - pal.Entries[44] = Color.FromArgb(255, 67, 59, 131); - pal.Entries[45] = Color.FromArgb(255, 67, 60, 132); - pal.Entries[46] = Color.FromArgb(255, 66, 61, 132); - pal.Entries[47] = Color.FromArgb(255, 66, 62, 133); - pal.Entries[48] = Color.FromArgb(255, 66, 64, 133); - pal.Entries[49] = Color.FromArgb(255, 65, 65, 134); - pal.Entries[50] = Color.FromArgb(255, 65, 66, 134); - pal.Entries[51] = Color.FromArgb(255, 64, 67, 135); - pal.Entries[52] = Color.FromArgb(255, 64, 68, 135); - pal.Entries[53] = Color.FromArgb(255, 63, 69, 135); - pal.Entries[54] = Color.FromArgb(255, 63, 71, 136); - pal.Entries[55] = Color.FromArgb(255, 62, 72, 136); - pal.Entries[56] = Color.FromArgb(255, 62, 73, 137); - pal.Entries[57] = Color.FromArgb(255, 61, 74, 137); - pal.Entries[58] = Color.FromArgb(255, 61, 75, 137); - pal.Entries[59] = Color.FromArgb(255, 61, 76, 137); - pal.Entries[60] = Color.FromArgb(255, 60, 77, 138); - pal.Entries[61] = Color.FromArgb(255, 60, 78, 138); - pal.Entries[62] = Color.FromArgb(255, 59, 80, 138); - pal.Entries[63] = Color.FromArgb(255, 59, 81, 138); - pal.Entries[64] = Color.FromArgb(255, 58, 82, 139); - pal.Entries[65] = Color.FromArgb(255, 58, 83, 139); - pal.Entries[66] = Color.FromArgb(255, 57, 84, 139); - pal.Entries[67] = Color.FromArgb(255, 57, 85, 139); - pal.Entries[68] = Color.FromArgb(255, 56, 86, 139); - pal.Entries[69] = Color.FromArgb(255, 56, 87, 140); - pal.Entries[70] = Color.FromArgb(255, 55, 88, 140); - pal.Entries[71] = Color.FromArgb(255, 55, 89, 140); - pal.Entries[72] = Color.FromArgb(255, 54, 90, 140); - pal.Entries[73] = Color.FromArgb(255, 54, 91, 140); - pal.Entries[74] = Color.FromArgb(255, 53, 92, 140); - pal.Entries[75] = Color.FromArgb(255, 53, 93, 140); - pal.Entries[76] = Color.FromArgb(255, 52, 94, 141); - pal.Entries[77] = Color.FromArgb(255, 52, 95, 141); - pal.Entries[78] = Color.FromArgb(255, 51, 96, 141); - pal.Entries[79] = Color.FromArgb(255, 51, 97, 141); - pal.Entries[80] = Color.FromArgb(255, 50, 98, 141); - pal.Entries[81] = Color.FromArgb(255, 50, 99, 141); - pal.Entries[82] = Color.FromArgb(255, 49, 100, 141); - pal.Entries[83] = Color.FromArgb(255, 49, 101, 141); - pal.Entries[84] = Color.FromArgb(255, 49, 102, 141); - pal.Entries[85] = Color.FromArgb(255, 48, 103, 141); - pal.Entries[86] = Color.FromArgb(255, 48, 104, 141); - pal.Entries[87] = Color.FromArgb(255, 47, 105, 141); - pal.Entries[88] = Color.FromArgb(255, 47, 106, 141); - pal.Entries[89] = Color.FromArgb(255, 46, 107, 142); - pal.Entries[90] = Color.FromArgb(255, 46, 108, 142); - pal.Entries[91] = Color.FromArgb(255, 46, 109, 142); - pal.Entries[92] = Color.FromArgb(255, 45, 110, 142); - pal.Entries[93] = Color.FromArgb(255, 45, 111, 142); - pal.Entries[94] = Color.FromArgb(255, 44, 112, 142); - pal.Entries[95] = Color.FromArgb(255, 44, 113, 142); - pal.Entries[96] = Color.FromArgb(255, 44, 114, 142); - pal.Entries[97] = Color.FromArgb(255, 43, 115, 142); - pal.Entries[98] = Color.FromArgb(255, 43, 116, 142); - pal.Entries[99] = Color.FromArgb(255, 42, 117, 142); - pal.Entries[100] = Color.FromArgb(255, 42, 118, 142); - pal.Entries[101] = Color.FromArgb(255, 42, 119, 142); - pal.Entries[102] = Color.FromArgb(255, 41, 120, 142); - pal.Entries[103] = Color.FromArgb(255, 41, 121, 142); - pal.Entries[104] = Color.FromArgb(255, 40, 122, 142); - pal.Entries[105] = Color.FromArgb(255, 40, 122, 142); - pal.Entries[106] = Color.FromArgb(255, 40, 123, 142); - pal.Entries[107] = Color.FromArgb(255, 39, 124, 142); - pal.Entries[108] = Color.FromArgb(255, 39, 125, 142); - pal.Entries[109] = Color.FromArgb(255, 39, 126, 142); - pal.Entries[110] = Color.FromArgb(255, 38, 127, 142); - pal.Entries[111] = Color.FromArgb(255, 38, 128, 142); - pal.Entries[112] = Color.FromArgb(255, 38, 129, 142); - pal.Entries[113] = Color.FromArgb(255, 37, 130, 142); - pal.Entries[114] = Color.FromArgb(255, 37, 131, 141); - pal.Entries[115] = Color.FromArgb(255, 36, 132, 141); - pal.Entries[116] = Color.FromArgb(255, 36, 133, 141); - pal.Entries[117] = Color.FromArgb(255, 36, 134, 141); - pal.Entries[118] = Color.FromArgb(255, 35, 135, 141); - pal.Entries[119] = Color.FromArgb(255, 35, 136, 141); - pal.Entries[120] = Color.FromArgb(255, 35, 137, 141); - pal.Entries[121] = Color.FromArgb(255, 34, 137, 141); - pal.Entries[122] = Color.FromArgb(255, 34, 138, 141); - pal.Entries[123] = Color.FromArgb(255, 34, 139, 141); - pal.Entries[124] = Color.FromArgb(255, 33, 140, 141); - pal.Entries[125] = Color.FromArgb(255, 33, 141, 140); - pal.Entries[126] = Color.FromArgb(255, 33, 142, 140); - pal.Entries[127] = Color.FromArgb(255, 32, 143, 140); - pal.Entries[128] = Color.FromArgb(255, 32, 144, 140); - pal.Entries[129] = Color.FromArgb(255, 32, 145, 140); - pal.Entries[130] = Color.FromArgb(255, 31, 146, 140); - pal.Entries[131] = Color.FromArgb(255, 31, 147, 139); - pal.Entries[132] = Color.FromArgb(255, 31, 148, 139); - pal.Entries[133] = Color.FromArgb(255, 31, 149, 139); - pal.Entries[134] = Color.FromArgb(255, 31, 150, 139); - pal.Entries[135] = Color.FromArgb(255, 30, 151, 138); - pal.Entries[136] = Color.FromArgb(255, 30, 152, 138); - pal.Entries[137] = Color.FromArgb(255, 30, 153, 138); - pal.Entries[138] = Color.FromArgb(255, 30, 153, 138); - pal.Entries[139] = Color.FromArgb(255, 30, 154, 137); - pal.Entries[140] = Color.FromArgb(255, 30, 155, 137); - pal.Entries[141] = Color.FromArgb(255, 30, 156, 137); - pal.Entries[142] = Color.FromArgb(255, 30, 157, 136); - pal.Entries[143] = Color.FromArgb(255, 30, 158, 136); - pal.Entries[144] = Color.FromArgb(255, 30, 159, 136); - pal.Entries[145] = Color.FromArgb(255, 30, 160, 135); - pal.Entries[146] = Color.FromArgb(255, 31, 161, 135); - pal.Entries[147] = Color.FromArgb(255, 31, 162, 134); - pal.Entries[148] = Color.FromArgb(255, 31, 163, 134); - pal.Entries[149] = Color.FromArgb(255, 32, 164, 133); - pal.Entries[150] = Color.FromArgb(255, 32, 165, 133); - pal.Entries[151] = Color.FromArgb(255, 33, 166, 133); - pal.Entries[152] = Color.FromArgb(255, 33, 167, 132); - pal.Entries[153] = Color.FromArgb(255, 34, 167, 132); - pal.Entries[154] = Color.FromArgb(255, 35, 168, 131); - pal.Entries[155] = Color.FromArgb(255, 35, 169, 130); - pal.Entries[156] = Color.FromArgb(255, 36, 170, 130); - pal.Entries[157] = Color.FromArgb(255, 37, 171, 129); - pal.Entries[158] = Color.FromArgb(255, 38, 172, 129); - pal.Entries[159] = Color.FromArgb(255, 39, 173, 128); - pal.Entries[160] = Color.FromArgb(255, 40, 174, 127); - pal.Entries[161] = Color.FromArgb(255, 41, 175, 127); - pal.Entries[162] = Color.FromArgb(255, 42, 176, 126); - pal.Entries[163] = Color.FromArgb(255, 43, 177, 125); - pal.Entries[164] = Color.FromArgb(255, 44, 177, 125); - pal.Entries[165] = Color.FromArgb(255, 46, 178, 124); - pal.Entries[166] = Color.FromArgb(255, 47, 179, 123); - pal.Entries[167] = Color.FromArgb(255, 48, 180, 122); - pal.Entries[168] = Color.FromArgb(255, 50, 181, 122); - pal.Entries[169] = Color.FromArgb(255, 51, 182, 121); - pal.Entries[170] = Color.FromArgb(255, 53, 183, 120); - pal.Entries[171] = Color.FromArgb(255, 54, 184, 119); - pal.Entries[172] = Color.FromArgb(255, 56, 185, 118); - pal.Entries[173] = Color.FromArgb(255, 57, 185, 118); - pal.Entries[174] = Color.FromArgb(255, 59, 186, 117); - pal.Entries[175] = Color.FromArgb(255, 61, 187, 116); - pal.Entries[176] = Color.FromArgb(255, 62, 188, 115); - pal.Entries[177] = Color.FromArgb(255, 64, 189, 114); - pal.Entries[178] = Color.FromArgb(255, 66, 190, 113); - pal.Entries[179] = Color.FromArgb(255, 68, 190, 112); - pal.Entries[180] = Color.FromArgb(255, 69, 191, 111); - pal.Entries[181] = Color.FromArgb(255, 71, 192, 110); - pal.Entries[182] = Color.FromArgb(255, 73, 193, 109); - pal.Entries[183] = Color.FromArgb(255, 75, 194, 108); - pal.Entries[184] = Color.FromArgb(255, 77, 194, 107); - pal.Entries[185] = Color.FromArgb(255, 79, 195, 105); - pal.Entries[186] = Color.FromArgb(255, 81, 196, 104); - pal.Entries[187] = Color.FromArgb(255, 83, 197, 103); - pal.Entries[188] = Color.FromArgb(255, 85, 198, 102); - pal.Entries[189] = Color.FromArgb(255, 87, 198, 101); - pal.Entries[190] = Color.FromArgb(255, 89, 199, 100); - pal.Entries[191] = Color.FromArgb(255, 91, 200, 98); - pal.Entries[192] = Color.FromArgb(255, 94, 201, 97); - pal.Entries[193] = Color.FromArgb(255, 96, 201, 96); - pal.Entries[194] = Color.FromArgb(255, 98, 202, 95); - pal.Entries[195] = Color.FromArgb(255, 100, 203, 93); - pal.Entries[196] = Color.FromArgb(255, 103, 204, 92); - pal.Entries[197] = Color.FromArgb(255, 105, 204, 91); - pal.Entries[198] = Color.FromArgb(255, 107, 205, 89); - pal.Entries[199] = Color.FromArgb(255, 109, 206, 88); - pal.Entries[200] = Color.FromArgb(255, 112, 206, 86); - pal.Entries[201] = Color.FromArgb(255, 114, 207, 85); - pal.Entries[202] = Color.FromArgb(255, 116, 208, 84); - pal.Entries[203] = Color.FromArgb(255, 119, 208, 82); - pal.Entries[204] = Color.FromArgb(255, 121, 209, 81); - pal.Entries[205] = Color.FromArgb(255, 124, 210, 79); - pal.Entries[206] = Color.FromArgb(255, 126, 210, 78); - pal.Entries[207] = Color.FromArgb(255, 129, 211, 76); - pal.Entries[208] = Color.FromArgb(255, 131, 211, 75); - pal.Entries[209] = Color.FromArgb(255, 134, 212, 73); - pal.Entries[210] = Color.FromArgb(255, 136, 213, 71); - pal.Entries[211] = Color.FromArgb(255, 139, 213, 70); - pal.Entries[212] = Color.FromArgb(255, 141, 214, 68); - pal.Entries[213] = Color.FromArgb(255, 144, 214, 67); - pal.Entries[214] = Color.FromArgb(255, 146, 215, 65); - pal.Entries[215] = Color.FromArgb(255, 149, 215, 63); - pal.Entries[216] = Color.FromArgb(255, 151, 216, 62); - pal.Entries[217] = Color.FromArgb(255, 154, 216, 60); - pal.Entries[218] = Color.FromArgb(255, 157, 217, 58); - pal.Entries[219] = Color.FromArgb(255, 159, 217, 56); - pal.Entries[220] = Color.FromArgb(255, 162, 218, 55); - pal.Entries[221] = Color.FromArgb(255, 165, 218, 53); - pal.Entries[222] = Color.FromArgb(255, 167, 219, 51); - pal.Entries[223] = Color.FromArgb(255, 170, 219, 50); - pal.Entries[224] = Color.FromArgb(255, 173, 220, 48); - pal.Entries[225] = Color.FromArgb(255, 175, 220, 46); - pal.Entries[226] = Color.FromArgb(255, 178, 221, 44); - pal.Entries[227] = Color.FromArgb(255, 181, 221, 43); - pal.Entries[228] = Color.FromArgb(255, 183, 221, 41); - pal.Entries[229] = Color.FromArgb(255, 186, 222, 39); - pal.Entries[230] = Color.FromArgb(255, 189, 222, 38); - pal.Entries[231] = Color.FromArgb(255, 191, 223, 36); - pal.Entries[232] = Color.FromArgb(255, 194, 223, 34); - pal.Entries[233] = Color.FromArgb(255, 197, 223, 33); - pal.Entries[234] = Color.FromArgb(255, 199, 224, 31); - pal.Entries[235] = Color.FromArgb(255, 202, 224, 30); - pal.Entries[236] = Color.FromArgb(255, 205, 224, 29); - pal.Entries[237] = Color.FromArgb(255, 207, 225, 28); - pal.Entries[238] = Color.FromArgb(255, 210, 225, 27); - pal.Entries[239] = Color.FromArgb(255, 212, 225, 26); - pal.Entries[240] = Color.FromArgb(255, 215, 226, 25); - pal.Entries[241] = Color.FromArgb(255, 218, 226, 24); - pal.Entries[242] = Color.FromArgb(255, 220, 226, 24); - pal.Entries[243] = Color.FromArgb(255, 223, 227, 24); - pal.Entries[244] = Color.FromArgb(255, 225, 227, 24); - pal.Entries[245] = Color.FromArgb(255, 228, 227, 24); - pal.Entries[246] = Color.FromArgb(255, 231, 228, 25); - pal.Entries[247] = Color.FromArgb(255, 233, 228, 25); - pal.Entries[248] = Color.FromArgb(255, 236, 228, 26); - pal.Entries[249] = Color.FromArgb(255, 238, 229, 27); - pal.Entries[250] = Color.FromArgb(255, 241, 229, 28); - pal.Entries[251] = Color.FromArgb(255, 243, 229, 30); - pal.Entries[252] = Color.FromArgb(255, 246, 230, 31); - pal.Entries[253] = Color.FromArgb(255, 248, 230, 33); - pal.Entries[254] = Color.FromArgb(255, 250, 230, 34); - - bmp.Palette = pal; - } - } -} diff --git a/src/Spectrogram/Image.cs b/src/Spectrogram/Image.cs index df4a8ef..7413b38 100644 --- a/src/Spectrogram/Image.cs +++ b/src/Spectrogram/Image.cs @@ -1,104 +1,26 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Drawing; -using System.Drawing.Imaging; -using System.Runtime.InteropServices; +using System.Collections.Generic; +using SkiaSharp; namespace Spectrogram { - class Image + public static class Image { - public static Bitmap BitmapFromFFTs(float[][] ffts, Settings.DisplaySettings displaySettings) + 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) { - if (ffts == null || ffts.Length == 0) - throw new ArgumentException("ffts must contain float arrays"); - - Bitmap bmp = new Bitmap(ffts.Length, displaySettings.height, PixelFormat.Format8bppIndexed); - ApplyColormap(bmp, displaySettings.colormap); - - var rect = new Rectangle(0, 0, bmp.Width, bmp.Height); - BitmapData bitmapData = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat); - byte[] pixels = new byte[bitmapData.Stride * bmp.Height]; - - for (int col = 0; col < bmp.Width; col++) - { - if (col >= bmp.Width) - continue; - - if (col == displaySettings.highlightColumn) - { - for (int row = 0; row < bmp.Height; row++) - { - int bytePosition = (bmp.Height - 1 - row) * bitmapData.Stride + col; - pixels[bytePosition] = 255; - } - continue; - } - - if (ffts[col] == null) - continue; - - for (int row = 0; row < bmp.Height; row++) - { - int bytePosition = (bmp.Height - 1 - row) * bitmapData.Stride + col; - float pixelValue; - pixelValue = ffts[col][row + displaySettings.pixelLower]; - if (displaySettings.decibels) - pixelValue = (float)(Math.Log10(pixelValue) * 20); - pixelValue = (pixelValue * displaySettings.brightness); - pixelValue = Math.Max(0, pixelValue); - pixelValue = Math.Min(255, pixelValue); - pixels[bytePosition] = (byte)(pixelValue); - } - } - - Marshal.Copy(pixels, 0, bitmapData.Scan0, pixels.Length); - bmp.UnlockBits(bitmapData); - - return bmp; - } - - public static Bitmap Rotate(Bitmap bmpIn, float angle = 90) - { - // TODO: this could be faster with byte manipulation since it's 90 degrees - - if (bmpIn == null) - return null; - - Bitmap bmp = new Bitmap(bmpIn); - Bitmap bmpRotated = new Bitmap(bmp.Height, bmp.Width); - - Graphics gfx = Graphics.FromImage(bmpRotated); - gfx.RotateTransform(angle); - gfx.DrawImage(bmp, new Point(0, -bmp.Height)); - - return bmpRotated; - } - - public static void ApplyColormap(Bitmap bmp, Colormap colormap) - { - switch (colormap) + ImageMaker maker = new() { - case Colormap.grayscale: - new Colormaps.Grayscale().Apply(bmp); - break; - case Colormap.grayscaleInverted: - new Colormaps.GrayscaleInverted().Apply(bmp); - break; - case Colormap.vdBlue: - new Colormaps.VdBlues().Apply(bmp); - break; - case Colormap.vdGreen: - new Colormaps.VdGreens().Apply(bmp); - break; - case Colormap.viridis: - new Colormaps.Viridis().Apply(bmp); - break; - default: - throw new NotImplementedException(); - } + Colormap = cmap, + Intensity = intensity, + IsDecibel = dB, + DecibelScaleFactor = dBScale, + IsRoll = roll, + RollOffset = rollOffset, + IsRotated = rotate, + }; + + return maker.GetBitmap(ffts); } } } diff --git a/src/Spectrogram/ImageMaker.cs b/src/Spectrogram/ImageMaker.cs new file mode 100644 index 0000000..c55ef99 --- /dev/null +++ b/src/Spectrogram/ImageMaker.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using SkiaSharp; + +namespace Spectrogram +{ + /// + /// This class converts a collection of FFTs to a colormapped spectrogram image + /// + public class ImageMaker + { + /// + /// Colormap used to translate intensity to pixel color + /// + public Colormap Colormap; + + /// + /// Intensity is multiplied by this number before converting it to the pixel color according to the colormap + /// + public double Intensity = 1; + + /// + /// If True, intensity will be log-scaled to represent Decibels + /// + public bool IsDecibel = false; + + /// + /// If is enabled, intensity will be scaled by this value prior to log transformation + /// + public double DecibelScaleFactor = 1; + + /// + /// If False, the spectrogram will proceed in time from left to right across the whole image. + /// If True, the image will be broken and the newest FFTs will appear on the left and oldest on the right. + /// + public bool IsRoll = false; + + /// + /// If is enabled, this value indicates the pixel position of the break point. + /// + public int RollOffset = 0; + + /// + /// If True, the spectrogram will flow top-down (oldest to newest) rather than left-right. + /// + public bool IsRotated = false; + + public ImageMaker() + { + + } + + 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; + + var imageInfo = new SKImageInfo(width, height, SKColorType.Gray8); + var bitmap = new SKBitmap(imageInfo); + + int pixelCount = width * height; + byte[] pixelBuffer = new byte[pixelCount]; + + Parallel.For(0, width, col => + { + int sourceCol = col; + if (IsRoll) + { + sourceCol += width - RollOffset % width; + if (sourceCol >= width) + sourceCol -= width; + } + + for (int row = 0; row < height; row++) + { + double value = IsRotated + ? ffts[height - row - 1][sourceCol] + : ffts[sourceCol][row]; + + if (IsDecibel) + value = 20 * Math.Log10(value * DecibelScaleFactor + 1); + + value *= Intensity; + value = Math.Min(value, 255); + + int bytePosition = (height - 1 - row) * width + col; + pixelBuffer[bytePosition] = (byte)value; + } + }); + + IntPtr pixelPtr = bitmap.GetPixels(); + Marshal.Copy(pixelBuffer, 0, pixelPtr, pixelBuffer.Length); + + SKBitmap newBitmap = Colormap.ApplyFilter(bitmap); + bitmap.Dispose(); + return newBitmap; + } + } +} diff --git a/src/Spectrogram/Operations.cs b/src/Spectrogram/Operations.cs deleted file mode 100644 index c150ea9..0000000 --- a/src/Spectrogram/Operations.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Spectrogram -{ - - public static class Operations - { - public enum WindowFunction { none, hanning, triangle }; - - public static bool IsPowerOfTwo(int number) - { - for (int i = 0; i < 20; i++) - { - int powerOfTwo = (1 << i); - if (number == powerOfTwo) - return true; - else if (powerOfTwo > number) - return false; - } - return false; - } - - public static double TriangleWindow(int n, int frameSize) - { - int pointsFromCenter = Math.Abs(frameSize / 2 - n); - int pointsFromEdge = frameSize / 2 - pointsFromCenter; - double fractionFromEdge = (double)pointsFromEdge / (frameSize / 2); - return fractionFromEdge; - } - - public static float[] FFT(float[] values, WindowFunction window = WindowFunction.hanning) - { - int fftSize = values.Length; - if (!IsPowerOfTwo(fftSize)) - throw new ArgumentException("FFT Size must be a power of 2"); - - NAudio.Dsp.Complex[] fft_buffer = new NAudio.Dsp.Complex[fftSize]; - for (int i = 0; i < fftSize; i++) - { - fft_buffer[i].X = (float)values[i]; - fft_buffer[i].Y = 0; - - switch (window) - { - case WindowFunction.none: - break; - case WindowFunction.hanning: - fft_buffer[i].X *= (float)NAudio.Dsp.FastFourierTransform.HammingWindow(i, fftSize); - break; - case WindowFunction.triangle: - fft_buffer[i].X *= (float)TriangleWindow(i, fftSize); - break; - default: - throw new NotImplementedException("unsupported window function"); - } - } - - NAudio.Dsp.FastFourierTransform.FFT(true, (int)Math.Log(fftSize, 2.0), fft_buffer); - - float[] fft = new float[fftSize / 2]; - for (int i = 0; i < fft.Length; i++) - { - var fftL = fft_buffer[i]; - var fftR = fft_buffer[fft_buffer.Length - i - 1]; - - // note that this is different than just taking the absolute value - float absL = (float)Math.Sqrt(fftL.X * fftL.X + fftL.Y * fftL.Y); - float absR = (float)Math.Sqrt(fftR.X * fftR.X + fftR.Y * fftR.Y); - fft[i] = (absL + absR) / 2; - } - - return fft; - } - - } -} diff --git a/src/Spectrogram/README.md b/src/Spectrogram/README.md new file mode 100644 index 0000000..959faa7 --- /dev/null +++ b/src/Spectrogram/README.md @@ -0,0 +1,36 @@ +**Spectrogram is a .NET library for creating frequency spectrograms from pre-recorded signals, streaming data, or microphone audio from the sound card.** Spectrogram uses FFT algorithms and window functions provided by the [FftSharp](https://github.com/swharden/FftSharp) project, and it targets .NET Standard so it can be used in .NET Framework and .NET Core projects. + +[![](https://raw.githubusercontent.com/swharden/Spectrogram/master/dev/graphics/hal-spectrogram.png)](https://github.com/swharden/Spectrogram) + +## Quickstart + +```cs +(double[] audio, int sampleRate) = ReadWavMono("hal.wav"); +var sg = new SpectrogramGenerator(sampleRate, fftSize: 4096, stepSize: 500, maxFreq: 3000); +sg.Add(audio); +sg.SaveImage("hal.png"); +``` + +This example generates the image at the top of the page. + + +## How to Read a WAV File + +There are many excellent libraries that read audio files. Consult the documentation _for those libraries_ to learn how to do this well. Here's an example method I use to read audio values from mono WAV files using the NAudio package: + +```cs +(double[] audio, int sampleRate) ReadWavMono(string filePath, double multiplier = 16_000) +{ + using var afr = new NAudio.Wave.AudioFileReader(filePath); + int sampleRate = afr.WaveFormat.SampleRate; + int bytesPerSample = afr.WaveFormat.BitsPerSample / 8; + int sampleCount = (int)(afr.Length / bytesPerSample); + int channelCount = afr.WaveFormat.Channels; + var audio = new List(sampleCount); + var buffer = new float[sampleRate * channelCount]; + int samplesRead = 0; + while ((samplesRead = afr.Read(buffer, 0, buffer.Length)) > 0) + audio.AddRange(buffer.Take(samplesRead).Select(x => x * multiplier)); + return (audio.ToArray(), sampleRate); +} +``` \ No newline at end of file diff --git a/src/Spectrogram/Reports.cs b/src/Spectrogram/Reports.cs deleted file mode 100644 index e49bb84..0000000 --- a/src/Spectrogram/Reports.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Spectrogram -{ - public static class Reports - { - public static void plot(float[] values, string saveFilePath, double sampleRateHz = 1, string title = null, string yLabel = null, string xLabel = null) - { - double[] values2 = new double[values.Length]; - for (int i = 0; i < values.Length; i++) - values2[i] = values[i]; - - var plt = new ScottPlot.Plot(); - plt.PlotSignal(values2, sampleRateHz, markerSize: 0); - plt.Title(title); - plt.YLabel(yLabel); - plt.XLabel(xLabel); - plt.AxisAuto(0); - plt.SaveFig(saveFilePath); - Console.WriteLine($"Saved: {System.IO.Path.GetFullPath(saveFilePath)}"); - } - - public static void plotValues(double[] values, string saveFilePath = "values.png", int sampleRateHz = 8000) - { - var plt = new ScottPlot.Plot(); - plt.PlotSignal(values, sampleRateHz, markerSize: 0, lineWidth: 2); - plt.Title("Signal"); - plt.YLabel("Value"); - plt.XLabel("Time (sec)"); - plt.AxisAuto(0); - plt.SaveFig(saveFilePath); - Console.WriteLine($"Saved: {System.IO.Path.GetFullPath(saveFilePath)}"); - } - - public static void plotFFT(double[] fft, string saveFilePath = "fft.png", int sampleRateHz = 8000) - { - var plt = new ScottPlot.Plot(); - double fftSampleRate = (double)fft.Length / sampleRateHz * 2; - plt.PlotSignal(fft, fftSampleRate, markerSize: 0, lineWidth: 2); - plt.Title("FFT"); - plt.YLabel("Power"); - plt.XLabel("Frequency (Hz)"); - plt.AxisAuto(0); - plt.SaveFig(saveFilePath); - Console.WriteLine($"Saved: {System.IO.Path.GetFullPath(saveFilePath)}"); - } - } -} diff --git a/src/Spectrogram/Scale.cs b/src/Spectrogram/Scale.cs new file mode 100644 index 0000000..e4a7788 --- /dev/null +++ b/src/Spectrogram/Scale.cs @@ -0,0 +1,58 @@ +using SkiaSharp; +using System.Collections.Generic; + +namespace Spectrogram; + +static class Scale +{ + public static SKBitmap 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) + { + 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; + } +} diff --git a/src/Spectrogram/Settings.cs b/src/Spectrogram/Settings.cs new file mode 100644 index 0000000..d36f137 --- /dev/null +++ b/src/Spectrogram/Settings.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Spectrogram +{ + class Settings + { + public readonly double SampleRate; + + // vertical information + public readonly int FftSize; + public readonly double FftLengthSec; + public readonly double FreqNyquist; + public readonly double HzPerPixel; + public readonly double PxPerHz; + public readonly int FftIndex1; + public readonly int FftIndex2; + public readonly double FreqMin; + public readonly double FreqMax; + public readonly double FreqSpan; + public readonly int Height; + public int OffsetHz; + + // horizontal information + public readonly double[] Window; + public readonly int StepSize; + public readonly double StepLengthSec; + public readonly double StepOverlapFrac; + public readonly double StepOverlapSec; + + public Settings(double sampleRate, int fftSize, int stepSize, double minFreq, double maxFreq, int offsetHz) + { + 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 + SampleRate = sampleRate; + FftSize = fftSize; + StepSize = stepSize; + FftLengthSec = (double)fftSize / sampleRate; + + // vertical + minFreq = Math.Max(minFreq, 0); + FreqNyquist = sampleRate / 2; + HzPerPixel = sampleRate / fftSize; + PxPerHz = (double)fftSize / sampleRate; + FftIndex1 = (minFreq == 0) ? 0 : (int)(minFreq / HzPerPixel); + FftIndex2 = (maxFreq >= FreqNyquist) ? fftSize / 2 : (int)(maxFreq / HzPerPixel); + Height = FftIndex2 - FftIndex1; + FreqMin = FftIndex1 * HzPerPixel; + FreqMax = FftIndex2 * HzPerPixel; + FreqSpan = FreqMax - FreqMin; + OffsetHz = offsetHz; + + // horizontal + StepLengthSec = (double)StepSize / sampleRate; + var window = new FftSharp.Windows.Hanning(); + Window = window.Create(fftSize); + StepOverlapSec = FftLengthSec - StepLengthSec; + StepOverlapFrac = StepOverlapSec / FftLengthSec; + } + + public int PixelY(double freq) + { + return (int)(Height - (freq - FreqMin + HzPerPixel) * PxPerHz - 1); + } + } +} diff --git a/src/Spectrogram/Settings/DisplaySettings.cs b/src/Spectrogram/Settings/DisplaySettings.cs deleted file mode 100644 index 6bda785..0000000 --- a/src/Spectrogram/Settings/DisplaySettings.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Text; - -namespace Spectrogram.Settings -{ - public class DisplaySettings - { - // The Spectrograph library does two things: - // 1) convert a signal to a FFT List - // 2) convert a FFT list to a Bitmap - - // This class stores settings that control how the Bitmap looks (#2) - - public double fftResolution; - public double freqLow; - public double freqHigh; - - public int pixelLower { get { return (int)(freqLow / fftResolution); } } - public int pixelUpper { get { return (int)(freqHigh / fftResolution); } } - public int height { get { return pixelUpper - pixelLower; } } - public int width; - - public float brightness = 1; - public bool decibels; - public Colormap colormap = Colormap.viridis; - public int? highlightColumn = null; - public bool showTicks = false; - public double tickSpacingSec = 1; - public double tickSpacingHz = 100; - - public bool renderNeeded; - - public double lastRenderMsec; - - public int tickSize = 5; - public Font tickFont = new Font(FontFamily.GenericMonospace, (float)8); - public StringFormat sfTicksRight = new StringFormat() - { - LineAlignment = StringAlignment.Center, - Alignment = StringAlignment.Far - }; - public StringFormat sfTicksLower = new StringFormat() - { - LineAlignment = StringAlignment.Far, - Alignment = StringAlignment.Center - }; - - } -} diff --git a/src/Spectrogram/Settings/FftSettings.cs b/src/Spectrogram/Settings/FftSettings.cs deleted file mode 100644 index 585eba9..0000000 --- a/src/Spectrogram/Settings/FftSettings.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Spectrogram.Settings -{ - public class FftSettings - { - // The Spectrograph library does two things: - // 1) convert a signal to a FFT List - // 2) convert a FFT list to a Bitmap - - // This class stores settings that control how the FFT list is created (#2) - - public readonly int sampleRate; - public readonly int fftSize; // todo: change this to fftInputPointCount - public int step; - - public FftSettings(int sampleRate, int fftSize, int step) - { - if (sampleRate <= 0) - throw new ArgumentException("Sample rate must be greater than 0"); - - if (!Operations.IsPowerOfTwo(fftSize)) - throw new ArgumentException("fftSize must be a power of 2"); - - this.sampleRate = sampleRate; - this.fftSize = fftSize; - this.step = step; - } - - public double maxFreq { get { return sampleRate / 2; } } - public int fftOutputPointCount { get { return fftSize / 2; } } - public double fftResolution { get { return maxFreq / fftOutputPointCount; } } - public double segmentsPerSecond { get { return sampleRate / step; } } - - public override string ToString() - { - string msg = ""; - msg += $"Sample rate: {sampleRate} Hz\n"; - msg += $"Maximum visible Frequency: {maxFreq} Hz\n"; - msg += $"FFT Size: {fftOutputPointCount} points\n"; - msg += $"FFT Resolution: {fftResolution} Hz\n"; - return msg.Trim(); - } - - public int IndexFromFrequency(double frequency) - { - return (int)(frequency / fftResolution); - } - - public double FrequencyFromIndex(int index) - { - return index * fftResolution; - } - - public int ImageHeight(double? lowerFrequency = null, double? upperFrequency = null) - { - if (lowerFrequency == null) - lowerFrequency = 0; - if (upperFrequency == null) - upperFrequency = maxFreq; - return IndexFromFrequency((double)upperFrequency - (double)lowerFrequency); - } - } -} diff --git a/src/Spectrogram/SignalGenerator.cs b/src/Spectrogram/SignalGenerator.cs deleted file mode 100644 index 33ac0e0..0000000 --- a/src/Spectrogram/SignalGenerator.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Spectrogram -{ - public static class SignalGenerator - { - public static float[] NoisySin( - double durationSeconds = 1, - int sampleRateHz = 8000, - double signalFrequencyHz = 2000, - double noiseLevel = .1 - ) - { - int pointCount = (int)(durationSeconds * sampleRateHz); - float[] values = new float[pointCount]; - - // create a sine wave - float oscillations = (float)(signalFrequencyHz * durationSeconds); - for (int i = 0; i < values.Length; i++) - values[i] = (float)Math.Sin(((float)i / values.Length) * 2 * Math.PI * oscillations); - - // add noise - Random rand = new Random(); - for (int i = 0; i < values.Length; i++) - values[i] += (float)((rand.NextDouble() - .5) * noiseLevel); - - return values; - } - } -} diff --git a/src/Spectrogram/Spectrogram.cs b/src/Spectrogram/Spectrogram.cs deleted file mode 100644 index 6337bd7..0000000 --- a/src/Spectrogram/Spectrogram.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; - -namespace Spectrogram -{ - public class Spectrogram - { - public readonly Settings.FftSettings fftSettings; - public readonly Settings.DisplaySettings displaySettings; - - public List fftList = new List(); - public List signal = new List(); - - public int nextIndex; - public float[] latestFFT; - - public Spectrogram(int sampleRate = 8000, int fftSize = 1024, int? step = null) - { - if (step == null) - step = sampleRate; - fftSettings = new Settings.FftSettings(sampleRate, fftSize, (int)step); - displaySettings = new Settings.DisplaySettings(); - displaySettings.fftResolution = fftSettings.fftResolution; - displaySettings.freqHigh = fftSettings.maxFreq; - } - - public override string ToString() - { - return $"Spectrogram ({fftSettings.sampleRate} Hz) " + - "with {ffts.Count} segments in memory " + - "({fftSettings.fftSize} points each)"; - } - - public string GetFftInfo() - { - return fftSettings.ToString(); - } - - public void AddExtend(float[] values) - { - signal.AddRange(values); - ProcessNewSegments(scroll: false, fixedSize: null); - } - - public void AddCircular(float[] values, int fixedSize) - { - signal.AddRange(values); - ProcessNewSegments(scroll: false, fixedSize: fixedSize); - } - - public void AddScroll(float[] values, int fixedSize) - { - signal.AddRange(values); - ProcessNewSegments(scroll: true, fixedSize: fixedSize); - } - - private void ProcessNewSegments(bool scroll, int? fixedSize) - { - int segmentsRemaining = (signal.Count - fftSettings.fftSize) / fftSettings.step; - float[] segment = new float[fftSettings.fftSize]; - - while (signal.Count > (fftSettings.fftSize + fftSettings.step)) - { - - int remainingSegments = (signal.Count - fftSettings.fftSize) / fftSettings.step; - if (remainingSegments % 10 == 0) - { - Console.WriteLine(string.Format("Processing segment {0} of {1} ({2:0.0}%)", - fftList.Count + 1, segmentsRemaining, 100.0 * (fftList.Count + 1) / segmentsRemaining)); - } - - signal.CopyTo(0, segment, 0, fftSettings.fftSize); - signal.RemoveRange(0, fftSettings.step); - - latestFFT = Operations.FFT(segment); - - if (fixedSize == null) - AddNewFftExtend(latestFFT); - else - AddNewFftFixed(latestFFT, (int)fixedSize, scroll); - } - - displaySettings.width = fftList.Count; - displaySettings.renderNeeded = true; - } - - private void AddNewFftExtend(float[] fft) - { - fftList.Add(fft); - } - - private void AddNewFftFixed(float[] fft, int fixedSize, bool scroll) - { - while (fftList.Count < fixedSize) - fftList.Add(null); - while (fftList.Count > fixedSize) - fftList.RemoveAt(fftList.Count - 1); - - if (scroll) - { - fftList.Add(fft); - fftList.RemoveAt(0); - } - else - { - nextIndex = Math.Min(nextIndex, fftList.Count - 1); - fftList[nextIndex] = fft; - nextIndex += 1; - if (nextIndex >= fftList.Count) - nextIndex = 0; - } - } - - public Bitmap GetBitmap( - double? intensity = null, - bool decibels = false, - bool vertical = false, - Colormap? colormap = null, - bool? showTicks = null, - double? tickSpacingHz = null, - double? tickSpacingSec = null, - double? freqLow = null, - double? freqHigh = null, - bool highlightLatestColumn = false - ) - { - if (fftList.Count == 0) - return null; - - if (displaySettings.height < 1) - throw new ArgumentException("FFT frequency range is too small"); - - if (intensity != null) - displaySettings.brightness = (float)intensity; - - displaySettings.decibels = decibels; - displaySettings.colormap = (colormap == null) ? displaySettings.colormap : (Colormap)colormap; - displaySettings.freqLow = (freqLow == null) ? 0 : (double)freqLow; - displaySettings.freqHigh = (freqHigh == null) ? fftSettings.maxFreq : (double)freqHigh; - displaySettings.showTicks = (showTicks == null) ? displaySettings.showTicks : (bool)showTicks; - displaySettings.tickSpacingHz = (tickSpacingHz == null) ? displaySettings.tickSpacingHz : (double)tickSpacingHz; - displaySettings.tickSpacingSec = (tickSpacingSec == null) ? displaySettings.tickSpacingSec : (double)tickSpacingSec; - - if (highlightLatestColumn) - displaySettings.highlightColumn = nextIndex; - else - displaySettings.highlightColumn = null; - - Bitmap bmpIndexed; - Bitmap bmpRgb; - - using (var benchmark = new Benchmark(true)) - { - bmpIndexed = Image.BitmapFromFFTs(fftList.ToArray(), displaySettings); - if (vertical) - bmpIndexed = Image.Rotate(bmpIndexed); - bmpRgb = bmpIndexed.Clone(new Rectangle(0, 0, bmpIndexed.Width, bmpIndexed.Height), PixelFormat.Format32bppPArgb); - displaySettings.lastRenderMsec = benchmark.elapsedMilliseconds; - } - - // TODO: put spacing in displaySettings - if (displaySettings.showTicks) - Annotations.drawTicks(bmpRgb, fftSettings, displaySettings); - - return bmpRgb; - } - - public void SaveBitmap(Bitmap bmp, string fileName) - { - string filePath = System.IO.Path.GetFullPath(fileName); - string extension = System.IO.Path.GetExtension(fileName).ToUpper(); - - var imageFormat = System.Drawing.Imaging.ImageFormat.Bmp; - if (extension == ".JPG" || extension == ".JPEG") - imageFormat = System.Drawing.Imaging.ImageFormat.Jpeg; - else if (extension == ".PNG") - imageFormat = System.Drawing.Imaging.ImageFormat.Png; - else if (extension == ".TIF" || extension == ".TIFF") - imageFormat = System.Drawing.Imaging.ImageFormat.Tiff; - - bmp.Save(filePath, imageFormat); - Console.WriteLine($"Saved: {filePath}"); - } - - public double GetLastRenderTime() - { - return displaySettings.lastRenderMsec; - } - - } -} diff --git a/src/Spectrogram/Spectrogram.csproj b/src/Spectrogram/Spectrogram.csproj index 1aa49fc..4bfc42f 100644 --- a/src/Spectrogram/Spectrogram.csproj +++ b/src/Spectrogram/Spectrogram.csproj @@ -1,57 +1,34 @@  - - - net45;netcoreapp3.0 - Spectrogram is a .NET library which makes it easy to create spectrograms from pre-recorded signals or live audio from the sound card. - MIT - https://github.com/swharden/Spectrogram - - https://github.com/swharden/Spectrogram/releases - - Scott W Harden - Harden Technologies, LLC - 1.0.1 - true - - Library - - 1.0.1.0 - 1.0.1.0 - https://raw.githubusercontent.com/swharden/Spectrogram/master/src/Spectrogram/icon.png - spectrogram spectrograph fft spectrum frequency audio - - - - false - - - - C:\Users\scott\Documents\GitHub\Spectrogram\src\Spectrogram\Spectrogram.xml - - - - - d37e2a3e-8545-3a39-9f4f-31827c9124ab - 2 - 4 - tlbimp - - - - - - - - - - - - 4.5.1 - - - - - - - - + + netstandard2.0 + 2.0.0-alpha + A .NET Standard library for creating spectrograms + Scott Harden + Harden Technologies, LLC + MIT + https://github.com/swharden/Spectrogram + icon.png + https://github.com/swharden/Spectrogram + spectrogram spectrum fft frequency audio microphone signal + https://github.com/swharden/Spectrogram/releases + true + true + README.md + portable + true + snupkg + true + true + true + latest + + + + + + + + + + + \ No newline at end of file diff --git a/src/Spectrogram/Spectrogram.xml b/src/Spectrogram/Spectrogram.xml deleted file mode 100644 index 164a4e3..0000000 --- a/src/Spectrogram/Spectrogram.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - Spectrogram - - - - diff --git a/src/Spectrogram/SpectrogramGenerator.cs b/src/Spectrogram/SpectrogramGenerator.cs new file mode 100644 index 0000000..2c8d593 --- /dev/null +++ b/src/Spectrogram/SpectrogramGenerator.cs @@ -0,0 +1,409 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using SkiaSharp; + +namespace Spectrogram; + +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 double 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) + { + Settings = new Settings(sampleRate, fftSize, stepSize, minFreq, maxFreq, offsetHz); + + UnprocessedData = initialAudioList ?? new List(); + + if (fixedWidth.HasValue) + SetFixedWidth(fixedWidth.Value); + } + + /// + /// 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"); + + for (int i = 0; i < Settings.FftSize; i++) + Settings.Window[i] = 0; + + int offset = (Settings.FftSize - newWindow.Length) / 2; + Array.Copy(newWindow, 0, Settings.Window, offset, newWindow.Length); + } + + /// + /// Load new data into the spectrogram generator + /// + public void Add(IEnumerable audio, bool process = true) + { + UnprocessedData.AddRange(audio); + if (process) + Process(); + } + + /// + /// 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; + } + + /// + /// Perform FFT analysis on all unprocessed data + /// + public double[][] Process() + { + if (FftsToProcess < 1) + return null; + + int newFftCount = FftsToProcess; + double[][] newFfts = new double[newFftCount][]; + + Parallel.For(0, newFftCount, newFftIndex => + { + 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.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; + }); + + foreach (var newFft in newFfts) + FFTs.Add(newFft); + FftsProcessed += newFfts.Length; + + UnprocessedData.RemoveRange(0, newFftCount * Settings.StepSize); + PadOrTrimForFixedWidth(); + + 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"); + + var fftsMel = new List(); + foreach (var fft in FFTs) + fftsMel.Add(FftSharp.Mel.Scale(fft, (int)SampleRate, melBinCount)); + + 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 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); + } + + /// + /// 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. + /// Controls overflow behavior. True wraps new data around to the start. False slides new data in. + public void SaveImage(string fileName, double intensity = 1, bool dB = false, double dBScale = 1, bool roll = false) + { + string extension = Path.GetExtension(fileName).ToLower(); + byte[] bytes = GetImageBytes(extension, intensity, dB, dBScale, roll); + File.WriteAllBytes(fileName, bytes); + } + + public byte[] GetImageBytes(string extension, 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."); + + SKEncodedImageFormat fmt = extension.ToLower() switch + { + ".bmp" => SKEncodedImageFormat.Bmp, + ".png" => SKEncodedImageFormat.Png, + ".gif" => SKEncodedImageFormat.Gif, + ".jpg" => SKEncodedImageFormat.Jpeg, + ".jpeg" => SKEncodedImageFormat.Jpeg, + _ => throw new ArgumentException("unknown file extension"), + }; + + using SKBitmap image = Image.GetBitmap(FFTs, Colormap, intensity, dB, dBScale, roll, NextColumnIndex); + using SKData encodedImage = image.Encode(fmt, 80); + byte[] bytes = encodedImage.ToArray(); + return bytes; + } + + /// + /// 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++) + { + 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; + + /// + /// 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() + { + if (fixedWidth > 0) + { + int overhang = Width - fixedWidth; + if (overhang > 0) + FFTs.RemoveRange(0, overhang); + + 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 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 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); + + if (latestFft == false) + throw new NotImplementedException("peak of mean of all FFTs not yet supported"); + + double[] freqs = FFTs[FFTs.Count - 1]; + + int peakIndex = 0; + double peakMagnitude = 0; + for (int i = 0; i < freqs.Length; i++) + { + if (freqs[i] > peakMagnitude) + { + peakMagnitude = freqs[i]; + peakIndex = i; + } + } + + double maxFreq = SampleRate / 2; + double peakFreqFrac = peakIndex / (double)freqs.Length; + double peakFreqHz = maxFreq * peakFreqFrac; + + return (peakFreqHz, peakMagnitude); + } +} diff --git a/src/Spectrogram/Tools.cs b/src/Spectrogram/Tools.cs deleted file mode 100644 index 519a78c..0000000 --- a/src/Spectrogram/Tools.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Text; - -namespace Spectrogram -{ - public static class Tools - { - private static string FindFile(string filePath) - { - // look for it in this folder - string pathFileHere = System.IO.Path.GetFullPath(filePath); - if (System.IO.File.Exists(pathFileHere)) - return pathFileHere; - else - Console.WriteLine($"File not found in same folder: {pathFileHere}"); - - // look for it in the package data folder - string fileName = System.IO.Path.GetFileName(filePath); - string pathDataFolder = System.IO.Path.GetFullPath("../../../../data/"); - string pathInDataFolder = System.IO.Path.Combine(pathDataFolder, fileName); - if (System.IO.File.Exists(pathInDataFolder)) - return pathInDataFolder; - else - Console.WriteLine($"File not found in data folder: {pathInDataFolder}"); - - throw new ArgumentException($"Could not locate {filePath}"); - } - - public static float[] FloatsFromBytesINT16(byte[] bytes, int skipFirstBytes = 0) - { - float[] pcm = new float[(bytes.Length - skipFirstBytes) / 2]; - for (int i = skipFirstBytes; i < bytes.Length - 2; i += 2) - { - if (i / 2 >= pcm.Length) - break; - pcm[i / 2] = BitConverter.ToInt16(bytes, i); - } - return pcm; - } - - public static float[] ReadWav(string wavFilePath, int? sampleLimit = null) - { - // quick and drity WAV file reader (only for 16-bit signed mono files) - string actualPath = FindFile(wavFilePath); - if (actualPath == null) - throw new ArgumentException("file not found: " + actualPath); - byte[] bytes = System.IO.File.ReadAllBytes(actualPath); - int sampleCount = bytes.Length / 2; - if (sampleLimit != null) - sampleCount = Math.Min(sampleCount, (int)sampleLimit); - return FloatsFromBytesINT16(bytes, skipFirstBytes: 44); - } - - public static float[] ReadMp3(string mp3FilePath, int? sampleLimit = null) - { - string actualPath = FindFile(mp3FilePath); - var reader = new NAudio.Wave.Mp3FileReader(actualPath); - int bytesToRead = (int)reader.Length; - if (sampleLimit != null) - bytesToRead = Math.Min(bytesToRead, (int)sampleLimit * 2); - byte[] bytes = new byte[bytesToRead]; - reader.Read(bytes, 0, bytesToRead); - float[] pcm = FloatsFromBytesINT16(bytes); - return pcm; - } - - } -} diff --git a/src/Spectrogram/readme.md b/src/Spectrogram/readme.md deleted file mode 100644 index 358e10e..0000000 --- a/src/Spectrogram/readme.md +++ /dev/null @@ -1,20 +0,0 @@ -**Spectrogram** is a .NET library which makes it easy to create spectrograms from pre-recorded signals or live audio from the sound card. - -### Quickstart - -```cs -// load audio and process FFT -var spec = new Spectrogram.Spectrogram(sampleRate: 8000, fftSize: 2048, step: 700); -float[] values = Spectrogram.Tools.ReadWav("mozart.wav"); -spec.AddExtend(values); - -// convert FFT to an image and save it -Bitmap bmp = spec.GetBitmap(intensity: 2, freqHigh: 2500); -spec.SaveBitmap(bmp, "mozart.jpg"); -``` - -![](https://raw.githubusercontent.com/swharden/Spectrogram/master/data/mozartSmall.jpg) - -### Additional Resources -Much more is on the Spectrogram project page:\ -**[https://github.com/swharden/Spectrogram](https://github.com/swharden/Spectrogram)** \ No newline at end of file diff --git a/src/WaterfallDemo/App.config b/src/WaterfallDemo/App.config deleted file mode 100644 index 8e15646..0000000 --- a/src/WaterfallDemo/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/WaterfallDemo/Form1.Designer.cs b/src/WaterfallDemo/Form1.Designer.cs deleted file mode 100644 index 47a4801..0000000 --- a/src/WaterfallDemo/Form1.Designer.cs +++ /dev/null @@ -1,95 +0,0 @@ -namespace WaterfallDemo -{ - partial class Form1 - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.components = new System.ComponentModel.Container(); - this.scottPlotUC1 = new ScottPlot.FormsPlot(); - this.timer1 = new System.Windows.Forms.Timer(this.components); - this.waterfall1 = new WaterfallDemo.Waterfall(); - this.scottPlotUC2 = new ScottPlot.FormsPlot(); - this.SuspendLayout(); - // - // scottPlotUC1 - // - this.scottPlotUC1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.scottPlotUC1.Location = new System.Drawing.Point(12, 410); - this.scottPlotUC1.Name = "scottPlotUC1"; - this.scottPlotUC1.Size = new System.Drawing.Size(776, 129); - this.scottPlotUC1.TabIndex = 0; - // - // timer1 - // - this.timer1.Enabled = true; - this.timer1.Interval = 10; - this.timer1.Tick += new System.EventHandler(this.Timer1_Tick); - // - // waterfall1 - // - this.waterfall1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.waterfall1.Location = new System.Drawing.Point(12, 147); - this.waterfall1.Name = "waterfall1"; - this.waterfall1.Size = new System.Drawing.Size(776, 257); - this.waterfall1.TabIndex = 1; - // - // scottPlotUC2 - // - this.scottPlotUC2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.scottPlotUC2.Location = new System.Drawing.Point(12, 12); - this.scottPlotUC2.Name = "scottPlotUC2"; - this.scottPlotUC2.Size = new System.Drawing.Size(776, 129); - this.scottPlotUC2.TabIndex = 2; - // - // Form1 - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(800, 551); - this.Controls.Add(this.scottPlotUC2); - this.Controls.Add(this.waterfall1); - this.Controls.Add(this.scottPlotUC1); - this.Name = "Form1"; - this.Text = "Waterfall Demo"; - this.Load += new System.EventHandler(this.Form1_Load); - this.ResumeLayout(false); - - } - - #endregion - - private ScottPlot.FormsPlot scottPlotUC1; - private Waterfall waterfall1; - private System.Windows.Forms.Timer timer1; - private ScottPlot.FormsPlot scottPlotUC2; - } -} - diff --git a/src/WaterfallDemo/Form1.cs b/src/WaterfallDemo/Form1.cs deleted file mode 100644 index 80f0d0e..0000000 --- a/src/WaterfallDemo/Form1.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; - -namespace WaterfallDemo -{ - public partial class Form1 : Form - { - public Form1() - { - InitializeComponent(); - } - - private void Form1_Load(object sender, EventArgs e) - { - waterfall1.StartListening(deviceIndex: 0); - } - - private void UpdateWaterfallPlot() - { - scottPlotUC1.plt.Clear(); - - var fft = waterfall1.spec.fftList[waterfall1.spec.fftList.Count - 1]; - double[] fft2 = new double[fft.Length]; - for (int i = 0; i < fft.Length; i++) - fft2[i] = fft[i]; - - scottPlotUC1.plt.PlotSignal(fft2, 1.0 / waterfall1.spec.fftSettings.fftResolution, - markerSize: 0, color: Color.Black); - scottPlotUC1.plt.AxisAuto(0, .01, xExpandOnly: false, yExpandOnly: true); - - scottPlotUC1.plt.Grid(false); - scottPlotUC1.plt.Style(dataBg: SystemColors.Control); - scottPlotUC1.plt.Frame(drawFrame: false); - scottPlotUC1.plt.TightenLayout(padding: 0); - - scottPlotUC1.Render(); - } - - private void UpdateSignalPlot() - { - scottPlotUC2.plt.Clear(); - - int pointCount = 1000; - var sigList = waterfall1.spec.signal; - var lastSamples = sigList.GetRange(sigList.Count - pointCount, pointCount); - - double[] pcm = new double[pointCount]; - for (int i = 0; i < pointCount; i++) - pcm[i] = lastSamples[i]; - - scottPlotUC2.plt.PlotSignal(pcm, waterfall1.spec.fftSettings.sampleRate, - markerSize: 0, color: Color.Black); - scottPlotUC2.plt.AxisAuto(0, .01, xExpandOnly: false, yExpandOnly: true); - - scottPlotUC2.plt.Grid(false); - scottPlotUC2.plt.Style(dataBg: SystemColors.Control); - scottPlotUC2.plt.Frame(drawFrame: false); - scottPlotUC2.plt.TightenLayout(padding: 0); - - scottPlotUC2.Render(); - } - - private void Timer1_Tick(object sender, EventArgs e) - { - if ((waterfall1.spec.fftList == null) || (waterfall1.spec.fftList.Count == 0)) - return; - - UpdateSignalPlot(); - UpdateWaterfallPlot(); - } - } -} diff --git a/src/WaterfallDemo/Form1.resx b/src/WaterfallDemo/Form1.resx deleted file mode 100644 index 1f666f2..0000000 --- a/src/WaterfallDemo/Form1.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - \ No newline at end of file diff --git a/src/WaterfallDemo/Program.cs b/src/WaterfallDemo/Program.cs deleted file mode 100644 index fa55ed4..0000000 --- a/src/WaterfallDemo/Program.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Forms; - -namespace WaterfallDemo -{ - static class Program - { - /// - /// The main entry point for the application. - /// - [STAThread] - static void Main() - { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new Form1()); - } - } -} diff --git a/src/WaterfallDemo/Properties/AssemblyInfo.cs b/src/WaterfallDemo/Properties/AssemblyInfo.cs deleted file mode 100644 index 2f2800f..0000000 --- a/src/WaterfallDemo/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("WaterfallDemo")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("WaterfallDemo")] -[assembly: AssemblyCopyright("Copyright © 2019")] -[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("3264be41-10b0-4121-b837-d7b134bd1a9b")] - -// 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/WaterfallDemo/Properties/Resources.Designer.cs b/src/WaterfallDemo/Properties/Resources.Designer.cs deleted file mode 100644 index 79c4704..0000000 --- a/src/WaterfallDemo/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 WaterfallDemo.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("WaterfallDemo.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/WaterfallDemo/Properties/Resources.resx b/src/WaterfallDemo/Properties/Resources.resx deleted file mode 100644 index af7dbeb..0000000 --- a/src/WaterfallDemo/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/WaterfallDemo/Properties/Settings.Designer.cs b/src/WaterfallDemo/Properties/Settings.Designer.cs deleted file mode 100644 index cc472d5..0000000 --- a/src/WaterfallDemo/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 WaterfallDemo.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/WaterfallDemo/Properties/Settings.settings b/src/WaterfallDemo/Properties/Settings.settings deleted file mode 100644 index 3964565..0000000 --- a/src/WaterfallDemo/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/WaterfallDemo/Waterfall.Designer.cs b/src/WaterfallDemo/Waterfall.Designer.cs deleted file mode 100644 index 338a4d4..0000000 --- a/src/WaterfallDemo/Waterfall.Designer.cs +++ /dev/null @@ -1,71 +0,0 @@ -namespace WaterfallDemo -{ - partial class Waterfall - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Component Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.components = new System.ComponentModel.Container(); - this.pictureBox1 = new System.Windows.Forms.PictureBox(); - this.timer1 = new System.Windows.Forms.Timer(this.components); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); - this.SuspendLayout(); - // - // pictureBox1 - // - this.pictureBox1.BackColor = System.Drawing.SystemColors.ControlDark; - this.pictureBox1.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch; - this.pictureBox1.Dock = System.Windows.Forms.DockStyle.Fill; - this.pictureBox1.Location = new System.Drawing.Point(0, 0); - this.pictureBox1.Name = "pictureBox1"; - this.pictureBox1.Size = new System.Drawing.Size(537, 428); - this.pictureBox1.TabIndex = 0; - this.pictureBox1.TabStop = false; - // - // timer1 - // - this.timer1.Enabled = true; - this.timer1.Interval = 10; - this.timer1.Tick += new System.EventHandler(this.Timer1_Tick); - // - // Waterfall - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.Controls.Add(this.pictureBox1); - this.Name = "Waterfall"; - this.Size = new System.Drawing.Size(537, 428); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.PictureBox pictureBox1; - private System.Windows.Forms.Timer timer1; - } -} diff --git a/src/WaterfallDemo/Waterfall.cs b/src/WaterfallDemo/Waterfall.cs deleted file mode 100644 index f195522..0000000 --- a/src/WaterfallDemo/Waterfall.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Drawing; -using System.Data; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; - -namespace WaterfallDemo -{ - public partial class Waterfall : UserControl - { - private NAudio.Wave.WaveInEvent wvin; - public Spectrogram.Spectrogram spec; - bool renderNeeded; - bool busyRendering; - - public Waterfall() - { - InitializeComponent(); - } - - public void StartListening(int deviceIndex = 0, int sampleRate = 8000, int fftSize = 1024) - { - spec = new Spectrogram.Spectrogram(sampleRate, fftSize, 250); - - int bitRate = 16; - int channels = 1; - int bufferMilliseconds = 10; - - wvin = new NAudio.Wave.WaveInEvent(); - wvin.DeviceNumber = deviceIndex; - wvin.WaveFormat = new NAudio.Wave.WaveFormat(sampleRate, bitRate, channels); - wvin.DataAvailable += OnDataAvailable; - wvin.BufferMilliseconds = bufferMilliseconds; - wvin.StartRecording(); - } - - private void OnDataAvailable(object sender, NAudio.Wave.WaveInEventArgs args) - { - int bytesPerSample = wvin.WaveFormat.BitsPerSample / 8; - float[] buffer = new float[args.BytesRecorded / bytesPerSample]; - for (int i = 0; i < buffer.Length; i++) - buffer[i] = BitConverter.ToInt16(args.Buffer, i * bytesPerSample); - - try - { - spec.AddScroll(buffer, fixedSize: pictureBox1.Height); - renderNeeded = true; - } - catch (Exception ex) - { - Console.WriteLine("EXCEPTION: " + ex); - } - } - - private void Timer1_Tick(object sender, EventArgs e) - { - - if (!renderNeeded) - return; - - if ((spec == null) || (spec.fftList.Count == 0)) - return; - - if (busyRendering) - return; - else - busyRendering = true; - - pictureBox1.BackgroundImage = spec.GetBitmap(vertical: true, intensity: 3, freqHigh: 4000); - renderNeeded = false; - busyRendering = false; - } - } -} diff --git a/src/WaterfallDemo/Waterfall.resx b/src/WaterfallDemo/Waterfall.resx deleted file mode 100644 index 1f666f2..0000000 --- a/src/WaterfallDemo/Waterfall.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - \ No newline at end of file diff --git a/src/WaterfallDemo/WaterfallDemo.csproj b/src/WaterfallDemo/WaterfallDemo.csproj deleted file mode 100644 index 445b2c6..0000000 --- a/src/WaterfallDemo/WaterfallDemo.csproj +++ /dev/null @@ -1,104 +0,0 @@ - - - - - Debug - AnyCPU - {3264BE41-10B0-4121-B837-D7B134BD1A9B} - WinExe - WaterfallDemo - WaterfallDemo - v4.5 - 512 - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\NAudio.1.9.0\lib\net35\NAudio.dll - - - ..\packages\ScottPlot.3.1.1\lib\net45\ScottPlot.dll - - - - - - - - - - - - - - - - Form - - - Form1.cs - - - - - UserControl - - - Waterfall.cs - - - Form1.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - - - Waterfall.cs - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - - - - - - {8717f4ce-4497-4eaa-b95d-0f7a04fb397d} - Spectrogram - - - - \ No newline at end of file diff --git a/src/WaterfallDemo/packages.config b/src/WaterfallDemo/packages.config deleted file mode 100644 index 487e7c4..0000000 --- a/src/WaterfallDemo/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file