VSTi Audio output sounds rubbish

Topics: Audio, Newbie
Apr 10, 2011 at 9:23 PM

Hi,

I'm creating a simple host program and have managed to load and process some Midi data in a VSTi. Unfortunatly the audio that is produced is just a stuttered noise. The code im using for audio out is as follows:

(Thanks to Yuryk for most of this!)

class VSTStream : WaveStream
    {
        public override WaveFormat WaveFormat { get { return new WaveFormat(); } }
        public override long Length { get { throw new System.NotSupportedException(); } }
        public override long Position { get { throw new System.NotSupportedException(); } set { throw new System.NotSupportedException(); } }

        public override int Read(byte[] buffer, int offset, int sampleCount)

        {
            // FILL THE AUDIO BUFFER WITH THE VST BUFFER HERE
          //  byte[] tempBuffer = vsti.processReplace(sampleCount);

            // Copying Vst buffer inside Audio buffer, no conversion needed for WaveProvider32
           // for (int i = 0; i < tempBuffer.Length; i++)
             //   buffer[i] = tempBuffer[i];


            // LET'S PLAY A SINE WAVE FOR NOW...
           // int sampleRate = 44100;
            //double amplitude = 0.25 * byte.MaxValue;
            //double frequency = 150;

            for (int n = 0; n < buffer.Length; n++)
            {
                // buffer[n] = (byte)(amplitude * Math.Sin((2 * Math.PI * n * frequency) / sampleRate));
                buffer[n] = (byte)Globals.globuf[n];
            }
            return sampleCount;
        }
    }

 

The Golbal variable globuf is loaded with data from the output buffers of the VSTi with the following code: (Again thanks to Yuryk for this!)

private void audioOut()
        {
            int inputCount = this.ctx.PluginInfo.AudioInputCount;
            int outputCount = this.ctx.PluginInfo.AudioOutputCount;
            int blockSize = 1024;
            VstAudioBufferManager inputMgr = new VstAudioBufferManager(inputCount, blockSize);
            VstAudioBufferManager outputMgr = new VstAudioBufferManager(outputCount, blockSize);

            foreach (VstAudioBuffer buffer in inputMgr.ToArray())
            {
                for (int i = 0; i < blockSize; i++)
                {
                    buffer[i] = 0; // Initialize to 0
                }
            }

            this.ctx.PluginCommandStub.SetBlockSize(blockSize);
            this.ctx.PluginCommandStub.SetSampleRate(44100f);

            VstAudioBuffer[] inputBuffers = inputMgr.ToArray();
            VstAudioBuffer[] outputBuffers = outputMgr.ToArray();

            this.ctx.PluginCommandStub.ProcessReplacing(inputBuffers, outputBuffers);

            for (int i = 0;/* i < inputBuffers.Length &&*/ i < outputBuffers.Length; i++)
            {
                for (int j = 0; j < blockSize; j++)
                {
                    if (outputBuffers[i][j] != 0.0)
                    {
                        // IF THIS LINE IS HIT YOU HAVE SOMETHING PLAYING
                        outputBuffers[i][j] = outputBuffers[i][j] * 1000000000;  //(I multiply by this due to the conversion to byte in the playback read function otherwise the data just becomes 0, is there a way to avoid this?)
                        Globals.globuf[j] = outputBuffers[i][j];

                    }
                }
            }
        }

 

The audio output sounds terrible and stuttered. I want to produce a steady held note that i can manipulate by adjusting the plugin parameters.

 

Hope someone can help me out please?

Perm

Coordinator
Apr 11, 2011 at 1:06 PM

If you call the audioOut repeadetly you should strip some code out of it. Stuttering audio is usually a sign of too much processing going on in the process loop - or- excessive processing by the garbage collector. I would be benificial to read up on real-time programming in .NET in general (search 4 it).

- The VstAudioBufferManager classes are meant to be constructed once and used for each call to process. Do not re-create it on each time-slice in the audio loop. It will allocate unmanaged memory (something you are leaking now, please make sure you call Dispose when you're done with the instance). Recreating them will allocate that memory during audio conversion - not a good idea (Garbage Collection issue).

- Also I would not re-apply the block size and sample rate to the plugin. That is something you do during init or (again) when the values are changed (by the user). These calls might trigger reinitialization within the plugin itself.

- I dont think the conversion of the buffer data, after process is called, is correct. I dont know the format that you use, but I believe it is an 32-bit int you need. So the max range to convert to is [Int32.MinValue, Int32.MaxValue] that represents the float range of [-1.0, 1.0] used in VST. So you need to multiply with Int32.MaxValue and make sure you pack the MSB/LSB's correctly in the target buffer. See also http://stackoverflow.com/questions/3088593/vst-net-vs-naudio-vstaudiobuffer-vs-pcmstream-buffer

- Also take a look at the implementation of the Copy method on the VstAudioBuffer. It uses the 'unsafe' way, which is faster. You might use the same copying logic for your buffer conversion logic.

Hope it helps,
Marc

Apr 11, 2011 at 7:10 PM
Edited Apr 11, 2011 at 7:19 PM

Hi Sergeant,

To avoid the tricky float to integer conversion derive VstStream class from WaveProvider32 and use float everywhere :

class VSTStream : WaveProvider32

public override int Read(float[] buffer, int offset, int sampleCount)

 

Also consider changing 'buffer.Length' with 'sampleCount' because the number of samples expected might be lower than buffer capacity.

for (int n = 0; n < sampleCount; n++)

 

A common cause of stuttering occurs when a significant chunk of data is missing somewhere in the buffer.

Let's illustrate this with underscore for empty buffer space (0) and pipes for filled space (not 0) :

||||||||_______

When you repeat this error with each buffer you get :

||||||||________||||||||_______||||||||_______||||||||_______||||||||________||||||||_______||||||||_______

As you can see, this forms a trigger pattern alternating between sound and no sound resulting in audible stuttering if the gaps are wide enough.

 

Yury

Apr 11, 2011 at 8:16 PM

Hi Guys,

Thanks for helping me out I really appreciate it. Marc, I adjusted my code and changed the functions as follows:

// I use this to set up the VSTi buffers and only call it once when the user clicks a button.

private void initiateAudioOutput()
        {
            int inputCount = this.ctx.PluginInfo.AudioInputCount;
            int outputCount = this.ctx.PluginInfo.AudioOutputCount;
           
            this.inputMgr = new VstAudioBufferManager(inputCount, blockSize);
            this.outputMgr = new VstAudioBufferManager(outputCount, blockSize);

            foreach (VstAudioBuffer buffer in inputMgr.ToArray())
            {
                for (int i = 0; i < blockSize; i++)
                {
                    buffer[i] = 0; // Initialize to 0
                }
            }

            this.ctx.PluginCommandStub.SetBlockSize(blockSize);
            this.ctx.PluginCommandStub.SetSampleRate(sampleRate);
            this.inputBuffers = inputMgr.ToArray();
            this.outputBuffers = outputMgr.ToArray();

           
        }

 

// I use this to pass midi data into the VSTi again happens when the user clicks a button

private void processMidiOnce(int volume, int pitch)
        {
            this.ctx.PluginCommandStub.StartProcess();
            VstEvent[] vEvent = createEvent(volume, pitch);
            this.ctx.PluginCommandStub.ProcessEvents(vEvent);           
           
        }

//This function then calls process replacing and fills the audio output buffers, this is called in a while loop (this is the function i am most unsure about where and when to call)

private void processBuffers(VstAudioBuffer[] inputBuffers, VstAudioBuffer[] outputBuffers, int blockSize)
        {
            this.ctx.PluginCommandStub.ProcessReplacing(inputBuffers, outputBuffers);

            for (int i = 0;/* i < inputBuffers.Length &&*/ i < outputBuffers.Length; i++)
            {
                for (int j = 0; j < blockSize; j++)
                {
                    if (outputBuffers[i][j] != 0.0)
                    {
                        // IF THIS LINE IS HIT YOU HAVE SOMETHING PLAYING
                        //outputBuffers[i][j] = outputBuffers[i][j] * Byte.MaxValue;
                        Globals.globuf[j] = outputBuffers[i][j];

                    }
                }
            }
        }

// and to dispose of objects

private void VSToff()
        {
            // Close resources
            this.ctx.PluginCommandStub.StopProcess();
            this.ctx.PluginCommandStub.MainsChanged(false);
            this.inputMgr.Dispose();
            this.outputMgr.Dispose();
           
            this.ctx.Dispose();
           
        }

 

I am still getting the stuttering sound and I believe you maybe right Yury, as when i look at the globuf there are periods of many zeros. globuf is declared as: static internal float[] globuf = new float[65536]; I have tried lowering the array size but this throws an error.

 

I have also tried using waveprovider32 but it throws the following error:

The runtime has encountered a fatal error. The address of the error was at 0x52fd3a3a, on thread 0x1df0. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.

 

Im really greatful for any further light you can shed as to what might be wrong as I have been trying to resolve this for weeks!

 

Thanks once again,

Perm

Apr 11, 2011 at 9:42 PM
Edited Apr 11, 2011 at 9:46 PM

I still have the sources to this project but there's a few bits I don't want to distribute at large. It's a very basic proof of concept that can load plugin, open GUI and play Vst note.

If you want it send me an email by clicking on my username then 'Contact YuryK' so I can send the attachment.

Also I'm working on a different (and more complete) VstHost that is 100% C code using VstSdk and PortAudio. The Host and Playback engine are wrapped in a single C dll conveniently called from .Net PInvoke.

I'm thinking about publishing the sources to this project if there's interest although it's better not to discuss about it on this forum because it doesn't use VST.NET. 

Regards,

Yury

Coordinator
Apr 12, 2011 at 7:01 AM

@Perm:
Is there some way to discover where those 0-values come from?
I see you commented-out the buffer data conversion...?

BTW: You call StartProcess when trying to output a Midi note. The Start- and StopProcess are meant to indicatie that the audio engine in host is started or stopped. In simple test cases like this, pair them up with the MainChanged calls.

@Yury: Start your own codeplex project ;-) kvraudio.com has a great dsp forum that would be a great place to discuss it.

 

