The Red Penguin

Realtime sound synthesis in JUCE


Thursday 30 July 2020

This introduces the various functions needed to play sound in JUCE.

Firstly: I had to do this in MainComponent::MainComponent in MainComponent.cpp (line 20):

setAudioChannels (0, 2);


No sound works unless I do this!

We are going to create a random noise generator.

1. We need to generate random numbers. JUCE has a class called Random which is perfect for doing this.

So in MainComponent.h in private: we can add a random object (just under juce::Slider volSlider;)

juce::Random rand;

2. Go to the MainComponent.cpp file and find the getNextAudioBlock function (look for void MainComponent::getNextAudioBlock)

We removed the comments so we are just left with bufferToFill.clearActiveBufferRegion(); This fills the buffer with zeroes and we commented this out for now.

We need to call some special functions to get random numbers into here. So we assign a new variable called leftChan:

    auto* leftChan = bufferToFill.buffer->getWritePointer(0, bufferToFill.startSample);


The arguments are 0 for the channel and bufferToFill.startSample for the sample position.

To fill the buffer up with samples we can use a For loop:

for (auto i=0; i < bufferToFill.numSamples; ++i)
{
double sample = rand.nextDouble() * 0.25;
leftChan[i] = sample;
rightChan[i] = sample;
}


Note that ++i is more efficient in a loop because i++ increments and assigns, whereas i++ makes a copy, increments and assigns. That's especially important in an audio loop, because for CD quality we need to generate 44100 samples per second.

numSamples is the number of samples the buffer requires.

double sample = rand.nextDouble(); generates a random number

We multiplied it by 0.25 to restrict the number between 0.25 and -0.25, which will stop the noise being super loud.

We then added the code for the right channel too:

under auto* leftChan we write: auto* rightChan = bufferToFill.buffer->getWritePointer(0, bufferToFill.startSample); // exactly the same as for left

under leftChan[i] = sample; we write: rightChan[i] = sample;

When I coded this first time, it didn't work, but it did after I set the channels to 0, 2 as mentioned at the top

We then played about with pitch, and looked to create a sawtooth waveform.

1. We need to create a variable called phase in MainComponent.h, just under our Random rand;

double phase;

2. We then went to MainComponent.cpp and looked for MainComponent::prepareToPlay function. We removed all the commented lines. We initialised phase to zero.

phase = 0.0;

3. We then need to use phase to compute the waveform. We went to MainComponent::getNextAudioBlock and commented out the noise generator line and we then get the sample a new way:

double sample = fmod(phase, 0.2);

4. Finally we need to increase phase so we added

phase += 0.1;

Function now looks like this:

void MainComponent::getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill)
{

auto* leftChan = bufferToFill.buffer->getWritePointer(0, bufferToFill.startSample);
auto* rightChan = bufferToFill.buffer->getWritePointer(0, bufferToFill.startSample);

for (auto i = 0; i < bufferToFill.numSamples; ++i)
{
// double sample = rand.nextDouble() * 0.25;
double sample = fmod(phase, 0.2);

leftChan[i] = sample;
rightChan[i] = sample;

phase += 0.005;
}

// bufferToFill.clearActiveBufferRegion();
}


We now added another variable to the header - dphase, which is the change in phase. So in .h we add

double dphase;

In the .cpp in MainComponent::prepareToPlay we set dphase to 0.0001

dphase = 0.0001;

We then add this to phase in the audio loop, to change the pitch.

phase += dphase;

We're now going to use the volSlider to change the frequency. So we go to void MainComponent::sliderValueChanged(juce::Slider* slider) and type this:


void MainComponent::sliderValueChanged(juce::Slider* slider)
{
if (slider == &volSlider)
{
// DBG("Vol slider moved to " << slider->getValue());
dphase = volSlider.getValue() * 0.001;
}
}