mirror of
https://codeberg.org/armin/chromaflock.git
synced 2026-09-01 04:10:47 +02:00
rework arpeggiator with blank notes and progressions
This commit is contained in:
parent
f79842893a
commit
d034139b5e
5 changed files with 219 additions and 104 deletions
|
|
@ -489,8 +489,75 @@ void MidiLed::paint(juce::Graphics& g) {
|
|||
g.fillEllipse(centre.x - radius * 0.75f, centre.y - radius * 0.75f, radius * 1.5f, radius * 1.5f);
|
||||
}
|
||||
|
||||
// --- Clickable state LED (on/off, direction) ---
|
||||
LedToggle::LedToggle(juce::AudioProcessorValueTreeState& apvts, const juce::String& paramId,
|
||||
const juce::String& onText, const juce::String& offText)
|
||||
: apvts(apvts), paramId(paramId), onText(onText), offText(offText) {
|
||||
startTimerHz(30);
|
||||
setMouseCursor(juce::MouseCursor::PointingHandCursor);
|
||||
}
|
||||
|
||||
void LedToggle::timerCallback() {
|
||||
bool v = apvts.getRawParameterValue(paramId)->load() > 0.5f;
|
||||
if (v != lit) {
|
||||
lit = v;
|
||||
repaint();
|
||||
if (onChange)
|
||||
onChange();
|
||||
}
|
||||
}
|
||||
|
||||
void LedToggle::mouseDown(const juce::MouseEvent&) {
|
||||
auto* param = apvts.getParameter(paramId);
|
||||
float newVal = param->getValue() > 0.5f ? 0.0f : 1.0f;
|
||||
param->beginChangeGesture();
|
||||
param->setValueNotifyingHost(newVal);
|
||||
param->endChangeGesture();
|
||||
}
|
||||
|
||||
void LedToggle::mouseEnter(const juce::MouseEvent&) {
|
||||
setMouseCursor(juce::MouseCursor::PointingHandCursor);
|
||||
}
|
||||
|
||||
void LedToggle::paint(juce::Graphics& g) {
|
||||
auto b = getLocalBounds().toFloat();
|
||||
float ledD = juce::jmin(22.0f, b.getHeight() - 8.0f);
|
||||
float radius = ledD * 0.5f;
|
||||
auto centre = juce::Point<float>(b.getX() + radius + 4.0f, b.getCentreY());
|
||||
|
||||
if (lit) {
|
||||
float auraR = radius * 1.9f;
|
||||
float maxAura = juce::jmin(centre.x - b.getX(), b.getRight() - centre.x,
|
||||
centre.y - b.getY(), b.getBottom() - centre.y) - 1.0f;
|
||||
auraR = juce::jmin(auraR, maxAura);
|
||||
g.setColour(juce::Colour(0xffccaa44).withAlpha(0.2f));
|
||||
g.fillEllipse(centre.x - auraR, centre.y - auraR, auraR * 2.0f, auraR * 2.0f);
|
||||
}
|
||||
|
||||
g.setColour(juce::Colour(0xff1a1200));
|
||||
g.fillEllipse(centre.x - radius, centre.y - radius, radius * 2.0f, radius * 2.0f);
|
||||
g.setColour(juce::Colour(0xff555555));
|
||||
g.drawEllipse(centre.x - radius, centre.y - radius, radius * 2.0f, radius * 2.0f, 1.5f);
|
||||
|
||||
g.setColour(lit ? juce::Colour(0xffeebb55) : juce::Colour(0xff3a2a06));
|
||||
g.fillEllipse(centre.x - radius * 0.78f, centre.y - radius * 0.78f, radius * 1.56f, radius * 1.56f);
|
||||
|
||||
g.setFont(juce::Font(juce::FontOptions(10.0f).withStyle("Bold")));
|
||||
g.setColour(lit ? juce::Colour(0xffeebb55) : juce::Colour(0xff666666));
|
||||
g.drawText(lit ? onText : offText,
|
||||
juce::Rectangle<float>(b.getX() + ledD + 12.0f, b.getY(),
|
||||
b.getWidth() - ledD - 12.0f, b.getHeight()),
|
||||
juce::Justification::centredLeft);
|
||||
}
|
||||
|
||||
MainContentComponent::MainContentComponent(ChromaFlockProcessor& p)
|
||||
: processorRef(p), vuMeter(p), waveformDisplay(p), pianoRoll(p), midiLed(p) {
|
||||
: processorRef(p), vuMeter(p), waveformDisplay(p), pianoRoll(p), midiLed(p),
|
||||
arpEnabledLed(p.apvts, "arpEnabled", "ON", "OFF"),
|
||||
arpDirectionLed(p.apvts, "arpDirection", "ON", "OFF"),
|
||||
delayPingPongLed(p.apvts, "delayPingPong", "ON", "OFF"),
|
||||
lfo1SyncLed(p.apvts, "lfo1Sync", "SYNC ON", "SYNC OFF"),
|
||||
lfo2SyncLed(p.apvts, "lfo2Sync", "SYNC ON", "SYNC OFF"),
|
||||
delaySyncLed(p.apvts, "delaySync", "SYNC ON", "SYNC OFF") {
|
||||
|
||||
auto setupParam = [&](juce::Slider& knob, std::unique_ptr<SliderAttachment>& attach,
|
||||
const juce::String& paramId, const juce::String& name) {
|
||||
|
|
@ -571,32 +638,32 @@ MainContentComponent::MainContentComponent(ChromaFlockProcessor& p)
|
|||
setupParam(lfo1DepthKnob, lfo1DepthAttach, "lfo1Depth", "DEPTH");
|
||||
setupCB(lfo1ShapeBox, lfo1ShapeAttach, "lfo1Shape", {"Sine", "Triangle", "Saw", "Square"});
|
||||
setupCB(lfo1DestBox, lfo1DestAttach, "lfo1Dest", {"Filter", "OSC1", "OSC2", "Both OSC", "OSC2 Phase"});
|
||||
setupCB(lfo1SyncBox, lfo1SyncAttach, "lfo1Sync", {"Sync Off", "Sync On"});
|
||||
lfo1SyncBox.onChange = [this]() {
|
||||
setupSyncKnob(lfo1RateKnob, lfo1RateAttach, lfo1BeatAttach, "lfo1Rate", "lfo1Beat", lfo1SyncBox);
|
||||
addAndMakeVisible(lfo1SyncLed);
|
||||
lfo1SyncLed.onChange = [this]() {
|
||||
setupSyncKnob(lfo1RateKnob, lfo1RateAttach, lfo1BeatAttach, "lfo1Rate", "lfo1Beat", lfo1SyncLed.isLit());
|
||||
};
|
||||
setupSyncKnob(lfo1RateKnob, lfo1RateAttach, lfo1BeatAttach, "lfo1Rate", "lfo1Beat", lfo1SyncBox);
|
||||
setupSyncKnob(lfo1RateKnob, lfo1RateAttach, lfo1BeatAttach, "lfo1Rate", "lfo1Beat", lfo1SyncLed.isLit());
|
||||
|
||||
setupKnob(lfo2RateKnob, "RATE");
|
||||
setupParam(lfo2DepthKnob, lfo2DepthAttach, "lfo2Depth", "DEPTH");
|
||||
setupCB(lfo2ShapeBox, lfo2ShapeAttach, "lfo2Shape", {"Sine", "Triangle", "Saw", "Square"});
|
||||
setupCB(lfo2DestBox, lfo2DestAttach, "lfo2Dest", {"Filter", "OSC1", "OSC2", "Both OSC", "OSC2 Phase"});
|
||||
setupCB(lfo2SyncBox, lfo2SyncAttach, "lfo2Sync", {"Sync Off", "Sync On"});
|
||||
lfo2SyncBox.onChange = [this]() {
|
||||
setupSyncKnob(lfo2RateKnob, lfo2RateAttach, lfo2BeatAttach, "lfo2Rate", "lfo2Beat", lfo2SyncBox);
|
||||
addAndMakeVisible(lfo2SyncLed);
|
||||
lfo2SyncLed.onChange = [this]() {
|
||||
setupSyncKnob(lfo2RateKnob, lfo2RateAttach, lfo2BeatAttach, "lfo2Rate", "lfo2Beat", lfo2SyncLed.isLit());
|
||||
};
|
||||
setupSyncKnob(lfo2RateKnob, lfo2RateAttach, lfo2BeatAttach, "lfo2Rate", "lfo2Beat", lfo2SyncBox);
|
||||
setupSyncKnob(lfo2RateKnob, lfo2RateAttach, lfo2BeatAttach, "lfo2Rate", "lfo2Beat", lfo2SyncLed.isLit());
|
||||
|
||||
// FX2
|
||||
setupKnob(delayTimeKnob, "TIME");
|
||||
setupCB(delaySyncBox, delaySyncAttach, "delaySync", {"Sync Off", "Sync On"});
|
||||
delaySyncBox.onChange = [this]() {
|
||||
setupSyncKnob(delayTimeKnob, delayTimeAttach, delayBeatAttach, "delayTime", "delayBeat", delaySyncBox);
|
||||
addAndMakeVisible(delaySyncLed);
|
||||
delaySyncLed.onChange = [this]() {
|
||||
setupSyncKnob(delayTimeKnob, delayTimeAttach, delayBeatAttach, "delayTime", "delayBeat", delaySyncLed.isLit());
|
||||
};
|
||||
setupSyncKnob(delayTimeKnob, delayTimeAttach, delayBeatAttach, "delayTime", "delayBeat", delaySyncBox);
|
||||
setupSyncKnob(delayTimeKnob, delayTimeAttach, delayBeatAttach, "delayTime", "delayBeat", delaySyncLed.isLit());
|
||||
setupParam(delayFbKnob, delayFbAttach, "delayFeedback", "FBACK");
|
||||
setupParam(delayMixKnob, delayMixAttach, "delayMix", "MIX");
|
||||
setupParam(delayPingPongKnob, delayPingPongAttach, "delayPingPong", "PING");
|
||||
addAndMakeVisible(delayPingPongLed);
|
||||
setupParam(reverbSizeKnob, reverbSizeAttach, "reverbSize", "SIZE");
|
||||
setupParam(reverbDampKnob, reverbDampAttach, "reverbDamping", "DAMP");
|
||||
setupParam(reverbMixKnob, reverbMixAttach, "reverbMix", "MIX");
|
||||
|
|
@ -691,20 +758,21 @@ MainContentComponent::MainContentComponent(ChromaFlockProcessor& p)
|
|||
label.setJustificationType(juce::Justification::centred);
|
||||
addAndMakeVisible(label);
|
||||
};
|
||||
setupArpLabel(arpEnabledLabel, "ON");
|
||||
setupArpLabel(arpEnabledLabel, "ON/OFF");
|
||||
setupArpLabel(arpPatternLabel, "PATTERN");
|
||||
setupArpLabel(arpOctavesLabel, "OCTAVES");
|
||||
setupArpLabel(arpDirectionLabel, "DIR");
|
||||
setupArpLabel(arpDirectionLabel, "INVERT");
|
||||
setupArpLabel(arpRateLabel, "RATE");
|
||||
|
||||
setupCB(arpEnabledBox, arpEnabledAttach, "arpEnabled", {"Off", "On"});
|
||||
addAndMakeVisible(arpEnabledLed);
|
||||
addAndMakeVisible(arpDirectionLed);
|
||||
|
||||
setupCB(arpPatternBox, arpPatternAttach, "arpPattern",
|
||||
{"Up / Minor", "Down / Minor", "Up & Down / Major", "Down & Up / Major", "Random",
|
||||
"As Played", "Chord", "Up & Down X", "Down & Up X", "Random Once", "Octave Up",
|
||||
"Octave Down", "Pinky Up", "Pinky Down", "Up / Major", "Down / Major",
|
||||
"Up & Down / Minor", "C3/C4 1", "C3/C4 2", "C3/C4 3"});
|
||||
"Up & Down / Minor", "C3/C4 1", "C3/C4 2", "C3/C4 3", "C3/C4 4", "C3/C4 5"});
|
||||
setupCB(arpOctavesBox, arpOctavesAttach, "arpOctaves", {"1", "2", "3"});
|
||||
setupCB(arpDirectionBox, arpDirectionAttach, "arpDirection", {"Up", "Down"});
|
||||
setupCB(arpRateBox, arpRateAttach, "arpRate", {"1/32", "1/16", "1/8", "1/4", "1/2", "1", "2"});
|
||||
|
||||
setupCombo(uiScaleBox);
|
||||
|
|
@ -762,11 +830,10 @@ void MainContentComponent::setupSyncKnob(juce::Slider& knob,
|
|||
std::unique_ptr<SliderAttachment>& beatAttach,
|
||||
const juce::String& rateId,
|
||||
const juce::String& beatId,
|
||||
juce::ComboBox& syncBox) {
|
||||
bool on = syncBox.getSelectedId() == 2;
|
||||
bool syncOn) {
|
||||
rateAttach.reset();
|
||||
beatAttach.reset();
|
||||
if (on)
|
||||
if (syncOn)
|
||||
beatAttach = std::make_unique<SliderAttachment>(processorRef.apvts, beatId, knob);
|
||||
else
|
||||
rateAttach = std::make_unique<SliderAttachment>(processorRef.apvts, rateId, knob);
|
||||
|
|
@ -916,9 +983,9 @@ void MainContentComponent::loadPresetFile() {
|
|||
currentPresetIndex = -1;
|
||||
patchLCD.setText(file.getFileNameWithoutExtension());
|
||||
|
||||
setupSyncKnob(lfo1RateKnob, lfo1RateAttach, lfo1BeatAttach, "lfo1Rate", "lfo1Beat", lfo1SyncBox);
|
||||
setupSyncKnob(lfo2RateKnob, lfo2RateAttach, lfo2BeatAttach, "lfo2Rate", "lfo2Beat", lfo2SyncBox);
|
||||
setupSyncKnob(delayTimeKnob, delayTimeAttach, delayBeatAttach, "delayTime", "delayBeat", delaySyncBox);
|
||||
setupSyncKnob(lfo1RateKnob, lfo1RateAttach, lfo1BeatAttach, "lfo1Rate", "lfo1Beat", lfo1SyncLed.isLit());
|
||||
setupSyncKnob(lfo2RateKnob, lfo2RateAttach, lfo2BeatAttach, "lfo2Rate", "lfo2Beat", lfo2SyncLed.isLit());
|
||||
setupSyncKnob(delayTimeKnob, delayTimeAttach, delayBeatAttach, "delayTime", "delayBeat", delaySyncLed.isLit());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -930,11 +997,11 @@ void MainContentComponent::applyCurrentPreset() {
|
|||
processorRef.presetManager.applyPreset(presets[currentPresetIndex].name, processorRef.apvts);
|
||||
patchLCD.setText(presets[currentPresetIndex].name);
|
||||
|
||||
// The ComboBoxAttachment updates the sync boxes without firing onChange,
|
||||
// so re-sync the rate/beat knob attachments to match.
|
||||
setupSyncKnob(lfo1RateKnob, lfo1RateAttach, lfo1BeatAttach, "lfo1Rate", "lfo1Beat", lfo1SyncBox);
|
||||
setupSyncKnob(lfo2RateKnob, lfo2RateAttach, lfo2BeatAttach, "lfo2Rate", "lfo2Beat", lfo2SyncBox);
|
||||
setupSyncKnob(delayTimeKnob, delayTimeAttach, delayBeatAttach, "delayTime", "delayBeat", delaySyncBox);
|
||||
// The LedToggle updates itself via its timer, so re-sync the rate/beat
|
||||
// knob attachments to match.
|
||||
setupSyncKnob(lfo1RateKnob, lfo1RateAttach, lfo1BeatAttach, "lfo1Rate", "lfo1Beat", lfo1SyncLed.isLit());
|
||||
setupSyncKnob(lfo2RateKnob, lfo2RateAttach, lfo2BeatAttach, "lfo2Rate", "lfo2Beat", lfo2SyncLed.isLit());
|
||||
setupSyncKnob(delayTimeKnob, delayTimeAttach, delayBeatAttach, "delayTime", "delayBeat", delaySyncLed.isLit());
|
||||
}
|
||||
|
||||
void MainContentComponent::randomizeParameters() {
|
||||
|
|
@ -957,9 +1024,9 @@ void MainContentComponent::randomizeParameters() {
|
|||
patchLCD.setText("RANDOM");
|
||||
|
||||
// Re-sync the rate/beat knob attachments to match the randomized sync states.
|
||||
setupSyncKnob(lfo1RateKnob, lfo1RateAttach, lfo1BeatAttach, "lfo1Rate", "lfo1Beat", lfo1SyncBox);
|
||||
setupSyncKnob(lfo2RateKnob, lfo2RateAttach, lfo2BeatAttach, "lfo2Rate", "lfo2Beat", lfo2SyncBox);
|
||||
setupSyncKnob(delayTimeKnob, delayTimeAttach, delayBeatAttach, "delayTime", "delayBeat", delaySyncBox);
|
||||
setupSyncKnob(lfo1RateKnob, lfo1RateAttach, lfo1BeatAttach, "lfo1Rate", "lfo1Beat", lfo1SyncLed.isLit());
|
||||
setupSyncKnob(lfo2RateKnob, lfo2RateAttach, lfo2BeatAttach, "lfo2Rate", "lfo2Beat", lfo2SyncLed.isLit());
|
||||
setupSyncKnob(delayTimeKnob, delayTimeAttach, delayBeatAttach, "delayTime", "delayBeat", delaySyncLed.isLit());
|
||||
}
|
||||
|
||||
void MainContentComponent::paint(juce::Graphics& g) {
|
||||
|
|
@ -1219,14 +1286,14 @@ void MainContentComponent::resized() {
|
|||
lfo1DepthKnob.setBounds(132, lfoKnobY, lfoKnobSize, lfoKnobSize);
|
||||
lfo1ShapeBox.setBounds(242, lfoKnobY + (lfoKnobSize - 28) / 2, 105, 28);
|
||||
lfo1DestBox.setBounds(357, lfoKnobY + (lfoKnobSize - 28) / 2, 105, 28);
|
||||
lfo1SyncBox.setBounds(22, lfoKnobY + lfoKnobSize + 6, 200, 28);
|
||||
lfo1SyncLed.setBounds(22, lfoKnobY + lfoKnobSize + 6, 200, 28);
|
||||
|
||||
// LFO 2 sub-section (X=560, Y=642, W=530, H=110)
|
||||
lfo2RateKnob.setBounds(568, lfoKnobY, lfoKnobSize, lfoKnobSize);
|
||||
lfo2DepthKnob.setBounds(678, lfoKnobY, lfoKnobSize, lfoKnobSize);
|
||||
lfo2ShapeBox.setBounds(788, lfoKnobY + (lfoKnobSize - 28) / 2, 105, 28);
|
||||
lfo2DestBox.setBounds(903, lfoKnobY + (lfoKnobSize - 28) / 2, 105, 28);
|
||||
lfo2SyncBox.setBounds(568, lfoKnobY + lfoKnobSize + 6, 200, 28);
|
||||
lfo2SyncLed.setBounds(568, lfoKnobY + lfoKnobSize + 6, 200, 28);
|
||||
|
||||
// --- FX2 SECTION ---
|
||||
int fx2KnobY = lfoKnobY;
|
||||
|
|
@ -1237,8 +1304,8 @@ void MainContentComponent::resized() {
|
|||
delayTimeKnob.setBounds(dx, fx2KnobY, lfoKnobSize, lfoKnobSize);
|
||||
delayFbKnob.setBounds(dx + dGap, fx2KnobY, lfoKnobSize, lfoKnobSize);
|
||||
delayMixKnob.setBounds(dx + dGap * 2, fx2KnobY, lfoKnobSize, lfoKnobSize);
|
||||
delayPingPongKnob.setBounds(dx + dGap * 3, fx2KnobY, lfoKnobSize, lfoKnobSize);
|
||||
delaySyncBox.setBounds(dx, fx2KnobY + lfoKnobSize + 6, 250, 28);
|
||||
delayPingPongLed.setBounds(dx + dGap * 3, fx2KnobY, lfoKnobSize, lfoKnobSize);
|
||||
delaySyncLed.setBounds(dx, fx2KnobY + lfoKnobSize + 6, 250, 28);
|
||||
|
||||
// Reverb sub-section: SIZE, DAMP, MIX (right edge aligned to 1650)
|
||||
{
|
||||
|
|
@ -1297,7 +1364,7 @@ void MainContentComponent::resized() {
|
|||
int x = arpX + (arpW - totalW) / 2;
|
||||
|
||||
arpEnabledLabel.setBounds(x, labelY, widths[0], labelH);
|
||||
arpEnabledBox.setBounds(x, comboY, widths[0], comboH);
|
||||
arpEnabledLed.setBounds(x, comboY, widths[0], comboH);
|
||||
x += widths[0] + gap;
|
||||
|
||||
arpPatternLabel.setBounds(x, labelY, widths[1], labelH);
|
||||
|
|
@ -1309,7 +1376,7 @@ void MainContentComponent::resized() {
|
|||
x += widths[2] + gap;
|
||||
|
||||
arpDirectionLabel.setBounds(x, labelY, widths[3], labelH);
|
||||
arpDirectionBox.setBounds(x, comboY, widths[3], comboH);
|
||||
arpDirectionLed.setBounds(x, comboY, widths[3], comboH);
|
||||
x += widths[3] + gap;
|
||||
|
||||
arpRateLabel.setBounds(x, labelY, widths[4], labelH);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue