Skip to content

Commit

Permalink
fix(zitareverb)#!: FIx crash when computing zita reverb in Unity 2019…
Browse files Browse the repository at this point in the history
….4.19f1.

Change span-accepting function signature of ZitaReverb.Compute to accept an optional chunk size. Now accepts arbitrary-sized input data to be processed in chunks.

More carefully handle reorientation of samples on the stack and use fixed. This appears to fix the aforementioned unity bug.
  • Loading branch information
BlueCyro committed Dec 6, 2024
1 parent f5f2fbf commit 2c3a8e3
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 85 deletions.
68 changes: 4 additions & 64 deletions SharpPipe.Example/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,75 +37,15 @@ public static void Main(string[] args)


// Read a raw file and cast the read bytes to floats
byte[] inputFile = File.ReadAllBytes("./Input.raw"); // Stereo, 32 bit float, 44100hz, DTMF tones as a test
Span<Stereo> samples = MemoryMarshal.Cast<byte, Stereo>(inputFile);
byte[] inputFile = File.ReadAllBytes("./Input.raw"); // Stereo, 32 bit float, 44100hz, DTMF tones as a test (use audacity or similar to import/export raw samples)
Span<float> samples = MemoryMarshal.Cast<byte, float>(inputFile);

zita.Compute(samples, samples);

// Process a batch of samples at once.
int processLength = 16384;
for (int i = 0; i < samples.Length;)
{
// 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);
}


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();
}
26 changes: 20 additions & 6 deletions SharpPipe/Docs.xml

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

78 changes: 64 additions & 14 deletions SharpPipe/Managed wrapper classes/ZitaReverb.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,45 +139,95 @@ public ZitaReverb(SoundPipe pipe)


/// <summary>
/// Computes reverb on a single audio sample for left and right channels
/// Computes reverb on a single audio sample for left and right channels.
/// </summary>
/// <param name="left">Left audio sample</param>
/// <param name="right">Right audio sample</param>
/// <param name="outLeft">Reference to the variable where the result for the left channel will be stored</param>
/// <param name="outRight">Reference to the variable where the result for the right channel will be stored</param>
[Obsolete("Use the span-accepting overload to avoid significant performance penalties.")]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Compute(float left, float right, ref float outLeft, ref float outRight)
{
SharpPipeNatives.sp_zitarev_compute(Pipe.pipeObject, zitaRevObject, ref left, ref right, ref outLeft, ref outRight);
}


// 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
/// Computes reverb from an arbitrarily-sized span of stereo samples and places them into an output buffer. Processes the signal in chunks.
/// <para>1 stereo sample == 2 floats. E.g. A chunkSize of 1024 will process 2048 floats at once.</para>
/// </summary>
/// <param name="stereoIn">Left audio sample</param>
/// <param name="stereoOut">Right audio sample</param>
/// <param name="stereoIn">Interleaved input stereo signal.</param>
/// <param name="stereoOut">Interleaved output stereo signal.</param>
/// <param name="chunkSize">The size of each chunk to be processed.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void Compute(Span<float> stereoIn, Span<float> stereoOut)
public unsafe void Compute(Span<float> stereoIn, Span<float> stereoOut, int chunkSize = 1024)
{
if (stereoIn.Length != stereoOut.Length)
throw new ArgumentException($"Input and output spans are of inequal length! (stereoIn length: {stereoIn.Length}, stereoOut length: {stereoOut.Length})");
if (stereoIn.Length > stereoOut.Length)
throw new ArgumentOutOfRangeException(nameof(stereoIn), $"The input span is larger than {nameof(stereoOut)}!");

int length = stereoIn.Length;
int stereoBlockSize = Math.Abs(chunkSize) * 2; // Two channels.

// Process a batch of samples at once.
for (int i = 0; i < length;)
{
// Get the length to process. This accounts for when you're close to the end of input buffer.
int remainingLength = length - i;
int lengthToProcess = Math.Min(remainingLength, stereoBlockSize);

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

// Slice the output into an equivalent chunk.
Span<float> outputSliced = stereoOut.Slice(i, lengthToProcess);

// Compute a N blocks of data depending on the length of the input data.
ComputeBlock(inputSliced, outputSliced);

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



/// <summary>
/// Computes reverb on a span of audio samples and places them into an output buffer. Processes a single chunk of samples. Does not do bounds checking.
/// <para>
/// NOTE: This reorients the interleaved samples from left/right/left/right into two separate left and right
/// buffers on the stack. Be mindful of how big your buffer size is with this function. If you want to process an arbitrary
/// amount of samples, use: <see cref="Compute(Span{float}, Span{float}, int)"/>
/// </para>
/// </summary>
/// <param name="stereoIn">Interleaved input stereo signal.</param>
/// <param name="stereoOut">Interleaved output stereo signal.</param>
public unsafe void ComputeBlock(Span<float> stereoIn, Span<float> stereoOut)
{
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;
Span<float> inLeft = stackalloc float[halfLength];
Span<float> inRight = stackalloc float[halfLength];

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

SharpPipeNatives.sp_zitarev_compute_many(Pipe.pipeObject, zitaRevObject, halfLength, inSamples, inSamples);
Span<nint> inSamples = stackalloc nint[2];
unsafe
{
fixed (float* inLeftPtr = inLeft)
fixed (float* inRightPtr = inRight)
fixed (nint* inSamplesPtr = inSamples)
{
inSamplesPtr[0] = (nint)inLeftPtr;
inSamplesPtr[1] = (nint)inRightPtr;

SharpPipeNatives.sp_zitarev_compute_many(Pipe.pipeObject, zitaRevObject, halfLength, ref *inSamplesPtr, ref *inSamplesPtr);
}
}


for (int i = 0; i < halfLength; i++)
{
Expand Down
2 changes: 1 addition & 1 deletion SharpPipe/Native functions/SharpPipeNatives_Zitarev.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,5 @@ public static partial class SharpPipeNatives
/// <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);
public static unsafe extern int sp_zitarev_compute_many(IntPtr spObject, IntPtr zitaRevObject, int count, ref IntPtr inputs, ref IntPtr outputs);
}

0 comments on commit 2c3a8e3

Please sign in to comment.