mirror of
https://codeberg.org/armin/justasample.git
synced 2026-09-01 04:10:48 +02:00
328 lines
13 KiB
C++
328 lines
13 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
ModulationSection.cpp
|
|
Created: 24 Jul 2026
|
|
Author: Armin
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#include <JuceHeader.h>
|
|
|
|
#include "ModulationSection.h"
|
|
|
|
ModulationSection::ModulationSection(JustaSampleAudioProcessor& processor) :
|
|
apvts(processor.APVTS()),
|
|
|
|
filterLabel("", "Filter"),
|
|
lfoLabel("", "LFO"),
|
|
phaserLabel("", "Phaser"),
|
|
ringModLabel("", "Ring Mod"),
|
|
|
|
filterTypeAttachment(apvts, PluginParameters::MOD_FILTER_TYPE, filterTypeBox),
|
|
filterCutoffAttachment(apvts, PluginParameters::MOD_FILTER_CUTOFF, filterCutoff),
|
|
filterResonanceAttachment(apvts, PluginParameters::MOD_FILTER_RESONANCE, filterResonance),
|
|
|
|
lfoWaveformAttachment(apvts, PluginParameters::MOD_LFO_WAVEFORM, lfoWaveformBox),
|
|
lfoRateAttachment(apvts, PluginParameters::MOD_LFO_RATE, lfoRate),
|
|
lfoSyncAttachment(apvts, PluginParameters::MOD_LFO_SYNC, lfoSyncButton),
|
|
lfoSyncDivAttachment(apvts, PluginParameters::MOD_LFO_SYNC_DIV, lfoSyncDivBox),
|
|
lfoFilterDepthAttachment(apvts, PluginParameters::MOD_LFO_FILTER_DEPTH, lfoFilterDepth),
|
|
lfoSpeedDepthAttachment(apvts, PluginParameters::MOD_LFO_SPEED_DEPTH, lfoSpeedDepth),
|
|
|
|
phaserEnabledAttachment(apvts, PluginParameters::MOD_PHASER_ENABLED, phaserEnabled),
|
|
phaserDepthAttachment(apvts, PluginParameters::MOD_PHASER_DEPTH, phaserDepth),
|
|
phaserFeedbackAttachment(apvts, PluginParameters::MOD_PHASER_FEEDBACK, phaserFeedback),
|
|
phaserStagesAttachment(apvts, PluginParameters::MOD_PHASER_STAGES, phaserStages),
|
|
|
|
ringModEnabledAttachment(apvts, PluginParameters::MOD_RINGMOD_ENABLED, ringModEnabled),
|
|
ringModMixAttachment(apvts, PluginParameters::MOD_RINGMOD_MIX, ringModMix)
|
|
{
|
|
// Labels
|
|
auto setupLabel = [this](juce::Label& label)
|
|
{
|
|
label.setJustificationType(juce::Justification::centred);
|
|
label.setInterceptsMouseClicks(false, false);
|
|
addAndMakeVisible(label);
|
|
};
|
|
setupLabel(filterLabel);
|
|
setupLabel(lfoLabel);
|
|
setupLabel(phaserLabel);
|
|
setupLabel(ringModLabel);
|
|
|
|
// Filter type combo
|
|
filterTypeBox.addItemList(PluginParameters::MOD_FILTER_TYPE_LABELS, 1);
|
|
filterTypeBox.setJustificationType(juce::Justification::centred);
|
|
addAndMakeVisible(filterTypeBox);
|
|
|
|
// Filter rotaries
|
|
auto setupRotary = [this](CustomRotary& rotary, const juce::String& unit)
|
|
{
|
|
rotary.setSliderStyle(juce::Slider::RotaryVerticalDrag);
|
|
rotary.setMouseDragSensitivity(150);
|
|
rotary.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 0, 0);
|
|
rotary.getProperties().set(ComponentProps::ROTARY_UNIT, unit);
|
|
addAndMakeVisible(rotary);
|
|
rotaries.add(&rotary);
|
|
};
|
|
|
|
setupRotary(filterCutoff, PluginParameters::FREQUENCY_UNIT);
|
|
setupRotary(filterResonance, "");
|
|
|
|
// LFO waveform combo
|
|
lfoWaveformBox.addItemList(PluginParameters::MOD_LFO_WAVEFORM_LABELS, 1);
|
|
lfoWaveformBox.setJustificationType(juce::Justification::centred);
|
|
addAndMakeVisible(lfoWaveformBox);
|
|
|
|
setupRotary(lfoRate, PluginParameters::FREQUENCY_UNIT);
|
|
|
|
lfoSyncButton.useShape(juce::Path());
|
|
lfoSyncButton.setButtonText("Sync");
|
|
addAndMakeVisible(lfoSyncButton);
|
|
|
|
lfoSyncDivBox.addItemList(PluginParameters::MOD_LFO_SYNC_DIV_LABELS, 1);
|
|
lfoSyncDivBox.setJustificationType(juce::Justification::centred);
|
|
addAndMakeVisible(lfoSyncDivBox);
|
|
|
|
setupRotary(lfoFilterDepth, "");
|
|
setupRotary(lfoSpeedDepth, "");
|
|
|
|
// Phaser
|
|
phaserEnabled.setButtonText("On");
|
|
addAndMakeVisible(phaserEnabled);
|
|
setupRotary(phaserDepth, "");
|
|
setupRotary(phaserFeedback, "");
|
|
phaserStages.getProperties().set(ComponentProps::ROTARY_UNIT, "stg");
|
|
setupRotary(phaserStages, "stg");
|
|
|
|
// Ring mod
|
|
ringModEnabled.setButtonText("On");
|
|
addAndMakeVisible(ringModEnabled);
|
|
setupRotary(ringModMix, "%");
|
|
}
|
|
|
|
void ModulationSection::paint(juce::Graphics& g)
|
|
{
|
|
auto colors = getTheme();
|
|
auto bounds = getLocalBounds().toFloat();
|
|
|
|
g.setColour(colors.foreground);
|
|
g.fillRect(bounds);
|
|
|
|
// Draw section dividers
|
|
g.setColour(colors.slate.withAlpha(0.2f));
|
|
|
|
float dividerY1 = bounds.getY() + bounds.getHeight() * 0.125f;
|
|
float dividerY2 = bounds.getY() + bounds.getHeight() * 0.875f;
|
|
|
|
// Dividers between Filter | LFO | Phaser | Ring Mod
|
|
float filterEnd = scalef(380.f);
|
|
float lfoEnd = filterEnd + scalef(600.f);
|
|
float phaserEnd = lfoEnd + scalef(530.f);
|
|
|
|
g.drawVerticalLine(int(filterEnd), dividerY1, dividerY2);
|
|
g.drawVerticalLine(int(lfoEnd), dividerY1, dividerY2);
|
|
g.drawVerticalLine(int(phaserEnd), dividerY1, dividerY2);
|
|
}
|
|
|
|
void ModulationSection::resized()
|
|
{
|
|
auto bounds = getLocalBounds().toFloat();
|
|
|
|
// Section labels
|
|
auto filterSection = bounds.removeFromLeft(scalef(380.f));
|
|
auto lfoSection = bounds.removeFromLeft(scalef(600.f));
|
|
auto phaserSection = bounds.removeFromLeft(scalef(530.f));
|
|
auto ringModSection = bounds;
|
|
|
|
// Reduce all sections vertically
|
|
filterSection.reduce(scalef(16.f), scalef(8.f));
|
|
lfoSection.reduce(scalef(16.f), scalef(8.f));
|
|
phaserSection.reduce(scalef(16.f), scalef(8.f));
|
|
ringModSection.reduce(scalef(16.f), scalef(8.f));
|
|
|
|
// === Filter section ===
|
|
auto filterTitleBounds = filterSection.removeFromTop(scalef(28.f));
|
|
filterLabel.setBounds(filterTitleBounds.toNearestInt());
|
|
filterLabel.setFont(getInriaSans().withHeight(scalef(22.f)));
|
|
filterSection.removeFromTop(scalef(4.f));
|
|
|
|
auto filterTypeBounds = filterSection.removeFromTop(scalef(28.f)).reduced(scalef(20.f), 0.f);
|
|
filterTypeBox.setBounds(filterTypeBounds.toNearestInt());
|
|
filterSection.removeFromTop(scalef(6.f));
|
|
|
|
float filterRotarySize = scalef(50.f);
|
|
float filterRotaryPad = filterRotarySize * Layout::rotaryPadding;
|
|
|
|
auto filterCutoffBounds = filterSection.removeFromLeft(filterRotarySize + 2 * filterRotaryPad);
|
|
filterCutoff.setBounds(filterCutoffBounds.toNearestInt());
|
|
filterCutoff.sendLookAndFeelChange();
|
|
|
|
filterSection.removeFromLeft(scalef(16.f));
|
|
|
|
auto filterResBounds = filterSection.removeFromLeft(filterRotarySize + 2 * filterRotaryPad);
|
|
filterResonance.setBounds(filterResBounds.toNearestInt());
|
|
filterResonance.sendLookAndFeelChange();
|
|
|
|
// === LFO section ===
|
|
auto lfoTitleBounds = lfoSection.removeFromTop(scalef(28.f));
|
|
lfoLabel.setBounds(lfoTitleBounds.toNearestInt());
|
|
lfoLabel.setFont(getInriaSans().withHeight(scalef(22.f)));
|
|
lfoSection.removeFromTop(scalef(4.f));
|
|
|
|
auto lfoWaveformBounds = lfoSection.removeFromTop(scalef(28.f)).reduced(scalef(20.f), 0.f);
|
|
lfoWaveformBox.setBounds(lfoWaveformBounds.toNearestInt());
|
|
lfoSection.removeFromTop(scalef(4.f));
|
|
|
|
auto lfoTopRow = lfoSection.removeFromTop(scalef(56.f));
|
|
auto lfoBottomRow = lfoSection;
|
|
|
|
float lfoRotarySize = scalef(48.f);
|
|
float lfoRotaryPad = lfoRotarySize * Layout::rotaryPadding;
|
|
|
|
// Top row: Rate, Sync button, Sync div
|
|
auto lfoRateBounds = lfoTopRow.removeFromLeft(lfoRotarySize + 2 * lfoRotaryPad);
|
|
lfoRate.setBounds(lfoRateBounds.toNearestInt());
|
|
lfoRate.sendLookAndFeelChange();
|
|
|
|
lfoTopRow.removeFromLeft(scalef(8.f));
|
|
auto lfoSyncBounds = lfoTopRow.removeFromLeft(scalef(50.f));
|
|
lfoSyncButton.setBounds(lfoSyncBounds.toNearestInt());
|
|
lfoSyncButton.setBorder(scalef(2.f), scalef(4.f));
|
|
|
|
lfoTopRow.removeFromLeft(scalef(8.f));
|
|
auto lfoSyncDivBounds = lfoTopRow.removeFromLeft(scalef(70.f));
|
|
lfoSyncDivBox.setBounds(lfoSyncDivBounds.toNearestInt());
|
|
|
|
// Bottom row: Filter depth, Speed depth
|
|
auto lfoFilterDepthBounds = lfoBottomRow.removeFromLeft(lfoRotarySize + 2 * lfoRotaryPad);
|
|
lfoFilterDepth.setBounds(lfoFilterDepthBounds.toNearestInt());
|
|
lfoFilterDepth.sendLookAndFeelChange();
|
|
|
|
lfoBottomRow.removeFromLeft(scalef(8.f));
|
|
auto lfoSpeedDepthBounds = lfoBottomRow.removeFromLeft(lfoRotarySize + 2 * lfoRotaryPad);
|
|
lfoSpeedDepth.setBounds(lfoSpeedDepthBounds.toNearestInt());
|
|
lfoSpeedDepth.sendLookAndFeelChange();
|
|
|
|
// === Phaser section ===
|
|
auto phaserTitleBounds = phaserSection.removeFromTop(scalef(28.f));
|
|
phaserLabel.setBounds(phaserTitleBounds.toNearestInt());
|
|
phaserLabel.setFont(getInriaSans().withHeight(scalef(22.f)));
|
|
phaserSection.removeFromTop(scalef(4.f));
|
|
|
|
auto phaserEnableBounds = phaserSection.removeFromTop(scalef(24.f)).reduced(scalef(20.f), 0.f);
|
|
phaserEnabled.setBounds(phaserEnableBounds.toNearestInt());
|
|
phaserSection.removeFromTop(scalef(4.f));
|
|
|
|
float phaserRotarySize = scalef(48.f);
|
|
float phaserRotaryPad = phaserRotarySize * Layout::rotaryPadding;
|
|
|
|
auto phaserDepthBounds = phaserSection.removeFromLeft(phaserRotarySize + 2 * phaserRotaryPad);
|
|
phaserDepth.setBounds(phaserDepthBounds.toNearestInt());
|
|
phaserDepth.sendLookAndFeelChange();
|
|
|
|
phaserSection.removeFromLeft(scalef(8.f));
|
|
auto phaserFbBounds = phaserSection.removeFromLeft(phaserRotarySize + 2 * phaserRotaryPad);
|
|
phaserFeedback.setBounds(phaserFbBounds.toNearestInt());
|
|
phaserFeedback.sendLookAndFeelChange();
|
|
|
|
phaserSection.removeFromLeft(scalef(8.f));
|
|
auto phaserStagesBounds = phaserSection.removeFromLeft(phaserRotarySize + 2 * phaserRotaryPad);
|
|
phaserStages.setBounds(phaserStagesBounds.toNearestInt());
|
|
phaserStages.sendLookAndFeelChange();
|
|
|
|
// === Ring Mod section ===
|
|
auto ringModTitleBounds = ringModSection.removeFromTop(scalef(28.f));
|
|
ringModLabel.setBounds(ringModTitleBounds.toNearestInt());
|
|
ringModLabel.setFont(getInriaSans().withHeight(scalef(22.f)));
|
|
ringModSection.removeFromTop(scalef(4.f));
|
|
|
|
auto ringModEnableBounds = ringModSection.removeFromTop(scalef(24.f)).reduced(scalef(20.f), 0.f);
|
|
ringModEnabled.setBounds(ringModEnableBounds.toNearestInt());
|
|
ringModSection.removeFromTop(scalef(4.f));
|
|
|
|
float ringModRotarySize = scalef(48.f);
|
|
float ringModRotaryPad = ringModRotarySize * Layout::rotaryPadding;
|
|
|
|
auto ringModMixBounds = ringModSection.removeFromLeft(ringModRotarySize + 2 * ringModRotaryPad);
|
|
ringModMix.setBounds(ringModMixBounds.toNearestInt());
|
|
ringModMix.sendLookAndFeelChange();
|
|
}
|
|
|
|
void ModulationSection::lookAndFeelChanged()
|
|
{
|
|
auto colors = getTheme();
|
|
|
|
filterLabel.setColour(juce::Label::textColourId, colors.dark);
|
|
lfoLabel.setColour(juce::Label::textColourId, colors.dark);
|
|
phaserLabel.setColour(juce::Label::textColourId, colors.dark);
|
|
ringModLabel.setColour(juce::Label::textColourId, colors.dark);
|
|
|
|
phaserEnabled.setColour(juce::ToggleButton::textColourId, colors.dark);
|
|
ringModEnabled.setColour(juce::ToggleButton::textColourId, colors.dark);
|
|
|
|
filterTypeBox.setColour(juce::ComboBox::backgroundColourId, colors.darkerSlate);
|
|
filterTypeBox.setColour(juce::ComboBox::textColourId, colors.light);
|
|
filterTypeBox.setColour(juce::ComboBox::arrowColourId, colors.dark);
|
|
|
|
lfoWaveformBox.setColour(juce::ComboBox::backgroundColourId, colors.darkerSlate);
|
|
lfoWaveformBox.setColour(juce::ComboBox::textColourId, colors.light);
|
|
lfoWaveformBox.setColour(juce::ComboBox::arrowColourId, colors.dark);
|
|
|
|
lfoSyncButton.setColors(colors.darkerSlate, colors.highlight, colors.dark.withAlpha(0.25f));
|
|
|
|
lfoSyncDivBox.setColour(juce::ComboBox::backgroundColourId, colors.darkerSlate);
|
|
lfoSyncDivBox.setColour(juce::ComboBox::textColourId, colors.light);
|
|
lfoSyncDivBox.setColour(juce::ComboBox::arrowColourId, colors.dark);
|
|
}
|
|
|
|
void ModulationSection::enablementChanged()
|
|
{
|
|
bool enabled = isEnabled();
|
|
|
|
filterTypeBox.setEnabled(enabled);
|
|
filterCutoff.setEnabled(enabled);
|
|
filterResonance.setEnabled(enabled);
|
|
|
|
lfoWaveformBox.setEnabled(enabled);
|
|
lfoRate.setEnabled(enabled);
|
|
lfoSyncButton.setEnabled(enabled);
|
|
lfoSyncDivBox.setEnabled(enabled);
|
|
lfoFilterDepth.setEnabled(enabled);
|
|
lfoSpeedDepth.setEnabled(enabled);
|
|
|
|
phaserEnabled.setEnabled(enabled);
|
|
phaserDepth.setEnabled(enabled);
|
|
phaserFeedback.setEnabled(enabled);
|
|
phaserStages.setEnabled(enabled);
|
|
|
|
ringModEnabled.setEnabled(enabled);
|
|
ringModMix.setEnabled(enabled);
|
|
|
|
repaint();
|
|
}
|
|
|
|
void ModulationSection::mouseDown(const juce::MouseEvent& event)
|
|
{
|
|
auto parent = event.originalComponent->getParentComponent();
|
|
for (auto* rotary : rotaries)
|
|
if (parent == rotary)
|
|
rotary->mouseDown(event.getEventRelativeTo(rotary));
|
|
}
|
|
|
|
void ModulationSection::mouseUp(const juce::MouseEvent& event)
|
|
{
|
|
auto parent = event.originalComponent->getParentComponent();
|
|
for (auto* rotary : rotaries)
|
|
if (parent == rotary)
|
|
rotary->mouseUp(event.getEventRelativeTo(rotary));
|
|
}
|
|
|
|
void ModulationSection::mouseDrag(const juce::MouseEvent& event)
|
|
{
|
|
auto parent = event.originalComponent->getParentComponent();
|
|
for (auto* rotary : rotaries)
|
|
if (parent == rotary)
|
|
rotary->mouseDrag(event.getEventRelativeTo(rotary));
|
|
}
|