Skip to content

Commit b8a79b0

Browse files
committed
update documentation to use new class name #30
1 parent 4993d3b commit b8a79b0

File tree

9 files changed

+124
-140
lines changed

9 files changed

+124
-140
lines changed

README.md

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,60 +16,59 @@ _"I'm sorry Dave... I'm afraid I can't do that"_
1616

1717
## Quickstart
1818

19-
_Spectrogram is [available on NuGet](https://www.nuget.org/packages/Spectrogram)_
19+
* This code generates the spectrogram above.
2020

21-
```cs
22-
(int sampleRate, double[] audio) = WavFile.ReadMono("hal.wav");
21+
* Source code for the WAV reading method is at the bottom of this page.
2322

24-
var spec = new Spectrogram(sampleRate, fftSize: 4096, stepSize: 500, maxFreq: 3000);
25-
spec.Add(audio);
26-
spec.SaveImage("hal.png", intensity: .4);
23+
```cs
24+
(double[] audio, int sampleRate) = ReadWAV("hal.wav");
25+
var sg = new SpectrogramGenerator(sampleRate, fftSize: 4096, stepSize: 500, maxFreq: 3000);
26+
sg.Add(audio);
27+
sg.SaveImage("hal.png");
2728
```
2829

29-
This code generates the image displayed at the top of this page.
3030

3131
## Windows Forms
3232

3333
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:
3434

3535
```cs
36-
Bitmap bmp = spec.GetBitmap();
37-
pictureBox1.Image = bmp;
36+
pictureBox1.Image = sg.GetBitmap();
3837
```
3938

4039
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.
4140

4241
## Real-Time Spectrogram
4342

44-
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.
43+
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.
4544

46-
**Run this demo: [Spectrogram.MicrophoneDemo.exe](dev/SpectrogramDemo.zip)** 👈 64-bit Windows Application
45+
* **Click-to-run demo** for 64-bit Windows: [SpectrogramDemo.exe](dev/SpectrogramDemo.zip)
4746

4847
![](dev/microphone-spectrogram.gif)
4948

5049
To do this, keep your Spectrogram at the class level:
5150
```cs
52-
Spectrogram spec;
51+
SpectrogramGenerator sg;
5352

5453
public Form1()
5554
{
5655
InitializeComponent();
57-
spec = new Spectrogram(sampleRate, fftSize: 4096, stepSize: 500, maxFreq: 3000);
56+
sg = new SpectrogramGenerator(sampleRate, fftSize: 4096, stepSize: 500, maxFreq: 3000);
5857
}
5958
```
6059

6160
Whenever an audio buffer gets filled, add the data to your Spectrogram:
6261
```cs
6362
private void GotNewBuffer(double[] audio)
6463
{
65-
spec.Add(audio);
64+
sg.Add(audio);
6665
}
6766
```
6867

6968
Then set up a timer to trigger rendering:
7069
```cs
7170
private void timer1_Tick(object sender, EventArgs e){
72-
Bitmap bmp = spec.GetBitmap(intensity: .4);
71+
Bitmap bmp = sg.GetBitmap(intensity: .4);
7372
pictureBox1.Image?.Dispose();
7473
pictureBox1.Image = bmp;
7574
}
@@ -82,11 +81,15 @@ Review the source code of the demo application for additional details and consid
8281
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).
8382

8483
```cs
85-
(int sampleRate, double[] audio) = WavFile.ReadMono("Handel.wav");
84+
(double[] audio, int sampleRate) = ReadWAV("song.wav");
8685

87-
var spec = new Spectrogram(sampleRate, fftSize: 16384, stepSize: 2500, maxFreq: 2200);
88-
spec.Add(audio);
89-
spec.SaveImage("spectrogram-song.jpg", intensity: 5, dB: true);
86+
int fftSize = 16384;
87+
int targetWidthPx = 3000;
88+
int stepSize = audio.Length / targetWidthPx;
89+
90+
var sg = new SpectrogramGenerator(sampleRate, fftSize, stepSize, maxFreq: 2200);
91+
sg.Add(audio);
92+
sg.SaveImage("song.png", intensity: 5, dB: true);
9093
```
9194

