Skip to content

Commit

Permalink
AudioIOPort refactor. Moved managed I/O to subclass. Fixed issues wit…
Browse files Browse the repository at this point in the history
…h hosts forcing 32bit audio.
  • Loading branch information
mikeoliphant committed Mar 18, 2024
1 parent 83fbef1 commit 8a04a3a
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 113 deletions.
251 changes: 146 additions & 105 deletions AudioPlugSharp/AudioIOPort.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,31 +63,28 @@ public EAudioBitsPerSample BitsPerSample
/// </summary>
/// <param name="name">The port name</param>
/// <param name="channelConfiguration">The port channel configuration</param>
/// <param name="forceCopy">Whether to force having a managed copy of the sample data</param>
public AudioIOPort(string name, EAudioChannelConfiguration channelConfiguration, bool forceCopy = false)
public AudioIOPort(string name, EAudioChannelConfiguration channelConfiguration)
{
this.Name = name;
this.ChannelConfiguration = channelConfiguration;
this.forceCopy = forceCopy;

numChannels = (channelConfiguration == EAudioChannelConfiguration.Mono) ? 1 : 2;
audioBuffers = new double[numChannels][];
}

int numChannels;
IntPtr audioBufferPtrs = IntPtr.Zero;
double[][] audioBuffers;
EAudioBitsPerSample bitsPerSample;
uint currentBufferSize = 0;
bool forceCopy;
bool doCopy;

protected IntPtr hostAudioBufferPtrs = IntPtr.Zero;
IntPtr backingAudioBufferPtrs = IntPtr.Zero;

/// <summary>
/// Sets the maximum number of samples that will be processed, and the number of bits per sample
/// </summary>
/// <param name="maxSamples">The maximum number of samples that will be processed in a block</param>
/// <param name="bitsPerSample">The number of bits in each sample (current 32 or 64)</param>
public void SetMaxSize(uint maxSamples, EAudioBitsPerSample bitsPerSample)
public virtual void SetMaxSize(uint maxSamples, EAudioBitsPerSample bitsPerSample)
{
this.bitsPerSample = bitsPerSample;

Expand All @@ -97,41 +94,25 @@ public void SetMaxSize(uint maxSamples, EAudioBitsPerSample bitsPerSample)
{
CreateBackingBuffer(maxSamples);
}

if (forceCopy)
{
int size = 0;

if (audioBuffers[0] != null)
size = audioBuffers[0].Length;

if (size != maxSamples)
{
for (int i = 0; i < numChannels; i++)
{
Array.Resize(ref audioBuffers[i], (int)maxSamples);
}
}
}
}

