Synthesiser pitchwheel during renderNextBlock()?

Hey all,

I’m having a hard time understanding how to get access to the pitchwheel position in the SynthesiserVoice::renderNextBlock().

In my inherited class i have the pitchbend working in the startNote() method. That’s simple enough becuase currentPitchWheelPosition is an argument:

void MySynthVoice::startNote (int midiNoteNumber, float velocity, SynthesiserSound*, int currentPitchWheelPosition)

So i can play a pitch shifted note, but can’t shift pitch during playback.

Is void MySynthVoice::pitchWheelMoved(int newPitchWheelValue) a callback to be called during the renderNextBlock()? API says

This will be called during the rendering callback, so must be fast and thread-safe.

but, then where do i get the value for the argument newPitchWheelValue?

I fear i’m overlooking something incredibly straightforward. :thinking:

// Pitch Wheel methods:
void MySynthVoice::pitchWheelMoved(int newPitchWheelValue)
{
    setPitchBend(newPitchWheelValue);
    shiftHz = calcShiftHz( pitchBendCents() );
}

/// Pitch wheel position to pitchBend up or down
void MySynthVoice::setPitchBend(int pitchWheelPos)
{
    if (pitchWheelPos > 8192)
        {
            // shifting up
            pitchBend = float(pitchWheelPos - 8192) / (16383 - 8192);
        }
        else
        {
            // shifting down
            pitchBend = float(8192 - pitchWheelPos) / -8192;   // negative number
        }
}

/// calculates pitch wheel's shift in hz
float MySynthVoice::calcShiftHz(float centsOffset)
{
    return std::powf(2.0f, centsOffset / 1200.0f);
}

/// maps pitchwheel min/max positions to bend in cents
float MySynthVoice::pitchBendCents()
{
        if (pitchBend >= 0.0f)
        {
            // shifting up
            return pitchBend * pitchBendUpSemitones * 100;
        }
        else
        {
            // shifting down
            return pitchBend * pitchBendDownSemitones * 100;
        }
    }
void MySynthVoice::startNote (int midiNoteNumber, float velocity, SynthesiserSound*, int currentPitchWheelPosition)
{
    playing = true;
    ending  = false;
    
    setPortamentoTime(sampleRate, *portamentoAmount);
    
    // Sets Amp ADSR for each note
    setAmpADSRValues();
    setFilterADSRValues();
    
    // Set Sub Octave
    incrementDenominator = subOscParamControl.subOctaveSelector(subOctave);
    
    // Converts incoming MIDI note and pitch bend to frequency
    setPitchBend( currentPitchWheelPosition );
    freq = MidiMessage::getMidiNoteInHertz(midiNoteNumber) * calcShiftHz( pitchBendCents() );
    
    portamento.setTargetValue(freq);
    
    // Envelopes
    // Amp envelope
    env.noteOn();   // Start envelope
    
    //Filter Envelope
    filtEnv.noteOn();
    filtLFOClickingEnv.noteOn();
    
    // Velocity
    vel = velocity;
}

renderNextBlock, minus all the code not relevant to pitchwheel/bending:

void MySynthVoice::renderNextBlock(AudioSampleBuffer& outputBuffer, int startSample, int numSamples)
{
    if (playing) // check to see if this voice should be playing
    {
        for (int sampleIndex = startSample;   sampleIndex < (startSample+numSamples);   sampleIndex++)
        {
            // Portamento processing
            float portaFreq = portamento.getNextValue();
          
           // ***
           // *** HERE IS WHERE I WANT TO do something like: ***
           //     setPitchBend( currentPitchWheelPosition );         // How do i get the pitchWheelPos here?
           //     portaFreq *= calcShiftHz( pitchBendCents() )
           // ***
           // ***

            // Main Oscillators
            wtSine.setIncrement  (portaFreq);
            wtSaw.setIncrement   (portaFreq);
            wtSpike.setIncrement (portaFreq);
            subOsc.setIncrement  (portaFreq, incrementDenominator);
            
            // Mod Oscs
            ringMod.modFreq   (portaFreq, ringModPitch);
            freqShift.modFreq (portaFreq, freqShiftPitch);
            sAndH.modFreq     (portaFreq, sAndHPitch);
            
         // more post-pitch-bend code here

        }   // END DSP!!
    }       // END if (playing)
}           // END ProcessBlock

Thanks in advance. If you want to see the full code it’s available here.
Cheers!

What about detecting incoming midi messages with MidiMessage::isPitchWheel() and MidiMessage::getPitchWheelValue() ?

if i’m underatanding correctly you may just want to use the MPESynthesiser base class and use the notePitchbendChanged() callback. then the first block of code basically becomes this function in your Voice class with no math required:

void notePitchbendChanged() {
  auto hz = currentlyPlayingNote.getFrequencyInHertz();
  // updateFrequency(hz) or whatever ...
}

this requires getting pitchbend configured correctly and probably something else i’m forgetting, but even if you’re not using MPE you can run this in legacy mode and it works well with regular midi.

@modosc Not the MPESynth but the Synthesiser, Synthesiser Voice, and Synthesiser Sound base classes.

@oxxyyd I was trying to figure out where in the program to try that yesterday before i posted. I was thinking in my PluginProcessor’s processBlock(). I’d have to iterate through the midiMessages MidiBuffer and send the pitchWheel messages to my SynthesiserVoice::renderNextBlock() . But that doesn’t seem like the correct thing to do for a few reasons.

