|
1 | 1 | # Spectrogram
|
2 |
| -**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/). |
| 2 | +**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 2.0 so it can be used in .NET Framework and .NET Core projects. |
| 3 | + |
| 4 | +<div align="center"> |
| 5 | + |
| 6 | + |
| 7 | + |
| 8 | +_"I'm sorry Dave... I'm afraid I can't do that"_ |
| 9 | + |
| 10 | +</div> |
3 | 11 |
|
4 |
| - |
5 | 12 |
|
6 | 13 | ## Quickstart
|
7 | 14 |
|
8 |
| -### Song to Spectrogram |
9 |
| -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. |
| 15 | +_Spectrogram is [available on NuGet](https://www.nuget.org/packages/Spectrogram)_ |
10 | 16 |
|
11 | 17 | ```cs
|
12 |
| -// load audio and process FFT |
13 |
| -var spec = new Spectrogram.Spectrogram(sampleRate: 8000, fftSize: 2048, step: 700); |
14 |
| -float[] values = Spectrogram.Tools.ReadWav("mozart.wav"); |
15 |
| -spec.AddExtend(values); |
16 |
| - |
17 |
| -// convert FFT to an image and save it |
18 |
| -Bitmap bmp = spec.GetBitmap(intensity: 2, freqHigh: 2500); |
19 |
| -spec.SaveBitmap(bmp, "mozart.jpg"); |
| 18 | +(int sampleRate, double[] audio) = WavFile.ReadMono("hal.wav"); |
| 19 | + |
| 20 | +var spec = new Spectrogram(sampleRate, fftSize: 4096, stepSize: 500, maxFreq: 3000); |
| 21 | +spec.Add(audio); |
| 22 | +spec.SaveImage("hal.png", intensity: .4); |
20 | 23 | ```
|
21 | 24 |
|
22 |
| -### Human Voice |
23 |
| -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. |
| 25 | +This code generates the image displayed at the top of this page. |
| 26 | + |
| 27 | +## Windows Forms |
| 28 | + |
| 29 | +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: |
24 | 30 |
|
25 | 31 | ```cs
|
26 |
| -// load audio and process FFT |
27 |
| -var spec = new Spectrogram.Spectrogram(sampleRate: 15000, fftSize: 4096, step: 400); |
28 |
| -float[] values = Spectrogram.Tools.ReadMp3("cant-do-that.mp3"); |
29 |
| -spec.AddExtend(values); |
30 |
| - |
31 |
| -// convert FFT to an image and save it |
32 |
| -Bitmap bmp = spec.GetBitmap(intensity: .2, freqHigh: 1000, |
33 |
| - colormap: Spectrogram.Colormap.grayscaleInverted); |
34 |
| -spec.SaveBitmap(bmp, "cant-do-that-grayscale-inverted.jpg"); |
| 32 | +Bitmap bmp = spec.GetBitmap(); |
| 33 | +pictureBox1.Image = bmp; |
35 | 34 | ```
|
36 |
| -colormap | sample output |
37 |
| ----|--- |
38 |
| -**Grayscale Inverted** is used in many scientific publications when analyzing things like human voices and bird sounds| |
39 |
| -**Grayscale** provides highest contrast output but does not benefit from color vision| |
40 |
| -**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).| |
41 |
| -**vdGreen** is the default colormap used for [QRSS-VD](https://github.com/swharden/QRSS-VD), a very old software project of mine. | |
42 | 35 |
|
43 |
| -### QRSS Analysis |
| 36 | +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. |
| 37 | + |
| 38 | +## Real-Time Spectrogram |
| 39 | + |
| 40 | +An example program is included in this repository which demonstrates how to use [NAudio](https://github.com/naudio/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. |
| 41 | + |
| 42 | + |
| 43 | + |
| 44 | +To do this, keep your Spectrogram at the class level: |
| 45 | +```cs |
| 46 | +Spectrogram spec; |
44 | 47 |
|
45 |
| -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. |
| 48 | +public Form1() |
| 49 | +{ |
| 50 | + InitializeComponent(); |
| 51 | + spec = new Spectrogram(sampleRate, fftSize: 4096, stepSize: 500, maxFreq: 3000); |
| 52 | +} |
| 53 | +``` |
46 | 54 |
|
47 |
| -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. |
| 55 | +Whenever an audio buffer gets filled, add the data to your Spectrogram: |
| 56 | +```cs |
| 57 | +private void GotNewBuffer(double[] audio) |
| 58 | +{ |
| 59 | + spec.Add(audio); |
| 60 | +} |
| 61 | +``` |
48 | 62 |
|
| 63 | +Then set up a timer to trigger rendering: |
49 | 64 | ```cs
|
50 |
| -// load audio and process FFT |
51 |
| -var spec = new Spectrogram.Spectrogram(sampleRate: 8000, fftSize: 16384, step: 8000); |
52 |
| -float[] values = Spectrogram.Tools.ReadMp3("qrss-w4hbk.mp3"); |
53 |
| -spec.AddExtend(values); |
54 |
| - |
55 |
| -// convert FFT to an image and save it |
56 |
| -Bitmap bmp = spec.GetBitmap(intensity: 1.5, freqLow: 1100, freqHigh: 1500, |
57 |
| - showTicks: true, tickSpacingHz: 50, tickSpacingSec: 60); |
58 |
| -spec.SaveBitmap(bmp, "qrss.png"); |
| 65 | +private void timer1_Tick(object sender, EventArgs e){ |
| 66 | + Bitmap bmp = spec.GetBitmap(intensity: .4); |
| 67 | + pictureBox1.Image?.Dispose(); |
| 68 | + pictureBox1.Image = bmp; |
| 69 | +} |
59 | 70 | ```
|
60 | 71 |
|
| 72 | +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. |
61 | 73 |
|
62 |
| - |
| 74 | +## Song-to-Spectrogram |
63 | 75 |
|
64 |
| -## Demo Applications |
65 |
| -This project comes with a few interactive applications which serve as useful references for some of the ways this Spectrogram library can be used. |
| 76 | +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). |
66 | 77 |
|
67 |
| -### Download Demo EXE Files |
68 |
| -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)** |
| 78 | +```cs |
| 79 | +(int sampleRate, double[] audio) = WavFile.ReadMono("Handel.wav"); |
69 | 80 |
|
70 |
| -### Audio Monitor |
| 81 | +var spec = new Spectrogram(sampleRate, fftSize: 16384, stepSize: 2500, maxFreq: 2200); |
| 82 | +spec.Add(audio); |
| 83 | +spec.SaveImage("spectrogram-song.jpg", intensity: 5, dB: true); |
| 84 | +``` |
71 | 85 |
|
72 |
| -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. |
| 86 | +Notice the optional conversion to Decibels while saving the image. |
73 | 87 |
|
74 |
| - |
| 88 | + |
75 | 89 |
|
76 |
| -### Waterfall with Graphs |
77 |
| -This demo program was created to demonstrate Spectrogram and ScottPlot working together. |
| 90 | +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. |
78 | 91 |
|
79 |
| - |
| 92 | +## Spectrogram Information |
80 | 93 |
|
81 |
| -## Resources |
| 94 | +The Spectrogram's `ToString()` method displays detailed information about the spectrogram: |
| 95 | + |
| 96 | +```cs |
| 97 | +Console.WriteLine(spec); |
| 98 | +``` |
| 99 | + |
| 100 | +``` |
| 101 | +Spectrogram (2993, 817) |
| 102 | + Vertical (817 px): 0 - 2,199 Hz, FFT size: 16,384 samples, 2.69 Hz/px |
| 103 | + Horizontal (2993 px): 2.96 min, window: 0.37 sec, step: 0.06 sec, overlap: 84% |
| 104 | +``` |
82 | 105 |
|
83 |
| -### Similar Software |
84 |
| -* Argo ([website](http://digilander.libero.it/i2phd/argo/)) - closed-source QRSS viewer for Windows |
85 |
| -* SpectrumLab ([website](http://www.qsl.net/dl4yhf/spectra1.html)) - closed-source spectrum analyzer for Windows |
86 |
| -* QrssPIG ([GitLab](https://gitlab.com/hb9fxx/qrsspig)) - open-source spectrograph for Raspberry Pi (C++) |
87 |
| -* Lopora ([GitHub](https://github.com/swharden/Lopora)) - open-source spectrograph (Python 3) |
88 |
| -* QRSS VD ([GitHub](https://github.com/swharden/QRSS-VD)) - open source spectrograph (Python 2) |
89 |
| - |
90 |
| -### QRSS Information |
91 |
| - * [What is QRSS?](https://www.qsl.net/m0ayf/What-is-QRSS.html) |
92 |
| - * [QRSS and you](http://www.ka7oei.com/qrss1.html) |
93 |
| - * [QRSS (slow CW)](https://sites.google.com/site/qrssinfo/QRSS-Slow-CW) |
| 106 | +## Colormaps |
| 107 | + |
| 108 | +These examples demonstrate the identical spectrogram analyzed with a variety of different colormaps. Spectrogram colormaps can be changed by calling the `SetColormap()` method: |
| 109 | + |
| 110 | +```cs |
| 111 | +(int sampleRate, double[] audio) = WavFile.ReadMono("hal.wav"); |
| 112 | +int fftSize = 8192; |
| 113 | +var spec = new Spectrogram(sampleRate, fftSize, stepSize: 200, maxFreq: 3000); |
| 114 | +spec.Add(audio); |
| 115 | +spec.SetColormap(Colormap.Jet); |
| 116 | +spec.SaveImage($"hal-Jet.png", intensity: .5); |
| 117 | +``` |
| 118 | + |
| 119 | +Viridis | Greens | Blues | Grayscale | GrayscaleR |
| 120 | +---|---|---|---|--- |
| 121 | +|||| |
| 122 | + |
| 123 | +## Spectrogram File Format (SFF) |
| 124 | + |
| 125 | +The Spectrogram library has methods which can read and write SFF files, a file format specifically designed for storing spectrogram data. SFF files contain 2D spectrogram data (repeated FFTs) with a [small header](dev/sff) describing the audio and FFT settings suitable for deriving scale information. |
| 126 | + |
| 127 | +SFF files store `double` values (8-byte floating-point data) which is far superior to saving spectrograms as indexed color images (which represent intensity with a single `byte` per pixel). |
| 128 | + |
| 129 | +SFF files be saved using `Complex` data format (with real and imaginary values for each point) to faithfully represent the FFT output, or `double` format to represent magnitude (with an optional pre-conversion to Decibels to represent power). |
| 130 | + |
| 131 | +### Create SFF Files with C# |
| 132 | + |
| 133 | +This example creates a spectrogram but saves it using the SFF file format instead of saving it as an image. The SFF file can then be read in any language. |
| 134 | + |
| 135 | +```cs |
| 136 | +(int sampleRate, double[] audio) = WavFile.ReadMono("hal.wav"); |
| 137 | +int fftSize = 1 << 12; |
| 138 | +var spec = new Spectrogram(sampleRate, fftSize, stepSize: 700, maxFreq: 2000); |
| 139 | +spec.Add(audio); |
| 140 | +spec.SaveData("hal.sff"); |
| 141 | +``` |
| 142 | + |
| 143 | +### Read SFF Files with Python |
| 144 | +A Python module to read SFF files has been created (in [dev/sff](dev/sff)) which allows Spectrograms created by this library and stored in SFF format to be loaded as 2D numpy arrays in Python. |
| 145 | + |
| 146 | +This example demonstrates how the SFF file created in the previous C# example can be loaded into Python and displayed with matplotlib: |
| 147 | + |
| 148 | +```python |
| 149 | +import matplotlib.pyplot as plt |
| 150 | +import sffLib |
| 151 | + |
| 152 | +# load spectrogram data as a 2D numpy array |
| 153 | +sf = sffLib.SpectrogramFile("hal.sff") |
| 154 | + |
| 155 | +# plot the spectrogram as a heatmap |
| 156 | +freqs = np.arange(sf.values.shape[1]) * sf.hzPerPx / 1000 |
| 157 | +times = np.arange(sf.values.shape[0]) * sf.secPerPx |
| 158 | +plt.pcolormesh(freqs, times, sf.values) |
| 159 | + |
| 160 | +# decorate the plot |
| 161 | +plt.colorbar() |
| 162 | +plt.title("Spectrogram Magnitude (RMS)") |
| 163 | +plt.ylabel("Time (seconds)") |
| 164 | +plt.xlabel("Frequency (kHz)") |
| 165 | +plt.show() |
| 166 | +``` |
| 167 | + |
| 168 | + |
| 169 | + |
| 170 | +## Resources |
| 171 | +* [FftSharp](https://github.com/swharden/FftSharp) - the module which actually performs the FFT and related transformations |
| 172 | +* [MP3Sharp](https://github.com/ZaneDubya/MP3Sharp) - a library I use to read MP3 files during testing |
| 173 | +* [FSKview](https://github.com/swharden/FSKview) - a real-time spectrogram for viewing frequency-shift-keyed (FSK) signals from audio transmitted over radio frequency. |
| 174 | +* [NAudio](https://github.com/naudio/NAudio) - an open source .NET library which makes it easy to get samples from the microphone or sound card in real time |
0 commit comments