Skip to content

Commit

Permalink
Re-using buffers instead of allocating for every batch of audio data.…
Browse files Browse the repository at this point in the history
… Vectorized part of WASAPI. Working on making short default everywhere.
  • Loading branch information
CaiB committed Feb 20, 2024
1 parent 762c9cd commit 844f248
Show file tree
Hide file tree
Showing 8 changed files with 544 additions and 240 deletions.
2 changes: 1 addition & 1 deletion ColorChord.NET-API/ColorChordAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ public static class ColorChordAPI
{
/// <summary>Use this version as return value for <see cref="Extensions.IExtension.APIVersion"/> in your extension.</summary>
/// <remarks>This allows ColorChord.NET to know if the installed version of your extension is compatible.</remarks>
public const uint APIVersion = 13;
public const uint APIVersion = 14;
}
93 changes: 92 additions & 1 deletion ColorChord.NET-API/NoteFinder/NoteFinderCommon.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using ColorChord.NET.API.Config;
using System;
using System.Diagnostics;

namespace ColorChord.NET.API.NoteFinder;

Expand Down Expand Up @@ -43,6 +43,97 @@ public abstract class NoteFinderCommon : IConfigurableAttr
public abstract void AdjustOutputSpeed(uint period);

public abstract void SetSampleRate(int sampleRate);


private const int INTERMEDIATE_BUFFER_COUNT = 4;
private static readonly IntermediateBuffer[] IntermediateBuffers = new IntermediateBuffer[INTERMEDIATE_BUFFER_COUNT];
private static readonly int[] IntermediateBuffersToRead = new int[INTERMEDIATE_BUFFER_COUNT];

public static void SetupBuffers()
{
for (int i = 0; i < INTERMEDIATE_BUFFER_COUNT; i++)
{
IntermediateBuffers[i] = new();
IntermediateBuffersToRead[i] = -1;
}
}

public static short[]? GetBufferToWrite(out int bufferRef)
{
for (int i = 0; i < INTERMEDIATE_BUFFER_COUNT; i++)
{
if (!IntermediateBuffers[i].ReadMode)
{
bufferRef = i;
return IntermediateBuffers[i].Buffer;
}
}
Log.Warn("NoteFinder is unable to keep up with incoming audio, some data will be dropped.");
bufferRef = -1;
return null;
}

public static short[]? GetBufferToRead(out int bufferRef, out uint amountToRead, out bool moreAvailable)
{
lock (IntermediateBuffersToRead)
{
if (IntermediateBuffersToRead[0] == -1 || !IntermediateBuffers[IntermediateBuffersToRead[0]].ReadMode)
{
bufferRef = -1;
amountToRead = 0;
moreAvailable = false;
return null;
}
else
{
bufferRef = IntermediateBuffersToRead[0];
amountToRead = IntermediateBuffers[bufferRef].DataCount;
moreAvailable = IntermediateBuffersToRead[1] != -1 && IntermediateBuffers[IntermediateBuffersToRead[1]].ReadMode;
for (int i = 0; i < INTERMEDIATE_BUFFER_COUNT - 1; i++) { IntermediateBuffersToRead[i] = IntermediateBuffersToRead[i + 1]; } // Shift everything left 1
IntermediateBuffersToRead[INTERMEDIATE_BUFFER_COUNT - 1] = -1;
return IntermediateBuffers[bufferRef].Buffer;
}
}
}

public static void FinishBufferRead(int bufferRef)
{
IntermediateBuffers[bufferRef].ReadMode = false;
}

public static void FinishBufferWrite(int bufferRef, uint amountWritten)
{
IntermediateBuffers[bufferRef].DataCount = amountWritten;
IntermediateBuffers[bufferRef].ReadMode = true;
lock (IntermediateBuffersToRead)
{
for (int i = 0; i < INTERMEDIATE_BUFFER_COUNT; i++)
{
if (IntermediateBuffersToRead[i] == -1)
{
IntermediateBuffersToRead[i] = bufferRef;
return;
}
}
Debug.Fail("Have buffer but nowhere to put it???");
Log.Error("NoteFinder buffer system encountered something that should be impossible.");
}
}
}

internal struct IntermediateBuffer
{
private const int BUFFER_SIZE = 4096; // Even at 20ms 192KHz, audio data is 3840 elements long
public short[] Buffer;
public uint DataCount;
public bool ReadMode;

public IntermediateBuffer()
{
this.Buffer = new short[BUFFER_SIZE];
this.DataCount = 0;
this.ReadMode = false;
}
}

/// <summary> A note after filtering, stabilization and denoising of the raw DFT output. </summary>
Expand Down
2 changes: 1 addition & 1 deletion ColorChord.NET/ColorChord.NET.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
<ProjectReference Include="..\ColorChord.NET-API\ColorChord.NET-API.csproj" />
</ItemGroup>
<PropertyGroup>

<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<Target Name="CopyOver" AfterTargets="Build">
<Exec Command="XCOPY /f /y $(ProjectDir)..\Libraries\CNFA.dll $(TargetDir)" />
Expand Down
20 changes: 9 additions & 11 deletions ColorChord.NET/NoteFinder/ShinNoteFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public ShinNoteFinder(string name, Dictionary<string, object> config)
PersistentNoteIDs = new int[NOTE_QTY];
OctaveBinValues = new float[BINS_PER_OCTAVE];
AllBinValues = new float[BINS_PER_OCTAVE * ShinNoteFinderDFT.OctaveCount];
SetupBuffers();
}

public override int NoteCount => NOTE_QTY;
Expand Down Expand Up @@ -61,18 +62,15 @@ private static void DoProcessing()

private static void Cycle()
{
int WriteHead = AudioBufferHeadWrite;
if (WriteHead > AudioBufferHeadRead) // Single chunk
do
{
ShinNoteFinderDFT.AddAudioData(AudioBuffer.AsSpan(AudioBufferHeadRead, WriteHead - AudioBufferHeadRead));
AudioBufferHeadRead = WriteHead;
}
else if (WriteHead < AudioBufferHeadRead) // Write has wrapped around, process both sections
{
ShinNoteFinderDFT.AddAudioData(AudioBuffer.AsSpan(AudioBufferHeadRead));
ShinNoteFinderDFT.AddAudioData(AudioBuffer.AsSpan(0, WriteHead));
AudioBufferHeadRead = WriteHead;
}
short[]? Buffer = GetBufferToRead(out int NFBufferRef, out uint BufferSize, out bool MoreBuffers);
if (Buffer == null) { break; }
ShinNoteFinderDFT.AddAudioData(Buffer, BufferSize);

FinishBufferRead(NFBufferRef);
if (!MoreBuffers) { break; }
} while (true);

ShinNoteFinderDFT.CalculateOutput();
}
Expand Down
8 changes: 4 additions & 4 deletions ColorChord.NET/NoteFinder/ShinNoteFinderDFT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,16 +196,16 @@ public static void UpdateSampleRate(uint newSampleRate)
Reconfigure();
}

public static void AddAudioData(Span<short> newData, bool useVec = USE_VECTORIZED)
public static void AddAudioData(short[] newData, uint dataLength, bool useVec = USE_VECTORIZED)
{
if (Avx2.IsSupported && useVec)
{
Debug.Assert((newData.Length & 15) == 0, $"Length of new audio data ({newData.Length}) is not a multiple of 16. This makes SIMD mode sad :(");
for (int i = 0; i < newData.Length; i += 16) { AddAudioData256(Vector256.LoadUnsafe(ref newData[i])); }
Debug.Assert((dataLength & 15) == 0, $"Length of new audio data ({dataLength}) is not a multiple of 16. This makes SIMD mode sad :(");
for (int i = 0; i < dataLength; i += 16) { AddAudioData256(Vector256.LoadUnsafe(ref newData[i])); }
}
else
{
for (int i = 0; i < newData.Length; i++)
for (int i = 0; i < dataLength; i++)
{
AddAudioDataToOctave(newData[i]);
//GlobalSampleCounter++;
Expand Down
21 changes: 14 additions & 7 deletions ColorChord.NET/Sources/CNFABinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using ColorChord.NET.Config;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace ColorChord.NET.Sources
Expand Down Expand Up @@ -60,20 +61,26 @@ public void Stop()
/// <param name="output"> Place data here to send to the system. </param>
/// <param name="framesIn"> How many frames of input data are available. </param>
/// <param name="framesOut"> How many frames of space are available for output data. </param>
private void SoundCallback(IntPtr driver, IntPtr input, IntPtr output, int framesIn, int framesOut)
private unsafe void SoundCallback(IntPtr driver, IntPtr input, IntPtr output, int framesIn, int framesOut)
{
if (input == IntPtr.Zero) { return; } // Needed for ALSA?
//Console.WriteLine("CALLBACK with " + framesIn + " frames of input, and " + framesOut + " frames of space for output.");
short[] AudioData = new short[framesIn * this.Driver.ChannelCountRecord];
Marshal.Copy(input, AudioData, 0, (framesIn * this.Driver.ChannelCountRecord));

short[]? ProcessedData = NoteFinderCommon.GetBufferToWrite(out int NFBufferRef);
if (ProcessedData == null) { return; }
Debug.Assert(framesIn <= ProcessedData.Length); // TODO: Handle this properly (if needed?)

short Channels = this.Driver.ChannelCountRecord;
for (int Frame = 0; Frame < framesIn; Frame++)
{
float Sample = 0;
for (ushort Chn = 0; Chn < this.Driver.ChannelCountRecord; Chn++) { Sample += AudioData[(Frame * this.Driver.ChannelCountRecord) + Chn] / 32767.5F; }
NoteFinderCommon.AudioBuffer[NoteFinderCommon.AudioBufferHeadWrite] = Sample / this.Driver.ChannelCountRecord; // Use the average of the channels.
NoteFinderCommon.AudioBufferHeadWrite = (NoteFinderCommon.AudioBufferHeadWrite + 1) % NoteFinderCommon.AudioBuffer.Length;
int Sample = 0;
for (ushort Chn = 0; Chn < Channels; Chn++) { Sample += BitConverter.ToInt16(new ReadOnlySpan<byte>(((short*)input) + ((Frame * Channels) + Chn), sizeof(short))); } // TODO: Double-check if this is correct
ProcessedData[Frame] = (short)Sample;
}

NoteFinderCommon.FinishBufferWrite(NFBufferRef, (uint)framesIn);
NoteFinderCommon.LastDataAdd = DateTime.UtcNow;
NoteFinderCommon.InputDataEvent.Set();
}

[StructLayout(LayoutKind.Sequential)]
Expand Down
Loading

0 comments on commit 844f248

Please sign in to comment.