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!
