This project has moved and is read-only. For the latest updates, please go here.

Class for audio output using PortAudioSharp

Feb 16, 2011 at 1:33 AM
Edited Feb 16, 2011 at 1:58 AM

This is a class to help people get started with Vst Hosting. IMO PortAudioSharp is easier to use and more stable than NAudio library.

To use it, you need PortAudio.dll in your build directory and you need to add PortAudioSharp.dll reference to your project. To use the class first call "Init()", then call "Start()". You have to provide a pointer to a VstHost class that implements ProcessReplacing. You can refer to my previous posts for  VstHost implementation.

// Standard parameters for audio processing
[type: System.CLSCompliant(false)]
static class AudioParameters
{    
    public static int NbChannels = 2;
    public static int SampleRate = 44100;
    public static uint BlockSize = 512;
    public static float globalVolume = 1f;
}
// Class for VstPlayblack using PortAudio

[type: System.CLSCompliant(false)]
public class VstPlayback : IDisposable
{
    // Pointer to VstHost singleton
    private VstHost host;

    // Port audio instance
    private PortAudioSharp.Audio audio;

    // Audio buffer pointer
    private unsafe float* audioBuffer;

    // Index for number of audio output buffer samples
    private uint i;

    // Index for number of vst output buffer samples per channel
    private int j;

    // Index for number of channels
    private int k;
               
    private PortAudio.PaStreamCallbackResult AudioCallback(IntPtr audioInputBuffer, IntPtr audioOutputBuffer, uint blockSize, ref PortAudio.PaStreamCallbackTimeInfo timeInfo, PortAudio.PaStreamCallbackFlags statusFlags, IntPtr userData)
    {
        // If host and VstBuffer are initialized
        if (host != null && host.vstOutputBuffers != null)
        {
            // This function fills vstOutputBuffers with audio processed by a plugin
            host.ProcessReplacing(blockSize);

            // Init loop indexes
            i = 0;
            j = 0;

            unsafe
            {
                // Fill audio buffer for all channels, multiply samples by volume range [0, 1]
                audioBuffer = (float*)audioOutputBuffer.ToPointer();

                while (i < blockSize * AudioParameters.NbChannels)
                {
                    for (k = 0; k < AudioParameters.NbChannels; k++)
                        audioBuffer[i++] = host.vstOutputBuffers[k][j] * AudioParameters.globalVolume;

                    j++;
                }
            }
        }
        else if (host == null)
        {
            // Check if pointer to VstHost is available
            host = VstHostActions.Obj.Host;
        }
        
        return PortAudio.PaStreamCallbackResult.paContinue;
    }

    public void Init(int sampleRate)
    {
        try
        {
            audio = new PortAudioSharp.Audio(AudioParameters.NbChannels,
                                                                AudioParameters.SampleRate,
                                                                AudioParameters.BlockSize,
                                                                new PortAudio.PaStreamCallbackDelegate(AudioCallback));
        }
        catch (Exception)
        {
            audio = null;
        }
        finally
        {
            if (audio == null)
                audio.Dispose();
        }
    }

    public void Play()
    {
        audio.Start();
    }

    public void Stop()
    {
        audio.Stop();
    }

    public void Dispose()
    {
        GC.SuppressFinalize(this);
    }
}

Feb 24, 2011 at 12:50 PM

Hi Yuryk,

 

Firstly just let me say thanks to you for posting your code on here it has been a very useful example for me to follow. I have been following your previous thread using NAudio and have posted a comment on that, I have just seen this thread and thought i would try this implementation but I am having the same trouble. You use the function ProcessReplacing( ), but you only pass it blocksize. When I try to call this function it asks for the inputBuffers and outputBuffers, of the VSTi to be passed to it. I am struggeling to see how to implement it your way. I'd be greatful for any help you can offer me.

 

Thanks,

Perm

Feb 27, 2011 at 4:26 PM
Edited Feb 27, 2011 at 5:18 PM

Hi Sergeant,

Input and output buffers are VstAudioBuffer arrays initialized using the function at the bottom of this post. The host.processReplacing function in my code is just a wrapper for calling the one in Vst.Net. Since your scenario involves a single Vsti, you can call IVstPluginCommandsBase.ProcessReplacing(VstAudioBuffer[] inputs, VstAudioBuffer[] outputs) directly and access the output buffer in the Audio class like this:

replace:
host.ProcessReplacing(blockSize);

by:
IVstPluginCommandStub plugin = your_plugin_here;
plugin.ProcessReplacing(vstInputBuffers, vstOutputBuffers);

There's quite a bit of plumbing involved in loading, initializing and starting the Vsti plugin. Check out the VstHost sample project in VST.Net source code, it shows how to do these operations before calling processReplacing. Buffer should be initialized each time you change blockSize (usually you would set blockSize once). Then they are used as placeholder for the audio processed by the Vst plugin.

private VstAudioBuffer[] vstInputBuffers = null;
private VstAudioBuffer[] vstOutputBuffers = null;

private void InitBuffer(int inputCount, int outputCount, int blockSize)
{
    VstAudioBufferManager inputMgr = new VstAudioBufferManager(inputCount, blockSize);
    VstAudioBufferManager outputMgr = new VstAudioBufferManager(outputCount, blockSize);
    vstInputBuffers = inputMgr.ToArray();
    vstOutputBuffers = outputMgr.ToArray();

    IVstPluginCommandStub plugin = your_plugin_here;
    plugin.SetBlockSize(blockSize);
    plugin.SetSampleRate(AudioParameters.SampleRate);
    plugin.SetProcessPrecision(VstProcessPrecision.Process32);
}

If you have more questions, just ask.
Feb 28, 2011 at 10:10 AM

Thanks for the help Yuryk have now managed to get a sound out of the VSTi! Just got to work on controlling the parameters now, thanks again for the help!

SgtPerm

Jun 11, 2011 at 10:09 PM

To user 'mmmartins', I have sent you personal message through forum, check your message in profile if you do not have notifications sent to your email inbox.

Oct 21, 2011 at 8:43 AM

Fantastic, thanks for all your help.. I am currently building a midi workstation and this code and vst.net have help me intergrate a VST host into my workstation, so thumbs up!

One point of interest I gather PortSharp support ASIO, do you know off hand what I would have to do use the ASIO driver from your code

Regards T

Oct 21, 2011 at 10:41 AM

Found how to do get PortAudio working with ASIO. :)  It seems at the moment although PortAudioSharp is capable of supporting ASIO it will never get selected.

If you get the latest of PortAudioSharp and have a look at Audio.cs you will find a function called 'apiSelect'

	private int apiSelect() {
			int selectedHostApi = PortAudio.Pa_GetDefaultHostApi();
			int apiCount = PortAudio.Pa_GetHostApiCount();
			for (int i = 0; i<apiCount; i++) {
				PortAudio.PaHostApiInfo apiInfo = PortAudio.Pa_GetHostApiInfo(i);
				if ((apiInfo.type == PortAudio.PaHostApiTypeId.paDirectSound)
				    || (apiInfo.type == PortAudio.PaHostApiTypeId.paALSA))
					selectedHostApi = i;
			}
			return selectedHostApi;
		}
Notice the line  apiInfo ==PortAudio.PaHostApiTypeId.paDirectSound

This means ASIO never get used.  Changing the line to apiInfo.type == PortAudio.PaHostApiTypeId.paASIO make this work ASIO.

So I would suggest the selected driver enum value be placed in a config.  This could be set from the ApiHostSelectionForm.

I would also suggest that the code should fall back to using paDirectSound (for windows) if ASIO could no longer be found



        
    
Oct 21, 2011 at 2:53 PM

I think you might be right tfish because I also remember having modified and recompiled PortAudio to support ASIO.

Oct 21, 2011 at 3:34 PM

Just looking at the moment how I can have two host instances with the same exe sending midi to different plugins.  This works with paDirectSound not ASIO.

I might try the folowing found on the PortAudio FAQ, any thoughts YuryK , thanks in advance?

How do I make portAudio play two tones at the same time without distortion?

Create just one PortAudio stream. Then in the audio callback function, simply mix the two tones together by adding their samples. You will need to scale their amplitudes down so that they do not clip. In this example I generate two waveforms then mix them together with different amplitudes. I am using the paFloat32 data format.

sineValue = generateNextSine();
sawValue = generateNextSaw();
*outputPtr++ = (0.3f * sineValue) + (0.4f * sawValue);

Regards T
Oct 21, 2011 at 3:59 PM
Edited Oct 21, 2011 at 4:02 PM

You will need to scale their amplitudes down so that they do not clip.

*outputPtr++ = (0.3f * sineValue) * 0.5f + (0.4f * sawValue) * 0.5f;

I don't think the scaling makes much difference, distortion can also be caused by faulty block sizes and buffer transfer.


Oct 21, 2011 at 4:10 PM

Sorry YuryK  my C is a bit rusty, how would this look in c#

Regards T

Oct 21, 2011 at 4:14 PM

