chromaflock/Source/PluginEditor.h

280 lines
11 KiB
C
Raw Normal View History

2026-07-13 20:57:38 +02:00
#pragma once
#include <juce_audio_processors/juce_audio_processors.h>
#include <juce_gui_basics/juce_gui_basics.h>
#include <juce_dsp/juce_dsp.h>
#include "PluginProcessor.h"
#include <vector>
class KnobLookAndFeel : public juce::LookAndFeel_V4 {
public:
KnobLookAndFeel();
2026-07-13 20:57:38 +02:00
void drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height,
float sliderPos, float rotaryStartAngle,
float rotaryEndAngle, juce::Slider& slider) override;
private:
juce::Image knobOverlay;
2026-07-13 20:57:38 +02:00
};
class KnobSlider : public juce::Slider {
public:
using juce::Slider::Slider;
juce::String getTextFromValue(double v) override {
return juce::String::formatted("%.1f", v);
}
};
2026-07-13 20:57:38 +02:00
class ComboBoxLookAndFeel : public juce::LookAndFeel_V4 {
public:
void drawComboBox(juce::Graphics& g, int width, int height,
bool isButtonDown, int buttonX, int buttonY,
int buttonW, int buttonH, juce::ComboBox& box) override;
void drawPopupMenuItem(juce::Graphics& g, const juce::Rectangle<int>& area,
bool isSeparator, bool isActive, bool isHighlighted,
bool isTicked, bool hasSubMenu, const juce::String& text,
const juce::String& shortcutKeyText,
const juce::Drawable* icon, const juce::Colour* textColour) override;
};
class VuMeter : public juce::Component, public juce::Timer {
public:
explicit VuMeter(ChromaFlockProcessor& p) : processor(p) { startTimerHz(30); }
void paint(juce::Graphics& g) override;
void timerCallback() override { repaint(); }
private:
ChromaFlockProcessor& processor;
};
class WaveformDisplay : public juce::Component, public juce::Timer {
public:
explicit WaveformDisplay(ChromaFlockProcessor& p) : processor(p) {
2026-07-13 20:57:38 +02:00
fft = std::make_unique<juce::dsp::FFT>(ChromaFlockProcessor::fftOrder);
startTimerHz(30);
}
void paint(juce::Graphics& g) override;
void timerCallback() override { repaint(); }
private:
ChromaFlockProcessor& processor;
std::unique_ptr<juce::dsp::FFT> fft;
std::vector<float> specSmooth;
2026-07-13 20:57:38 +02:00
};
class PatchLCD : public juce::Component {
public:
std::function<void()> onClick;
void setText(const juce::String& t) { text = t; repaint(); }
const juce::String& getText() const { return text; }
void paint(juce::Graphics& g) override;
void mouseEnter(const juce::MouseEvent&) override { setMouseCursor(juce::MouseCursor::PointingHandCursor); }
void mouseDown(const juce::MouseEvent&) override { if (onClick) onClick(); }
private:
juce::String text = "INIT";
};
class MidiLed : public juce::Component, public juce::Timer {
public:
explicit MidiLed(ChromaFlockProcessor& p) : processor(p) { startTimerHz(30); }
void paint(juce::Graphics& g) override;
void timerCallback() override { repaint(); }
private:
ChromaFlockProcessor& processor;
};
class LedToggle : public juce::Component, public juce::Timer {
public:
LedToggle(juce::AudioProcessorValueTreeState& apvts, const juce::String& paramId,
const juce::String& onText, const juce::String& offText);
void paint(juce::Graphics& g) override;
void mouseDown(const juce::MouseEvent&) override;
void mouseEnter(const juce::MouseEvent&) override;
void timerCallback() override;
bool isLit() const { return lit; }
std::function<void()> onChange;
private:
juce::AudioProcessorValueTreeState& apvts;
juce::String paramId;
juce::String onText, offText;
bool lit = false;
};
2026-07-13 20:57:38 +02:00
class PianoRollComponent : public juce::Component, public juce::Timer {
public:
explicit PianoRollComponent(ChromaFlockProcessor& p) : processor(p) { startTimerHz(60); }
2026-07-13 20:57:38 +02:00
void paint(juce::Graphics& g) override;
void timerCallback() override;
2026-07-13 20:57:38 +02:00
void mouseDown(const juce::MouseEvent& e) override;
void mouseUp(const juce::MouseEvent& e) override;
void mouseDrag(const juce::MouseEvent& e) override;
private:
ChromaFlockProcessor& processor;
static constexpr int firstMidiNote = 48; // C3
static constexpr int numKeys = 49; // C3..C7
int whiteKeyWidth = 54;
int whiteKeyHeight = 130;
int blackKeyWidth = 35;
int blackKeyHeight = 80;
int pitchBendWidth = 40;
int midiNoteForKey(int index) const;
bool isBlackKey(int midiNote) const;
int keyAtPosition(juce::Point<int> pos) const;
void triggerNote(int note, bool on);
int lastTriggeredNote = -1;
int currentPitchBend = 64; // 0..127, 64 = center
bool pitchBendDragging = false;
int pitchBendStartY = 0;
bool pitchBendGliding = false;
float pitchBendGlideStart = 0.0f;
juce::int64 pitchBendGlideStartMs = 0;
static constexpr int pitchBendGlideMs = 50;
2026-07-13 20:57:38 +02:00
};
class MainContentComponent : public juce::Component {
public:
explicit MainContentComponent(ChromaFlockProcessor& p);
void paint(juce::Graphics& g) override;
void resized() override;
std::function<void(float)> onScaleChanged;
private:
ChromaFlockProcessor& processorRef;
KnobLookAndFeel knobLaf;
ComboBoxLookAndFeel comboLaf;
juce::Image bgImage;
bool bgImageLoaded = false;
juce::Image logoImage;
bool logoLoaded = false;
VuMeter vuMeter;
WaveformDisplay waveformDisplay;
PianoRollComponent pianoRoll;
juce::Label osc1Label, osc2Label, filterLabel, envLabel, fEnvLabel, globalLabel;
juce::Label fxLabel, lfoLabel, fx2Label;
juce::Label scaleLabel;
juce::Label midiLabel;
juce::Label octaveTransposeLabel, semitoneTransposeLabel;
2026-07-13 20:57:38 +02:00
juce::ComboBox octaveTransposeBox, semitoneTransposeBox;
2026-07-13 20:57:38 +02:00
juce::ComboBox osc1WaveBox;
KnobSlider osc1OctKnob, osc1SemiKnob, osc1FineKnob, osc1LevelKnob;
2026-07-13 20:57:38 +02:00
juce::ComboBox osc2WaveBox;
KnobSlider osc2OctKnob, osc2SemiKnob, osc2FineKnob, osc2LevelKnob, phaseOffsetKnob;
2026-07-13 20:57:38 +02:00
juce::ComboBox filterTypeBox;
KnobSlider filterCutoffKnob, filterResKnob, filterEnvAmtKnob, keyTrackKnob;
2026-07-13 20:57:38 +02:00
KnobSlider envAttackKnob, envDecayKnob, envSustainKnob, envReleaseKnob;
KnobSlider fEnvAttackKnob, fEnvDecayKnob, fEnvSustainKnob, fEnvReleaseKnob;
2026-07-13 20:57:38 +02:00
KnobSlider panKnob, driveKnob, masterKnob, pbRangeKnob;
2026-07-13 20:57:38 +02:00
// FX controls
juce::ComboBox distTypeBox;
KnobSlider distAmountKnob, distSymmetryKnob, distToneKnob;
KnobSlider compThresholdKnob, compRatioKnob, compAttackKnob, compReleaseKnob, compMakeupKnob;
KnobSlider autoPanRateKnob, autoPanDepthKnob, autoPanPhaseKnob;
KnobSlider limiterThresholdKnob, limiterCeilingKnob, limiterReleaseKnob;
2026-07-13 20:57:38 +02:00
// LFO controls
KnobSlider lfo1RateKnob, lfo1DepthKnob;
juce::ComboBox lfo1ShapeBox, lfo1DestBox;
KnobSlider lfo2RateKnob, lfo2DepthKnob;
juce::ComboBox lfo2ShapeBox, lfo2DestBox;
LedToggle lfo1SyncLed, lfo2SyncLed, delaySyncLed;
2026-07-13 20:57:38 +02:00
// FX2 controls
KnobSlider delayTimeKnob, delayFbKnob, delayMixKnob;
KnobSlider reverbSizeKnob, reverbDampKnob, reverbMixKnob;
2026-07-13 20:57:38 +02:00
// Arpeggiator controls
juce::Label arpEnabledLabel, arpPatternLabel, arpOctavesLabel, arpDirectionLabel, arpRateLabel;
juce::ComboBox arpPatternBox, arpOctavesBox, arpRateBox;
LedToggle arpEnabledLed, arpDirectionLed, delayPingPongLed;
2026-07-13 20:57:38 +02:00
// Preset menu
juce::TextButton presetButton{"PRESET"};
PatchLCD patchLCD;
MidiLed midiLed;
juce::TextButton prevPresetButton{"<"}, nextPresetButton{">"};
juce::TextButton randomizeButton{"RANDOM"};
int currentPresetIndex = 0;
2026-07-13 20:57:38 +02:00
juce::ComboBox uiScaleBox;
using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment;
using ComboBoxAttachment = juce::AudioProcessorValueTreeState::ComboBoxAttachment;
std::unique_ptr<SliderAttachment> osc1OctAttach, osc1SemiAttach, osc1FineAttach, osc1LevelAttach;
std::unique_ptr<SliderAttachment> osc2OctAttach, osc2SemiAttach, osc2FineAttach, osc2LevelAttach, phaseOffsetAttach;
std::unique_ptr<ComboBoxAttachment> osc1WaveAttach, osc2WaveAttach, filterTypeAttach;
std::unique_ptr<ComboBoxAttachment> octaveTransposeAttach, semitoneTransposeAttach;
2026-07-13 20:57:38 +02:00
std::unique_ptr<SliderAttachment> filterCutoffAttach, filterResAttach, filterEnvAmtAttach, keyTrackAttach;
std::unique_ptr<SliderAttachment> envAttackAttach, envDecayAttach, envSustainAttach, envReleaseAttach;
std::unique_ptr<SliderAttachment> fEnvAttackAttach, fEnvDecayAttach, fEnvSustainAttach, fEnvReleaseAttach;
std::unique_ptr<SliderAttachment> panAttach, driveAttach, masterAttach, pbRangeAttach;
// FX attachments
std::unique_ptr<ComboBoxAttachment> distTypeAttach;
2026-07-15 23:49:51 +02:00
std::unique_ptr<SliderAttachment> distAmountAttach, distSymmetryAttach, distToneAttach;
2026-07-13 20:57:38 +02:00
std::unique_ptr<SliderAttachment> compThresholdAttach, compRatioAttach, compAttackAttach;
2026-07-15 23:49:51 +02:00
std::unique_ptr<SliderAttachment> compReleaseAttach, compMakeupAttach;
std::unique_ptr<SliderAttachment> autoPanRateAttach, autoPanDepthAttach, autoPanPhaseAttach;
std::unique_ptr<SliderAttachment> limiterThresholdAttach, limiterCeilingAttach, limiterReleaseAttach;
2026-07-13 20:57:38 +02:00
// LFO attachments
std::unique_ptr<SliderAttachment> lfo1RateAttach, lfo1DepthAttach, lfo1BeatAttach;
std::unique_ptr<ComboBoxAttachment> lfo1ShapeAttach, lfo1DestAttach;
std::unique_ptr<SliderAttachment> lfo2RateAttach, lfo2DepthAttach, lfo2BeatAttach;
std::unique_ptr<ComboBoxAttachment> lfo2ShapeAttach, lfo2DestAttach;
2026-07-13 20:57:38 +02:00
// FX2 attachments
std::unique_ptr<SliderAttachment> delayTimeAttach, delayFbAttach, delayMixAttach, delayBeatAttach;
2026-07-13 20:57:38 +02:00
std::unique_ptr<SliderAttachment> reverbSizeAttach, reverbDampAttach, reverbMixAttach;
// Arpeggiator attachments
std::unique_ptr<ComboBoxAttachment> arpPatternAttach, arpOctavesAttach, arpRateAttach;
2026-07-13 20:57:38 +02:00
void setupLabel(juce::Label& label, const juce::String& text);
void setupKnob(juce::Slider& knob, const juce::String& name);
void setupCombo(juce::ComboBox& combo);
void setupSyncKnob(juce::Slider& knob,
std::unique_ptr<SliderAttachment>& rateAttach,
std::unique_ptr<SliderAttachment>& beatAttach,
const juce::String& rateId,
const juce::String& beatId,
bool syncOn);
2026-07-13 20:57:38 +02:00
void showPresetMenu();
void showFileMenu();
void loadPresetFile();
void savePresetFile();
juce::String currentPresetName() const;
void applyCurrentPreset();
void randomizeParameters();
2026-07-13 20:57:38 +02:00
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainContentComponent)
};
class ChromaFlockEditor : public juce::AudioProcessorEditor {
public:
ChromaFlockEditor(ChromaFlockProcessor& p);
~ChromaFlockEditor() override;
void paint(juce::Graphics&) override;
void resized() override;
void setUIScale(float newScale);
private:
MainContentComponent content;
float currentScale = 1.0f;
static constexpr int baseWidth = 1660;
static constexpr int baseHeight = 1028;
2026-07-13 20:57:38 +02:00
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ChromaFlockEditor)
};