@@ -73,12 +73,27 @@ public class SpectrogramGenerator
7373 /// <summary>
7474 /// The spectrogram is trimmed to cut-off frequencies above this value.
7575 /// </summary>
76- public double FreqMin { get { return settings . FreqMin ; } }
76+ public double FreqMin { get => Settings . FreqMin ; }
77+
78+ /// <summary>
79+ /// This module contains detailed FFT/Spectrogram settings
80+ /// </summary>
81+ private readonly Settings Settings ;
82+
83+ /// <summary>
84+ /// This is the list of FFTs which is translated to the spectrogram image when it is requested.
85+ /// The length of this list is the spectrogram width.
86+ /// The length of the arrays in this list is the spectrogram height.
87+ /// </summary>
88+ private readonly List < double [ ] > FFTs = new List < double [ ] > ( ) ;
89+
90+ /// <summary>
91+ /// This list contains data values which have not yet been processed.
92+ /// Process() processes all unprocessed data.
93+ /// This list may not be empty after processing if there aren't enough values to fill a full FFT (FftSize).
94+ /// </summary>
95+ private readonly List < double > UnprocessedData ;
7796
78- private readonly Settings settings ;
79- private readonly List < double [ ] > ffts = new List < double [ ] > ( ) ;
80- private readonly List < double > newAudio ;
81- private Colormap cmap = Colormap . Viridis ;
8297 /// <summary>
8398 /// Colormap to use when generating future FFTs.
8499 /// </summary>
@@ -107,30 +122,30 @@ public SpectrogramGenerator(
107122 int offsetHz = 0 ,
108123 List < double > initialAudioList = null )
109124 {
110- settings = new Settings ( sampleRate , fftSize , stepSize , minFreq , maxFreq , offsetHz ) ;
125+ Settings = new Settings ( sampleRate , fftSize , stepSize , minFreq , maxFreq , offsetHz ) ;
111126
112- newAudio = initialAudioList ?? new List < double > ( ) ;
127+ UnprocessedData = initialAudioList ?? new List < double > ( ) ;
113128
114129 if ( fixedWidth . HasValue )
115130 SetFixedWidth ( fixedWidth . Value ) ;
116131 }
117132
118133 public override string ToString ( )
119134 {
120- double processedSamples = ffts . Count * settings . StepSize + settings . FftSize ;
121- double processedSec = processedSamples / settings . SampleRate ;
135+ double processedSamples = FFTs . Count * Settings . StepSize + Settings . FftSize ;
136+ double processedSec = processedSamples / Settings . SampleRate ;
122137 string processedTime = ( processedSec < 60 ) ? $ "{ processedSec : N2} sec" : $ "{ processedSec / 60.0 : N2} min";
123138
124139 return $ "Spectrogram ({ Width } , { Height } )" +
125140 $ "\n Vertical ({ Height } px): " +
126- $ "{ settings . FreqMin : N0} - { settings . FreqMax : N0} Hz, " +
127- $ "FFT size: { settings . FftSize : N0} samples, " +
128- $ "{ settings . HzPerPixel : N2} Hz/px" +
141+ $ "{ Settings . FreqMin : N0} - { Settings . FreqMax : N0} Hz, " +
142+ $ "FFT size: { Settings . FftSize : N0} samples, " +
143+ $ "{ Settings . HzPerPixel : N2} Hz/px" +
129144 $ "\n Horizontal ({ Width } px): " +
130145 $ "{ processedTime } , " +
131- $ "window: { settings . FftLengthSec : N2} sec, " +
132- $ "step: { settings . StepLengthSec : N2} sec, " +
133- $ "overlap: { settings . StepOverlapFrac * 100 : N0} %";
146+ $ "window: { Settings . FftLengthSec : N2} sec, " +
147+ $ "step: { Settings . StepLengthSec : N2} sec, " +
148+ $ "overlap: { Settings . StepOverlapFrac * 100 : N0} %";
134149 }
135150
136151 [ Obsolete ( "Assign to the Colormap field" ) ]
@@ -148,14 +163,14 @@ public void SetColormap(Colormap cmap)
148163 /// </summary>
149164 public void SetWindow ( double [ ] newWindow )
150165 {
151- if ( newWindow . Length > settings . FftSize )
166+ if ( newWindow . Length > Settings . FftSize )
152167 throw new ArgumentException ( "window length cannot exceed FFT size" ) ;
153168
154- for ( int i = 0 ; i < settings . FftSize ; i ++ )
155- settings . Window [ i ] = 0 ;
169+ for ( int i = 0 ; i < Settings . FftSize ; i ++ )
170+ Settings . Window [ i ] = 0 ;
156171
157- int offset = ( settings . FftSize - newWindow . Length ) / 2 ;
158- Array . Copy ( newWindow , 0 , settings . Window , offset , newWindow . Length ) ;
172+ int offset = ( Settings . FftSize - newWindow . Length ) / 2 ;
173+ Array . Copy ( newWindow , 0 , Settings . Window , offset , newWindow . Length ) ;
159174 }
160175
161176 [ Obsolete ( "use the Add() method" , true ) ]
@@ -172,7 +187,7 @@ public void AddScroll(float[] values) { }
172187 /// </summary>
173188 public void Add ( IEnumerable < double > audio , bool process = true )
174189 {
175- newAudio . AddRange ( audio ) ;
190+ UnprocessedData . AddRange ( audio ) ;
176191 if ( process )
177192 Process ( ) ;
178193 }
@@ -207,23 +222,23 @@ public double[][] Process()
207222
208223 Parallel . For ( 0 , newFftCount , newFftIndex =>
209224 {
210- FftSharp . Complex [ ] buffer = new FftSharp . Complex [ settings . FftSize ] ;
211- int sourceIndex = newFftIndex * settings . StepSize ;
212- for ( int i = 0 ; i < settings . FftSize ; i ++ )
213- buffer [ i ] . Real = newAudio [ sourceIndex + i ] * settings . Window [ i ] ;
225+ FftSharp . Complex [ ] buffer = new FftSharp . Complex [ Settings . FftSize ] ;
226+ int sourceIndex = newFftIndex * Settings . StepSize ;
227+ for ( int i = 0 ; i < Settings . FftSize ; i ++ )
228+ buffer [ i ] . Real = UnprocessedData [ sourceIndex + i ] * Settings . Window [ i ] ;
214229
215230 FftSharp . Transform . FFT ( buffer ) ;
216231
217- newFfts [ newFftIndex ] = new double [ settings . Height ] ;
218- for ( int i = 0 ; i < settings . Height ; i ++ )
219- newFfts [ newFftIndex ] [ i ] = buffer [ settings . FftIndex1 + i ] . Magnitude / settings . FftSize ;
232+ newFfts [ newFftIndex ] = new double [ Settings . Height ] ;
233+ for ( int i = 0 ; i < Settings . Height ; i ++ )
234+ newFfts [ newFftIndex ] [ i ] = buffer [ Settings . FftIndex1 + i ] . Magnitude / Settings . FftSize ;
220235 } ) ;
221236
222237 foreach ( var newFft in newFfts )
223- ffts . Add ( newFft ) ;
238+ FFTs . Add ( newFft ) ;
224239 FftsProcessed += newFfts . Length ;
225240
226- newAudio . RemoveRange ( 0 , newFftCount * settings . StepSize ) ;
241+ UnprocessedData . RemoveRange ( 0 , newFftCount * Settings . StepSize ) ;
227242 PadOrTrimForFixedWidth ( ) ;
228243
229244 return newFfts ;
@@ -235,11 +250,11 @@ public double[][] Process()
235250 /// <param name="melBinCount">Total number of output bins to use. Choose a value significantly smaller than Height.</param>
236251 public List < double [ ] > GetMelFFTs ( int melBinCount )
237252 {
238- if ( settings . FreqMin != 0 )
253+ if ( Settings . FreqMin != 0 )
239254 throw new InvalidOperationException ( "cannot get Mel spectrogram unless minimum frequency is 0Hz" ) ;
240255
241256 var fftsMel = new List < double [ ] > ( ) ;
242- foreach ( var fft in ffts )
257+ foreach ( var fft in FFTs )
243258 fftsMel . Add ( FftSharp . Transform . MelScale ( fft , SampleRate , melBinCount ) ) ;
244259
245260 return fftsMel ;
@@ -255,7 +270,7 @@ public List<double[]> GetMelFFTs(int melBinCount)
255270 /// Roll (true) adds new columns on the left overwriting the oldest ones.
256271 /// Scroll (false) slides the whole image to the left and adds new columns to the right.</param>
257272 public Bitmap GetBitmap ( double intensity = 1 , bool dB = false , double dBScale = 1 , bool roll = false ) =>
258- Image . GetBitmap ( ffts , cmap , intensity , dB , dBScale , roll , NextColumnIndex ) ;
273+ Image . GetBitmap ( FFTs , Colormap , intensity , dB , dBScale , roll , NextColumnIndex ) ;
259274
260275 /// <summary>
261276 /// Create a Mel-scaled spectrogram.
@@ -268,7 +283,7 @@ public Bitmap GetBitmap(double intensity = 1, bool dB = false, double dBScale =
268283 /// Roll (true) adds new columns on the left overwriting the oldest ones.
269284 /// Scroll (false) slides the whole image to the left and adds new columns to the right.</param>
270285 public Bitmap GetBitmapMel ( int melBinCount = 25 , double intensity = 1 , bool dB = false , double dBScale = 1 , bool roll = false ) =>
271- Image . GetBitmap ( GetMelFFTs ( melBinCount ) , cmap , intensity , dB , dBScale , roll , NextColumnIndex ) ;
286+ Image . GetBitmap ( GetMelFFTs ( melBinCount ) , Colormap , intensity , dB , dBScale , roll , NextColumnIndex ) ;
272287
273288 [ Obsolete ( "use SaveImage()" , true ) ]
274289 public void SaveBitmap ( Bitmap bmp , string fileName ) { }
@@ -285,7 +300,7 @@ public void SaveBitmap(Bitmap bmp, string fileName) { }
285300 /// Scroll (false) slides the whole image to the left and adds new columns to the right.</param>
286301 public void SaveImage ( string fileName , double intensity = 1 , bool dB = false , double dBScale = 1 , bool roll = false )
287302 {
288- if ( ffts . Count == 0 )
303+ if ( FFTs . Count == 0 )
289304 throw new InvalidOperationException ( "Spectrogram contains no data. Use Add() to add signal data." ) ;
290305
291306 string extension = Path . GetExtension ( fileName ) . ToLower ( ) ;
@@ -302,7 +317,7 @@ public void SaveImage(string fileName, double intensity = 1, bool dB = false, do
302317 else
303318 throw new ArgumentException ( "unknown file extension" ) ;
304319
305- Image . GetBitmap ( ffts , cmap , intensity , dB , dBScale , roll , NextColumnIndex ) . Save ( fileName , fmt ) ;
320+ Image . GetBitmap ( FFTs , Colormap , intensity , dB , dBScale , roll , NextColumnIndex ) . Save ( fileName , fmt ) ;
306321 }
307322
308323 /// <summary>
@@ -317,16 +332,16 @@ public void SaveImage(string fileName, double intensity = 1, bool dB = false, do
317332 public Bitmap GetBitmapMax ( double intensity = 1 , bool dB = false , double dBScale = 1 , bool roll = false , int reduction = 4 )
318333 {
319334 List < double [ ] > ffts2 = new List < double [ ] > ( ) ;
320- for ( int i = 0 ; i < ffts . Count ; i ++ )
335+ for ( int i = 0 ; i < FFTs . Count ; i ++ )
321336 {
322- double [ ] d1 = ffts [ i ] ;
337+ double [ ] d1 = FFTs [ i ] ;
323338 double [ ] d2 = new double [ d1 . Length / reduction ] ;
324339 for ( int j = 0 ; j < d2 . Length ; j ++ )
325340 for ( int k = 0 ; k < reduction ; k ++ )
326341 d2 [ j ] = Math . Max ( d2 [ j ] , d1 [ j * reduction + k ] ) ;
327342 ffts2 . Add ( d2 ) ;
328343 }
329- return Image . GetBitmap ( ffts2 , cmap , intensity , dB , dBScale , roll , NextColumnIndex ) ;
344+ return Image . GetBitmap ( ffts2 , Colormap , intensity , dB , dBScale , roll , NextColumnIndex ) ;
330345 }
331346
332347 /// <summary>
@@ -360,10 +375,10 @@ private void PadOrTrimForFixedWidth()
360375 {
361376 int overhang = Width - fixedWidth ;
362377 if ( overhang > 0 )
363- ffts . RemoveRange ( 0 , overhang ) ;
378+ FFTs . RemoveRange ( 0 , overhang ) ;
364379
365- while ( ffts . Count < fixedWidth )
366- ffts . Insert ( 0 , new double [ Height ] ) ;
380+ while ( FFTs . Count < fixedWidth )
381+ FFTs . Insert ( 0 , new double [ Height ] ) ;
367382 }
368383 }
369384
@@ -376,26 +391,27 @@ private void PadOrTrimForFixedWidth()
376391 /// <param name="reduction">bin size for vertical data reduction</param>
377392 public Bitmap GetVerticalScale ( int width , int offsetHz = 0 , int tickSize = 3 , int reduction = 1 )
378393 {
379- return Scale . Vertical ( width , settings , offsetHz , tickSize , reduction ) ;
394+ return Scale . Vertical ( width , Settings , offsetHz , tickSize , reduction ) ;
380395 }
381396
382397 /// <summary>
383398 /// Return the vertical position (pixel units) for the given frequency
384399 /// </summary>
385400 public int PixelY ( double frequency , int reduction = 1 )
386401 {
387- int pixelsFromZeroHz = ( int ) ( settings . PxPerHz * frequency / reduction ) ;
388- int pixelsFromMinFreq = pixelsFromZeroHz - settings . FftIndex1 / reduction + 1 ;
389- int pixelRow = settings . Height / reduction - 1 - pixelsFromMinFreq ;
402+ int pixelsFromZeroHz = ( int ) ( Settings . PxPerHz * frequency / reduction ) ;
403+ int pixelsFromMinFreq = pixelsFromZeroHz - Settings . FftIndex1 / reduction + 1 ;
404+ int pixelRow = Settings . Height / reduction - 1 - pixelsFromMinFreq ;
390405 return pixelRow - 1 ;
391406 }
392407
393408 /// <summary>
394- /// Return a list of the FFTs in memory underlying the spectrogram
409+ /// Return the list of FFTs in memory underlying the spectrogram.
410+ /// This list may continue to evolve after it is returned.
395411 /// </summary>
396412 public List < double [ ] > GetFFTs ( )
397413 {
398- return ffts ;
414+ return FFTs ;
399415 }
400416
401417 /// <summary>
@@ -404,13 +420,13 @@ public List<double[]> GetFFTs()
404420 /// <param name="latestFft">If true, only the latest FFT will be assessed.</param>
405421 public ( double freqHz , double magRms ) GetPeak ( bool latestFft = true )
406422 {
407- if ( ffts . Count == 0 )
423+ if ( FFTs . Count == 0 )
408424 return ( double . NaN , double . NaN ) ;
409425
410426 if ( latestFft == false )
411427 throw new NotImplementedException ( "peak of mean of all FFTs not yet supported" ) ;
412428
413- double [ ] freqs = ffts [ ffts . Count - 1 ] ;
429+ double [ ] freqs = FFTs [ FFTs . Count - 1 ] ;
414430
415431 int peakIndex = 0 ;
416432 double peakMagnitude = 0 ;
0 commit comments