Apr 12, 2011 at 4:50 PM

Ok I've figured out why the audio is stuttering, and you were pretty much right Yury. My read function for playback is as follows:

 

 public override int Read(byte[] buffer, int offset, int sampleCount)

        {

            for (int n = 0; n < buffer.Length; n++)
            {
                buffer[n] = Globals.globuf[n]; // globuf is now a byte array of size [1024]
            }
            return sampleCount;
        }
    }

I looked at the values of buffer and found that the length of it was far longer than globuf. Passed the length of globuf buffer was just being filled with 0's. I tested Yuryk's theory by coping a sine wave into any buffer values set at zero using the following code:

 

 public override int Read(byte[] buffer, int offset, int sampleCount)

        {
            // FILL THE AUDIO BUFFER WITH THE VST BUFFER HERE
          //  byte[] tempBuffer = vsti.processReplace(sampleCount);

            // Copying Vst buffer inside Audio buffer, no conversion needed for WaveProvider32
           // for (int i = 0; i < tempBuffer.Length; i++)
             //   buffer[i] = tempBuffer[i];


            // LET'S PLAY A SINE WAVE FOR NOW...
            int sampleRate = 44100;
            double amplitude = 0.25 * byte.MaxValue;
            double frequency = 150;

            for (int n = 0; n < buffer.Length; n++)
            {
                // buffer[n] = (byte)(amplitude * Math.Sin((2 * Math.PI * n * frequency) / sampleRate));
                buffer[n] = Globals.globuf[n];
                if (buffer[n] == 0)
                {
                    buffer[n] = (byte)(amplitude * Math.Sin((2 * Math.PI * n * frequency) / sampleRate)); // copying sine way into empty buffer.
                }
            }
            return sampleCount;
        }
    }

 

This produced a continous mix of VST audio and sine wave.

My dilema now is i am unsure where to go: should i just alter the length of buffer[n] to match globuf (if so how as it is a parameter passed in by an inherited object (i think, you will have to forgive me I have only been using C# since November) or am I not converting globuf correctly, the VSTi seems to only output 1024 samples per buffer, should i lengthen this?

Perm

Apr 12, 2011 at 5:04 PM

Right I've got it! I changed the blockSize when initializing the output managers to match the buffer.Length attribute in the read function. The sound is now continuous wahoo! Still not sure if this is the best way to do this but it works! However it is still 8-bit audio, not to good really, if anyone has an explanation for this error:

"The runtime has encountered a fatal error. The address of the error was at 0x52fd3a3a, on thread 0x1df0. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack."

I would be very greatful as it occurs every time i try to implement Waveprovider32 using this code:

class VSTStream : WaveProvider32
    {

        public override int Read(float[] buffer, int offset, int sampleCount)

        {

            for (int n = 0; n < buffer.Length; n++)
            {
               
                buffer[n] = Globals.globuf[n];
               
            }
            return sampleCount;
        }
    }

Coordinator
Apr 12, 2011 at 6:03 PM

Do you know ASIO.NET?

http://www.codeproject.com/KB/audio-video/Asio_Net.aspx

Would be something I would consider using when building a Host...

Hope it helps,
Marc

Apr 12, 2011 at 8:54 PM
"The runtime has encountered a fatal error. The address of the error was at 0x52fd3a3a, on thread 0x1df0. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack."

It's hard to determine the cause of this bug, maybe you could boil it down to the simplest code that will trigger the error.

One bug I had with NAudio is instancing the WaveOut from a non UI thread, you need to add one parameter :

IWavePlayer waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback());