Skip to content

Commit e47df43

Browse files
committed
Making number of bins per octave adjustable
1 parent d846fcf commit e47df43

File tree

3 files changed

+37
-26
lines changed

3 files changed

+37
-26
lines changed

ColorChord.NET/NoteFinder/Gen2NoteFinder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public Gen2NoteFinder(string name, Dictionary<string, object> config)
7777

7878
SetupBuffers();
7979
this.SampleRate = 48000; // TODO: Temporary until source is connected ahead of time
80-
this.DFT = new(this.OctaveCount, this.SampleRate, this.StartFrequency, this.LoudnessCorrectionAmount, RunTimingReceivers);
80+
this.DFT = new(this.OctaveCount, 24, this.SampleRate, this.StartFrequency, this.LoudnessCorrectionAmount, RunTimingReceivers);
8181
Reconfigure();
8282
}
8383

@@ -106,7 +106,7 @@ public override void AdjustOutputSpeed(uint period)
106106
public override void SetSampleRate(int sampleRate)
107107
{
108108
this.SampleRate = (uint)sampleRate;
109-
this.DFT = new(this.OctaveCount, this.SampleRate, this.StartFrequency, this.LoudnessCorrectionAmount, RunTimingReceivers);
109+
this.DFT = new(this.OctaveCount, 24, this.SampleRate, this.StartFrequency, this.LoudnessCorrectionAmount, RunTimingReceivers);
110110
Reconfigure();
111111
Log.Debug($"There are {TimingReceivers.Length} timing receivers");
112112
for (int i = 0; i < TimingReceivers.Length; i++)

ColorChord.NET/NoteFinder/Gen2NoteFinderDFT.cs

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ public sealed class Gen2NoteFinderDFT
2424
private const uint MAX_WINDOW_SIZE = 6144; // At 48KHz, scaled for other sample rates
2525
private const uint ABSOLUTE_MAX_WINDOW_SIZE = 32768; // Cannot exceed this regardless of sample rate, otherwise the accumulators may overflow
2626
private const uint USHORT_RANGE = ((uint)ushort.MaxValue) + 1;
27-
private const uint BINS_PER_OCTAVE = 24;
2827

2928
private const ushort SINE_TABLE_90_OFFSET = 8;
3029
private const ushort SINE_TABLE_90_OFFSET_SCALED = 8 << 11;
@@ -48,6 +47,9 @@ public sealed class Gen2NoteFinderDFT
4847
/// <summary> The number of octaves to analyze. </summary>
4948
public uint OctaveCount { get; private init; }
5049

50+
/// <summary> How many output bins should be calculated for each octave. </summary>
51+
public uint BinsPerOctave { get; private init; }
52+
5153
/// <summary> How long our sample window is. </summary>
5254
public uint MaxPresentWindowSize { get; private init; } = 8192;
5355

@@ -57,9 +59,6 @@ public sealed class Gen2NoteFinderDFT
5759

5860
public float StartFrequency { get; private init; }
5961

60-
/// <summary> The number of DFT bins per octave. </summary>
61-
public uint BinsPerOctave => BINS_PER_OCTAVE;
62-
6362
/// <summary> The total number of bins over all octaves. </summary>
6463
public readonly ushort BinCount;
6564

@@ -84,7 +83,7 @@ public sealed class Gen2NoteFinderDFT
8483
private readonly ushort[] AudioBufferSubHeads;
8584

8685
/// <summary> How far forward in the sine table this bin should step with every added sample, such that one full sine wave (wrap back to 0) occurs after the number of steps corresponding to the bin frequency. Format is fixed-point 5b+11b. </summary>
87-
/// <remarks> Indexed by [Bin], size is [<see cref="BINS_PER_OCTAVE"/>] </remarks>
86+
/// <remarks> Indexed by [Bin], size is [<see cref="BinsPerOctave"/>] </remarks>
8887
private readonly DualU16[] SinTableStepSize;
8988

9089
/// <summary>
@@ -137,16 +136,24 @@ public sealed class Gen2NoteFinderDFT
137136

138137
private float IIR_CONST = 0.85F; // TODO: Scale dynamically, lower => more older data
139138

140-
public Gen2NoteFinderDFT(uint octaveCount, uint sampleRate, float startFrequency, float loudnessCorrection, TimingReceiverCallback? timingCallback) // TODO: Why are some properties while others not
139+
/// <summary> Instantiates a Gen2DFT with the desired settings. </summary>
140+
/// <param name="octaveCount"> The number of octaves to analyze </param>
141+
/// <param name="binsPerOctave"> The number of output bins to calculate for each octave, must be a multiple of 4 </param>
142+
/// <param name="sampleRate"> The sample rate of the input audio signal </param>
143+
/// <param name="startFrequency"> The desired frequency of the lowest bin </param>
144+
/// <param name="loudnessCorrection"> The amount of human ear-modelled loudness correction to apply to the output bin values, between 0.0 and 1.0 </param>
145+
/// <param name="timingCallback"> A callback that will be dispatched at defined regular intervals during audio processing </param>
146+
public Gen2NoteFinderDFT(uint octaveCount, uint binsPerOctave, uint sampleRate, float startFrequency, float loudnessCorrection, TimingReceiverCallback? timingCallback) // TODO: Why are some properties while others not
141147
{
142148
this.OctaveCount = octaveCount;
149+
this.BinsPerOctave = binsPerOctave;
143150
this.SampleRate = sampleRate;
144151
this.StartFrequency = startFrequency;
145152
this.LoudnessCorrectionAmount = loudnessCorrection;
146153
this.TimingCallback = timingCallback;
147154

148-
this.BinCount = (ushort)(OctaveCount * BINS_PER_OCTAVE);
149-
this.StartOfTopOctave = (ushort)((OctaveCount - 1) * BINS_PER_OCTAVE);
155+
this.BinCount = (ushort)(OctaveCount * BinsPerOctave);
156+
this.StartOfTopOctave = (ushort)((OctaveCount - 1) * BinsPerOctave);
150157
this.StartFrequencyOfTopOctave = StartFrequency * MathF.Pow(2, OctaveCount - 1);
151158

152159
AudioBufferSizes = new uint[BinCount];
@@ -172,9 +179,9 @@ public sealed class Gen2NoteFinderDFT
172179
{
173180
float BinFrequency;
174181
uint ThisBufferSize;
175-
float ThisOctaveStart = StartFrequency * MathF.Pow(2, Bin / BINS_PER_OCTAVE);
176-
BinFrequency = CalculateNoteFrequency(StartFrequency, BINS_PER_OCTAVE, Bin);
177-
float NextBinFrequency = CalculateNoteFrequency(StartFrequency, BINS_PER_OCTAVE, Bin + 2);
182+
float ThisOctaveStart = StartFrequency * MathF.Pow(2, Bin / BinsPerOctave);
183+
BinFrequency = CalculateNoteFrequency(StartFrequency, BinsPerOctave, Bin);
184+
float NextBinFrequency = CalculateNoteFrequency(StartFrequency, BinsPerOctave, Bin + 2);
178185
//float IdealWindowSize = WindowSizeForBinWidth(TopOctaveNextBinFreq - TopOctaveBinFreq); // TODO: Add scale factor to shift this from no overlap to -3dB point
179186
ThisBufferSize = RoundedWindowSizeForBinWidth(NextBinFrequency - BinFrequency, BinFrequency, SampleRate);
180187

@@ -185,8 +192,8 @@ public sealed class Gen2NoteFinderDFT
185192
MaxAudioBufferSize = Math.Max(MaxAudioBufferSize, ThisBufferSize);
186193

187194
float NCOffset = SampleRate / (AudioBufferSizes[Bin] * 2F);
188-
float StepSizeNCL = USHORT_RANGE * (CalculateNoteFrequency(StartFrequency, BINS_PER_OCTAVE, Bin) - NCOffset) / SampleRate;
189-
float StepSizeNCR = USHORT_RANGE * (CalculateNoteFrequency(StartFrequency, BINS_PER_OCTAVE, Bin) + NCOffset) / SampleRate;
195+
float StepSizeNCL = USHORT_RANGE * (CalculateNoteFrequency(StartFrequency, BinsPerOctave, Bin) - NCOffset) / SampleRate;
196+
float StepSizeNCR = USHORT_RANGE * (CalculateNoteFrequency(StartFrequency, BinsPerOctave, Bin) + NCOffset) / SampleRate;
190197
SinTableStepSize[Bin].NCLeft = (ushort)Math.Round(StepSizeNCL);
191198
SinTableStepSize[Bin].NCRight = (ushort)Math.Round(StepSizeNCR);
192199

@@ -214,10 +221,10 @@ public sealed class Gen2NoteFinderDFT
214221
for (int Octave = 0; Octave < OctaveCount; Octave++)
215222
{
216223
StringBuilder OctaveOutput = new();
217-
OctaveOutput.Append($"{RawBinFrequencies[Octave * BINS_PER_OCTAVE]:F1}~{RawBinFrequencies[((Octave + 1) * BINS_PER_OCTAVE) - 1]:F1}Hz: ");
218-
for (uint Bin = 0; Bin < BINS_PER_OCTAVE; Bin++)
224+
OctaveOutput.Append($"{RawBinFrequencies[Octave * BinsPerOctave]:F1}~{RawBinFrequencies[((Octave + 1) * BinsPerOctave) - 1]:F1}Hz: ");
225+
for (uint Bin = 0; Bin < BinsPerOctave; Bin++)
219226
{
220-
OctaveOutput.Append(AudioBufferSizes[(Octave * BINS_PER_OCTAVE) + Bin]);
227+
OctaveOutput.Append(AudioBufferSizes[(Octave * BinsPerOctave) + Bin]);
221228
OctaveOutput.Append(',');
222229
}
223230
Log.Debug(OctaveOutput.ToString());
@@ -567,7 +574,7 @@ public void CalculateOutput()
567574
lock (this.MergedDataLockObj)
568575
{
569576
if (this.MergedDataAmplitude == 0) { return; } // This may have (and actually often does) happen if multiple outputs are present and operating on different threads.
570-
for (int Bin = 0; Bin < BINS_PER_OCTAVE; Bin++) { this.OctaveBinValues[Bin] = 0; }
577+
for (int Bin = 0; Bin < BinsPerOctave; Bin++) { this.OctaveBinValues[Bin] = 0; }
571578

572579
int BinIndex = 0;
573580
if (Avx2.IsSupported && ENABLE_SIMD)
@@ -619,7 +626,7 @@ public void CalculateOutput()
619626
for (int Bin = 0; Bin < BinCount; Bin++)
620627
{
621628
float OutBinVal = this.RawBinMagnitudes[Bin] * this.LoudnessCorrectionFactors[Bin];
622-
this.OctaveBinValues[Bin % BINS_PER_OCTAVE] += OutBinVal / this.OctaveCount;
629+
this.OctaveBinValues[Bin % BinsPerOctave] += OutBinVal / this.OctaveCount;
623630
this.AllBinValues[Bin + 1] = MathF.Max(0, OutBinVal - 0.08F);
624631
}
625632
}

Gen2DFTLib/Gen2DFT.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,28 @@
55
namespace Gen2DFTLib;
66

77
/// <summary> Provides the core DFT algorithm used in the Gen2NoteFinder in ColorChord.NET for use in other software. </summary>
8-
/// <remarks> Make sure to initialize with <see cref="Init(uint, uint, float, float)"/> before attempting to use. </remarks>
8+
/// <remarks> Make sure to initialize with <see cref="Init(uint, uint, uint, float, float)"/> before attempting to use. </remarks>
99
public static unsafe class Gen2DFT
1010
{
1111
private static Gen2NoteFinderDFT DFT;
1212
private static GCHandle BinMagnitudesHandle, BinFrequenciesHandle, BinWidthsHandle;
1313

1414
/// <summary> Prepares the DFT for use, initializing internal data structures. </summary>
1515
/// <param name="octaveCount"> The number of octaves to analyze </param>
16+
/// <param name="binsPerOctave"> The number of output bins to calculate for each octave. Must be a multiple of 4 </param>
1617
/// <param name="sampleRate"> The sample rate of the input audio </param>
1718
/// <param name="startFrequency"> The desired frequency of the lowest bin </param>
1819
/// <param name="loudnessCorrection"> The amount of human-modelled loudness equalization to apply to the output bins </param>
20+
/// <returns> 0 for success, negative value for failure </returns>
1921
[UnmanagedCallersOnly(EntryPoint = "Gen2DFT_Init")]
20-
public static void Init(uint octaveCount, uint sampleRate, float startFrequency, float loudnessCorrection)
22+
public static int Init(uint octaveCount, uint binsPerOctave, uint sampleRate, float startFrequency, float loudnessCorrection)
2123
{
22-
DFT = new(octaveCount, sampleRate, startFrequency, loudnessCorrection, null);
24+
if (binsPerOctave % 4 != 0) { return -1; }
25+
DFT = new(octaveCount, binsPerOctave, sampleRate, startFrequency, loudnessCorrection, null);
2326
BinMagnitudesHandle = GCHandle.Alloc(DFT.RawBinMagnitudes, GCHandleType.Pinned);
2427
BinFrequenciesHandle = GCHandle.Alloc(DFT.RawBinFrequencies, GCHandleType.Pinned);
2528
BinWidthsHandle = GCHandle.Alloc(DFT.RawBinWidths, GCHandleType.Pinned);
29+
return 0;
2630
}
2731

2832
/// <summary> Processes the given audio data into the DFT. </summary>
@@ -49,18 +53,18 @@ public static void AddAudioData(short* newData, uint count)
4953
public static uint GetBinCount() => DFT.BinCount;
5054

5155
/// <summary> Gets the current DFT output data. Make sure to call <see cref="CalculateOutput"/> before this. </summary>
52-
/// <returns> The raw magnitudes of each DFT bin, array length is <see cref="GetBinCount"/>. This pointer will not change during runtime unless <see cref="Init(uint, uint, float, float)"/> is called again, so it is safe to reuse </returns>
56+
/// <returns> The raw magnitudes of each DFT bin, array length is <see cref="GetBinCount"/>. This pointer will not change during runtime unless <see cref="Init(uint, uint, uint, float, float)"/> is called again, so it is safe to reuse </returns>
5357
[UnmanagedCallersOnly(EntryPoint = "Gen2DFT_GetBinMagnitudes")]
5458
public static float* GetBinMagnitudes() => (float*)BinMagnitudesHandle.AddrOfPinnedObject();
5559

5660
/// <summary> Gets the central response frequencies of each DFT bin. </summary>
57-
/// <returns> The frequency of each DFT bin in Hz, array length is <see cref="GetBinCount"/>. This pointer will not change during runtime unless <see cref="Init(uint, uint, float, float)"/> is called again, so it is safe to reuse </returns>
61+
/// <returns> The frequency of each DFT bin in Hz, array length is <see cref="GetBinCount"/>. This pointer will not change during runtime unless <see cref="Init(uint, uint, uint, float, float)"/> is called again, so it is safe to reuse </returns>
5862
[UnmanagedCallersOnly(EntryPoint = "Gen2DFT_GetBinFrequencies")]
5963
public static float* GetBinFrequencies() => (float*)BinFrequenciesHandle.AddrOfPinnedObject();
6064

6165
/// <summary> Gets the sensitivity range width of each bin, in number of bins. </summary>
6266
/// <remarks> A value of 2 would mean that this bin stops responding to frequencies around the center of the bin above and below (this bin's range = 1, plus 0.5 bins on either side) </remarks>
63-
/// <returns> The width of each bin in number of bins, array length is <see cref="GetBinCount"/>. This pointer will not change during runtime unless <see cref="Init(uint, uint, float, float)"/> is called again, so it is safe to reuse </returns>
67+
/// <returns> The width of each bin in number of bins, array length is <see cref="GetBinCount"/>. This pointer will not change during runtime unless <see cref="Init(uint, uint, uint, float, float)"/> is called again, so it is safe to reuse </returns>
6468
[UnmanagedCallersOnly(EntryPoint = "Gen2DFT_GetBinWidths")]
6569
public static float* GetBinWidths() => (float*)BinWidthsHandle.AddrOfPinnedObject();
6670
}

0 commit comments

Comments
 (0)