9295
Notice the optional conversion to Decibels while saving the image.
@@ -100,7 +103,7 @@ If you [listen to the audio track](https://www.youtube.com/watch?v=Mza-xqk770k)
100103
The Spectrogram's `ToString()` method displays detailed information about the spectrogram:
101104

102105
```cs
103-
Console.WriteLine(spec);
106+
Console.WriteLine(sg);
104107
```
105108

106109
```
@@ -114,12 +117,11 @@ Spectrogram (2993, 817)
114117
These examples demonstrate the identical spectrogram analyzed with a variety of different colormaps. Spectrogram colormaps can be changed by calling the `SetColormap()` method:
115118

116119
```cs
117-
(int sampleRate, double[] audio) = WavFile.ReadMono("hal.wav");
118-
int fftSize = 8192;
119-
var spec = new Spectrogram(sampleRate, fftSize, stepSize: 200, maxFreq: 3000);
120-
spec.Add(audio);
121-
spec.SetColormap(Colormap.Jet);
122-
spec.SaveImage($"hal-Jet.png", intensity: .5);
120+
(double[] audio, int sampleRate) = ReadWAV("hal.wav");
121+
var sg = new SpectrogramGenerator(sampleRate, fftSize: 8192, stepSize: 200, maxFreq: 3000);
122+
sg.Add(audio);
123+
sg.SetColormap(Colormap.Jet);
124+
sg.SaveImage($"jet.png");
123125
```
124126

125127
Viridis | Greens | Blues | Grayscale | GrayscaleR
@@ -139,16 +141,15 @@ Cropped Linear Scale (0-3kHz) | Mel Scale (0-22 kHz)
139141
Amplitude perception in humans, like frequency perception, is logarithmic. Therefore, Mel spectrograms typically display log-transformed spectral power and are presented using Decibel units.
140142

141143
```cs
142-
// Load "I'm sorry dave, I'm afraid I can't do that" audio
143-
(int sampleRate, double[] audio) = WavFile.ReadMono("hal.wav");
144+
(double[] audio, int sampleRate) = ReadWAV("hal.wav");
145+
var sg = new SpectrogramGenerator(sampleRate, fftSize: 4096, stepSize: 500, maxFreq: 3000);
146+
sg.Add(audio);
144147

145-
// Create a traditional (linear) Spectrogram with dB units
146-
var spec = new Spectrogram(sampleRate, fftSize: 4096, stepSize: 500, maxFreq: 3000);
147-
spec.Add(audio);
148-
spec.SaveImage("hal.png", intensity: 4);
148+
// Create a traditional (linear) Spectrogram
149+
sg.SaveImage("hal.png");
149150

150-
// Create a Mel Spectrogram with dB units
151-
Bitmap bmp = spec.GetBitmapMel(melSizePoints: 250, intensity: 4);
151+
// Create a Mel Spectrogram
152+
Bitmap bmp = sg.GetBitmapMel(melSizePoints: 250);
152153
bmp.Save("halMel.png", ImageFormat.Png);
153154
```
154155

@@ -166,10 +167,9 @@ This example creates a spectrogram but saves it using the SFF file format instea
166167

167168
```cs
168169
(int sampleRate, double[] audio) = WavFile.ReadMono("hal.wav");
169-
int fftSize = 1 << 12;
170-
var spec = new Spectrogram(sampleRate, fftSize, stepSize: 700, maxFreq: 2000);
171-
spec.Add(audio);
172-
spec.SaveData("hal.sff");
170+
var sg = new SpectrogramGenerator(sampleRate, fftSize: 4096, stepSize: 700, maxFreq: 2000);
171+
sg.Add(audio);
172+
sg.SaveData("hal.sff");
173173
```
174174

175175
### Display SFF Files with C#
@@ -203,4 +203,24 @@ plt.show()
203203
* [FftSharp](https://github.com/swharden/FftSharp) - the module which actually performs the FFT and related transformations
204204
* [MP3Sharp](https://github.com/ZaneDubya/MP3Sharp) - a library I use to read MP3 files during testing
205205
* [FSKview](https://github.com/swharden/FSKview) - a real-time spectrogram for viewing frequency-shift-keyed (FSK) signals from audio transmitted over radio frequency.
206-
* [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
206+
* [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
207+
208+
## Read data from a WAV File
209+
210+
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.
211+
212+
```cs
213+
(double[] audio, int sampleRate) ReadWAV(string filePath, double multiplier = 16_000)
214+
{
215+
using var afr = new NAudio.Wave.AudioFileReader(filePath);
216+
int sampleRate = afr.WaveFormat.SampleRate;
217+
int sampleCount = (int)(afr.Length / afr.WaveFormat.BitsPerSample / 8);
218+
int channelCount = afr.WaveFormat.Channels;
219+
var audio = new List<double>(sampleCount);
220+
var buffer = new float[sampleRate * channelCount];
221+
int samplesRead = 0;
222+
while ((samplesRead = afr.Read(buffer, 0, buffer.Length)) > 0)
223+
audio.AddRange(buffer.Take(samplesRead).Select(x => x * multiplier));
224+
return (audio.ToArray(), sampleRate);
225+
}
226+
```

src/Spectrogram.Tests/AudioFile.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
6+
namespace Spectrogram.Tests
7+
{
8+
public static class AudioFile
9+
{
10+
public static (double[] audio, int sampleRate) ReadWAV(string filePath, double multiplier = 16_000)
11+
{
12+
using var afr = new NAudio.Wave.AudioFileReader(filePath);
13+
int sampleRate = afr.WaveFormat.SampleRate;
14+
int sampleCount = (int)(afr.Length / afr.WaveFormat.BitsPerSample / 8);
15+
int channelCount = afr.WaveFormat.Channels;
16+
var audio = new List<double>(sampleCount);
17+
var buffer = new float[sampleRate * channelCount];
18+
int samplesRead = 0;
19+
while ((samplesRead = afr.Read(buffer, 0, buffer.Length)) > 0)
20+
audio.AddRange(buffer.Take(samplesRead).Select(x => x * multiplier));
21+
return (audio.ToArray(), sampleRate);
22+
}
23+
24+
public static double[] ReadMP3(string filePath, int bufferSize = 4096)
25+
{
26+
List<double> audio = new List<double>();
27+
MP3Sharp.MP3Stream stream = new MP3Sharp.MP3Stream(filePath);
28+
byte[] buffer = new byte[bufferSize];
29+
int bytesReturned = 1;
30+
while (bytesReturned > 0)
31+
{
32+
bytesReturned = stream.Read(buffer, 0, bufferSize);
33+
for (int i = 0; i < bytesReturned / 2 - 1; i += 2)
34+
audio.Add(BitConverter.ToInt16(buffer, i * 2));
35+
}
36+
stream.Close();
37+
return audio.ToArray();
38+
}
39+
}
40+
}

src/Spectrogram.Tests/ColormapExamples.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class ColormapExamples
99
[Test]
1010
public void Test_Make_CommonColormaps()
1111
{
12-
(double[] audio, int sampleRate) = WavFile.ReadWavWithNAudio("../../../../../data/cant-do-that-44100.wav");
12+
(double[] audio, int sampleRate) = AudioFile.ReadWAV("../../../../../data/cant-do-that-44100.wav");
1313
int fftSize = 1 << 12;
1414
var spec = new SpectrogramGenerator(sampleRate, fftSize, stepSize: 700, maxFreq: 2000);
1515
spec.SetWindow(FftSharp.Window.Hanning(fftSize / 3)); // sharper window than typical

src/Spectrogram.Tests/FileFormat.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class FileFormat
1111
[Test]
1212
public void Test_SFF_Linear()
1313
{
14-
(double[] audio, int sampleRate) = WavFile.ReadWavWithNAudio("../../../../../data/cant-do-that-44100.wav");
14+
(double[] audio, int sampleRate) = AudioFile.ReadWAV("../../../../../data/cant-do-that-44100.wav");
1515
int fftSize = 1 << 12;
1616
var spec = new SpectrogramGenerator(sampleRate, fftSize, stepSize: 700, maxFreq: 2000);
1717
spec.SetWindow(FftSharp.Window.Hanning(fftSize / 3)); // sharper window than typical
@@ -31,7 +31,7 @@ public void Test_SFF_Linear()
3131
[Test]
3232
public void Test_SFF_Mel()
3333
{
34-
(double[] audio, int sampleRate) = WavFile.ReadWavWithNAudio("../../../../../data/cant-do-that-44100.wav");
34+
(double[] audio, int sampleRate) = AudioFile.ReadWAV("../../../../../data/cant-do-that-44100.wav");
3535
int fftSize = 1 << 12;
3636
var spec = new SpectrogramGenerator(sampleRate, fftSize, stepSize: 700);
3737
spec.SetWindow(FftSharp.Window.Hanning(fftSize / 3)); // sharper window than typical
@@ -57,7 +57,7 @@ public void Test_SFF_Linear2()
5757
// test creating SFF file from 16-bit 48kHz mono WAV file
5858

5959
// read the wav file
60-
(double[] audio, int sampleRate) = WavFile.ReadWavWithNAudio("../../../../../data/03-02-03-01-02-01-19.wav");
60+
(double[] audio, int sampleRate) = AudioFile.ReadWAV("../../../../../data/03-02-03-01-02-01-19.wav");
6161
Assert.AreEqual(48000, sampleRate);
6262

6363
// save the SFF
@@ -83,7 +83,7 @@ public void Test_SFF_LinearBigMaxFreq()
8383
{
8484
// test creating SFF file from 16-bit 48kHz mono WAV file
8585

86-
(double[] audio, int sampleRate) = WavFile.ReadWavWithNAudio("../../../../../data/03-02-03-01-02-01-19.wav");
86+
(double[] audio, int sampleRate) = AudioFile.ReadWAV("../../../../../data/03-02-03-01-02-01-19.wav");
8787
Assert.AreEqual(48000, sampleRate);
8888

8989
int fftSize = 1 << 12;

src/Spectrogram.Tests/Mel.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ class Mel
1212
[Test]
1313
public void Test_MelSpectrogram_MelScale()
1414
{
15-
(double[] audio, int sampleRate) = WavFile.ReadWavWithNAudio("../../../../../data/cant-do-that-44100.wav");
15+
(double[] audio, int sampleRate) = AudioFile.ReadWAV("../../../../../data/cant-do-that-44100.wav");
1616
int fftSize = 4096;
17-
var spec = new SpectrogramGenerator(sampleRate, fftSize, stepSize: 500);
18-
spec.Add(audio);
17+
var sg = new SpectrogramGenerator(sampleRate, fftSize, stepSize: 500);
18+
sg.Add(audio);
1919

20-
Bitmap bmpMel = spec.GetBitmapMel(250);
20+
Bitmap bmpMel = sg.GetBitmapMel(250);
2121
bmpMel.Save("../../../../../dev/graphics/halMel-MelScale.png", ImageFormat.Png);
2222

23-
Bitmap bmpRaw = spec.GetBitmap();
23+
Bitmap bmpRaw = sg.GetBitmap();
2424
Bitmap bmpCropped = new Bitmap(bmpRaw.Width, bmpMel.Height);
2525
using (Graphics gfx = Graphics.FromImage(bmpCropped))
2626
{
@@ -93,7 +93,7 @@ public void Test_Mel_Graph()
9393
[Test]
9494
public void Test_SaveEmpty_Throws()
9595
{
96-
(double[] audio, int sampleRate) = WavFile.ReadWavWithNAudio("../../../../../data/cant-do-that-44100.wav");
96+
(double[] audio, int sampleRate) = AudioFile.ReadWAV("../../../../../data/cant-do-that-44100.wav");
9797
int fftSize = 4096;
9898
var spec = new SpectrogramGenerator(sampleRate, fftSize, stepSize: 500);
9999
//spec.Add(audio);

src/Spectrogram.Tests/Mp3.cs

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)