Add delay invert + flatten LEDs; fix flatten to narrow bounce width instead of shortening tail

This commit is contained in:
Armin 2026-08-13 04:28:58 +02:00
commit cd2caf7821
4 changed files with 67 additions and 18 deletions

View file

@ -13,13 +13,20 @@ public:
writePos = 0;
}
void setParameters(float timeMs, float fb, float mx, bool pingpong = false) {
void setParameters(float timeMs, float fb, float mx, bool pingpong = false,
bool invert = false, bool flatten = false) {
timeSamplesF = timeMs * 0.001f * static_cast<float>(sampleRate);
float bufMax = static_cast<float>(bufferL.size()) - 1.0f;
timeSamplesF = std::clamp(timeSamplesF, 1.0f, bufMax);
feedback = std::clamp(fb, 0.0f, 0.95f);
mix = std::clamp(mx, 0.0f, 1.0f);
pingPong = pingpong;
pingSign = invert ? -1 : 1;
// Flatten reduces the bounce *width* by 60% (0.4 of full) without
// touching the echo tail — tail length is governed solely by feedback.
pingStrength = flatten ? 0.4f : 1.0f;
timeSamplesI = static_cast<int>(std::round(timeSamplesF));
if (timeSamplesI < 1) timeSamplesI = 1;
}
void process(float& left, float& right) {
@ -32,35 +39,53 @@ public:
int i1 = (i0 + 1) % bufSize;
float frac = readPosF - std::floor(readPosF);
if (pingPong) {
// Mono echo line. The tail length is set by feedback alone; the
// ping-pong bounce is applied as alternating L/R panning whose
// width is scaled by pingStrength, so flattening narrows the image
// without shortening the number of repeats.
float out = bufferL[i0] * (1.0f - frac) + bufferL[i1] * frac;
float in = 0.5f * (left + right);
bufferL[writePos] = in + out * feedback;
pingCounter += 1;
if (pingCounter >= timeSamplesI) {
pingCounter -= timeSamplesI;
pingPhase = -pingPhase;
}
float s = pingStrength * static_cast<float>(pingPhase * pingSign);
float lPan = 0.5f + 0.5f * s;
float rPan = 0.5f - 0.5f * s;
float wet = out * mix;
left = left * (1.0f - mix) + wet * lPan;
right = right * (1.0f - mix) + wet * rPan;
} else {
float outL = bufferL[i0] * (1.0f - frac) + bufferL[i1] * frac;
float outR = bufferR[i0] * (1.0f - frac) + bufferR[i1] * frac;
if (pingPong) {
// Classic ping-pong: the (summed) input enters one line and each
// echo is bounced to the opposite channel on the next repeat, so the
// taps alternate L/R. Only the cross-coupled echo is fed into the
// opposite line (never the dry input) so the bounce is audible even
// for a mono source where L == R.
float in = 0.5f * (left + right);
bufferL[writePos] = in + outR * feedback;
bufferR[writePos] = outL * feedback;
} else {
bufferL[writePos] = left + outL * feedback;
bufferR[writePos] = right + outR * feedback;
}
writePos = (writePos + 1) % bufSize;
left = left * (1.0f - mix) + outL * mix;
right = right * (1.0f - mix) + outR * mix;
}
writePos = (writePos + 1) % bufSize;
}
private:
std::vector<float> bufferL, bufferR;
int writePos = 0;
double sampleRate = 44100;
float timeSamplesF = 8820.0f;
int timeSamplesI = 8820;
float feedback = 0.3f;
float mix = 0.25f;
bool pingPong = false;
int pingSign = 1;
float pingStrength = 1.0f;
int pingCounter = 0;
int pingPhase = 1;
};

View file

