#pragma once #include #include #include "PluginProcessor.h" // === Small waveform thumbnail ================================================= class WaveThumbnail : public juce::Component { public: WaveThumbnail (const std::vector* tbl = nullptr) : table (tbl) {} void setTable (const std::vector* tbl) { table = tbl; repaint(); } void setHighlight (bool shouldHighlight) { if (highlight == shouldHighlight) return; highlight = shouldHighlight; repaint(); } void setOnClick (std::function handler) { onClick = std::move (handler); } void paint (juce::Graphics& g) override; void mouseDown (const juce::MouseEvent& e) override { juce::ignoreUnused (e); if (onClick) onClick(); } private: const std::vector* table { nullptr }; bool highlight { false }; std::function onClick; }; // === Draw-your-own wave pad =================================================== // (Grid + thicker antialiased stroke + smooth “ink” interpolation while dragging) class DrawWaveComponent : public juce::Component { public: DrawWaveComponent(); void clear(); void paint (juce::Graphics& g) override; void mouseDown (const juce::MouseEvent& e) override; void mouseDrag (const juce::MouseEvent& e) override; const std::vector& getTable() const { return samples; } private: void drawAt (const juce::MouseEvent& e); void setSampleAt (int idx, float value); std::vector samples; int lastDrawIndex { -1 }; }; // === Custom looks ============================================================= class MetalKnobLookAndFeel : public juce::LookAndFeel_V4 { public: void drawRotarySlider (juce::Graphics& g, int x, int y, int width, int height, float sliderPosProportional, float rotaryStartAngle, float rotaryEndAngle, juce::Slider& slider) override; }; class FlatButtonLookAndFeel : public juce::LookAndFeel_V4 { public: void drawButtonBackground (juce::Graphics& g, juce::Button& b, const juce::Colour& bg, bool isHighlighted, bool isDown) override { auto r = b.getLocalBounds().toFloat(); auto base = bg; if (isDown) base = base.darker (0.15f); else if (isHighlighted) base = base.brighter (0.08f); g.setColour (base); g.fillRoundedRectangle (r, 4.0f); g.setColour (juce::Colours::black.withAlpha (0.5f)); g.drawRoundedRectangle (r.reduced (0.5f), 4.0f, 1.0f); } }; // === Editor =================================================================== class WavetableSynthAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::Timer { public: explicit WavetableSynthAudioProcessorEditor (WavetableSynthAudioProcessor&); ~WavetableSynthAudioProcessorEditor() override = default; void paint (juce::Graphics&) override; void resized() override; void mouseDown (const juce::MouseEvent& e) override; private: WavetableSynthAudioProcessor& audioProcessor; // Morph juce::Slider morphSlider; juce::ToggleButton morphLoopToggle { "Loop Morph" }; juce::ComboBox morphLoopMode; // Forward / Ping-Pong / Half Trip // Master juce::Slider master; std::unique_ptr morphAttach, masterAttach; // A/B/C thumbnails WaveThumbnail slotABox, slotBBox, slotCBox; // Synth knobs juce::Slider cutoffSlider, attack, decay, sustain, release; juce::Slider lfoRate, lfoDepth, fenvA, fenvD, fenvS, fenvR, fenvAmt; // FX knobs juce::Slider chRate, chDepth, chDelay, chFb, chMix; juce::Slider rvRoom, rvDamp, rvWidth, rvWet; // Attachments std::unique_ptr cutoffAttach, attAttach, decAttach, susAttach, relAttach, lfoRateAttach, lfoDepthAttach, fenvAAttach, fenvDAttach, fenvSAttach, fenvRAttach, fenvAmtAttach, chRateAttach, chDepthAttach, chDelayAttach, chFbAttach, chMixAttach, rvRoomAttach, rvDampAttach, rvWidthAttach, rvWetAttach; // Hidden slot params juce::Slider slotAParam, slotBParam, slotCParam; std::unique_ptr slotAAttach, slotBAttach, slotCAttach; // Toggles juce::ToggleButton chorusOn { "Chorus" }, reverbOn { "Reverb" }, osc2Mute { "Deactivate Osc2" }; std::unique_ptr chOnAttach, rvOnAttach, osc2MuteAttach; std::unique_ptr morphLoopToggleAttach; std::unique_ptr morphLoopModeAttach; // Browser grid cells std::vector> browserCells; // 4 x 10 = 40 // Draw pad + buttons DrawWaveComponent userDraw; juce::TextButton addToBrowser { "Add to Browser" }, clearDraw { "Clear" }, presetButton { "Presets" }; // Labels juce::Label lblCutoff, lblAttack, lblDecay, lblSustain, lblRelease; juce::Label lblLfoRate, lblLfoDepth, lblFenvA, lblFenvD, lblFenvS, lblFenvR, lblFenvAmt; juce::Label lblChRate, lblChDepth, lblChDelay, lblChFb, lblChMix; juce::Label lblRvRoom, lblRvDamp, lblRvWidth, lblRvWet; juce::Label lblMaster, lblDrawWave; // L&F MetalKnobLookAndFeel metalKnobLNF; FlatButtonLookAndFeel flatBtnLNF; // Layout helpers juce::Rectangle getTopPanelBounds() const; juce::Rectangle getBottomPanelBounds() const; // Logic void timerCallback() override; void handleBrowserClick (juce::Point pos); void showPresetMenu(); void setActiveSlot (int slotIndex); // Helpers static void labelAbove (juce::Label& L, const juce::String& text); int activeSlot { 0 }; // Local morph animation (fallback) double lastTimerMs { juce::Time::getMillisecondCounterHiRes() }; float localMorphPhase { 0.0f }; // 0..1 };