Accessing member object from synthesiser voice crashes

Hi guys i´m quite new to JUCE and i´m trying to add a modulator to my synthesiser voice, I extended the synthesiser voice class and I made a custom class that looks something like this  

class LFOModulator
{
public:
    LFOModulator();
    void retrigger();
    void advance(double sampleRate);
    double getValue();
private:
    double lfoBuffer[2048];
    double phase;
    const long double PI = 3.141592653589793238L;
    JUCE_LEAK_DETECTOR(LFOModulator)
};

Now i´m trying to call the advance function for every sample that the synthesiser voice processes from the synthesiser voice which I made a dummy for at the moment to see why its crashing, right now it does this.

void LFOModulator::advance(double sampleRate){


}

The strange thing is that it doesnt crash instantly but it slowly grinds to a halt causing audio dropouts first. Is there internal blocking in calling member object functions, or is there something else going wrong here. I know that the error is in the function call because removing the call removes the crash. 




quite a few things look wonky here to me.

for a start, you probably should just return a double from your advance function rather than making a separate getValue() function.

Secondly, you shouldn't be feeding the samplerate into your advance function.  Check it once per process block at most, not every single sample.  Samplerate is not something that usually ever even changes.  Checking on every sample is a total waste.   

I know that the error is in the function call because removing the call removes the crash.

I have a sneeking suspicion that you are declaring your LFOModulator object inside the synth process loop (which would be wrong).  Can you share the rest of the code?

 


void SamplerAudioProcessor::initialiseSynth()
{
    const int numVoices = 8;

    //Voicing
    for (int i = numVoices; --i >= 0;){
        synth.addVoice(new GranularVoice(LFOModulator(*lfo1RateParam)));
    }
}

 


class GranularVoice : public SamplerVoice
{
public:
    GranularVoice(LFOModulator lfoModulator1);
    ~GranularVoice();
    
    bool canPlaySound(SynthesiserSound*) override;
    void startNote(int midiNoteNumber, float velocity, SynthesiserSound*, int pitchWheel) override;
    void stopNote(float velocity, bool allowTailOff) override;
    void pitchWheelMoved(int newValue) override;
    void controllerMoved(int controllerNumber, int newValue) override;
    void renderNextBlock(AudioSampleBuffer&, int startSample, int numSamples) override;
private:
    //Envelope
    bool isInAttack, isInRelease;
    float velocityGain, envelopeLevel, attackDelta, releaseDelta;
    int attackSamples, decaySamples, releaseSamples;
    
    //Grain phasor
    double grainPhasorDelta;        //Same for all grains (synchronous)
    double grainPhasorPhases[8];
    //Voiced Modulators
    double scanPosition;
    LFOModulator& lfoModulator1;    //this has too much overhead on audio rate modulation ??

    //SAH per note
    int noteTransposition;
    int totalTransposition;


    //SAH per grain
    double pitchRatio;
    double readPositions[10];
    JUCE_LEAK_DETECTOR(GranularVoice)
};

 

By the way right now I am not even using the get value function yet only the advance just to see why its crashing

 


void GranularVoice::renderNextBlock(AudioSampleBuffer& outputBuffer, int startSample, int numSamples)
{
    if (const GranularSound* const playingSound = static_cast <GranularSound*> (getCurrentlyPlayingSound().get()))
    {
        const float* const sourceL = playingSound->sourceBuffer->getReadPointer(0);            
        const float* const sourceR = playingSound->sourceBuffer->getNumChannels() > 1
                                   ? playingSound->sourceBuffer->getReadPointer(1) : nullptr;
        //Get output write pointers. The pointer address is incremented in the while loop.
        float* outputL = outputBuffer.getWritePointer(0, startSample);
        float* outputR = outputBuffer.getNumChannels() > 1 ? outputBuffer.getWritePointer(1, startSample) : nullptr;
            
        while (--numSamples >= 0)
        {
            float grainsL = 0.0f;
            float grainsR = 0.0f;
            for (int g = 0; g < (playingSound->grainDensity * 7 + 1); g++){
                //Source sample interpolation
                const int posTrunc = (int)readPositions[g];
                float singleL;
                float singleR;
                if (posTrunc + 1 < playingSound->length && posTrunc >= 0){
                    const float alpha = (float)(readPositions[g] - posTrunc);                        //Weight of next sample
                    const float invAlpha = 1.0f - alpha;                                            //Weight of current sample
                    singleL = (sourceL[posTrunc] * invAlpha + sourceL[posTrunc + 1] * alpha);
                    singleR = (sourceR != nullptr) ? (sourceR[posTrunc] * invAlpha + sourceR[posTrunc + 1] * alpha) : singleL;
                }
                else {        //If reading outside of the buffer bounds output 0
                    singleL = 0.0;
                    singleR = 0.0;
                }
                //Window interpolation
                const double windowPos = grainPhasorPhases[g] * 2047;
                const int windowPosTrunc = (int)(grainPhasorPhases[g] * 2047.0);
                const float windowAlpha = (float)(windowPos - windowPosTrunc);                        //Weight of next sample
                const float windowInvAlpha = 1.0f - windowAlpha;                                    //Weight of current sample
                const float windowValue = (playingSound->windowBuffer[windowPosTrunc] * windowInvAlpha + playingSound->windowBuffer[windowPosTrunc + 1] * windowAlpha);
                singleL *= windowValue;
                singleR *= windowValue;
                grainsL += singleL;
                grainsR += singleR;
                //Increment read/transposition step
                readPositions[g] += pitchRatio;
                //Increment Phasor
                grainPhasorDelta = ((1.0 / getSampleRate()) * (pow(playingSound->grainRate, 4) * 199.0 + 1.0));
                grainPhasorPhases[g] += grainPhasorDelta;
                if (grainPhasorPhases[g] >= 1.0)
                {
                    //SAH Transposition
                    totalTransposition = noteTransposition + ((playingSound->grainPitch - 0.5) * 48); //+ lfoModulator1.getValue() * 12;
                    pitchRatio = pow(2.0, totalTransposition / 12.0) * playingSound->sourceSampleRate / getSampleRate();
                    //SAH Read position
                    readPositions[g] = playingSound->startSample + scanPosition;
                    //Reset phasor phase
                    while (grainPhasorPhases[g] >= 1.0){
                        grainPhasorPhases[g] -= 1.0;
                    }
                }
            }
            grainsL *= velocityGain;
            grainsR *= velocityGain;
                
            grainsL *= (envelopeLevel*envelopeLevel);
            grainsR *= (envelopeLevel*envelopeLevel);
            scanPosition += (playingSound->scanSpeed-0.5f)*10.0f; //-5 to 5
            lfoModulator1.advance(getSampleRate());
            if (isInAttack)
            {
                envelopeLevel += attackDelta;
                if (envelopeLevel >= 1.0f)
                {
                    envelopeLevel = 1.0f;
                    isInAttack = false;
                }
            }
            else if (isInRelease)
            {
                envelopeLevel += releaseDelta;
                if (envelopeLevel <= 0.0f)
                {
                    stopNote(0.0f, false);
                    break; //Exit while loop when release is done (no need to write more into the output buffer)
                }
            }
            
            //Write output buffers
            if (outputR != nullptr)    { *outputL++ += grainsL; *outputR++ += grainsR; }        //MONO OUTPUT WRITE
            else                    { *outputL++ += (grainsL + grainsR) * 0.5f;        }        //STEREO OUTPUT WRITE
        }
    }
}


 

This runs fine when i remove the lfo1modulator.advance line which I think is strange since there is no actual code inside that function it does absolutely nothing except for being called. Is it really just the passing of the samplerate that causes this crash (I just had this right now because I didnt think of a proper way to set the modulator sample rates yet) ? Does memory allocation cause blocking when a value gets copied in a function call ? I know that passing the sample rate here is stupid I'm just trying to figure out the limitations of the audio thread. I would like to have audio rate modulations for some granular parameters thats why I'm looking into this.

Ok i did another test to simplify things even more, now i have an advance function without any arguments and still nothing in the body and it still crashes. Maybe its in the way i referenced to the LFOModulator object? I cant seem to find out how a void function with absolutely no content can grind down the process loop.

You declare your lfoModulator1 as reference, so can you make sure, that this reference is actually referencing an instance? Otherwise your advance is called in the nirvana, hence the crash...

LFOModulator& lfoModulator1; 

Removing the ampersand will call at least the default constructor, and advance can be called.

I was just browsing your code, not actually debugging, so I could be wrong...

Good Luck

In the top part of the code I posted it shows that for every voice I pass a new instance of LFOModulator into the constructor. If I'm correct this should stay in the same address on the heap right?

I did it this way because I need to pass an AudioProcessorParameter into the LFOModulator constructor from the PluginProcessor

I need to see the implementation of the constructor of GranularVoice:

 GranularVoice(LFOModulator lfoModulator1);

If you give the argument the same name like the member variable, you will not be able to set the member variable. You are fine if it starts like:

 GranularVoice(LFOModulator& lfo) : lfoModulator1 (lfo)
{
}

A reference is nothing more than a pointer, the actual instance needs to be constructed somewhere...

HTH

 


GranularVoice::GranularVoice(LFOModulator lfoModulator1_)
    : lfoModulator1(lfoModulator1_),
    pitchRatio(0.0),
    velocityGain(0.0),
    envelopeLevel(0.0), attackDelta(0), releaseDelta(0),
    grainPhasorDelta(0.0f), 
    isInAttack(false), isInRelease(false)
 

I'm using an underscore in the constructor

Ive been looking trough the synthesiservoice class and it shows that they reference the playingSound with a referenceCountedObjectPointer, since accesing the sound parameters and functions seem to cause no trouble, but when I call something from my LFOModulator it seems to be really slow (I'm geussing)?

Omg sorry guys for asking these questions I think i figured out what the problem was, it had something to do with using directSound driver in the juce plugin host that caused problems. Seems to be running fine on windows audio driver now and in ableton aswell, sorry.