#pragma once #include #include "PluginProcessor.h" #include "WaveformDisplay.h" class MonoslicerLookAndFeel : public juce::LookAndFeel_V4 { public: void drawButtonBackground(juce::Graphics& g, juce::Button& button, const juce::Colour& backgroundColour, bool isMouseOverButton, bool isButtonDown) override { if (backgroundColour.getAlpha() == 0) return; auto bounds = button.getLocalBounds().toFloat().reduced(0.5f); auto cornerSize = 4.0f; auto baseColour = backgroundColour.withMultipliedAlpha(button.isEnabled() ? 1.0f : 0.5f); g.setColour(baseColour); g.fillRoundedRectangle(bounds, cornerSize); g.setColour(juce::Colour(0xff444466)); g.drawRoundedRectangle(bounds, cornerSize, 1.0f); } void drawButtonText(juce::Graphics& g, juce::TextButton& button, bool isMouseOverButton, bool isButtonDown) override { auto font = getTextButtonFont(button, button.getHeight()); g.setFont(font); g.setColour(button.findColour(button.getToggleState() ? juce::TextButton::textColourOnId : juce::TextButton::textColourOffId) .withMultipliedAlpha(button.isEnabled() ? 1.0f : 0.5f)); auto textArea = button.getLocalBounds().reduced(6, 1); g.drawText(button.getButtonText(), textArea, juce::Justification::centredLeft, true); } }; class VuMeter : public juce::Component, public juce::Timer { public: VuMeter(MonoslicerProcessor& p) : processor(p) { startTimerHz(30); } ~VuMeter() override { stopTimer(); } void paint(juce::Graphics& g) override { auto bounds = getLocalBounds().toFloat(); float legendW = 20.0f; float legendGap = 5.0f; float barH = (bounds.getHeight() - 6.0f) / 2.0f; float gap = 6.0f; float barX = bounds.getX() + legendW + legendGap; float barW = bounds.getWidth() - legendW - legendGap; float levelL = processor.outputLevelL.load(); float levelR = processor.outputLevelR.load(); // Apply falloff decay peakL = juce::jmax(levelL, peakL - decayRate); peakR = juce::jmax(levelR, peakR - decayRate); if (levelL > peakL) peakL = levelL; if (levelR > peakR) peakR = levelR; auto drawBar = [&](float y, float level, float peak, const juce::String& legend) { float frac = juce::jlimit(0.0f, 1.0f, level); float peakFrac = juce::jlimit(0.0f, 1.0f, peak); // Legend text g.setColour(juce::Colour(0xff888888)); g.setFont(juce::Font(10.0f)); g.drawText(legend, bounds.getX(), y, legendW, barH, juce::Justification::centredRight); // Inactive background g.setColour(juce::Colour(0xff1a192d)); g.fillRoundedRectangle(barX, y, barW, barH, 2.0f); // Active fill from left float fillW = barW * frac; g.setColour(juce::Colour(0xff3a73e5)); g.fillRoundedRectangle(barX, y, fillW, barH, 2.0f); // Peak hold indicator float peakX = barX + barW * peakFrac - 2.0f; g.setColour(juce::Colour(0xff6699cc)); g.fillRect(peakX, y + 2.0f, 2.0f, barH - 4.0f); }; drawBar(bounds.getY(), levelL, peakL, "L"); drawBar(bounds.getY() + barH + gap, levelR, peakR, "R"); } void timerCallback() override { repaint(); } private: MonoslicerProcessor& processor; float peakL = 0.0f, peakR = 0.0f; float decayRate = 0.02f; }; class MonoslicerEditor : public juce::AudioProcessorEditor, private juce::FileDragAndDropTarget, private juce::Button::Listener, private juce::ComboBox::Listener, private juce::Timer { public: MonoslicerEditor(MonoslicerProcessor&); ~MonoslicerEditor() override; void paint(juce::Graphics&) override; void resized() override; bool isInterestedInFileDrag(const juce::StringArray& files) override; void filesDropped(const juce::StringArray& files, int x, int y) override; void buttonClicked(juce::Button* button) override; void comboBoxChanged(juce::ComboBox* comboBoxThatHasChanged) override; void timerCallback() override; private: MonoslicerLookAndFeel lnf; MonoslicerProcessor& processorRef; WaveformDisplay waveformDisplay; // Bottom panel buttons juce::TextButton autoSliceButton { "Auto Slice" }; juce::TextButton clearSlicesButton { "Clear Slices" }; // Title bar buttons juce::TextButton loadSampleButton { "Load" }; juce::TextButton clearSampleButton { "Clear" }; juce::TextButton prevSampleButton { "<" }; juce::TextButton nextSampleButton { ">" }; juce::TextButton undoButton { "Undo" }; juce::TextButton redoButton { "Redo" }; juce::TextButton zoomInButton { "+" }; juce::TextButton zoomOutButton { "-" }; juce::TextButton zoomResetButton { "1:1" }; juce::TextButton scrollLeftButton { "<" }; juce::TextButton scrollRightButton { ">" }; juce::ComboBox scaleComboBox; juce::ComboBox octaveComboBox; static constexpr int baseWidth = 920; static constexpr int baseHeight = 606; // Slice knobs juce::Slider bassSlider, trebleSlider, sensitivitySlider; juce::Label bassLabel, trebleLabel, sensitivityLabel; // Amp ADSR knobs juce::Slider ampAttackSlider, ampDecaySlider, ampSustainSlider, ampReleaseSlider; juce::Label ampAttackLabel, ampDecayLabel, ampSustainLabel, ampReleaseLabel; // Filter type selector juce::ComboBox filterTypeComboBox; juce::Label filterTypeLabel; // Filter knobs juce::Slider filterCutoffSlider, filterResoSlider, filterEnvDepthSlider; juce::Label filterCutoffLabel, filterResoLabel, filterEnvDepthLabel; using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment; using ComboBoxAttachment = juce::AudioProcessorValueTreeState::ComboBoxAttachment; // Filter ADSR knobs juce::Slider filterAttackSlider, filterDecaySlider, filterSustainSlider, filterReleaseSlider; juce::Label filterAttackLabel, filterDecayLabel, filterSustainLabel, filterReleaseLabel; // Time-stretch controls juce::Slider bpmSlider; juce::ComboBox stretchBeatsComboBox; juce::TextButton stretchButton { "Match Length" }; juce::ComboBox keyShiftComboBox; juce::Label keyShiftLabel; VuMeter vuMeter; std::unique_ptr stretchBpmAttachment; std::unique_ptr stretchBeatsAttachment; std::unique_ptr keyShiftAttachment; std::unique_ptr scaleAttachment; std::unique_ptr octaveAttachment; std::unique_ptr bassAttachment, trebleAttachment, sensitivityAttachment; std::unique_ptr ampAttackAttachment, ampDecayAttachment, ampSustainAttachment, ampReleaseAttachment; std::unique_ptr filterCutoffAttachment, filterResoAttachment, filterEnvDepthAttachment; std::unique_ptr filterAttackAttachment, filterDecayAttachment, filterSustainAttachment, filterReleaseAttachment; std::unique_ptr filterTypeAttachment; juce::FileChooser fileChooser { "Load Audio File", juce::File{}, "*.wav;*.aiff;*.flac;*.ogg" }; juce::TextButton fileInfoLabel; juce::Label buildInfoLabel; juce::File currentLoadedFile; void setupSlider(juce::Slider& slider, juce::Label& label, const juce::String& name); void attachSlider(juce::Slider& slider, juce::Label& label, const juce::String& name, const juce::String& paramId, std::unique_ptr& attachment); int keyClickNote = -1; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MonoslicerEditor) };