Hello there, I am trying to implement ADSR Functions into the DSP Tutorial. However, when I click multiple notes it starts to sounds buggy. The first note seems to play correct. But when I play multiple notes at once the sound either breaks completely or overdrives.
My Voice Class:
#include "Voice.h"
#include "JuceHeader.h"
#include "CustomOscillator.cpp"
class Voice : public juce::MPESynthesiserVoice
{
public:
Voice()
{
auto& masterGain = processorChain.get<masterGainIndex>();
masterGain.setGainLinear (0.7f);
auto& filter = processorChain.get<filterIndex>();
filter.setCutoffFrequencyHz (1000.0f); // [3]
filter.setResonance (0.7f);
}
//==============================================================================
void prepare (const juce::dsp::ProcessSpec& spec)
{
tempBlock = juce::dsp::AudioBlock<float> (heapBlock, spec.numChannels, spec.maximumBlockSize);
adsr.setSampleRate(spec.sampleRate);
adsr.setParameters(juce::ADSR::Parameters(1.0f, 1.0f, 1.0f, 3.0f));
processorChain.prepare (spec);
}
//==============================================================================
void noteStarted() override
{
auto velocity = getCurrentlyPlayingNote().noteOnVelocity.asUnsignedFloat();
auto freqHz = (float) getCurrentlyPlayingNote().getFrequencyInHertz();
processorChain.get<osc1Index>().setFrequency (freqHz, true);
processorChain.get<osc1Index>().setLevel (velocity);
processorChain.get<osc2Index>().setFrequency (freqHz * 1.01f, true);
processorChain.get<osc2Index>().setLevel (velocity);
adsr.noteOn();
}
//==============================================================================
void notePitchbendChanged() override
{
auto freqHz = (float) getCurrentlyPlayingNote().getFrequencyInHertz();
processorChain.get<osc1Index>().setFrequency (freqHz);
processorChain.get<osc2Index>().setFrequency (freqHz * 1.01f);
}
//==============================================================================
void noteStopped (bool) override
{
//clearCurrentNote();
adsr.noteOff();
}
//==============================================================================
void notePressureChanged() override {}
void noteTimbreChanged() override {}
void noteKeyStateChanged() override {}
//==============================================================================
void renderNextBlock (juce::AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override
{
auto output = tempBlock.getSubBlock (0, (size_t) numSamples);
output.clear();
if(isActive() && adsr.isActive() == false)
{
DBG("ADSR end");
clearCurrentNote();
}
auto audioBlock = juce::dsp::AudioBlock<float>(outputBuffer).getSubBlock(startSample, numSamples);
juce::dsp::ProcessContextReplacing<float>context(audioBlock);
processorChain.process (context);
juce::dsp::AudioBlock<float> (outputBuffer)
.getSubBlock ((size_t) startSample, (size_t) numSamples)
.add (tempBlock);
adsr.applyEnvelopeToBuffer(outputBuffer, startSample, numSamples);
}
private:
//==============================================================================
juce::HeapBlock<char> heapBlock;
juce::dsp::AudioBlock<float> tempBlock;
juce::ADSR adsr;
enum
{
osc1Index,
osc2Index,
filterIndex,
masterGainIndex
};
juce::dsp::ProcessorChain<CustomOscillator<float>,CustomOscillator<float>,juce::dsp::LadderFilter<float>,juce::dsp::Gain<float>> processorChain;
static constexpr size_t lfoUpdateRate = 100;
};
I have found another thread which deals with the same problem (ADSR behaving oddly on polyphonic notes). However I don’t really know how to implement that solution.
Could somebody maybe help me?
Thanks
It seems that in renderNextBlock(), you are applying the ADSR over and
over again to the joint output buffer.
The buffer is shared with all other voices so if you apply the ADSR to
the output buffer, you will also apply it to the output of all voices rendered before.
A proper approach would be:
- render current voice to a temp buffer
- apply ADSR of current voice to your temp buffer
- add temp contents to joint outputBuffer
1 Like
Thanks for your response. I am fairly new to JUCE and C++. How could I do your described steps? I am pretty lost right now.
I am also quite new to JUCE, so I cannot guarantee that this works!
But I think you should build the context from your temp block.
So if you render the voice, the raw voice output will be contained
in the temp block.
Next, the envelope should be applied.
The ADSR call wants an AudioBuffer and not an AudioBlock.
(I have not checked if an overload for AudioBlock exists).
But there is a non-owning constructor for AudioBuffer that lets you construct
a temp buffer from the temp block (or from its write pointers… this may get ugly).
(I think it will get less ugly if you start from a temp AudioBuffer rather than a
temp AudioBlock).
The ADSR can be applied to this buffer.
Now that the temp block contains the properly envelope shaped contents,
you can finally add it to the output buffer.
This add operation is already contained in your code snippet.
You must take care of the existing contents of the outputBuffer passed to MPESynthesiserVoice::renderNextBlock() because this buffer is
shared by all voices. This means that you should only change it by adding
to its existing content.
Other operations will have unwanted side-effects on other voices.
The ADSR, for example, performs a multiplication rather than addition
on the buffer. So it affects other voices that have already been rendered,
by changing their envelope as well.
Hello again, I tried to create a AudioBuffer from the AudioBlock. However I get a error, saying that there is no matching constructor for initialization of 'juce::AudioBuffer
My code:
auto output = tempBlock.getSubBlock (0, (size_t) numSamples);
output.clear();
if(isActive() && adsr.isActive() == false)
{
DBG("ADSR end");
clearCurrentNote();
}
auto audioBlock = juce::dsp::AudioBlock<float>(outputBuffer).getSubBlock(startSample, numSamples);
juce::dsp::ProcessContextReplacing<float>context(audioBlock);
juce::AudioBuffer<float> SingleVoiceBuffer = juce::AudioBuffer<float>{tempBlock};
processorChain.process (context);
juce::dsp::AudioBlock<float> (outputBuffer)
.getSubBlock ((size_t) startSample, (size_t) numSamples)
.add (tempBlock);
//adsr.applyEnvelopeToBuffer(outputBuffer, startSample, numSamples);
Could you maybe provide a example how to program that?
It would be easier to start from a tempBuffer (instance of AudioBuffer) and construct
the tempBlock from the tempBuffer.
(The reverse way is ugly.)
I now created a AudioBuffer and tried to apply the adsr to this Audiobuffer and then adding it to the output buffer. However I now get a assertion error: at: jassert (startSample + numSamples <= buffer.getNumSamples());
My Code:
void renderNextBlock (juce::AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override
{
auto output = tempBlock.getSubBlock (0, (size_t) numSamples);
output.clear();
if(isActive() && adsr.isActive() == false)
{
DBG("ADSR end");
clearCurrentNote();
}
auto audioBlock = juce::dsp::AudioBlock<float>(outputBuffer).getSubBlock(startSample, numSamples);
juce::dsp::ProcessContextReplacing<float>context(audioBlock);
adsr.applyEnvelopeToBuffer(audioBuffer, startSample, numSamples);
processorChain.process (context);
juce::dsp::AudioBlock<float> (outputBuffer)
.getSubBlock ((size_t) startSample, (size_t) numSamples)
.add (tempBlock);
juce::dsp::AudioBlock<float> myAudioBlock (audioBuffer);
juce::dsp::AudioBlock<float> (outputBuffer)
.getSubBlock ((size_t) startSample, (size_t) numSamples)
.add (myAudioBlock);
}
I think it should work roughly this way.
But I just rearranged the code here in the edit window, didn’t check in the IDE
for errors.
void renderNextBlock (juce::AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override
{
if (isActive() && adsr.isActive() == false)
{
DBG("ADSR end");
clearCurrentNote();
return;
}
// I assume that audioBuffer is your temp buffer, properly resized in prepareToPlay()
// get subblock of desired size:
auto audioBlock = juce::dsp::AudioBlock<float> (audioBuffer).getSubBlock (startSample, numSamples);
// clear contents of temp block for current voice:
audioBlock.clear();
// create process context:
juce::dsp::ProcessContextReplacing<float> context (audioBlock);
// render voice (without envelope yet):
processorChain.process (context);
// apply envelope to raw voice:
adsr.applyEnvelopeToBuffer(audioBuffer, startSample, numSamples);
// add finished voice to shared outputBuffer for all voices:
juce::dsp::AudioBlock<float> (outputBuffer)
.getSubBlock ((size_t) startSample, (size_t) numSamples)
.add (audioBlock);
}
I tried that code and it seems to work. However when I press a key down a bit longer, it suddenly becomes a bit louder and then very quiet. And I don’t know why? It really sounds odd when playing some melodies because of this extreme loudness change
That’s how my prepare Method looks like:
void prepare (const juce::dsp::ProcessSpec& spec)
{
tempBlock = juce::dsp::AudioBlock<float> (heapBlock, spec.numChannels, spec.maximumBlockSize);
adsr.setSampleRate(spec.sampleRate);
adsr.setParameters(juce::ADSR::Parameters(1.0f, 0.1f, 0.1f, 1.0f));
processorChain.prepare (spec);
audioBuffer.setSize(spec.numChannels, spec.maximumBlockSize);
}
You may want to check your ADSR::Parameters.
The chosen settings result in a slow 1s attack, followed by a quick (0.1s) decay down to a quiet (magnitude 0.1 = -20 dB) sustain level.
Hello there, I still get a distorted sound whenever I start to play three notes or more. I don’t really know why. Could you perhaps try to run the code at your machine and tell me if It works with more than 2 voices?
Just reduce the master gain.
It appears that the sound only starts to bug whenever I change the Parameters of the adsr. Only when I reopen my project and then recompile the sound becomes clear and without bugs. Maybe I don’t clear some buffers? Since this sound bug only appears after I change the Parameters of the adsr without reopening my entire project? Maybe there are some driver issues?
EDIT: The problem only seems to occur when I got headphones on. When playing via the audio speaker of my MacBook I have no problems. I assume it is a audio driver problem with the headphones.
A big THANKS for your effort hilberturi!
You’re welcome!
But please don’t use headphones for listening to an untested plugin, this may potentially damage your hearing (especially when using a Mac).