I guess what i’m not connecting in my brain is:
AudioProcessor::processBlock() hands off DSP to the Synthesiser class, sending it the MidiBuffer with the MidiMessages in it:

void DirtyLittleBassSynthAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
{
    ScopedNoDenormals noDenormals;

    // Hand off DSP to Synthesiser class
    synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());
    
    for (int i=0; i<voiceCount; i++)
    {
        MySynthVoice* v = dynamic_cast<MySynthVoice*>(synth.getVoice(i));
        
        mainOscVisualBuffer = v->oscVisualBuffer();
        subOscVisualBuffer  = v->subVisualBuffer();
        lfoOscVisualBuffer  = v->lfoVisualBuffer();
    }
}

The synth.renderNextBlock() is using the MidiBlock to handle the MidiMessages within:

void Synthesiser::renderNextBlock (AudioBuffer<float>& outputAudio, const MidiBuffer& inputMidi,
                                   int startSample, int numSamples)
{
    processNextBlock (outputAudio, inputMidi, startSample, numSamples);
}

Inside processNextBlock() it renders the voices in SynthesiserVoice::renderNextBlock(), but doesn’t send the MidiBlock or any MidiMessages as arguments:

for (; numSamples > 0; ++midiIterator)
    {
        if (midiIterator == midiData.cend())
        {
            if (targetChannels > 0)
                renderVoices (outputAudio, startSample, numSamples);

            return;
        }

And here’s where i lose the trail. It doesn’t seem like there’s any way for me to check the MidiMessages::isPitchWheel() from within the SynthesiserVoice::renderNextBlock() so i can apply that to the playback pitch. The Synthesiser has the handlePitchWheel() method that says it’s telling the voices of pitchwheel changes, but I have no idea inside the Voice where to get that info and utilize it.

/** Sends a pitch-wheel message to any active voices.

        This will send a pitch-wheel message to any voices that are playing sounds on
        the given midi channel.

        This method will be called automatically according to the midi data passed into
        renderNextBlock(), but may be called explicitly too.

        @param midiChannel          the midi channel, from 1 to 16 inclusive
        @param wheelValue           the wheel position, from 0 to 0x3fff, as returned by MidiMessage::getPitchWheelValue()
    */
    virtual void handlePitchWheel (int midiChannel,
                                   int wheelValue);

Sorry if i’m over-explaining or being obtuse. I always feel a bit extra-dumb when i have to ask questions here and don’t want to waste anyone’s time. :grimacing:

And thanks again.

This is because what the Synth class code is doing is breaking the input buffer up in to chunks in between all the incoming midi messages, then calling SynthVoice’s methods to update on midi events in between the chunks of rendered audio – the idea being that the synth can then have a respond to these midi events in “real time” with consideration to its rendered output, it will be as synchronous as possible.

Because of this approach, you have several assurances about the SynthesiserVoice’s renderNextBlock calls:

  • A midi message will never be “recieved” or happening at the same time as a renderNextBlock call

  • All the SynthVoice’s internal state variables will always be up-to-date & reflect the latest Midi messages relevant to the point in time that the renderNextBlock call is supposed to be rendering

What I would do is create a member variable in your SynthesiserVoice inherited class called lastRecievedPitchWheelValue. This should store the raw parameter coming in from the parent Synth, and not any kind of transformed or interpreted value. Then in your pitchWheelMoved, you can do:

void MySynthVoice::pitchWheelMoved (int newPitchWheelValue)
{
    if (lastRecievedPitchWheelValue != newPitchWheelValue) // also helps avoid unnecessary operations
    {
        lastRecievedPitchWheelValue = newPitchWheelValue
        setPitchBend(newPitchWheelValue);
        shiftHz = calcShiftHz( pitchBendCents() );
    }
}

and then inside your voice’s renderNextBlock, if you need to access the raw current value of the pitchwheel, you can just use lastRecievedPitchWheelValue

1 Like

Alternatively, if you’re not that worried about MPE, you could store the lastRecievedPitchWheelValue in the parent synth itself, instead of each voice individually

1 Like

and just to reiterate: if your goal with this is to check/update the pitchwheel position in your voice’s renderNextBlock, that will be redundant.

Unless you change any of the Synthesiser base class’s renderNextBlock code, then the pitchwheel value during a voice’s renderNextBlock execution will always be exactly the same as it was from the last time pitchwheelMoved was called.

1 Like

omg thank you so much @benvining . After reading your explanation, i was able to trace the pitchWheelValue through the code from the Synth’s render block, to the voice call, where it calls my overridden pitchWheelMoved(), which does the calculations and saves it to a member variable.

I even had all the pieces in my code to make this work, and for whatever reason i wasn’t putting the pieces together in my head. With your explanation it clicked, that the synth’s render block is calling my inherited SynthVoice’s pitchWheelMoved() and passing the necessary wheel value for me to calculate with.

It’s all so obvious now. It was staring me in my face the whole time and i didn’t see it! And thanks for the lastRecievedPitchWheel idea to save on unnecessary computations, too.

Thanks again for humoring me. :partying_face:

1 Like

you’re welcome, I’m glad I could be of help!

1 Like