#pragma once #include #include #include #include "PluginProcessor.h" #include class KnobLookAndFeel : public juce::LookAndFeel_V4 { public: void drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height, float sliderPos, float rotaryStartAngle, float rotaryEndAngle, juce::Slider& slider) override; }; 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& 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) { startTimerHz(30); } void paint(juce::Graphics& g) override; void timerCallback() override { repaint(); } private: ChromaFlockProcessor& processor; }; class SpectrumAnalyzer : public juce::Component, public juce::Timer { public: explicit SpectrumAnalyzer(ChromaFlockProcessor& p) : processor(p) { fft = std::make_unique(ChromaFlockProcessor::fftOrder); startTimerHz(30); } void paint(juce::Graphics& g) override; void timerCallback() override { repaint(); } private: ChromaFlockProcessor& processor; std::unique_ptr fft; }; class PatchLCD : public juce::Component { public: std::function 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 PianoRollComponent : public juce::Component, public juce::Timer { public: explicit PianoRollComponent(ChromaFlockProcessor& p) : processor(p) { startTimerHz(60); } void paint(juce::Graphics& g) override; void timerCallback() override; 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 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; }; class MainContentComponent : public juce::Component { public: explicit MainContentComponent(ChromaFlockProcessor& p); void paint(juce::Graphics& g) override; void resized() override; std::function onScaleChanged; private: ChromaFlockProcessor& processorRef; KnobLookAndFeel knobLaf; ComboBoxLookAndFeel comboLaf; juce::Image bgImage; bool bgImageLoaded = false; juce::Image logoImage; bool logoLoaded = false; VuMeter vuMeter; WaveformDisplay waveformDisplay; SpectrumAnalyzer spectrumAnalyzer; PianoRollComponent pianoRoll; juce::Label osc1Label, osc2Label, filterLabel, envLabel, fEnvLabel, globalLabel; juce::Label fxLabel, lfoLabel, fx2Label; juce::Label scaleLabel; juce::Label octaveTransposeLabel, semitoneTransposeLabel; juce::ComboBox octaveTransposeBox, semitoneTransposeBox; juce::ComboBox osc1WaveBox; juce::Slider osc1OctKnob, osc1SemiKnob, osc1FineKnob, osc1LevelKnob; juce::ComboBox osc2WaveBox; juce::Slider osc2OctKnob, osc2SemiKnob, osc2FineKnob, osc2LevelKnob, phaseOffsetKnob; juce::ComboBox filterTypeBox; juce::Slider filterCutoffKnob, filterResKnob, filterEnvAmtKnob, keyTrackKnob; juce::Slider envAttackKnob, envDecayKnob, envSustainKnob, envReleaseKnob; juce::Slider fEnvAttackKnob, fEnvDecayKnob, fEnvSustainKnob, fEnvReleaseKnob; juce::Slider panKnob, driveKnob, masterKnob, pbRangeKnob; // FX controls juce::ComboBox distTypeBox; juce::Slider distAmountKnob, distSymmetryKnob, distToneKnob; juce::Slider compThresholdKnob, compRatioKnob, compAttackKnob, compReleaseKnob, compMakeupKnob; juce::Slider autoPanRateKnob, autoPanDepthKnob, autoPanPhaseKnob; juce::Slider limiterThresholdKnob, limiterCeilingKnob, limiterReleaseKnob; // LFO controls juce::Slider lfo1RateKnob, lfo1DepthKnob; juce::ComboBox lfo1ShapeBox, lfo1DestBox, lfo1SyncBox; juce::Slider lfo2RateKnob, lfo2DepthKnob; juce::ComboBox lfo2ShapeBox, lfo2DestBox, lfo2SyncBox; // FX2 controls juce::Slider delayTimeKnob, delayFbKnob, delayMixKnob, delayPingPongKnob; juce::ComboBox delaySyncBox; juce::Slider reverbSizeKnob, reverbDampKnob, reverbMixKnob; // Preset menu juce::TextButton presetButton{"PRESET"}; PatchLCD patchLCD; juce::TextButton prevPresetButton{"<"}, nextPresetButton{">"}; juce::TextButton randomizeButton{"RANDOM"}; int currentPresetIndex = 0; juce::ComboBox uiScaleBox; using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment; using ComboBoxAttachment = juce::AudioProcessorValueTreeState::ComboBoxAttachment; std::unique_ptr osc1OctAttach, osc1SemiAttach, osc1FineAttach, osc1LevelAttach; std::unique_ptr osc2OctAttach, osc2SemiAttach, osc2FineAttach, osc2LevelAttach, phaseOffsetAttach; std::unique_ptr osc1WaveAttach, osc2WaveAttach, filterTypeAttach; std::unique_ptr octaveTransposeAttach, semitoneTransposeAttach; std::unique_ptr filterCutoffAttach, filterResAttach, filterEnvAmtAttach, keyTrackAttach; std::unique_ptr envAttackAttach, envDecayAttach, envSustainAttach, envReleaseAttach; std::unique_ptr fEnvAttackAttach, fEnvDecayAttach, fEnvSustainAttach, fEnvReleaseAttach; std::unique_ptr panAttach, driveAttach, masterAttach, pbRangeAttach; // FX attachments std::unique_ptr distTypeAttach; std::unique_ptr distAmountAttach, distSymmetryAttach, distToneAttach; std::unique_ptr compThresholdAttach, compRatioAttach, compAttackAttach; std::unique_ptr compReleaseAttach, compMakeupAttach; std::unique_ptr autoPanRateAttach, autoPanDepthAttach, autoPanPhaseAttach; std::unique_ptr limiterThresholdAttach, limiterCeilingAttach, limiterReleaseAttach; // LFO attachments std::unique_ptr lfo1RateAttach, lfo1DepthAttach, lfo1BeatAttach; std::unique_ptr lfo1ShapeAttach, lfo1DestAttach, lfo1SyncAttach; std::unique_ptr lfo2RateAttach, lfo2DepthAttach, lfo2BeatAttach; std::unique_ptr lfo2ShapeAttach, lfo2DestAttach, lfo2SyncAttach; // FX2 attachments std::unique_ptr delayTimeAttach, delayFbAttach, delayMixAttach, delayBeatAttach; std::unique_ptr delaySyncAttach; std::unique_ptr delayPingPongAttach; std::unique_ptr reverbSizeAttach, reverbDampAttach, reverbMixAttach; 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& rateAttach, std::unique_ptr& beatAttach, const juce::String& rateId, const juce::String& beatId, juce::ComboBox& syncBox); void showPresetMenu(); void showFileMenu(); void loadPresetFile(); void savePresetFile(); juce::String currentPresetName() const; void applyCurrentPreset(); void randomizeParameters(); 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 = 1100; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ChromaFlockEditor) };