Hey,
I am currently using JUCE for the first time and my plan is to implement a small, standalone subtractive Synth.
The structure is as follows:
- the MainComponent inherits AudioAppComponent and ChangeListener
- the MainComponent containts a Synthesiser, a MidiMessageCollector and an AudioSetupComponent
- I implemented a SynthesiserVoice which consist of a ProcessorChain<Oscillator, Gain> that I added to the Synthesiser in the MainComponent
- I use the AudioDeviceSelectorComponent to change the MidiDevice, and the ChangeListenerCallback to register the MidiCallback of the selected devices
What works: selecting Midi devices, recieving Midi notes and passing them through a ProcessContextReplacing to the processorChain. The Synth plays the notes and stops when I let go.
Now the Issue: As soon as I start generating CCs or Aftertouch my Audio starts crackling really bad.
Weirdly the controllerMoved method of my SynthVoice never gets called.
SytnthVoice Implentation:
SynthVoice::SynthVoice() {
auto &osc = processorChain.get<oscillatorIndex>();
osc.initialise([](float x) { return x / juce::MathConstants<float>::pi; },
100);
osc.setFrequency(440.0f);
auto &gain = processorChain.get<gainIndex>();
gain.setGainLinear(0.0f);
}
bool SynthVoice::canPlaySound(juce::SynthesiserSound *sound) {
return dynamic_cast<SynthSound *>(sound) != 0;
}
void SynthVoice::startNote(int midiNoteNumber, float velocity,
juce::SynthesiserSound *sound,
int currentPitchWheelPosition) {
noteNumber = midiNoteNumber;
frequency = noteHz(midiNoteNumber, 0);
level = velocity;
auto &osc = processorChain.get<oscillatorIndex>();
osc.setFrequency(frequency);
auto &gain = processorChain.get<gainIndex>();
gain.setGainLinear(0.125f);
}
void SynthVoice::stopNote(float velocity, bool allowTailOff) {
clearCurrentNote();
auto &gain = processorChain.get<gainIndex>();
gain.setGainLinear(0.0f);
}
void SynthVoice::pitchWheelMoved(int newPitchWheelValue) {}
void SynthVoice::controllerMoved(int controllerNumber, int newControllerValue) {
}
void SynthVoice::prepare(const juce::dsp::ProcessSpec &spec) {
processorChain.prepare(spec);
}
void SynthVoice::renderNextBlock(juce::AudioBuffer<float> &outputBuffer,
int startSample, int numSamples) {
juce::dsp::AudioBlock<float> block(outputBuffer, startSample);
juce::dsp::ProcessContextReplacing<float> context(block);
processorChain.process(context);
}
MainComponent Implementation:
MainComponent::MainComponent()
: audioSetupComponent(deviceManager, 0, 0, 0, 2, true, true, true, true) {
setSize(800, 600);
synth.clearVoices();
synth.clearSounds();
synth.addVoice(new SynthVoice);
synth.addSound(new SynthSound);
deviceManager.addChangeListener(this);
if (juce::RuntimePermissions::isRequired(
juce::RuntimePermissions::recordAudio) &&
!juce::RuntimePermissions::isGranted(
juce::RuntimePermissions::recordAudio)) {
juce::RuntimePermissions::request(
juce::RuntimePermissions::recordAudio,
[&](bool granted) { setAudioChannels(0, 2); });
} else {
setAudioChannels(0, 2);
}
addAndMakeVisible(audioSetupComponent);
}
MainComponent::~MainComponent() { shutdownAudio(); }
//==============================================================================
void MainComponent::prepareToPlay(int samplesPerBlockExpected,
double sampleRate) {
synth.setCurrentPlaybackSampleRate(sampleRate);
auto voice = dynamic_cast<SynthVoice *>(synth.getVoice(0));
voice->prepare({sampleRate, (juce::uint32)samplesPerBlockExpected, 2});
midiCollector.reset(sampleRate);
}
void MainComponent::getNextAudioBlock(
const juce::AudioSourceChannelInfo &bufferToFill) {
bufferToFill.clearActiveBufferRegion();
int numSamples = bufferToFill.numSamples;
int startSample = bufferToFill.startSample;
juce::MidiBuffer midiBuffer;
midiCollector.removeNextBlockOfMessages(midiBuffer, numSamples);
synth.renderNextBlock(*bufferToFill.buffer, midiBuffer, startSample,
numSamples);
}
void MainComponent::releaseResources() {}
//==============================================================================
void MainComponent::paint(juce::Graphics &g) {
g.fillAll(
getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
}
void MainComponent::resized() {
audioSetupComponent.setBounds(0, 50, getWidth() - 210, 20);
}
void MainComponent::changeListenerCallback(juce::ChangeBroadcaster *source) {
auto midiDevices = juce::MidiInput::getAvailableDevices();
for (auto dev : midiDevices) {
if (deviceManager.isMidiInputDeviceEnabled(dev.identifier)) {
deviceManager.addMidiInputDeviceCallback(dev.identifier,
&midiCollector);
} else {
deviceManager.removeMidiInputDeviceCallback(dev.identifier,
&midiCollector);
}
}
}
}
I am using Linux (tried ALSA, and Jack) and the Issue seems to be indipendent of samplerate and buffersize. I can send more of my source code if it helps, but not much is happening there.
Thanks in advance for any guidance