-
Notifications
You must be signed in to change notification settings - Fork 12
Make Grains End
As it is now we create a single Grain
object at the start of the program execution. That grain loops forever. The first thing we can do now is make that grain stop at some point.
The first thing to do here is adding member variables for the length and the onset time of the grain to the class. We will also be adding a startPosition member but we won't be using it until the next chapter. For that we declare the member variables and initialize them in the constructor:
public:
int onset;
int length;
int startPosition;
Grain(int onset, int length, int startPos) : onset(onset), length(length), startPosition(startPos) // [1]
{
};
Grain()
{
onset = 0;
length = 0;
startPosition = 0;
}
~Grain(){};
Note that we now have two constructors. The first is the one we'll be actually using. The second one is the default constructor that gets called whenever we don't supply arguments for the creation of a new grain.
We also need to adjust the Grain
classes process
member to take the onset value into account:
void process (AudioSampleBuffer& currentBlock, AudioSampleBuffer& fileBuffer, int numChannels, int blockNumSamples, int fileNumSamples, int time)
{
for(int channel=0; channel<numChannels; ++channel){
float* channelData = currentBlock.getWritePointer(channel);
const float* fileData = fileBuffer.getReadPointer(channel%fileBuffer.getNumChannels());
int position = (time - onset) + startPosition;
channelData[time % blockNumSamples] = fileData[position % fileNumSamples];
}
}
For this we define an integer position
that offsets the global time by the value in samples. We now use this 'position' variable to index into our buffer. This will assure that the grain will start playing the buffer from startPosition
no matter how far we schedule it into the future.
Then we delete our grain
variable from the AudioProcessor
instead declaring an array called grainStack
. Note that this is an Array
class defined in the JUCE framework that is different from the std::array
or the C-style Array
classes. Most notably it allows adding and removing of elements much like the vector
class but also sorting of the array and other useful features.
The type of object that we want the array to hold is supplied in the declaration:
Array<Grain> grainStack;
For now we will be only adding one grain to array in the constructor of our AudioProcessor
class. Later we will write a scheduling loop that will add new grains to the array. Here's our updated constructor:
time = 0;
grainStack.add( Grain(88200, 44100, 0) );
Here we create a new Grain
object with the values for onset and length of 88200 and 44100. We plan to test with these values if the grain should be playing or not. startPosition
will remain 0 for now.
Also we have to do some changes to the processBlock
function:
// [...]
const Array<Grain> localStack = grainStack; // [1]
// [2]
for (int i = 0; i < numSamplesInBlock; ++i) {
if (localStack[0].onset < time) {
if (time < (localStack[0].onset + localStack[0].length)) {
localStack[0].process(buffer, *currentBuffer, buffer.getNumChannels(), numSamplesInBlock, numSamplesInFile, time);
}
}
++time; // increment time
}
// [...]
- We make a copy of the grainStack so when we add and delete grains we don't cause undefined behavior. This array will be declared
const
because we don't want it to change. If it is changed anyway the compiler will throw an error. This is preferred because it will generate a readable error message instead of causing undefined behavior which might lead to a crash without that is much harder to debug. - Here we first check if the time is greater then the onset (start time) of the grain, and afterwards if the time is not greater than the time the grain starts added to its length. If thats both the case we process the grain in
localStack
.
In the next chapter we will implement a scheduling loop that will trigger grains with a random value for startPosition
.