Skip to content

Commit bfc9082

Browse files
committed
Gen2NF now auto-scales bins to adapt to various volume levels. Gen2DFT had some cleanup and magic number formulas replaced by actual math I derived for the paper. The debug view scales bar intensity by value, which makes for nicer visualization.
1 parent 1be52b7 commit bfc9082

File tree

6 files changed

+78
-37
lines changed

6 files changed

+78
-37
lines changed

ColorChord.NET/NoteFinder/Gen2NoteFinder.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using ColorChord.NET.API.NoteFinder;
44
using ColorChord.NET.API.Sources;
55
using ColorChord.NET.Config;
6-
using ColorChord.NET.Outputs;
76
using System;
87
using System.Collections.Generic;
98
using System.Diagnostics;
@@ -46,6 +45,10 @@ public sealed class Gen2NoteFinder : NoteFinderCommon, ITimingSource
4645
/// <summary> The frequencies (in Hz) of each of the raw bins in <see cref="AllBinValues"/>. </summary>
4746
public ReadOnlySpan<float> BinFrequencies => this.DFT.RawBinFrequencies;
4847

48+
private float AllBinMax = 0.01F, AllBinMaxSmoothed = 0.01F;
49+
50+
public float[] AllBinValuesScaled;
51+
4952
public byte[] PeakBits, WidebandBits;
5053
private byte[] AllowedBinWidths;
5154

@@ -87,6 +90,7 @@ private void Reconfigure()
8790
AllowedBinWidths = new byte[BinCount];
8891
PreviousBinValues = new float[BinCount];
8992
RecentBinChanges = new float[BinCount];
93+
AllBinValuesScaled = new float[BinCount];
9094

9195
for (int i = 0; i < AllowedBinWidths.Length; i++) { AllowedBinWidths[i] = (byte)MathF.Max(4F, 1.5F * MathF.Ceiling(this.DFT.RawBinWidths[i])); }
9296
}
@@ -166,6 +170,7 @@ public override void UpdateOutputs()
166170
for (int i = 0; i < WidebandBitsPacked.Length; i++) { WidebandBitsPacked[i] = 0; }
167171

168172
Debug.Assert(((RawBinValuesPadded.Length - 2) % 8) == 0, "Bin count must be a multiple of 8"); // TODO: Add non-SIMD version to avoid this
173+
Vector256<float> BinMaxIntermediate = Vector256<float>.Zero;
169174
for (int Step = 0; Step <= RawBinValuesPadded.Length - 10; Step += 8)
170175
{
171176
Vector256<float> LeftValues = Vector256.LoadUnsafe(ref RawBinValuesPadded[Step]); // TODO: Is there a better way to do this?
@@ -191,6 +196,18 @@ public override void UpdateOutputs()
191196
Vector256<float> NewRecentChange = Avx.Add(Avx.Multiply(Vector256.Create(RECENT_BIN_CHANGE_IIR), OldRecentChange), Avx.Multiply(Vector256.Create(RecentBinChangeIIRb), AbsDiffFromPrev));
192197
NewRecentChange.StoreUnsafe(ref RecentBinChanges[Step]);
193198
MiddleValues.StoreUnsafe(ref PreviousBinValues[Step]);
199+
BinMaxIntermediate = Avx.Max(BinMaxIntermediate, MiddleValues);
200+
}
201+
Vector128<float> BinMaxCondense1 = Sse.Max(BinMaxIntermediate.GetLower(), BinMaxIntermediate.GetUpper());
202+
float NewAllBinMax = MathF.Max(MathF.Max(BinMaxCondense1[0], BinMaxCondense1[1]), MathF.Max(BinMaxCondense1[2], BinMaxCondense1[3]));
203+
this.AllBinMaxSmoothed = this.AllBinMax < NewAllBinMax ? (this.AllBinMax * 0.8F) + (NewAllBinMax * 0.2F) : (this.AllBinMax * 0.995F) + (NewAllBinMax * 0.005F);
204+
this.AllBinMax = Math.Max(0.01F, AllBinMaxSmoothed);
205+
206+
for (int Step = 0; Step < AllBinValuesScaled.Length; Step += 8)
207+
{
208+
Vector256<float> MiddleValues = Vector256.LoadUnsafe(ref RawBinValuesPadded[Step + 1]);
209+
Vector256<float> Scaled = Avx.Divide(MiddleValues, Vector256.Create(this.AllBinMaxSmoothed * 2.2F));
210+
Scaled.StoreUnsafe(ref AllBinValuesScaled[Step]);
194211
}
195212

