I came up with a pretty solid solution. All of my voices already have access to the synth that owns them, so I created an audio buffer in my synth for voices to emergency write to.
void SmoothWaveVoice::stopNote (float /*velocity*/, bool allowTailOff) {
filterAdsr.noteOff();
gainAdsr.noteOff();
if (allowTailOff == false) {
int numSamples = SmoothWaveSynth::FRAMES_TO_FADE;
synth->voiceStolenFadeNumSamples = numSamples;
renderNextBlock(synth->voiceStolenFade, synth->voiceStolenFadeCurrentSample, numSamples, true);
gainAdsr.reset();
filterAdsr.reset();
clearCurrentNote();
}
}
Then in my renderNextBlock function, I was already rendering the entire envelope at once, so I just grab the last value and then do a linear fade which will make up the difference. For example, if at the end of these samples the env gain would be 0.25, a linear fade to drop by that much is mixed in. But if the env gain would be 0 already, nothing is done. In this way, it doesn’t mess with my curved releases any more than is absolutely necessary.
void SmoothWaveVoice::renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples, bool stolen) {
float gainEnv[numSamples];
int s = 0;
for (; s < numSamples; s++) {
if (gainAdsr.isActive() == false) {
if (s == 0) { return; }
numSamples = s;
break;
}
gainEnv[s] = gainAdsr.getNextSample();
}
float gainLossRate = 0.0f;
if (stolen) {
gainLossRate = gainEnv[s-1]/numSamples;
}
.... actual rendering code ...
}
The hardest part of this was that my voiceStolenFade
AudioBuffer could not consistently be the size of any render phases outputBuffer, so I made a rotating buffer and manually copy the frames over… I don’t really like the following code, it feels too verbose. But it works for now.
void SmoothWaveSynth::renderNextBlock(
AudioBuffer< float > & outputAudio,
const MidiBuffer & inputMidi,
int startSample,
int numSamples
) {
Synthesiser::renderNextBlock(outputAudio, inputMidi, startSample, numSamples);
if (voiceStolenFadeNumSamples > 0) {
for (auto i = outputAudio.getNumChannels(); --i >= 0;) {
for (int s = 0; s < numSamples; s++) {
int destSampleNum = startSample + s;
int sourceSampleNum = (voiceStolenFadeCurrentSample + s) % FRAMES_TO_FADE;
outputAudio.addSample(
i,
destSampleNum,
voiceStolenFade.getSample(i, sourceSampleNum)
);
}
}
voiceStolenFadeNumSamples -= numSamples;
if (voiceStolenFadeCurrentSample + numSamples > FRAMES_TO_FADE) {
int tailSamples = FRAMES_TO_FADE - voiceStolenFadeCurrentSample;
voiceStolenFade.clear(voiceStolenFadeCurrentSample, tailSamples);
voiceStolenFade.clear(0, numSamples - tailSamples);
} else {
voiceStolenFade.clear(voiceStolenFadeCurrentSample, numSamples);
}
voiceStolenFadeCurrentSample += numSamples;
voiceStolenFadeCurrentSample %= FRAMES_TO_FADE;
}
Any pointers on any of this code would be welcome.
Also, I’m not sure where the problem lies in this, but this code still pops if I really hammer on some long release keys in AudioPluginHost
, but when I use the vst in Bitwig, there’s no pops at all.
Do you guys know if AudioPluginHost is at fault, or is Bitwig just idiot proofing my audio?