I’m having a problem correctly implementing a Juce Synthesiser plug-in.
I can get my synth to function how I want it to, producing sin waves in response to midi-on messages and allowing the sound to tail off after the note is released. However, I noticed that, when releasing the midi key, there is often a “pop” in the audio output.
I have spent a few days trying to figure this out. To simplify things for troubleshooting, I started a brand new plug-in in the projucer, and made the most basic SynthesiserVoice and SynthesiserSound subclasses possible. The sound automatically plays on all channels and notes. The voice’s startNote and stopNote functions are empty. The voice “renderNextBlock” function returns “0.25” for all samples of all channels.
When I open the plugin in the Juce host, there is an expected pop as the speaker adjusts to the 0.25 position. And when I start pressing midi keys, there is a click for each on and off message. If i adjust the “renderNextBlock” function to return 0.0 instead of 0.25, there is no click when pressing keys.
The “renderNextBlock” function should be filling the buffer with a consistent value. It seems like midi on-off messages are zeroing some of the buffer outside the “renderNextBlock” function. I don’t know how to debug where/how this is happening.
Any help would be much appreciated. I feel like the answer must be incredibly simple, but I just can’t find it.
Are you sure that you’re rendering only from the startSample upto numSamples in the renderNextBlock() callback? A common cause of clicks is to assume startSample is always zero.
I do set startSample to zero. However, I’m working on an audio plugin, as opposed to an audio application. The audio processing function “processBlock” in an audio plugin gets passed an AudioSampleBuffer which does not have a “startSample” member. From every Juce plugin example I can find online, processing begins at index 0 of the buffer.
Is there a way to determine the startsample for an AudioSampleBuffer in this context other than zero?
You both are talking of different things:
Yes, the AudioProcessor::processBlock() method expects the whole buffer to pe processed, i.e. either copy samples into or clearing it.
When you call Synthesiser:::renderNextBlock(), I would say, calling from 0 to buffer.getNumSamples() should be correct, because, the midi buffer is responsible to determine the offsets.
Maybe if you don’t mind sharing your AudioProcessor::processBlock() and your SynthesiserVoiice::renderNextBlock() implementation? (maybe simplified, if you are worried about IP details…)
That makes sense. And here’s all code that might be relevant. There’s not much IP at this point As you can see, I haven’t changed much from what the projucer generates automatically.
@trickyflemming
I see what you mean, but I don’t think that could be the problem here. When I call SynthesiserVoice::renderNextBlock from AudioProcessor::processBlock, I pass a hard-coded “0” in as the startSample value. So startSample can never be anything other than 0.
This is probably my own lack of knowledge or experience but im having a similar problem with my project. Everything works fine in one DAW (FL) but in another like Live or reaper i get these glitches that look like an interruption in processblock when midi note on and off messages are received. Also moving mod wheel or pitch wheel is causing lots of glitches in these daws (same asio driver in all hosts).
For example when i even remove everything and set a constant value like 0.5 for the buffer (inside processblock called by renderNextBlock). the output is constant 0.5 but with two short moments that the output gets 0.0 and it happens at the moment midi on/off messages are recieved.
I’ve already studied the examples and doing a similar thing. I didn’t notice any problems until i tried another host. Can you guys point me to the direction that might cause something like this?
As Joshua says post your code. But to me that sounds like a classic case of not respecting the startSample value and always rendering from the first sample in the block (as per my post above). MIDI messages arrive at offsets within a block. But post your code!
void SynthVoice::processBlock(AudioBuffer<FloatType>& outputBuffer, int startSample, int numSamples)
{
if (Envelope.State > 0)
{
for (int SN = startSample; SN < numSamples; SN++)
{
...
float EnvVelo = Envelope.GetValue(); //release done -> state = 0
outputBuffer.setSample(0, SN, (OscillatorOutputL*EnvVelo));
outputBuffer.setSample(1, SN, (OscillatorOutputR*EnvVelo));
}
}
if (Envelope.State == 0)
{
clearCurrentNote();
}
}
void SynthVoice::startNote(int midiNoteNumber, float velocity, SynthesiserSound* /*sound*/, int /*currentPitchWheelPosition*/)
{
Freq = ...;
Envelope.setGate(1); //state = 1 & attack
}
void SynthVoice::stopNote(float /*velocity*/, bool allowTailOff)
{
if (allowTailOff)
{
Envelope.SetGate(0); //Release
}
else
{
ResetStuff();
clearCurrentNote();
}
}
of course i have added some sound and voice to the Synth in the
audioProcessor and in its processBlock i just pass it the buffer: Synth.renderNextBlock(buffer, midiMessages, 0, numSamples);
here buffer is filled with 0.6, but as you can see whenever a midi event is happening, in this case midi on and off, process block takes a break and starts again exactly where it has left off (tried it with sine wave).
And again this is not happening in FL for some reason, but so far in live and reaper its always happening. Constant glitches like that when i move mod or pitch wheels. Looks like midi messages are not being handled properly. What do you think im doing wrong or forgetting to do?
That’s probably part of it. So a similar bug to the one I mentioned but slightly different. That doesn’t count the correct number of samples. If you’re near the end of the buffer then startSample will be large and numSamples will be small so that loop won’t run at all
Dude! That worked (i have to test it further but it seems glitches are gone for good). Thanks for the help.
Im not sure what exactly happening here. We’re always giving it a startSample of 0 Synth.renderNextBlock(buffer, midiMessages, 0, numSamples);.
So it can start from any sample it wants and loop back?
Shouldn’t i be worried about (startSample + numSamples) exceeding the buffer size or something like that?
edit: to be safe i changed to a while loop and there is no issues.
Yes, but internally the Synthesiser will subdivide the block based on the timestamps of the MIDI messages. Then your voices process these sub-blocks based on the sub-block offset (startSample) and the number of samples to the start of the next sub-block (numSamples).
We had a similar issue, and using @martinrobinson-2’s method fixed it. I didn’t see this asked or mentioned, but how come there is no offset issue (the glitchy sound) when pressing the PC keyboard or clicking a note with the mouse?
That is almost certainly because most DAWs will probably collect messages from the PC keyboard on the message thread then insert them at position 0 on the next audio thread callback, whereas real MIDI messages from devices will have timestamps that may be distributed throughout the audio block for a callback
I’m having the same issue and I tried @martinrobinson-2 solution but it seems like using startSample + numSamples causes the for loop to exceed the buffer size. My synth voice subclass is running this function:
void FmVoice::renderNextBlock(juce::AudioBuffer<float> &outputBuffer, int startSample, int numSamples)
{
for (int i = startSample; i < (numSamples + startSample); ++i)
{
. . .
for(int channel = 0; channel < outputBuffer.getNumChannels(); ++channel)
{
outputBuffer.setSample(channel, startSample, sum);
}
++startSample;
}
}
Inside the audio processor I just call this with startSample always as 0. Is this an issue with not handling the buffer’s size correctly?