XY Pad for stereo delay

Hello,

I’m working on my first JUCE plugin and I’m having some issues with implementing my XY pad. I’m trying to create a stereo delay, but it’s not behaving as expected.

In the center of my XY pad, it reports a value of 35 (35ms maximum delay) for the left channel. However, this value doesn’t change when I move the pad to the left on the X-axis. On the left side, I’m getting incorrect values, and the maximum delay it’s showing in DBG as an error is 16.

Here are the relevant parts of my code:
plugineditor:

XyPadAudioProcessorEditor::XyPadAudioProcessorEditor(XyPadAudioProcessor& p) :
    AudioProcessorEditor(&p),
    audioProcessor(p),
    delayTimeAttachment(audioProcessor.getApvts(), "delayTime", delayTimeSlider),
    dryWetMixAttachment(audioProcessor.getApvts(), "dryWetMix", dryWetMixSlider)
{
    addAndMakeVisible(xyPad);


    // Aggiungi e rendi visibili gli slider per delayTime e dryWetMix
    addAndMakeVisible(delayTimeSlider);
    addAndMakeVisible(dryWetMixSlider);

    // Configurazione degli slider
    delayTimeSlider.setRange(-35.f, 35.f, 0.01f);
    delayTimeSlider.setValue(0.f, dontSendNotification);

    dryWetMixSlider.setRange(0.f, 1.f, 0.01f);  // Imposta l'intervallo e lo step

    // Aggiungi e rendi visibili le label
    addAndMakeVisible(delayLabel);
    addAndMakeVisible(mixLabel);

    // Configura le label
    delayLabel.attachToComponent(&delayTimeSlider, false);
    mixLabel.attachToComponent(&dryWetMixSlider, false);

    xyPad.registerSlider(&dryWetMixSlider, Gui::XyPad::Axis::Y);
    xyPad.registerSlider(&delayTimeSlider, Gui::XyPad::Axis::X);
    
  


    // Imposta la dimensione dell'editor
    setSize(500, 300);
}

pluginprocessor:

void XyPadAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    juce::ScopedNoDenormals noDenormals;
    const float maxDelayTimeMs = 35.0f; // Maximum delay time in milliseconds

    auto delayTimeValue = parameters.getRawParameterValue("delayTime")->load();
    DBG("PluginProcessor - Delay Time Value in Processor: " << delayTimeValue);
    auto dryWetValue = parameters.getRawParameterValue("dryWetMix")->load();

    for (int channel = 0; channel < buffer.getNumChannels(); ++channel)
    {
        auto* channelData = buffer.getWritePointer(channel);
        auto* delayData = delayBuffer.getWritePointer(channel);

        // Calculate the delay time for each channel based on the XY Pad's X-axis position
        int delayTimeInSamples = getChannelSpecificDelayTime(channel, delayTimeValue, maxDelayTimeMs, getSampleRate());

        for (int i = 0; i < buffer.getNumSamples(); ++i)
        {
            const int delayReadPosition = (delayWritePosition - delayTimeInSamples + delayBufferLength) % delayBufferLength;

            // Process the delay for each channel
            float wetSignal = delayData[delayReadPosition];
            float drySignal = channelData[i];

            // Apply dry/wet mix
            channelData[i] = drySignal * (1.0f - dryWetValue) + wetSignal * dryWetValue;

            // Update delayWritePosition for each channel
            delayWritePosition = (delayWritePosition + 1) % delayBufferLength;
        }
    }
}

int XyPadAudioProcessor::getChannelSpecificDelayTime(int channel, float delayTimeValue, float maxDelayTimeMs, double sampleRate)
{
    DBG("Before calculation - Delay Time Value: " << delayTimeValue);
    // Calcola la differenza dal centro (17.5 è il valore centrale per un range da 0 a 35)
    float centeredValue = delayTimeValue - 17.5f;
    DBG("After centeredValue calculation - Centered Value: " << centeredValue);
    // Calcola un valore di delay modificato per ogni canale
    // Per il canale sinistro (0), aumenta il delay quando si sposta a sinistra (valori negativi)
    // Per il canale destro (1), aumenta il delay quando si sposta a destra (valori positivi)
    float modifiedDelayTime = 0.0f;
    if (channel == 0) // Canale sinistro
    {
        // Il canale sinistro ottiene un delay quando lo slider è nel lato sinistro (negativo)
        modifiedDelayTime = (centeredValue < 0.0f) ? (-centeredValue * maxDelayTimeMs / 17.5f) : 0.0f;
    }
    else if (channel == 1) // Canale destro
    {
        // Il canale destro ottiene un delay quando lo slider è nel lato destro (positivo)
        modifiedDelayTime = (centeredValue > 0.0f) ? (centeredValue * maxDelayTimeMs / 17.5f) : 0.0f;
    }






    DBG("Channel: " << channel << ", Modified Delay Time: " << modifiedDelayTime);
    return static_cast<int>(modifiedDelayTime * sampleRate / 1000.0);
}

xypad

#include "XyPad.h"

namespace Gui
{
	/*
	 * XY Pad Thumb section
	 */
	XyPad::Thumb::Thumb()
	{
		constrainer.setMinimumOnscreenAmounts(thumbSize, thumbSize, thumbSize, thumbSize);
	}

	void XyPad::Thumb::paint(Graphics& g)
	{
		g.setColour(Colours::white);
		g.fillEllipse(getLocalBounds().toFloat());
	}

	void XyPad::Thumb::mouseDown(const MouseEvent& event)
	{
		dragger.startDraggingComponent(this, event);
	}

void XyPad::Thumb::mouseDrag(const MouseEvent& event)
{
    dragger.dragComponent(this, event, &constrainer);
    if (moveCallback)
    {
        moveCallback(getPosition().toDouble());
        // Non impostare direttamente il valore dello slider qui
    }
    DBG("XyPad::Thumb - Mouse Drag - Position: " << getPosition().getX() << ", " << getPosition().getY());
}

	/*
	 * XY Pad section
	 */
	XyPad::XyPad()
	{
		addAndMakeVisible(thumb);
		thumb.moveCallback = [&](Point<double> position)
		{
			const std::lock_guard<std::mutex> lock(vectorMutex);
			const auto bounds = getLocalBounds().toDouble();
			const auto w = static_cast<double>(thumbSize);
			for (auto* slider : xSliders)
			{
				// Calcola la posizione X centrata rispetto al centro del XY Pad
				double centerX = bounds.getWidth() / 2.0;  // Centro del pad XY
				double centeredX = (position.getX() - centerX) / (centerX - w / 2.0);

				
				double delayValue = centeredX * 35.0;  // centeredX va da -1 a 1


				// Imposta il valore nel range dello slider
				slider->setValue(delayValue);
				DBG("XyPad - Thumb Position X: " << position.getX() << ", Delay Value: " << delayValue);
				
			}
			for (auto* slider : ySliders)
			{
				slider->setValue(jmap(position.getY(), bounds.getHeight() - w, 0.0, slider->getMinimum(), slider->getMaximum()));
			}
			

		};
	}

	void XyPad::resized()
	{
		thumb.setBounds(getLocalBounds().withSizeKeepingCentre(thumbSize, thumbSize));
		if (!xSliders.empty())
			sliderValueChanged(xSliders[0]);
		if (!ySliders.empty())
			sliderValueChanged(ySliders[0]);
	}

	void XyPad::paint(Graphics& g)
	{
		g.setColour(Colours::black);
		g.fillRoundedRectangle(getLocalBounds().toFloat(), 10.f);
	}

	void XyPad::registerSlider(Slider* slider, Axis axis)
	{
		slider->addListener(this);
		const std::lock_guard<std::mutex> lock(vectorMutex);		
		if (axis == Axis::X)
			xSliders.push_back(slider);
		if (axis == Axis::Y)
			ySliders.push_back(slider);
	}

	void XyPad::deregisterSlider(Slider* slider)
	{
		slider->removeListener(this);
		const std::lock_guard<std::mutex> lock(vectorMutex);
		// remove/erase idiom
		xSliders.erase(std::remove(xSliders.begin(), xSliders.end(), slider), xSliders.end());
		ySliders.erase(std::remove(ySliders.begin(), ySliders.end(), slider), ySliders.end());
	}

void XyPad::sliderValueChanged(Slider* slider)
{
    // Evita loopback se il valore dello slider è stato impostato manualmente
    if (thumb.isMouseOverOrDragging(false) || sliderValueChangedManually)
        return;

    // Figure out if the slider belongs to xSliders or ySliders
    const auto isXAxisSlider = std::find(xSliders.begin(), xSliders.end(), slider) != xSliders.end();
    const auto bounds = getLocalBounds().toDouble();
    const auto w = static_cast<double>(thumbSize);

    if (isXAxisSlider)
    {
		double centerX = bounds.getWidth() / 2.0;  // Centro del pad XY
		double centeredX = (thumb.getX() - centerX) / (centerX - w / 2.0);
		double delayValue = centeredX * 35.0;
		DBG("CenteredX: " << centeredX << ", Delay Value: " << delayValue);
		slider->setValue(delayValue);
    }
    else
    {
        // Calcola la posizione Y relativa al centro del pad
        double centeredY = (thumb.getY() - bounds.getHeight() / 2.0) / ((bounds.getHeight() - w) / 2.0);

        // Calcola il valore del delay in base alla posizione Y centrata
        double delayValue = centeredY * 35.0;
        
        // Imposta il valore dello slider
        slider->setValue(delayValue);
    }
    
    repaint();
}



}

I’m not sure why I’m getting these unexpected results, and I’d appreciate any guidance or suggestions on how to resolve this issue. Thank you in advance for your help!

You are not writing anything into your delaybuffer!
this is missing:
delayData[delayWritePosition] = channelData[i]

Solved! :sweat_smile:

Full implementation available at:

1 Like