unsafe void CreateBackingBuffer(uint numSamples)
{
if (audioBufferPtrs != IntPtr.Zero)
if (backingAudioBufferPtrs != IntPtr.Zero)
{
for (int i = 0; i < numChannels; i++)
{
IntPtr channelPtr = (IntPtr)((double**)audioBufferPtrs)[i];
IntPtr channelPtr = (IntPtr)((double**)backingAudioBufferPtrs)[i];

Marshal.FreeHGlobal(channelPtr);
}

Marshal.FreeHGlobal(audioBufferPtrs);
Marshal.FreeHGlobal(backingAudioBufferPtrs);
}

audioBufferPtrs = Marshal.AllocHGlobal(numChannels * Marshal.SizeOf(typeof(IntPtr)));
backingAudioBufferPtrs = Marshal.AllocHGlobal(numChannels * Marshal.SizeOf(typeof(IntPtr)));

double** bufferPtrs = (double **)audioBufferPtrs;
double** bufferPtrs = (double **)backingAudioBufferPtrs;

for (int i = 0; i < numChannels; i++)
{
Expand All @@ -154,150 +135,210 @@ public void SetCurrentBufferSize(uint numSamples)
/// <param name="ptrs">The unmanaged buffer pointers</param>
public unsafe void SetAudioBufferPtrs(IntPtr ptrs)
{
hostAudioBufferPtrs = ptrs;

if (bitsPerSample == EAudioBitsPerSample.Bits64)
{
audioBufferPtrs = ptrs;
backingAudioBufferPtrs = hostAudioBufferPtrs;
}
else
}

/// <summary>
/// Get the unmanaged audio pointers
/// </summary>
/// <returns>An IntPtr to the unmanaged audio buffers</returns>
public IntPtr GetAudioBufferPtrs()
{
return backingAudioBufferPtrs;
}

/// <summary>
/// Reads the data from the host pointers (if necessary)
/// </summary>
internal unsafe virtual void ReadData()
{
if (bitsPerSample == EAudioBitsPerSample.Bits32)
{
// We need to convert float samples to double
for (int i = 0; i < numChannels; i++)
{
float* floatPtr = ((float**)ptrs)[i];
double* dblPtr = ((double**)audioBufferPtrs)[i];
float* floatPtr = ((float**)hostAudioBufferPtrs)[i];
double* dblPtr = ((double**)backingAudioBufferPtrs)[i];

for (int s = 0; s < this.currentBufferSize; s++)
for (int sample = 0; sample < currentBufferSize; sample++)
{
dblPtr[s] = floatPtr[s];
dblPtr[sample] = floatPtr[sample];
}
}
}
}

/// <summary>
/// Get the unmanaged audio pointers
/// Writes data to the host pointers (if necessary)
/// </summary>
/// <returns>An IntPtr to the unmanaged audio buffers</returns>
public IntPtr GetAudioBufferPtrs()
internal unsafe virtual void WriteData()
{
return audioBufferPtrs;
if (bitsPerSample == EAudioBitsPerSample.Bits32)
{
// We need to convert float samples to back to float
for (int i = 0; i < numChannels; i++)
{
float* floatPtr = ((float**)hostAudioBufferPtrs)[i];
double* dblPtr = ((double**)backingAudioBufferPtrs)[i];

for (int sample = 0; sample < currentBufferSize; sample++)
{
floatPtr[sample] = (float)dblPtr[sample];
}
}
}
}

/// <summary>
/// Reads the data from unmanaged memory to managed memory
/// Gets the audio buffer for a channel
/// </summary>
internal unsafe void ReadData()
/// <returns>The sample array for the channel</returns>
public virtual unsafe Span<double> GetAudioBuffer(int channel)
{
if (!doCopy || (audioBufferPtrs == IntPtr.Zero))
return;
return new Span<double>((void*)((double**)backingAudioBufferPtrs)[channel], (int)currentBufferSize);
}

for (int i = 0; i < numChannels; i++)
/// <summary>
/// Pass through unmanaged data from this buffer to another buffer
/// </summary>
/// <param name="destinationPort">The port to copy data to</param>
public unsafe virtual void PassThroughTo(AudioIOPort destinationPort)
{
if (destinationPort.CurrentBufferSize != CurrentBufferSize)
throw new InvalidOperationException("Destination port does not have the same size");

if (destinationPort.BitsPerSample != BitsPerSample)
throw new InvalidOperationException("Destination port does not have the number of bits");

void** ptrs = (void**)GetAudioBufferPtrs();
void** destPtrs = (void**)destinationPort.GetAudioBufferPtrs();

uint length = currentBufferSize * (uint)((bitsPerSample == EAudioBitsPerSample.Bits32) ? 4 : 8);

for (int channel = 0; channel < numChannels; channel++)
{
if (bitsPerSample == EAudioBitsPerSample.Bits32)
Buffer.MemoryCopy(ptrs[channel], destPtrs[channel], length, length);
}
}
}

public class AudioIOPortManaged : AudioIOPort
{
double[][] audioBuffers;

public AudioIOPortManaged(string name, EAudioChannelConfiguration channelConfiguration)
: base(name, channelConfiguration)
{
audioBuffers = new double[NumChannels][];
}

public override void SetMaxSize(uint maxSamples, EAudioBitsPerSample bitsPerSample)
{
base.SetMaxSize(maxSamples, bitsPerSample);

int size = 0;

if (audioBuffers[0] != null)
size = audioBuffers[0].Length;

if (size != maxSamples)
{
for (int i = 0; i < NumChannels; i++)
{
Array.Resize(ref audioBuffers[i], (int)maxSamples);
}
}
}

unsafe internal override void ReadData()
{
for (int i = 0; i < NumChannels; i++)
{
if (BitsPerSample == EAudioBitsPerSample.Bits32)
{
float* bufferPtr = ((float**)audioBufferPtrs)[i];
float* bufferPtr = ((float**)hostAudioBufferPtrs)[i];
double[] audioBuffer = audioBuffers[i];

for (int sample = 0; sample < currentBufferSize; sample++)
for (int sample = 0; sample < CurrentBufferSize; sample++)
{
audioBuffer[sample] = bufferPtr[sample];
}
}
else
{
IntPtr bufferPtr = (IntPtr)((double**)audioBufferPtrs)[i];
IntPtr bufferPtr = (IntPtr)((double**)hostAudioBufferPtrs)[i];

Marshal.Copy(bufferPtr, audioBuffers[i], 0, (int)currentBufferSize);
Marshal.Copy(bufferPtr, audioBuffers[i], 0, (int)CurrentBufferSize);
}
}
}

/// <summary>
/// Writes the data from managed memory to unmanaged memory
/// </summary>
internal unsafe void WriteData()
unsafe internal override void WriteData()
{
if (!doCopy || (audioBufferPtrs == IntPtr.Zero))
return;

for (int i = 0; i < numChannels; i++)
for (int i = 0; i < NumChannels; i++)
{
if (bitsPerSample == EAudioBitsPerSample.Bits32)
if (BitsPerSample == EAudioBitsPerSample.Bits32)
{
float* bufferPtr = ((float**)audioBufferPtrs)[i];
float* bufferPtr = ((float**)hostAudioBufferPtrs)[i];
double[] audioBuffer = audioBuffers[i];

for (int sample = 0; sample < currentBufferSize; sample++)
for (int sample = 0; sample < CurrentBufferSize; sample++)
{
bufferPtr[sample] = (float)audioBuffer[sample];
}
}
else
{
IntPtr bufferPtr = (IntPtr)((double**)audioBufferPtrs)[i];
IntPtr bufferPtr = (IntPtr)((double**)hostAudioBufferPtrs)[i];

Marshal.Copy(audioBuffers[i], 0, bufferPtr, (int)currentBufferSize);
Marshal.Copy(audioBuffers[i], 0, bufferPtr, (int)CurrentBufferSize);
}
}
}

/// <summary>
/// Gets the audio buffer for a channel
/// </summary>
/// <returns>The sample array for the channel</returns>
public unsafe Span<double> GetAudioBuffer(int channel)
{
if (!doCopy)
return new Span<double>((void*)((double**)audioBufferPtrs)[channel], (int)currentBufferSize);

return audioBuffers[channel];
}

/// <summary>
/// Get a managed copy of the audio buffers (requires "forceCopy" in constructor)
/// </summary>
/// <returns>The managed audio buffer arrays</returns>
public double[][] GetAudioBuffers()
{
if (!doCopy)
throw new InvalidOperationException("Managed buffers only available if \"forceCopy\" is true constructor");

return audioBuffers;
}

/// <summary>
/// Pass through unmanaged data from this buffer to another buffer
/// </summary>
/// <param name="destinationPort">The port to copy data to</param>
public unsafe void PassThroughTo(AudioIOPort destinationPort)
public override void PassThroughTo(AudioIOPort destinationPort)
{
if (destinationPort.CurrentBufferSize != CurrentBufferSize)
throw new InvalidOperationException("Destination port does not have the same size");

if (doCopy != destinationPort.doCopy)
{
throw new InvalidOperationException("Pass through only supported when \"forceCopy\" is the same for source and destination");
}
AudioIOPortManaged managedPort = destinationPort as AudioIOPortManaged;

if (doCopy)
if (managedPort == null)
{
for (int channel = 0; channel < numChannels; channel++)
for (int channel = 0; channel < NumChannels; channel++)
{
audioBuffers[channel].CopyTo(destinationPort.audioBuffers[channel], 0);
ReadOnlySpan<double> srcSpan = GetAudioBuffer(channel);
Span<double> dstSpan = destinationPort.GetAudioBuffer(channel);

srcSpan.CopyTo(dstSpan);
}
}
else
{
void** ptrs = (void**)audioBufferPtrs;
void** destPtrs = (void**)destinationPort.GetAudioBufferPtrs();

uint length = currentBufferSize * (uint)((bitsPerSample == EAudioBitsPerSample.Bits32) ? 4 : 8);

for (int channel = 0; channel < numChannels; channel++)
for (int channel = 0; channel < NumChannels; channel++)
{
Buffer.MemoryCopy(ptrs[channel], destPtrs[channel], length, length);
audioBuffers[channel].CopyTo(managedPort.audioBuffers[channel], 0);
}
}
}

public override Span<double> GetAudioBuffer(int channel)
{
return audioBuffers[channel];
}

/// <summary>
/// Get a managed copy of the audio buffers
/// </summary>
/// <returns>The managed audio buffer arrays</returns>
public double[][] GetAudioBuffers()
{
return audioBuffers;
}
}
}
2 changes: 1 addition & 1 deletion AudioPlugSharp/AudioPlugSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<OutputType>Library</OutputType>
<Version>0.5.10</Version>
<Version>0.5.11</Version>
<Authors>Mike Oliphant</Authors>
<Description>Easily create VST (VST3) audio plugins in C# .NET.</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand Down
Loading

0 comments on commit 8a04a3a

Please sign in to comment.