196213
for (int Outer = 0; Outer < PeakBitsPacked.Length; Outer++)

ColorChord.NET/NoteFinder/Gen2NoteFinderDFT.cs

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ namespace ColorChord.NET.NoteFinder;
1717
/// subscribed to periodic events. Then, when a visualizer or other output mechanism wants to use the data, it will call <see cref="Gen2NoteFinder.UpdateOutputs"/>
1818
/// which in turn will call <see cref="CalculateOutput"/>. This will merge together all of the data that has been received since the last output cycle,
1919
/// apply loudness correction, and format it into <see cref="AllBinValues"/> and <see cref="OctaveBinValues"/> for feature extraction in <see cref="Gen2NoteFinder.UpdateOutputs"/>.
20-
internal sealed class Gen2NoteFinderDFT
20+
public sealed class Gen2NoteFinderDFT
2121
{
2222
private const bool ENABLE_SIMD = true;
2323
private const int MIN_WINDOW_SIZE = 16; // At 48KHz, scaled for other sample rates
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
26-
private const uint USHORT_RANGE = ushort.MaxValue + 1;
26+
private const uint USHORT_RANGE = ((uint)ushort.MaxValue) + 1;
2727
private const uint BINS_PER_OCTAVE = 24;
2828

2929
private const ushort SINE_TABLE_90_OFFSET = 8;
@@ -127,7 +127,7 @@ internal sealed class Gen2NoteFinderDFT
127127

128128
/// <summary> The frequency in Hz of each raw bin. </summary>
129129
/// <remarks> This isn't used in the main algorithm, only for pre-calculation of some data, and to present to the user. </remarks>
130-
internal float[] RawBinFrequencies { get; init; }
130+
public float[] RawBinFrequencies { get; init; }
131131

132132
/// <summary> The range, in number of bins, of each bin's sensitivity to input signals. </summary>
133133
/// <remarks> A bin width of 2 would imply that this bin stops detecting anything in approximately the middle of the directly adjacent bin in both directions (a range of this bin's middle +/- 1.0 bins). </remarks>
@@ -172,22 +172,11 @@ internal sealed class Gen2NoteFinderDFT
172172
{
173173
float BinFrequency;
174174
uint ThisBufferSize;
175-
//if (Bin == 0)
176-
//{
177-
//BinFrequency = 1.5F + (2F * ((float)Bin / BINS_PER_OCTAVE));
178-
//ThisBufferSize = (uint)MathF.Round(WindowSizeForBinWidth(0.5F, SampleRate));
179-
//ThisBufferSize = Math.Min(ABSOLUTE_MAX_WINDOW_SIZE, ThisBufferSize);
180-
// BinFrequency = 75F;
181-
// ThisBufferSize = RoundedWindowSizeForBinWidth(110F, BinFrequency, SampleRate);
182-
//}
183-
//else
184-
{
185-
float ThisOctaveStart = StartFrequency * MathF.Pow(2, Bin / BINS_PER_OCTAVE);
186-
BinFrequency = CalculateNoteFrequency(StartFrequency, BINS_PER_OCTAVE, Bin);
187-
float NextBinFrequency = CalculateNoteFrequency(StartFrequency, BINS_PER_OCTAVE, Bin + 2);
188-
//float IdealWindowSize = WindowSizeForBinWidth(TopOctaveNextBinFreq - TopOctaveBinFreq); // TODO: Add scale factor to shift this from no overlap to -3dB point
189-
ThisBufferSize = RoundedWindowSizeForBinWidth(NextBinFrequency - BinFrequency, BinFrequency, SampleRate);
190-
}
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);
178+
//float IdealWindowSize = WindowSizeForBinWidth(TopOctaveNextBinFreq - TopOctaveBinFreq); // TODO: Add scale factor to shift this from no overlap to -3dB point
179+
ThisBufferSize = RoundedWindowSizeForBinWidth(NextBinFrequency - BinFrequency, BinFrequency, SampleRate);
191180

192181
RawBinWidths[Bin] = MathF.Log2((BinFrequency + BinWidthAtWindowSize(ThisBufferSize, SampleRate)) / BinFrequency) * BinsPerOctave;
193182

@@ -201,16 +190,8 @@ internal sealed class Gen2NoteFinderDFT
201190
SinTableStepSize[Bin].NCLeft = (ushort)Math.Round(StepSizeNCL);
202191
SinTableStepSize[Bin].NCRight = (ushort)Math.Round(StepSizeNCR);
203192

204-
//if (Bin > 0)
205-
{
206-
LoudnessCorrectionFactors[Bin] = GetLoudnessCorrection(BinFrequency, LoudnessCorrectionAmount);
207-
SmoothingFactors[Bin] = MathF.Min(MathF.Max(0.1F, IIR_CONST - ((6000 - (int)ThisBufferSize) * 0.00010F)), IIR_CONST);
208-
}
209-
//else
210-
//{
211-
// SmoothingFactors[Bin] = 1F;
212-
// LoudnessCorrectionFactors[Bin] = 0.5F;
213-
//}
193+
LoudnessCorrectionFactors[Bin] = LoudnessCorrectionAmount == 0F ? 1F : GetLoudnessCorrection(BinFrequency, LoudnessCorrectionAmount);
194+
SmoothingFactors[Bin] = MathF.Min(MathF.Max(0.1F, IIR_CONST - ((6000 - (int)ThisBufferSize) * 0.00008F)), IIR_CONST);
214195
}
215196
MaxPresentWindowSize = MaxAudioBufferSize;
216197

