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;
}
}
More posts in cpp
- Add a component ID and converting between ints and strings
- Implement a play button and add a listener
- Implement paintRowBackground and paintCell
- Add a vector to store a list of files
- Add a TableListBox
- Create a PlaylistComponent in the Projucer project
- Threads
- Implement a timer
- Add getPosition and setPosition functions
- Refactor DJAudioPlayer to use app-scope formatManager
- Draw the thumbnail
- Hook up the load button to trigger the AudioThumbnail load
- The AudioThumbnail class in the API
- Creating a new component - WaveformDisplay
- Implementing drag and drop triggers
- Use a MixerAudioSource to play more than one file at a time
- Implement the listener interfaces to DeckGUI
- Creating a DeckGUI class
- setPosition control
- Implementing setGain and setSpeed
- Add audio playback functionality
- Writing the DJAudioPlayer class
- Creating a new JUCE class with Projucer
- Refactoring our code
- Using ResamplingAudioPlayer to implement variable speed playback
- Add stop, start and volume functionality
- Add a file chooser
- Audio file playback in JUCE
- Realtime sound synthesis in JUCE
- Adding a slider listener
- Introduction to event listeners
- Macros
- Inheritance
- Adding a GUI widget to the JUCE app
- Introduction to JUCE