Trying to make a DSP panner

trying to make a panner, but it sounds like absolute garbage and can’t get it to pan correctly in standalone plugin…

Im trying this…

void JamSeshReleaseAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages)
{
juce::ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();

for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
    buffer.clear (i, 0, buffer.getNumSamples());

update_ADSR_Value();

for(int i =0; i < mSampler.getNumSounds(); ++i)
{
    if(auto sound = dynamic_cast<juce::SamplerSound*>(mSampler.getSound(i).get()))
    {
            auto* Buffer = sound->getAudioData();
            juce::dsp::AudioBlock<float> block (*Buffer);
            panner.setPan(panValue);
            panner.process(juce::dsp::ProcessContextReplacing<float> (block));
    }
    
}

mSampler.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());

DBG(juce::String(panValue));

}

DBG outputs pan values fine… panValue is getting the value from a pan slider in my editor

also updateADSR is for an update function for my ADSR sliders in my editor, those work fine and were created using the setEnvelopeParameters method in the SamplerSound class…

mSampler is a synth.

Im trying to take the input from the synth and get it to pan to both speakers using a slider.

panner was declared in audioProcessor header file along with panValue which can get set in a sliderListener in the Editor to the value of the pan Slider.

Output won’t sound good unless the panValue is exactly 1. Any other value gives nonsense sounds.

The workflow looks a bit strange to me:

You are altering the original sample in the SamplerSound. This process is destructive.
I think the better approach is to apply the panner to the sum (buffer).

To apply the panner to each sample individually (which makes only sense, if every voice has a different pan value), then you need to inherit samplerVoice and implement this before the sound is added to the output buffer, but without altering the original sample.

Im not following…

if its not too much trouble could you explain a little more perhaps with pseudo?

@Daniel

were you referencing that buffer in processBlock? I had tried to put the panner on that one but the problem was that it was rendered moot by the call

mSampler.renderNextBlock(buffers, etc, etc);

so the panner had no effect at least in that instance.

I had also tried to break the getAudioData down from the SamplerSound object but that resulted in a horrifically unpleasant sound and only played clean if the value was absolutely 1.

I tried troubleshooting with some white noise

it looks like this now

void JamSeshReleaseAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages)
{
juce::ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();

for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
    buffer.clear (i, 0, buffer.getNumSamples());

update_ADSR_Value();



juce::Random random;


for(auto i = 0; i < buffer.getNumChannels(); i++)
{
    float* Buffer = buffer.getWritePointer(i,0);
    
    for(auto j = 0 ; j < buffer.getNumSamples(); j++)
    {
        Buffer[j] = random.nextFloat() * 0.25f;
    }
}



juce::dsp::AudioBlock<float> block (buffer);
process(juce::dsp::ProcessContextReplacing<float> (block));

mSampler.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());


DBG(juce::String(panValue));

}

only the white noise pans but not the Synthesizer mSampler

So there are several problems with that code, some of them I tried to explain above:

The sound->getAudioData() returns the sample, that is read every time you hit the note, like I wrote above. You must not alter that, otherwise all subsequent voices and noteOn events using that sample will play back the altered sound. In a panner that can also result in silence.

The dsp::Panner is stateful, so you must not send different signals through it. You need a panner instance for each continuous signal stream. The state is in the SmoothedValue for leftGain and rightGain, if you look into the implementation.

Third: You are applying the panner multiple times, on each processBlock() call. So eventually it is to be expected that only garbage is left in the sounds.

Instead try this:

void JamSeshReleaseAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages)
{
    juce::ScopedNoDenormals noDenormals;
    auto totalNumInputChannels = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();

    for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
        buffer.clear (i, 0, buffer.getNumSamples());

    update_ADSR_Value();
    mSampler.renderNextBlock (buffer, midiMessages, 0, buffer.getNumSamples());

    juce::dsp::AudioBlock<float> block (buffer);
    panner.setPan(panValue);
    panner.process(juce::dsp::ProcessContextReplacing<float> (block));
}

This should apply the panning to the sum of the synth.

N.B. I haven’t checked or debugged your details, just reordered and fixed which audio buffer is being processed.