@@ -247,6 +228,12 @@ internal sealed class Gen2NoteFinderDFT
247228
if (!ENABLE_SIMD) { Log.Warn("SIMD has been compile-time disabled in this build. Expect significantly decreased efficiency."); }
248229
#pragma warning restore CS0162 // Unreachable code detected
249230
if (!Avx2.IsSupported) { Log.Warn("Your CPU does not appear to support AVX2, this may lead to poor performance."); }
231+
#else
232+
//Console.WriteLine("SSE? " + Sse.IsSupported);
233+
//Console.WriteLine("SSE2? " + Sse2.IsSupported);
234+
//Console.WriteLine("SSE4.2? " + Sse42.IsSupported);
235+
//Console.WriteLine("AVX? " + Avx.IsSupported);
236+
//Console.WriteLine("AVX2? " + Avx2.IsSupported);
250237
#endif
251238
}
252239

@@ -637,6 +624,23 @@ public void CalculateOutput()
637624
}
638625
}
639626

627+
/// <summary> Clears all data structures, resetting back to a state equivalent to not having received any data yet. </summary>
628+
public void Clear()
629+
{
630+
for (int i = 0; i < AudioBuffer.Length; i++) { AudioBuffer[i] = 0; }
631+
for (int i = 0; i < BinCount; i++)
632+
{
633+
SinProductAccumulators[i] = new();
634+
CosProductAccumulators[i] = new();
635+
ProductAccumulators[i] = Vector256<long>.Zero;
636+
MergedSinOutputs[i] = 0;
637+
MergedCosOutputs[i] = 0;
638+
AllBinValues[i + 1] = 0;
639+
RawBinMagnitudes[i] = 0;
640+
}
641+
for (int i = 0; i < BinsPerOctave; i++) { OctaveBinValues[i] = 0; }
642+
}
643+
640644
/// <summary> Calculates the sine value of 2 positions </summary>
641645
/// <remarks> Does not require any SIMD support, however is significantly slower than <see cref="GetSine256(Vector256{ushort})"/> </remarks>
642646
/// <param name="sineTablePosition"> The wave positions, where one full sweep through possible values gives 1 full wavelength </param>
@@ -710,10 +714,8 @@ public struct DualI64
710714
public override readonly string ToString() => $"2xI32 L={this.NCLeft}, R={this.NCRight}";
711715
}
712716

713-
// Everyone loves magic numbers :)
714-
// These were determined through simulations and regressions, which can be found in the Simulations folder in the root of the ColorChord.NET repository. See Simulations/WindowSizeVsBinWidthWithSampleRate.m.
715-
private static float BinWidthAtWindowSize(float windowSize, float sampleRate) => ((sampleRate * 1.0414032F) + 592.49176F) / (windowSize + ((sampleRate * 0.00022935479F) + 1.5730453F));
716-
private static float WindowSizeForBinWidth(float binWidth, float sampleRate) => (((sampleRate * 1.0414032F) + 592.49176F) / binWidth) - ((sampleRate * 0.00022935479F) + 1.5730453F);
717+
private static float BinWidthAtWindowSize(float windowSize, float sampleRate) => sampleRate / windowSize;
718+
private static float WindowSizeForBinWidth(float binWidth, float sampleRate) => sampleRate / binWidth;
717719