@ -579,6 +579,8 @@ MainContentComponent::MainContentComponent(ChromaFlockProcessor& p)
arpEnabledLed(p.apvts, "arpEnabled", "ON", "OFF"),
arpDirectionLed(p.apvts, "arpDirection", "ON", "OFF"),
delayPingPongLed(p.apvts, "delayPingPong", "PINGPONG", "OFF"),
delayInvertLed(p.apvts, "delayInvert", "INVERT", "OFF"),
delayFlattenLed(p.apvts, "delayFlatten", "FLATTEN", "OFF"),
reverbOnLed(p.apvts, "reverbOn", "ON", "OFF"),
lfo1SyncLed(p.apvts, "lfo1Sync", "SYNC ON", "SYNC OFF"),
lfo2SyncLed(p.apvts, "lfo2Sync", "SYNC ON", "SYNC OFF"),
@ -689,6 +691,8 @@ MainContentComponent::MainContentComponent(ChromaFlockProcessor& p)
setupParam(delayFbKnob, delayFbAttach, "delayFeedback", "FBACK");
setupParam(delayMixKnob, delayMixAttach, "delayMix", "MIX");
addAndMakeVisible(delayPingPongLed);
addAndMakeVisible(delayInvertLed);
addAndMakeVisible(delayFlattenLed);
addAndMakeVisible(reverbOnLed);
setupParam(reverbSizeKnob, reverbSizeAttach, "reverbSize", "SIZE");
setupParam(reverbDampKnob, reverbDampAttach, "reverbDamping", "DAMP");
@ -1312,13 +1316,24 @@ void MainContentComponent::resized() {
// --- FX2 SECTION ---
int fx2KnobY = lfoKnobY;
// Delay sub-section: TIME, FBACK, MIX, PING + sync combo
// Delay sub-section: TIME, FBACK, MIX knobs + ping-pong toggle column + sync combo.
// The ping-pong column (PINGPONG / INVERT / FLATTEN) is stacked on the right,
// just below/under the ping-pong LED, with ping-pong itself nudged up a bit.
int dx = 962;
int dGap = lfoKnobSize + 8;
delayTimeKnob.setBounds(dx, fx2KnobY, lfoKnobSize, lfoKnobSize);
delayFbKnob.setBounds(dx + dGap, fx2KnobY, lfoKnobSize, lfoKnobSize);
delayMixKnob.setBounds(dx + dGap * 2, fx2KnobY, lfoKnobSize, lfoKnobSize);
delayPingPongLed.setBounds(dx + dGap * 3, fx2KnobY, lfoKnobSize, lfoKnobSize);
int ppX = 1230;
int ppW = 104;
int ppH = 36;
int ppGap = 4;
int ppStep = ppH + ppGap;
delayPingPongLed.setBounds(ppX, fx2KnobY, ppW, ppH);
delayInvertLed.setBounds(ppX, fx2KnobY + ppStep, ppW, ppH);
delayFlattenLed.setBounds(ppX, fx2KnobY + ppStep * 2, ppW, ppH);
delaySyncLed.setBounds(dx, fx2KnobY + lfoKnobSize + 6, 250, 28);
// Reverb sub-section: SIZE, DAMP, MIX (right edge aligned to 1650)

View file

@ -196,7 +196,8 @@ private:
// Arpeggiator controls
juce::Label arpEnabledLabel, arpPatternLabel, arpOctavesLabel, arpDirectionLabel, arpRateLabel;
juce::ComboBox arpPatternBox, arpOctavesBox, arpRateBox;
LedToggle arpEnabledLed, arpDirectionLed, delayPingPongLed, reverbOnLed;
LedToggle arpEnabledLed, arpDirectionLed, delayPingPongLed, reverbOnLed,
delayInvertLed, delayFlattenLed;
// Preset menu
juce::TextButton presetButton{"PRESET"};

View file

@ -265,6 +265,12 @@ juce::AudioProcessorValueTreeState::ParameterLayout ChromaFlockProcessor::create
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID{"delayPingPong", 1}, "Delay Ping-Pong",
juce::StringArray{"Off", "On"}, 0));
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID{"delayInvert", 1}, "Delay Invert",
juce::StringArray{"Off", "On"}, 0));
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID{"delayFlatten", 1}, "Delay Flatten",
juce::StringArray{"Off", "On"}, 0));
// REVERB
layout.add(std::make_unique<juce::AudioParameterFloat>(
@ -526,7 +532,9 @@ void ChromaFlockProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::
delayTimeMs = beatValues[bi] * 60000.0f / static_cast<float>(currentBpm);
}
delay.setParameters(delayTimeMs, getParam("delayFeedback"), getParam("delayMix"),
getParam("delayPingPong") > 0.5f);
getParam("delayPingPong") > 0.5f,
getParam("delayInvert") > 0.5f,
getParam("delayFlatten") > 0.5f);
float reverbMix = getParam("reverbOn") > 0.5f ? getParam("reverbMix") : 0.0f;
reverbFX.setParameters(getParam("reverbSize"), getParam("reverbDamping"), 0.8f, reverbMix);
limiter.setParameters(getParam("limiterThreshold"), getParam("limiterCeiling"),