It really depends on the context and how you build your host, here is one loop I'm using for this :

        [method: System.CLSCompliant(false)]
        public void ProcessReplacing(uint blockSize)
        {           
            lock (this)
            {
                if (inputBuffers == null || outputBuffers == null || vstOutputBuffers == null)
                    return;

                for (int i = 0; i < outputBuffers.Length; i++)
                {
                    for (int j = 0; j < outputBuffers[i].SampleCount; j++)
                    {
                        outputBuffers[i][j] = 0;
                        vstOutputBuffers[i][j] = 0;
                    }
                }

                for (int i = 0; i < inputBuffers.Length; i++)
                    for (int j = 0; j < inputBuffers[i].SampleCount; j++)
                        inputBuffers[i][j] = 0;

                foreach (KeyValuePair<Guid, VstStatus> status in VstHostActions.Obj.PluginStatus)
                {
                    if (status.Value.IsInit && hostCmdStub.ContainsKey(status.Key))
                    {
                        isLocked = true;
                        IVstPluginCommandStub plugin = hostCmdStub[status.Key].PluginContext.PluginCommandStub;

                        if (plugin != null && plugin.PluginContext != null)
                        {
                            if ((plugin.PluginContext.PluginInfo.Flags & VstPluginFlags.CanReplacing) == 0)
                                return;

                            try
                            {
                                plugin.ProcessReplacing(inputBuffers, outputBuffers);
                                isLocked = false;
                            }
                            catch (Exception)
                            {
                            }

                            for (int i = 0; i < AudioParameters.NbChannels; i++)
                                for (int j = 0; j < blockSize; j++)
                                    if (status.Value.channels[i])
                                        vstOutputBuffers[i][j] += outputBuffers[i][j] * VstHost.velocity;
                        }
                    }
                }
            }

            isLocked = false;
        }

Oct 21, 2011 at 4:22 PM

Thank you , is this the line thats doing the mixing and scaling

vstOutputBuffers[i][j] += outputBuffers[i][j] * VstHost.velocity;
Regards T
Oct 21, 2011 at 4:24 PM

I also see there some locking going on, so can i presume this audio is being played in its own high priority thread ?

Oct 21, 2011 at 4:27 PM

Yes it is indeed the line but I do not scale the signal at this point, it's just multiplied by a volume constant [0 - 1], scaling didn't make much difference to me...

The locking is to avoid two threads writing the buffers or calling the plugin's functions at the same time.

Oct 21, 2011 at 4:38 PM

Thanks I'll give this a go.

This has saved me plenty of time

Regard T

Aug 16, 2014 at 1:28 PM
Edited Aug 16, 2014 at 1:29 PM
Hi YuryK

im trying to get multiple plugins to sound using the Host example too is it possible you could post the whole working project somewhere

Many thanks
Aug 16, 2014 at 10:23 PM
If by the whole project you mean the sequencer I'm working on, no. It's too big and contains licensed proprietary code that I can't strip out easily of the project. What you see up there are just some snippets of example use, it's not production code.
Aug 17, 2014 at 12:44 AM
YuryK wrote:
If by the whole project you mean the sequencer I'm working on, no. It's too big and contains licensed proprietary code that I can't strip out easily of the project. What you see up there are just some snippets of example use, it's not production code.
Hi
no i just mean the sample vst host project with the PortAudioSharp added in all the right places..:)
Aug 17, 2014 at 3:35 AM
All code related to port-audio is in the first post, except the port-audio.dll and port-audio-sharp.dll redistributables which are available in their respective web sites. I don't recall having made a minimal host project for this library, same goes for handling multiple plugins.
Aug 17, 2014 at 8:20 AM
ok thanks
dont worry i got the naudio version working with the host sample and seems to load 2 plugins and play them both simultaneously just need to clean up the exciting functions.
Nov 18, 2014 at 4:11 PM
Hi YuryK,
can you send me the host example?
thanks.
Nov 18, 2014 at 8:09 PM
lyh_007
Check your PM.
Nov 22, 2014 at 6:29 AM
Hi Yuryk,
I was learning VST recently, after seeing this, I tried this implementation but failed some how.i was wondering if you could send me the host example by using portAudioSharp? so that i could find out where i was wrong. many thanks.

Leo
Nov 22, 2014 at 7:36 PM
Have you read the thread?
See five post above.
Nov 23, 2014 at 3:30 AM
yes, but still get wrong..
Dec 23, 2014 at 1:47 PM
YuryK wrote:
lyh_007
Check your PM.
Hi YuryK,
after host vst plugin, how to play midi files?

Regards.
Dec 24, 2014 at 12:56 AM
Guess you're on your own, start by parsing a midi files for relevant events then send them to the vst host.
Dec 24, 2014 at 8:40 AM
I have had a stab at writing a Host some time ago and the hardest part was how to incorporate a pull for audio buffers (ASIO), and a push of the MIDI events in such a way that the time base is managed correctly and each time-slice (cycles) processes the correct audio and MIDI information. I could not find any description of how one would manage this anywhere. The only source is the open source VST host by Hermann Seib (C++) - scroll down to the bottom of the page.

[2c]
Marc