718720
private static uint RoundedWindowSizeForBinWidth(float binWidth, float frequency, float sampleRate)
719721
{

ColorChord.NET/Outputs/Display/Shaders/ShinNFDebug.frag

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ void main()
3636
float ChangeHere = texture(TextureRawBins, vec2((SectionHere + 0.5) / BinCount, 0.75)).r * 10.0;
3737
uint PeakHere = (texture(TexturePeakBits, vec2(((SectionHere / 8) + 0.5) / textureSize(TexturePeakBits, 0).x, 0.5)).r >> (SectionHere % 8)) & 1u;
3838
uint WidebandHere = (texture(TextureWidebandBits, vec2(((SectionHere / 8) + 0.5) / textureSize(TextureWidebandBits, 0).x, 0.5)).r >> (SectionHere % 8)) & 1u;
39-
vec3 Colour = AngleToRGB(mod(float(SectionHere) / BINS_PER_OCATVE, 1.0), 1.0 - (0.6 * WidebandHere), 1.0 - (0.8 * WidebandHere));
39+
vec3 Colour = AngleToRGB(mod(float(SectionHere) / BINS_PER_OCATVE, 1.0), 1.0 - (0.8 * WidebandHere), min(1.0, 0.1 + (HeightHere * 3.0)) - (0.9 * WidebandHere));
4040
float IsBar = step(abs(TexCoord.y), HeightHere);
4141
//float IsBar = (step(0.0, TexCoord.y) * step(TexCoord.y, HeightHere)) + ((step(0.0, -TexCoord.y) * step(-TexCoord.y, ChangeHere)));
42-
FragColor = vec4((IsBar * Colour) + ((1.0 - IsBar) * vec3(0.2) * PeakHere * Colour), 1.0);
42+
FragColor = vec4((IsBar * Colour) + ((1.0 - IsBar) * vec3(0.1) * PeakHere * Colour), 1.0);
4343
//FragColor = vec4(vec3(0.8), 1.0);
4444
}

ColorChord.NET/Outputs/Display/ShinNFDebug.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,17 @@ public void Render()
120120
this.Shader!.Use();
121121

122122
if (this.RawDataIn.Length != this.NoteFinder.AllBinValues.Length * 2) { this.RawDataIn = new float[this.NoteFinder.AllBinValues.Length * 2]; }
123-
this.NoteFinder.AllBinValues.CopyTo(this.RawDataIn);
124123
if (this.G2NoteFinder != null)
125124
{
125+
this.G2NoteFinder.AllBinValuesScaled.CopyTo(this.RawDataIn.AsSpan());
126126
this.G2NoteFinder.RecentBinChanges.CopyTo(this.RawDataIn, this.NoteFinder.AllBinValues.Length);
127127
GL.ActiveTexture(TextureUnit.Texture1);
128128
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.R8ui, this.NoteFinder.AllBinValues.Length / 8, 1, 0, PixelFormat.RedInteger, PixelType.UnsignedByte, this.G2NoteFinder.PeakBits);
129129
GL.ActiveTexture(TextureUnit.Texture2);
130130
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.R8ui, this.NoteFinder.AllBinValues.Length / 8, 1, 0, PixelFormat.RedInteger, PixelType.UnsignedByte, this.G2NoteFinder.WidebandBits);
131131
}
132+
else { this.NoteFinder.AllBinValues.CopyTo(this.RawDataIn); }
133+
132134
GL.ActiveTexture(TextureUnit.Texture0);
133135
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.R32f, this.NoteFinder.AllBinValues.Length, 2, 0, PixelFormat.Red, PixelType.Float, this.RawDataIn);
134136
GL.BindVertexArray(this.VertexArrayHandle);

Gen2DFTLib/Gen2DFTLib.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
<PublishAot>true</PublishAot>
99
<DefineConstants>STANDALONE_DFT_LIB</DefineConstants>
1010
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
11+
<IlcInstructionSet>native</IlcInstructionSet>
12+
<IlcDisableReflection>true</IlcDisableReflection>
1113
</PropertyGroup>
1214

1315
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
https://go.microsoft.com/fwlink/?LinkID=208121.
4+
-->
5+
<Project>
6+
<PropertyGroup>
7+
<Configuration>Release</Configuration>
8+
<Platform>x64</Platform>
9+
<PublishDir>bin\AOT\</PublishDir>
10+
<PublishProtocol>FileSystem</PublishProtocol>
11+
<_TargetId>Folder</_TargetId>
12+
<TargetFramework>net8.0</TargetFramework>
13+
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
14+
<SelfContained>true</SelfContained>
15+
<PublishSingleFile>false</PublishSingleFile>
16+
<PublishReadyToRun>false</PublishReadyToRun>
17+
</PropertyGroup>
18+
</Project>

0 commit comments

Comments
 (0)