Saturday 11 May 2013

The Audio Processing

Now it is time for us to write the actual processing in our plug-in.

All we want to do is take the value stored in our 'gain' variable and multiply the incoming audio by it.

The is done in the 'processBlock' function, which should currently look something like this:
void GainPluginAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
    // This is the place where you'd normally do the guts of your plugin's
    // audio processing...
    for (int channel = 0; channel < getNumInputChannels(); ++channel)
    {
        float* channelData = buffer.getSampleData (channel);

        // ..do something to the data...
    }

    // In case we have more outputs than inputs, we'll clear any output
    // channels that didn't contain input data, (because these aren't
    // guaranteed to be empty - they may contain garbage).
    for (int i = getNumInputChannels(); i < getNumOutputChannels(); ++i)
    {
        buffer.clear (i, 0, buffer.getNumSamples());
    }
}
You may now be thinking "Gulp! There is a lot of code there." Don't worry, we don't need to pay attention to all of it for now.

Firstly, any line that starts with a '//' is a comment (if you are looking at the code in The Introjucer they will be written in green text). These are not part of the code, they are put in by programmers to explain the code. Read them, they are useful.

Let's look at the first line now. We have a function called 'processBlock' which has two input arguments and returns 'void'. Looking at the input arguments you might be thinking "Oh No! Arbitrary punctuation, what does this mean". I'm fairly certain most people have this reaction when they first start programming in C/C++ . As your knowledge of the language progresses you will become much more comfortable with these things. Rather than describe exactly what it means (there are plenty of books/websites which will tell you that) I will try and give a brief overview.

The plug-in host gives audio to our plug-in in blocks called 'buffers'. These buffers contain the audio samples to be processed. Now, the functions we have seen so far are given data from the input arguments and then give some other data back using the 'return' keyword. The 'processBlock' function does things a different way. In essence, rather than send the buffer of samples to the function and expect a processed buffer of samples in return, the host gives the function access to edit the samples directly. This means we can do processing on the input arguments as if they were the data itself. This is what those pesky looking ampersands are doing in our code.

Now we have discussed that, we can look at what the input arguments are. We have an 'AudioSampleBuffer' called 'buffer' and a 'MidiBuffer' called 'midiMessages'. We are not doing anything with midi so let's ignore this second argument.

The first argument is an 'AudioSampleBuffer', this is an object that exists within the JUCE API to make things easier for us. All we need to know for now, is that it contains all the samples that we need to process.

Next let's look at the last bit of the code:
// In case we have more outputs than inputs, we'll clear any output
    // channels that didn't contain input data, (because these aren't
    // guaranteed to be empty - they may contain garbage).
    for (int i = getNumInputChannels(); i < getNumOutputChannels(); ++i)
    {
        buffer.clear (i, 0, buffer.getNumSamples());
    }
This has comments to describe what it does. We don't need to worry about changing it. Just leave it where it is.

That leaves this bit:
// This is the place where you'd normally do the guts of your plugin's
    // audio processing...
    for (int channel = 0; channel < getNumInputChannels(); ++channel)
    {
        float* channelData = buffer.getSampleData (channel);

        // ..do something to the data...
    }
We don't need any of that bit, so let's get rid of it and replace it with:
buffer.applyGain (gain);
This is, again, new syntax for you. That JUCE object 'AudioSampleBuffer', I mentioned earlier, has a bunch of functions associated with it (called its methods). We are using the 'applyGain' method here. This takes a float as an input argument and multiplies all the samples in the 'AudioSampleBuffer' by it. We are taking the float stored in our 'gain' variable and applying it to the samples in the 'AudioSampleBuffer' called 'buffer' (the input argument).

Remember that because of those ampersands we saw earlier, processing this input argument processes the host's samples themselves. We do not need to return the processed samples back to the host.

That is our processing done! Your 'processBlock' should now look something like this:
void GainPluginAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
    buffer.applyGain (gain);

    // In case we have more outputs than inputs, we'll clear any output
    // channels that didn't contain input data, (because these aren't
    // guaranteed to be empty - they may contain garbage).
    for (int i = getNumInputChannels(); i < getNumOutputChannels(); ++i)
    {
        buffer.clear (i, 0, buffer.getNumSamples());
    }
}
Save these changes to your project and we can move to the last bit of code we need to edit.

Have a nice day now!
Sean

4 comments:

  1. Hey thanks for the tutorial...
    I found a way to acess samples easily, you could raplace
    buffer.applyGain (gain);

    with:

    float* leftData = buffer.getSampleData(0);
    float* rightData = buffer.getSampleData(1);
    for(long i=0; i<buffer.getNumSamples();i++)
    {
    leftData[i]=leftData[i]*gain;
    rightData[i]=rightData[i]*gain;
    }
    and this way you get something closer to the steinberg sdk way of processing the samples.

    ReplyDelete
    Replies
    1. That is indeed the way to go if you want to do your own DSP.

      Be aware that the getSampleData() function has been deprecated in the most recent versions of JUCE. It has been replaced by the getReadPointer() and getWritePointer() functions.

      Have fun developing plug-ins.

      Delete
    2. I tried to substitute the getSampleData() function from his example with your getReadPointer() and getWritePointer() but it didn't seem to work. Any idea how it would be done?

      Delete
    3. What errors are you getting?

      A lot of the information in this tutorial is very out of date now, JUCE has changed a lot in the last few years.

      Delete