Skip to content

Commit

Permalink
feat(soundpipeinterop)#: Finally fix P/Invoke of bulk sample processi…
Browse files Browse the repository at this point in the history
…ng for zita reverb, vastly increasing sample throughput.

Renamte DTMF.raw to Input.raw and make it stereo to simplify example program
  • Loading branch information
BlueCyro committed Nov 26, 2024
1 parent 8dff801 commit 130b81c
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 50 deletions.
Binary file removed SharpPipe.Example/DTMF.raw
Binary file not shown.
Binary file added SharpPipe.Example/Input.raw
Binary file not shown.
Binary file added SharpPipe.Example/Output.raw
Binary file not shown.
88 changes: 68 additions & 20 deletions SharpPipe.Example/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace SharpPipe.Example;

Expand All @@ -9,6 +10,7 @@ public static void Main(string[] args)
{
// Make a new SoundPipe object and feed it to the ZitaReverb
using SoundPipe pipe = new(); // Defaults to sample rate of 44100hz
pipe.SampleRate = 44100; // Set it anyways for demonstration

// Bathroom preset
ZitaParameters zitaParams = new()
Expand All @@ -35,29 +37,75 @@ public static void Main(string[] args)


// Read a raw file and cast the read bytes to floats
byte[] inputFile = File.ReadAllBytes("./DTMF.raw"); // Single-channel, 32 bit float, 44100hz
Span<float> samples = MemoryMarshal.Cast<byte, float>(inputFile);

// Create a new output buffer of bytes and write to it as floats
byte[] outputFile = new byte[inputFile.Length];
Span<float> outputSamples = MemoryMarshal.Cast<byte, float>(outputFile);
byte[] inputFile = File.ReadAllBytes("./Input.raw"); // Stereo, 32 bit float, 44100hz, DTMF tones as a test
Span<Stereo> samples = MemoryMarshal.Cast<byte, Stereo>(inputFile);

// Left and right channel variables
float left = 0f;
float right = 0f;

// Process each sample individually
for (int i = 0; i < samples.Length; i++)
// Process a batch of samples at once.
int processLength = 16384;
for (int i = 0; i < samples.Length;)
{
// Pass in both samples since this is a mono input.
// You would usually pass in the left and right sample and receive
// a processed left and right sample in return
zita.Compute(samples[i], samples[i], ref left, ref right);
outputSamples[i] = (left + right) / 2f; // Average into a single sample
// Get the length to process. This accounts for when you're close to the end of the file
int lengthToProcess = Math.Min(samples.Length - i, processLength);

// Slice the input into a chunk that can be computed
Span<Stereo> inputSliced = samples.Slice(i, lengthToProcess);

// Interpret the sliced buffer of stereo samples to floats
Span<float> inBuf = MemoryMarshal.Cast<Stereo, float>(inputSliced);

/* Compute the buffer using itself as both input and output. You can
optionally write to a separate location however if you wish. */
zita.Compute(inBuf, inBuf);

// Increment the index by the amount of data that was just processed.
i += lengthToProcess;
}

// Print a happy little message to the console.
Console.WriteLine("Done!");

// Write processed samples back out to a separate raw
File.WriteAllBytes("./Output.raw", inputFile);
}

// Write processed samples back out to raw
File.WriteAllBytes("./Output.raw", outputFile);

public static void MonoToStereo(Span<Mono> mono, Span<Stereo> stereo)
{
for (int i = 0; i < mono.Length; i++)
stereo[i] = mono[i];
}


public static void StereoToMono(Span<Stereo> stereo, Span<Mono> mono)
{
for (int i = 0; i < stereo.Length; i++)
mono[i] = stereo[i];
}
}
}



public readonly struct Mono(float amplitude)
{
public readonly float Amplitude = amplitude;




public readonly Stereo AsStereo() => new(Amplitude, Amplitude);
public static implicit operator Stereo(Mono other) => other.AsStereo();
public static implicit operator Mono(float other) => Unsafe.As<float, Mono>(ref other);
}



public readonly struct Stereo(float left, float right)
{
public readonly float Left = left;
public readonly float Right = right;


public readonly Mono AsMono() => new((Left + Right) / 2);
public static implicit operator Mono(Stereo other) => other.AsMono();
}
20 changes: 19 additions & 1 deletion SharpPipe/Docs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 30 additions & 15 deletions SharpPipe/Managed wrapper classes/ZitaReverb.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,23 +153,38 @@ public void Compute(float left, float right, ref float outLeft, ref float outRig


// I don't understand P/Invoke well enough to do this yet and it was eating a lot of time.
// /// <summary>
// /// Computes reverb on a span of audio samples and places them into an output buffer
// /// </summary>
// /// <param name="stereoIn">Left audio sample</param>
// /// <param name="stereoOut">Right audio sample</param>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public unsafe void Compute(Span<float> stereoIn, Span<float> stereoOut)
// {
// if (stereoIn.Length != stereoOut.Length)
// throw new ArgumentException($"Input and output spans are of inequal length! (stereoIn length: {stereoIn.Length}, stereoOut length: {stereoOut.Length})");
/// <summary>
/// Computes reverb on a span of audio samples and places them into an output buffer
/// </summary>
/// <param name="stereoIn">Left audio sample</param>
/// <param name="stereoOut">Right audio sample</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void Compute(Span<float> stereoIn, Span<float> stereoOut)
{
if (stereoIn.Length != stereoOut.Length)
throw new ArgumentException($"Input and output spans are of inequal length! (stereoIn length: {stereoIn.Length}, stereoOut length: {stereoOut.Length})");

int halfLength = stereoIn.Length / 2;
float** inSamples = stackalloc float*[2];
float* inLeft = stackalloc float[halfLength];
float* inRight = stackalloc float[halfLength];
inSamples[0] = inLeft;
inSamples[1] = inRight;

for (int i = 0; i < halfLength; i++)
{
inLeft[i] = stereoIn[i * 2];
inRight[i] = stereoIn[i * 2 + 1];
}

// IntPtr inPtr = new(Unsafe.AsPointer(ref stereoIn[0]));
// IntPtr outPtr = new(Unsafe.AsPointer(ref stereoOut[0]));
SharpPipeNatives.sp_zitarev_compute_many(Pipe.pipeObject, zitaRevObject, halfLength, inSamples, inSamples);

// SharpPipeNatives.sp_zitarev_compute_many(Pipe.pipeObject, zitaRevObject, stereoIn.Length, ref inPtr, ref outPtr);
// }
for (int i = 0; i < halfLength; i++)
{
stereoOut[i * 2] = inLeft[i];
stereoOut[i * 2 + 1] = inRight[i];
}
}



Expand Down Expand Up @@ -198,4 +213,4 @@ private void Dispose(bool disposing)
{
Dispose(false);
}
}
}
28 changes: 14 additions & 14 deletions SharpPipe/Native functions/SharpPipeNatives_Zitarev.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,6 @@ public static partial class SharpPipeNatives
public static unsafe extern int sp_zitarev_compute(IntPtr spObject, IntPtr zitaRevObject, ref float in1, ref float in2, [In, Out] ref float out1, [In, Out] ref float out2);


// P/Invoking the weird pointer-to-a-pointer type this wants is tricky and was taking a lot of time.
// /// <summary>
// /// Computes a span of audio samples from an input buffer of stereo samples and places the result into an output buffer of stereo samples
// /// </summary>
// /// <param name="spObject">Pointer to a <see cref="sp_data"/> struct for gathering info</param>
// /// <param name="zitaRevObject">Pointer to a <see cref="sp_zitarev"/> struct</param>
// /// <param name="size">Size of the input/output buffers</param>
// /// <param name="stereoIn">Reference to the input buffer of stereo samples</param>
// /// <param name="stereoOut">Reference to the output buffer of stereo samples</param>
// /// <returns>Success code</returns>
// [DllImport("libsoundpipe")]
// public static unsafe extern int sp_zitarev_compute_many(IntPtr spObject, IntPtr zitaRevObject, int size, [In, Out] ref IntPtr stereoIn, [In, Out] ref IntPtr stereoOut);



/// <summary>
/// Frees a <see cref="sp_zitarev"/> struct from memory
Expand All @@ -61,4 +47,18 @@ public static partial class SharpPipeNatives
/// <returns>Success code</returns>
[DllImport("libsoundpipe")]
public static unsafe extern int sp_zitarev_destroy(ref IntPtr zitaRevObject);



/// <summary>
/// Computes a span of audio samples from an input buffer of stereo samples and places the result into an output buffer of stereo samples
/// </summary>
/// <param name="spObject">Pointer to a <see cref="sp_data"/> struct for gathering info</param>
/// <param name="zitaRevObject">Pointer to a <see cref="sp_zitarev"/> struct</param>
/// <param name="count">Length of the input/output buffers</param>
/// <param name="inputs">Pointer (float**) storing two float* buffers that each carry the left (pointer 0) and right (pointer 1) signal respectively</param>
/// <param name="outputs">Pointer (float**) storing to float* buffers that each will receive the processed left (pointer 0) and right (pointer 1) samples respectively</param>
/// <returns>Success code</returns>
[DllImport("libsoundpipe")]
public static unsafe extern int sp_zitarev_compute_many(IntPtr spObject, IntPtr zitaRevObject, int count, float** inputs, float** outputs);
}

0 comments on commit 130b81c

Please sign in to comment.