173 lines
6.2 KiB
C++
173 lines
6.2 KiB
C++
#pragma once
|
|
|
|
#include <JuceHeader.h>
|
|
#include <functional>
|
|
#include "PluginProcessor.h"
|
|
|
|
// === Small waveform thumbnail =================================================
|
|
class WaveThumbnail : public juce::Component
|
|
{
|
|
public:
|
|
WaveThumbnail (const std::vector<float>* tbl = nullptr) : table (tbl) {}
|
|
|
|
void setTable (const std::vector<float>* tbl) { table = tbl; repaint(); }
|
|
|
|
void setHighlight (bool shouldHighlight)
|
|
{
|
|
if (highlight == shouldHighlight) return;
|
|
highlight = shouldHighlight;
|
|
repaint();
|
|
}
|
|
|
|
void setOnClick (std::function<void()> 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<float>* table { nullptr };
|
|
bool highlight { false };
|
|
std::function<void()> 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<float>& getTable() const { return samples; }
|
|
|
|
private:
|
|
void drawAt (const juce::MouseEvent& e);
|
|
void setSampleAt (int idx, float value);
|
|
|
|
std::vector<float> 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<juce::AudioProcessorValueTreeState::SliderAttachment> 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<juce::AudioProcessorValueTreeState::SliderAttachment>
|
|
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<juce::AudioProcessorValueTreeState::SliderAttachment>
|
|
slotAAttach, slotBAttach, slotCAttach;
|
|
|
|
// Toggles
|
|
juce::ToggleButton chorusOn { "Chorus" }, reverbOn { "Reverb" }, osc2Mute { "Deactivate Osc2" };
|
|
std::unique_ptr<juce::AudioProcessorValueTreeState::ButtonAttachment> chOnAttach, rvOnAttach, osc2MuteAttach;
|
|
std::unique_ptr<juce::AudioProcessorValueTreeState::ButtonAttachment> morphLoopToggleAttach;
|
|
std::unique_ptr<juce::AudioProcessorValueTreeState::ComboBoxAttachment> morphLoopModeAttach;
|
|
|
|
// Browser grid cells
|
|
std::vector<juce::Rectangle<int>> 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<int> getTopPanelBounds() const;
|
|
juce::Rectangle<int> getBottomPanelBounds() const;
|
|
|
|
// Logic
|
|
void timerCallback() override;
|
|
void handleBrowserClick (juce::Point<int> 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
|
|
};
|