-
Notifications
You must be signed in to change notification settings - Fork 12
A Grain Class
The Grain
class will live in it's own header file so it's a little easier to read and reuse. New files are best added to the project with the Projucer:
You need to re-save the Projucer project for the file to appear in your IDE.
Now we need to add an include
statement to the file "PluginProcessor.h":
#include "Grain.h"
When implementing a class it is always a good idea to take a moment and think about what information should be stored in that class. For now it will just be one function which takes two buffers as arguments and writes a sample at a specific position (which is also passed as an argument) from one buffer into the other. We could also imagine the class holding it's own information about the position from which to read but for reasons that we will go into in the next chapter I implemented the plugin with a "global" position from which everything derives.
Starting out our Grain
class will look like this:
class Grain
{
// [1]
public:
Grain(){};
~Grain(){};
// [2.1]
void process (AudioSampleBuffer& currentBlock, AudioSampleBuffer& fileBuffer, int numChannels, int blockNumSamples, int fileNumSamples, int time)
{
// [2.2]
for(int channel=0; channel<numChannels; ++channel){
float* channelData = currentBlock.getWritePointer(channel);
const float* fileData = fileBuffer.getReadPointer(channel%fileBuffer.getNumChannels());
// [2.3]
channelData[time%blockNumSamples] += fileData[time%fileNumSamples];
}
}
};
- At the top of the class we see the access specifier
public
and the (empty) constructor and deconstructor. - This is the
process
member function. It is basically theprocessBlock
function we wrote at the end of the last chapter with some changes: - The function now takes a few more arguments:
- The
AudioSampleBuffer
is now calledcurrentBlock
to make the code more readable. - The second argument is also a reference to anAudioSampleBuffer
. But instead of the audio-block here we are referencing the buffer holding the data of our sound file. - We also have arguments for the number of channels and the number of samples in the block and in the loaded file. This function will be executed every sample (and later more than once for every sample). We don't expect these values to change much so we pass them in. - the last argument is an integer calledtime
from which we will derive the position to read from. - We are now only iterating over the channels because the function will be called every sample from the
processBlock
function in theAudioProcessor
. - We are now using the function argument
time
instead of thefilePosition
variable. We are also using the+=
instead of the=
operator. This results in the sample data from this grain being added to the current audio block instead of overwriting it allowing for multiple overlapping audible grains.
First we'll delete the filePosition
variable and substitute it with new variable long long int time
(time will be represented as samples resulting in the values getting quite big so we use a 64bit integer to avoid overflows) in the header of the AudioProcessor
class we will also assign time the value 0
in the constructor of the class. We must also make sure we initialize our grain variable in the constructors initialization list:
GrrnlrrAudioProcessor::GrrnlrrAudioProcessor() : grain(Grain())
{
time = 0;
Grain grain = *new Grain();
}
Our updated processBlock
function looks like this:
void GrrnlrrAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
const int numSamplesInBlock = buffer.getNumSamples();
const int numSamplesInFile = fileBuffer.getNumSamples();
for (int i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); ++i)
buffer.clear (i, 0, buffer.getNumSamples());
// return from the function if there's nothing in our buffer
if(fileBuffer.getNumSamples() == 0) return;
for (int i=0; i<numSamplesInBlock; ++i){
grain.process(buffer, fileBuffer, buffer.getNumChannels(), numSamplesInBlock, numSamplesInFile, time); // [1]
++time; // [2]
}
}
- We here access the member function
process
of the grain object that we just wrote. - We increment the time by 1 every sample.
At this point one should once again compile and test if everything works!
Next we will implement threads.