mirror of
https://codeberg.org/armin/chromaflock.git
synced 2026-09-01 04:10:47 +02:00
Add delay invert + flatten LEDs; fix flatten to narrow bounce width instead of shortening tail
This commit is contained in:
parent
0af34cd89d
commit
cd2caf7821
4 changed files with 67 additions and 18 deletions
|
|
@ -13,13 +13,20 @@ public:
|
||||||
writePos = 0;
|
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);
|
timeSamplesF = timeMs * 0.001f * static_cast<float>(sampleRate);
|
||||||
float bufMax = static_cast<float>(bufferL.size()) - 1.0f;
|
float bufMax = static_cast<float>(bufferL.size()) - 1.0f;
|
||||||
timeSamplesF = std::clamp(timeSamplesF, 1.0f, bufMax);
|
timeSamplesF = std::clamp(timeSamplesF, 1.0f, bufMax);
|
||||||
feedback = std::clamp(fb, 0.0f, 0.95f);
|
feedback = std::clamp(fb, 0.0f, 0.95f);
|
||||||
mix = std::clamp(mx, 0.0f, 1.0f);
|
mix = std::clamp(mx, 0.0f, 1.0f);
|
||||||
pingPong = pingpong;
|
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) {
|
void process(float& left, float& right) {
|
||||||
|
|
@ -32,35 +39,53 @@ public:
|
||||||
int i1 = (i0 + 1) % bufSize;
|
int i1 = (i0 + 1) % bufSize;
|
||||||
float frac = readPosF - std::floor(readPosF);
|
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 outL = bufferL[i0] * (1.0f - frac) + bufferL[i1] * frac;
|
||||||
float outR = bufferR[i0] * (1.0f - frac) + bufferR[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;
|
bufferL[writePos] = left + outL * feedback;
|
||||||
bufferR[writePos] = right + outR * feedback;
|
bufferR[writePos] = right + outR * feedback;
|
||||||
}
|
|
||||||
|
|
||||||
writePos = (writePos + 1) % bufSize;
|
|
||||||
|
|
||||||
left = left * (1.0f - mix) + outL * mix;
|
left = left * (1.0f - mix) + outL * mix;
|
||||||
right = right * (1.0f - mix) + outR * mix;
|
right = right * (1.0f - mix) + outR * mix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writePos = (writePos + 1) % bufSize;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<float> bufferL, bufferR;
|
std::vector<float> bufferL, bufferR;
|
||||||
int writePos = 0;
|
int writePos = 0;
|
||||||
double sampleRate = 44100;
|
double sampleRate = 44100;
|
||||||
float timeSamplesF = 8820.0f;
|
float timeSamplesF = 8820.0f;
|
||||||
|
int timeSamplesI = 8820;
|
||||||
float feedback = 0.3f;
|
float feedback = 0.3f;
|
||||||
float mix = 0.25f;
|
float mix = 0.25f;
|
||||||
bool pingPong = false;
|
bool pingPong = false;
|
||||||
|
int pingSign = 1;
|
||||||
|
float pingStrength = 1.0f;
|
||||||
|
int pingCounter = 0;
|
||||||
|
int pingPhase = 1;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -579,6 +579,8 @@ MainContentComponent::MainContentComponent(ChromaFlockProcessor& p)
|
||||||
arpEnabledLed(p.apvts, "arpEnabled", "ON", "OFF"),
|
arpEnabledLed(p.apvts, "arpEnabled", "ON", "OFF"),
|
||||||
arpDirectionLed(p.apvts, "arpDirection", "ON", "OFF"),
|
arpDirectionLed(p.apvts, "arpDirection", "ON", "OFF"),
|
||||||
delayPingPongLed(p.apvts, "delayPingPong", "PINGPONG", "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"),
|
reverbOnLed(p.apvts, "reverbOn", "ON", "OFF"),
|
||||||
lfo1SyncLed(p.apvts, "lfo1Sync", "SYNC ON", "SYNC OFF"),
|
lfo1SyncLed(p.apvts, "lfo1Sync", "SYNC ON", "SYNC OFF"),
|
||||||
lfo2SyncLed(p.apvts, "lfo2Sync", "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(delayFbKnob, delayFbAttach, "delayFeedback", "FBACK");
|
||||||
setupParam(delayMixKnob, delayMixAttach, "delayMix", "MIX");
|
setupParam(delayMixKnob, delayMixAttach, "delayMix", "MIX");
|
||||||
addAndMakeVisible(delayPingPongLed);
|
addAndMakeVisible(delayPingPongLed);
|
||||||
|
addAndMakeVisible(delayInvertLed);
|
||||||
|
addAndMakeVisible(delayFlattenLed);
|
||||||
addAndMakeVisible(reverbOnLed);
|
addAndMakeVisible(reverbOnLed);
|
||||||
setupParam(reverbSizeKnob, reverbSizeAttach, "reverbSize", "SIZE");
|
setupParam(reverbSizeKnob, reverbSizeAttach, "reverbSize", "SIZE");
|
||||||
setupParam(reverbDampKnob, reverbDampAttach, "reverbDamping", "DAMP");
|
setupParam(reverbDampKnob, reverbDampAttach, "reverbDamping", "DAMP");
|
||||||
|
|
@ -1312,13 +1316,24 @@ void MainContentComponent::resized() {
|
||||||
// --- FX2 SECTION ---
|
// --- FX2 SECTION ---
|
||||||
int fx2KnobY = lfoKnobY;
|
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 dx = 962;
|
||||||
int dGap = lfoKnobSize + 8;
|
int dGap = lfoKnobSize + 8;
|
||||||
delayTimeKnob.setBounds(dx, fx2KnobY, lfoKnobSize, lfoKnobSize);
|
delayTimeKnob.setBounds(dx, fx2KnobY, lfoKnobSize, lfoKnobSize);
|
||||||
delayFbKnob.setBounds(dx + dGap, fx2KnobY, lfoKnobSize, lfoKnobSize);
|
delayFbKnob.setBounds(dx + dGap, fx2KnobY, lfoKnobSize, lfoKnobSize);
|
||||||
delayMixKnob.setBounds(dx + dGap * 2, 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);
|
delaySyncLed.setBounds(dx, fx2KnobY + lfoKnobSize + 6, 250, 28);
|
||||||
|
|
||||||
// Reverb sub-section: SIZE, DAMP, MIX (right edge aligned to 1650)
|
// Reverb sub-section: SIZE, DAMP, MIX (right edge aligned to 1650)
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,8 @@ private:
|
||||||
// Arpeggiator controls
|
// Arpeggiator controls
|
||||||
juce::Label arpEnabledLabel, arpPatternLabel, arpOctavesLabel, arpDirectionLabel, arpRateLabel;
|
juce::Label arpEnabledLabel, arpPatternLabel, arpOctavesLabel, arpDirectionLabel, arpRateLabel;
|
||||||
juce::ComboBox arpPatternBox, arpOctavesBox, arpRateBox;
|
juce::ComboBox arpPatternBox, arpOctavesBox, arpRateBox;
|
||||||
LedToggle arpEnabledLed, arpDirectionLed, delayPingPongLed, reverbOnLed;
|
LedToggle arpEnabledLed, arpDirectionLed, delayPingPongLed, reverbOnLed,
|
||||||
|
delayInvertLed, delayFlattenLed;
|
||||||
|
|
||||||
// Preset menu
|
// Preset menu
|
||||||
juce::TextButton presetButton{"PRESET"};
|
juce::TextButton presetButton{"PRESET"};
|
||||||
|
|
|
||||||
|
|
@ -265,6 +265,12 @@ juce::AudioProcessorValueTreeState::ParameterLayout ChromaFlockProcessor::create
|
||||||
layout.add(std::make_unique<juce::AudioParameterChoice>(
|
layout.add(std::make_unique<juce::AudioParameterChoice>(
|
||||||
juce::ParameterID{"delayPingPong", 1}, "Delay Ping-Pong",
|
juce::ParameterID{"delayPingPong", 1}, "Delay Ping-Pong",
|
||||||
juce::StringArray{"Off", "On"}, 0));
|
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
|
// REVERB
|
||||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
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);
|
delayTimeMs = beatValues[bi] * 60000.0f / static_cast<float>(currentBpm);
|
||||||
}
|
}
|
||||||
delay.setParameters(delayTimeMs, getParam("delayFeedback"), getParam("delayMix"),
|
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;
|
float reverbMix = getParam("reverbOn") > 0.5f ? getParam("reverbMix") : 0.0f;
|
||||||
reverbFX.setParameters(getParam("reverbSize"), getParam("reverbDamping"), 0.8f, reverbMix);
|
reverbFX.setParameters(getParam("reverbSize"), getParam("reverbDamping"), 0.8f, reverbMix);
|
||||||
limiter.setParameters(getParam("limiterThreshold"), getParam("limiterCeiling"),
|
limiter.setParameters(getParam("limiterThreshold"), getParam("limiterCeiling"),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue