Add ping-pong delay, fix preset save extension, align reverb/scale UI

This commit is contained in:
Armin 2026-07-16 00:49:09 +02:00
commit af87193643
5 changed files with 129 additions and 1110 deletions

View file

@ -521,6 +521,7 @@ MainContentComponent::MainContentComponent(ChromaFlockProcessor& p)
setupSyncKnob(delayTimeKnob, delayTimeAttach, delayBeatAttach, "delayTime", "delayBeat", delaySyncBox);
setupParam(delayFbKnob, delayFbAttach, "delayFeedback", "FBACK");
setupParam(delayMixKnob, delayMixAttach, "delayMix", "MIX");
setupParam(delayPingPongKnob, delayPingPongAttach, "delayPingPong", "PING");
setupParam(reverbSizeKnob, reverbSizeAttach, "reverbSize", "SIZE");
setupParam(reverbDampKnob, reverbDampAttach, "reverbDamping", "DAMP");
setupParam(reverbMixKnob, reverbMixAttach, "reverbMix", "MIX");
@ -736,30 +737,36 @@ void MainContentComponent::savePresetFile() {
.getChildFile("chromaflock-" + currentPresetName().replaceCharacter(' ', '-') + ".cfl"),
"*.cfl");
chooser->launchAsync(juce::FileBrowserComponent::saveMode
| juce::FileBrowserComponent::canSelectFiles,
[this, chooser](const juce::FileChooser& c) {
std::unique_ptr<juce::FileChooser> deleter(chooser);
auto file = c.getResult();
if (file == juce::File())
return;
if (file.getFileExtension().toLowerCase() != ".cfl")
chooser->launchAsync(juce::FileBrowserComponent::saveMode
| juce::FileBrowserComponent::canSelectFiles
| juce::FileBrowserComponent::warnAboutOverwriting,
[this, chooser](const juce::FileChooser& c) {
std::unique_ptr<juce::FileChooser> deleter(chooser);
auto file = c.getResult();
if (file == juce::File())
return;
// Always force the .cfl extension (macOS native panel does not
// reliably append it) and lowercase the filename.
juce::String name = file.getFileNameWithoutExtension();
if (name.toLowerCase() != name)
file = file.getParentDirectory().getChildFile(name.toLowerCase());
file = file.withFileExtension(".cfl");
auto state = processorRef.apvts.copyState();
std::unique_ptr<juce::XmlElement> presetXml(state.createXml());
auto state = processorRef.apvts.copyState();
std::unique_ptr<juce::XmlElement> presetXml(state.createXml());
std::unique_ptr<juce::XmlElement> root(new juce::XmlElement(kCflTag));
root->setAttribute("version", kCflVersion);
root->setAttribute("generator", "ChromaFlock");
root->addChildElement(presetXml.release());
std::unique_ptr<juce::XmlElement> root(new juce::XmlElement(kCflTag));
root->setAttribute("version", kCflVersion);
root->setAttribute("generator", "ChromaFlock");
root->addChildElement(presetXml.release());
if (!root->writeTo(file))
juce::NativeMessageBox::showMessageBoxAsync(juce::AlertWindow::WarningIcon,
"Save Failed", "Could not write preset file:\n" + file.getFullPathName(), this);
else
patchLCD.setText(file.getFileNameWithoutExtension());
});
if (!root->writeTo(file))
juce::NativeMessageBox::showMessageBoxAsync(juce::AlertWindow::WarningIcon,
"Save Failed", "Could not write preset file:\n" + file.getFullPathName(), this);
else
patchLCD.setText(file.getFileNameWithoutExtension());
});
}
void MainContentComponent::loadPresetFile() {
@ -942,9 +949,9 @@ void MainContentComponent::paint(juce::Graphics& g) {
g.drawText(title, x + 6, y + titlePadY, w - 12, 14, juce::Justification::centred);
};
drawSubSection(10, 508, 512, 140, "DISTORTION", 6);
drawSubSection(528, 508, 470, 140, "COMPRESSOR", 6);
drawSubSection(1004, 508, 270, 140, "AUTO-PAN", 6);
drawSubSection(10, 508, 510, 140, "DISTORTION", 6);
drawSubSection(528, 508, 468, 140, "COMPRESSOR", 6);
drawSubSection(1004, 508, 268, 140, "AUTO-PAN", 6);
drawSubSection(1280, 508, 370, 140, "LIMITER", 6);
// LFO sub-sections
@ -952,8 +959,8 @@ void MainContentComponent::paint(juce::Graphics& g) {
drawSubSection(560, 658, 530, 150, "LFO 2", 4);
// FX2 sub-sections
drawSubSection(1100, 658, 265, 150, "DELAY", 4);
drawSubSection(1375, 658, 275, 150, "REVERB", 4);
drawSubSection(1015, 658, 375, 150, "DELAY", 4);
drawSubSection(1395, 658, 255, 150, "REVERB", 4);
}
void MainContentComponent::resized() {
@ -1076,39 +1083,40 @@ void MainContentComponent::resized() {
int lfoKnobY = 658 + 4 + 14 + 6; // subSectionY + titlePad + titleH + bottomPad
lfoLabel.setBounds(10, lfoY, 1080, fxLabelH);
fx2Label.setBounds(1100, lfoY, 540, fxLabelH);
fx2Label.setBounds(1015, lfoY, 635, fxLabelH);
// LFO 1 sub-section (X=15, Y=642, W=530, H=110)
lfo1RateKnob.setBounds(22, lfoKnobY, lfoKnobSize, lfoKnobSize);
lfo1DepthKnob.setBounds(132, lfoKnobY, lfoKnobSize, lfoKnobSize);
lfo1ShapeBox.setBounds(242, lfoKnobY + (lfoKnobSize - 28) / 2, 130, 28);
lfo1DestBox.setBounds(382, lfoKnobY + (lfoKnobSize - 28) / 2, 155, 28);
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);
// 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, 130, 28);
lfo2DestBox.setBounds(928, lfoKnobY + (lfoKnobSize - 28) / 2, 155, 28);
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);
// --- FX2 SECTION ---
int fx2KnobY = lfoKnobY;
// Delay sub-section (X=1100, Y=690, W=265, H=130)
// Row 1: TIME, FBACK, MIX
delayTimeKnob.setBounds(1110, fx2KnobY, lfoKnobSize, lfoKnobSize);
delayFbKnob.setBounds(1198, fx2KnobY, lfoKnobSize, lfoKnobSize);
delayMixKnob.setBounds(1286, fx2KnobY, lfoKnobSize, lfoKnobSize);
delaySyncBox.setBounds(1110, fx2KnobY + lfoKnobSize + 6, 240, 28);
// Delay sub-section: TIME, FBACK, MIX, PING + sync combo
int dx = 1020;
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);
delayPingPongKnob.setBounds(dx + dGap * 3, fx2KnobY, lfoKnobSize, lfoKnobSize);
delaySyncBox.setBounds(dx, fx2KnobY + lfoKnobSize + 6, 250, 28);
// Reverb sub-section (X=1375, Y=690, W=265, H=130)
// Reverb sub-section: SIZE, DAMP, MIX (right edge aligned to 1650)
{
int rowW = 3 * lfoKnobSize + 2 * 8;
int rx = 1650 - rowW;
int rx = 1402;
reverbSizeKnob.setBounds(rx, fx2KnobY, lfoKnobSize, lfoKnobSize);
reverbDampKnob.setBounds(rx + (lfoKnobSize + 8), fx2KnobY, lfoKnobSize, lfoKnobSize);
reverbMixKnob.setBounds(rx + (lfoKnobSize + 8) * 2, fx2KnobY, lfoKnobSize, lfoKnobSize);
reverbDampKnob.setBounds(rx + (lfoKnobSize + 4), fx2KnobY, lfoKnobSize, lfoKnobSize);
reverbMixKnob.setBounds(rx + (lfoKnobSize + 4) * 2, fx2KnobY, lfoKnobSize, lfoKnobSize);
}
// --- PRESET + SCALE (vertically centered within top section, y: 0..128) ---
@ -1134,8 +1142,8 @@ void MainContentComponent::resized() {
semitoneTransposeLabel.setBounds(1214, labelY2, 32, labelH2);
semitoneTransposeBox.setBounds(1246, comboY2, 74, comboH2);
scaleLabel.setBounds(1520, labelY2, 42, labelH2);
uiScaleBox.setBounds(1566, comboY2, 80, comboH2);
scaleLabel.setBounds(1514, labelY2, 42, labelH2);
uiScaleBox.setBounds(1560, comboY2, 80, comboH2);
// Visualizer area
int vizY = 818;