[SOLVED] Issue inserting Simple Delay Plugin onto the channel strip in Ableton Live

Hi,

JUCE Newbie here so any help would be appreciated, I hope to hand this in for an assignment on Monday. I have a few questions I would like to address with the implementation, but first of all I need to get it working again.

I have been experiencing this error since trying to add some saturation functionality to the wet signal.

Upon applying the plugin to a track, xcode opens and I get the following error:

private:
   void checkInvariants() const
   {
       jassert (end > start); //Thread 1 EXC_BREAKPOINT here
       jassert (interval >= ValueType());
       jassert (skew > ValueType());
   }

here is the saturation functionality:

I have just duplicated the signal and applied saturation to it based on a parameter value.

                  float outputleft  = leftChannel[i]  * (1 - *mDryWetParameter) + mFeedbackLeft  **mDryWetParameter;
                float outputright = rightChannel[i] * (1 - *mDryWetParameter) + mFeedbackRight **mDryWetParameter;
        
        
                mSaturationLeft   = * mSaturationLevel * tanh(outputleft + 1);  // (mGainSmoothed*1.5f)+0.5f)
                mSaturationRight  = * mSaturationLevel * tanh(outputright+ 1);  // (mGainSmoothed*1.5f)+0.5f);
        
        
                buffer.setSample (0, i,
                                 outputleft  * mSaturationLeft); //OUTPUT L
        
        
                buffer.setSample (1, i,
                                 outputright * mSaturationRight); //OUTPUT R
        
//        buffer.setSample(0, i, leftChannel[i]  * (1 - *mDryWetParameter) + mFeedbackLeft  * *mDryWetParameter);
//        buffer.setSample(1, i, rightChannel[i] * (1 - *mDryWetParameter) + mFeedbackRight * *mDryWetParameter);
        
        mDelayWriteHead++;
        
        if (mDelayWriteHead > mCircularBufferLength)
        {
            mDelayWriteHead = 0; //  wrap around
        }
    }
}

What class is checkInvariants part of?

edit : I make a guess the problem is about how you initialize a plugin parameter. (Maybe you switched the minimum and maximum values of the parameter around or something.)

Here is the code for the initialisation of the parameter in the constructor.

    addParameter(mSaturationLevel   = new AudioParameterFloat("saturation",
                                                              "Saturation",
                                                              0.0f,
                                                              5.0f,
                                                              0.1f));

Set the min to 0 as I add 1 before the calculation

realised an error in the code it should be:

       mSaturationLeft    =  (*mSaturationLevel+1) * tanh(outputleft); 
       mSaturationRight   =  (*mSaturationLevel+1) * tanh(outputright); 

But I still have the same problem.

Also the Error is appearing in this section of JUCE:

The “Error” is in fact an assert, which makes sure you supplied reasonable input, that will go unchecked in a release build:

    void checkInvariants() const
    {
        jassert (end > start);
        jassert (interval >= ValueType());
        jassert (skew > ValueType());
    }

I don’t know, which you are not satisfying, the debugger would certainly tell you the exact location (i.e. which of the three asserts)

Hi Daniel, the assert error is in the top one.

I have not actually learnt how to debug yet.

No worries, you will learn soon :wink:

Normally the juce team adds hints next to jassert statements, explaining, what you should change. Either they felt here it is obvious, or they thought it’s not necessary, since it is a private function…

A range should always have the end higher than the start, so if you hover over the two variables, your IDE should show the actual values. According to the code you posted it should be 0 and 5, but it seems not to be the case, otherwise the jassert wouldn’t have stopped…

All in good time :wink:

Ah ok, I’m just off to a show! I will have a look when I get back this evening.

Thanks for the help.

Hi,

So I changed the range of the slider values in the constructor to be between 1 and 6 and removed the +1 from the multiplication:

(I thought I would add the other Parameters too just to see if there are any errors)

note: I’m making a very short textural delay so my defined MAX_DELAY_TIME is in m/s hence the M_D_T/1000.

    addParameter(mDelayTimeParameter = new AudioParameterFloat("delay time",
                                                               "Delay Time",
                                                               0.0f,
                                                               (MAX_DELAY_TIME/1000),
                                                               (MAX_DELAY_TIME/1000)*0.2));
    
    addParameter(mFeedbackParameter = new AudioParameterFloat("feedback",
                                                              "Feedback",
                                                              0.0f,
                                                              0.99f,
                                                              0.1f));
    
    addParameter(mDryWetParameter   = new AudioParameterFloat("drywet",
                                                              "Dry / Wet",
                                                              0.0f,
                                                              1.0f,
                                                              0.5f));
    
    addParameter(mSaturationLevel   = new AudioParameterFloat("saturation",
                                                              "Saturation",
                                                              1.0f,
                                                              5.0f,
                                                              0.1f));

however I’m still running into the same issue…

08

What actually is this telling me? Is there a way to see which line in the corresponding .h or cpp is causing the thread issue?

yes, but 1000ms are one second, so you wanted to multiply instead… :wink:

I’m wanting to convert the m/s into seconds. I ran into issues where having 0.15 (seconds) in the header file definition was causing some feedback as soon as the plugin was inserted into the channel.

150 m/s * 1000m/s would give me 150,000 m/s max delay time?

I just want 0.15 seconds or 150m/s :slight_smile:

Excuse me if I’m being slow

It would for a mathematician, but not for a CS person…

If you specified MAX_DELAY_TIME as an int, then for any number smaller than 1000 the division result will be 0, multiplied by 0.2 is still zero.

That is also a heads up to avoid defines for that reason, since you don’t know the type, when you specify it. It is just a string replacement in the source file.

Your code should be:

MAX_DELAY_TIME * 1000.0 * 0.2;

Should I also change the values in the other sections I use the MAX_DELAY_TIME or just in the Parameter definition?

void AsdbasicDelayAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    // MD create the object and its memory space
    if (mCircularBufferLeft == nullptr)
    {
        mCircularBufferLeft = new float [static_cast<int>(sampleRate * (MAX_DELAY_TIME*1000))];
        zeromem(mCircularBufferLeft, mCircularBufferLength * sizeof(float));
    }
    
    if (mCircularBufferRight == nullptr)
    {
        mCircularBufferRight = new float [static_cast<int>(sampleRate * (MAX_DELAY_TIME*1000))];
        zeromem(mCircularBufferRight, mCircularBufferLength * sizeof(float));
    }
    
    mDelayWriteHead = 0; //MD just in case things break
    
    mCircularBufferLength = sampleRate * (MAX_DELAY_TIME*1000);
    mDelayTimeInSamples = 0 * sampleRate;
    
    
}

as changing to *1000 now gives me the following error when loading the plugin onto a channel in live:

Wait, what is your MAX_DELAY_TIME, seconds or ms?

I mean regardless, your calculation in integer explains the zero:
150 / 1000 = 0

the crash is probably due to the fact, that you accessed your delay buffer way beyond it’s bounds…
The maths is up to you, I don’t know all your code. But I think with this knowledge you should be able to fix that.

MAX_DELAY_TIME is in M/S

in which case /1000.0f & #define MAX_DELAY_TIME 150.00f should solve the issue.

I have fixed the issue now.

However, my delay time slider is not effecting the actual delay output. I’m not sure if this is an issue with int and float value calculations equalling 0?

I’m not too sure what I’m missing, but there must be some gaps in my knowledge.

Here is my understanding of the concepts as it stands:

  • I need to be copying the samples from one buffer into a delay buffer of size MAX_DELAY * SR.

MAX_DELAY * SR = Delay buffer size

my delay buffer is 2 * SR (eg 44100) = 88200 samples (I want it to only be 150/ms worth of samples, to make a short textural delay, but I thought instead I could create a buffer of 2 seconds and then simply have the parameter value go from 0-150m/s due to the issues in the thread above)

I’m then filling this with 64 samples at a time, at the end of each buffer the write pos goes to buffer index+1 so, for example the second buffer starts writing into the delay buffer at sample no 64 (starting at index 0).

In the constructor I have initialised the buffers to a nullptr so I can test that they’re empty in the destructor and clear them if not before the delay begins.

I have also set initial arguments for my Feedback, Write head, Circular buffer length, DT, DT smoothed and added my desired parameters:

  {
    mCircularBufferLeft = nullptr;
    mCircularBufferRight = nullptr;
    
    mFeedbackLeft = 0; // instantiate the values to 0
    mFeedbackLeft = 0;
    
    mDelayWriteHead = 0; // MD default delay time to 0 samples
    mCircularBufferLength = 0; // MD until further notice
    
    mDelayTimeInSamples = 0;
    mDelayPlayHead = 0;
    
    mDelayTimeSmoothed = 0;
    
    
    // mDryWet = 0.3;
   // mFeedbackAmount = 0.8; // used these before creating the parameters to test the code
    
    addParameter(mDelayTimeParameter = new AudioParameterFloat("delaytime",
                                                               "Delay Time",
                                                               0,
                                                               150.0f,
                                                               20.0f));
    
    addParameter(mFeedbackParameter = new AudioParameterFloat("feedback",
                                                              "Feedback",
                                                              0.0f,
                                                              0.99f,
                                                              0.1f));
    
    addParameter(mDryWetParameter = new AudioParameterFloat("drywet",
                                                            "Dry / Wet",
                                                            0.0f,
                                                            1.0f,
                                                            0.5f));
    
    addParameter(mDelayOutputLevelParameter = new AudioParameterFloat("output",
                                                                      "Output Level",
                                                                      0.0f,
                                                                      1.0f,
                                                                      1.0f));
    
}

In the constructor I have assigned these to a nullptr, so I can test that there is nothing in the buffer upon loading the plugin.

test in destructor:

   if (mCircularBufferLeft != nullptr)
    {
        delete [] mCircularBufferLeft;
    }
    
    if (mCircularBufferRight != nullptr)
    {
        delete [] mCircularBufferRight;
    }
  • I have a read head, which is the sample at [index] in the main buffer, which I am copying into my delay buffer at [index].

  • The delay time is the distance in samples [index] between the read head and the write head positions set by:

mDelayTimeInSamples = mDelayTimeSmoothed * sampleRate;

in my prepare to play

  • Other than in ptp I create a variable mLastSampleRate so I can call the sample rate in the process block later to calculate DT smoothed.

  • Then initialise the length of each buffer (after Daniels help I’m not sure cast to int where I’m running into issues at my DT parameter values are float)? also, I don’t fully understand the latter section in the code:

 zeromem(mCircularBufferLeft, mCircularBufferLength * sizeof(float));

  • Test that the buffers point to the empty pointer then create them at set length
void AsdbasicDelayAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    float mLastSampleRate = getSampleRate();
    
    // MD create the object and its memory space
    if (mCircularBufferLeft == nullptr)
    {
        mCircularBufferLeft = new float [static_cast<int>(sampleRate * MAX_DELAY_TIME)];
        zeromem(mCircularBufferLeft, mCircularBufferLength * sizeof(float));
    }
    
    if (mCircularBufferRight == nullptr)
    {
        mCircularBufferRight = new float [static_cast<int>(sampleRate * MAX_DELAY_TIME)];
        zeromem(mCircularBufferRight, mCircularBufferLength * sizeof(float));
    }
    
    //mDelayWriteHead = 0; MD just in case things break
    
    mCircularBufferLength = (int)sampleRate * MAX_DELAY_TIME;
    
    float mDelayTimeSmoothed = *mDelayTimeParameter;
    mDelayTimeInSamples = mDelayTimeSmoothed * sampleRate;
}

And finally here is my process block:

I have seen implementations creating a AudioBuffermDelayBuffer in the .h and then just using channel as an argument, but my teacher showed us the concept using buffers for each channel.

void AsdbasicDelayAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
{
    ScopedNoDenormals noDenormals;
    auto totalNumInputChannels  = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();
    
    for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
        buffer.clear (i, 0, buffer.getNumSamples());
    
    float * leftChannel  = buffer.getWritePointer(0); //gets the sample position (index) of the buffer for each channel
    float * rightChannel = buffer.getWritePointer(1);
    
    for (auto i = 0; i < buffer.getNumSamples(); i++)
    {

        mDelayTimeSmoothed = mDelayTimeSmoothed - 0.2 * (mDelayTimeSmoothed - *mDelayTimeParameter);
        mDelayTimeInSamples = mDelayTimeSmoothed * mLastSampleRate;
        
        mDelayPlayHead = mDelayWriteHead - mDelayTimeInSamples;
        
        if (mDelayPlayHead < 0)
        {
            mDelayPlayHead += mCircularBufferLength;
        }
        
        float delay_sample_left  = mCircularBufferLeft[(int)mDelayPlayHead]; // MD store the delay values
        float delay_sample_right = mCircularBufferRight[(int)mDelayPlayHead]; // MD store the delay values
        
        mFeedbackLeft   = delay_sample_left  * *mFeedbackParameter; // 0.8 is feedback coefficient
        mFeedbackRight = delay_sample_right * *mFeedbackParameter;
        
        mCircularBufferLeft [mDelayWriteHead] = leftChannel[i]  + mFeedbackLeft;
        mCircularBufferRight[mDelayWriteHead] = rightChannel[i] + mFeedbackRight;
        
        buffer.setSample(0, i, leftChannel[i]  * ((1 - *mDryWetParameter) + mFeedbackLeft  * *mDryWetParameter) * *mDelayOutputLevelParameter);
        buffer.setSample(1, i, rightChannel[i] * ((1 - *mDryWetParameter) + mFeedbackRight * *mDryWetParameter) * *mDelayOutputLevelParameter);
                         
        mDelayWriteHead++; //Moves the position of the right head along to the next sample
        
        if (mDelayWriteHead > mCircularBufferLength)  // Ensures we wrap around the buffer
        {
            mDelayWriteHead = 0; // wrap around
        }
    }
}

  • Here I create a pointer to the buffers for each channel

  • Then a for loop to process each sample in the channels

  • I set my ST smoothed which is the value from the parameter -0.2 * (DT smoothed - *pointer to param value)

  • Initialise my DT in samples to DTsmoothed * SR variable in the prepare to play (not sure If this is callable here) but calling getSampleRate can’t be done in processblock as far as I understand.

  • I set the playhead position in the delay buffer to be the write head - the Delaytime in samples so its 6,615 samples behind at a DT of 150m/s and 44100 SR.

  • I’m not 100% sure on the += test but I believe it means when the playhead reaches < 0 is adds the buffer length to the playhead so we maintain the distance between the write and playhead?

  • Then I process the delay buffers with feedback, dry/wet, and outputlevel for each sample

  • increment the play head

  • wrap around if the play head exceeds the circ buffer length

Sorry for the long afternoon read, but I really want to figure out where I’m going wrong!! plus cement my understanding of the principles before my hand in and presentation.

Take care,

Zander

Bump - The delayed signal on full wet just sounds like its been reduced in amplituded filtered and distorted?

Edit: Got it all working now :slight_smile: had some issues with how I was applying the feedback and dry signal!