2026-07-21 02:06:45 +02:00
|
|
|
#pragma once
|
|
|
|
|
#include <juce_audio_processors/juce_audio_processors.h>
|
|
|
|
|
#include "PluginProcessor.h"
|
|
|
|
|
#include "WaveformDisplay.h"
|
|
|
|
|
|
2026-07-22 23:10:55 +02:00
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
2026-07-22 00:33:03 +02:00
|
|
|
class MonoslicerEditor : public juce::AudioProcessorEditor,
|
2026-07-21 02:06:45 +02:00
|
|
|
private juce::FileDragAndDropTarget,
|
|
|
|
|
private juce::Button::Listener,
|
|
|
|
|
private juce::ComboBox::Listener,
|
|
|
|
|
private juce::Timer
|
|
|
|
|
{
|
|
|
|
|
public:
|
2026-07-22 00:33:03 +02:00
|
|
|
MonoslicerEditor(MonoslicerProcessor&);
|
|
|
|
|
~MonoslicerEditor() override;
|
2026-07-21 02:06:45 +02:00
|
|
|
|
|
|
|
|
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:
|
2026-07-22 23:10:55 +02:00
|
|
|
MonoslicerLookAndFeel lnf;
|
2026-07-22 00:33:03 +02:00
|
|
|
MonoslicerProcessor& processorRef;
|
2026-07-21 02:06:45 +02:00
|
|
|
|
|
|
|
|
WaveformDisplay waveformDisplay;
|
|
|
|
|
|
|
|
|
|
// Bottom panel buttons
|
|
|
|
|
juce::TextButton autoSliceButton { "Auto Slice" };
|
|
|
|
|
juce::TextButton clearSlicesButton { "Clear Slices" };
|
|
|
|
|
|
|
|
|
|
// Title bar buttons
|
|
|
|
|
juce::TextButton loadSampleButton { "Load" };
|
2026-07-22 00:33:03 +02:00
|
|
|
juce::TextButton clearSampleButton { "Clear" };
|
2026-07-21 02:06:45 +02:00
|
|
|
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" };
|
2026-07-22 13:27:26 +02:00
|
|
|
juce::TextButton scrollLeftButton { "<" };
|
|
|
|
|
juce::TextButton scrollRightButton { ">" };
|
2026-07-21 02:06:45 +02:00
|
|
|
juce::ComboBox scaleComboBox;
|
2026-07-21 15:20:49 +02:00
|
|
|
juce::ComboBox octaveComboBox;
|
2026-07-21 02:06:45 +02:00
|
|
|
|
2026-07-22 23:10:55 +02:00
|
|
|
static constexpr int baseWidth = 920;
|
|
|
|
|
static constexpr int baseHeight = 606;
|
2026-07-21 02:06:45 +02:00
|
|
|
|
|
|
|
|
// 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;
|
2026-07-22 23:10:55 +02:00
|
|
|
juce::Label filterTypeLabel;
|
2026-07-21 02:06:45 +02:00
|
|
|
|
|
|
|
|
// 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;
|
2026-07-22 19:27:41 +02:00
|
|
|
juce::TextButton stretchButton { "Match Length" };
|
2026-07-21 02:06:45 +02:00
|
|
|
juce::ComboBox keyShiftComboBox;
|
|
|
|
|
juce::Label keyShiftLabel;
|
2026-07-22 23:10:55 +02:00
|
|
|
VuMeter vuMeter;
|
2026-07-21 02:06:45 +02:00
|
|
|
std::unique_ptr<SliderAttachment> stretchBpmAttachment;
|
|
|
|
|
std::unique_ptr<ComboBoxAttachment> stretchBeatsAttachment;
|
|
|
|
|
std::unique_ptr<ComboBoxAttachment> keyShiftAttachment;
|
|
|
|
|
std::unique_ptr<ComboBoxAttachment> scaleAttachment;
|
2026-07-21 15:20:49 +02:00
|
|
|
std::unique_ptr<ComboBoxAttachment> octaveAttachment;
|
2026-07-21 02:06:45 +02:00
|
|
|
|
|
|
|
|
std::unique_ptr<SliderAttachment> bassAttachment, trebleAttachment, sensitivityAttachment;
|
|
|
|
|
std::unique_ptr<SliderAttachment> ampAttackAttachment, ampDecayAttachment, ampSustainAttachment, ampReleaseAttachment;
|
|
|
|
|
std::unique_ptr<SliderAttachment> filterCutoffAttachment, filterResoAttachment, filterEnvDepthAttachment;
|
|
|
|
|
std::unique_ptr<SliderAttachment> filterAttackAttachment, filterDecayAttachment, filterSustainAttachment, filterReleaseAttachment;
|
|
|
|
|
std::unique_ptr<ComboBoxAttachment> filterTypeAttachment;
|
|
|
|
|
|
|
|
|
|
juce::FileChooser fileChooser { "Load Audio File", juce::File{}, "*.wav;*.aiff;*.flac;*.ogg" };
|
2026-07-22 16:15:33 +02:00
|
|
|
juce::TextButton fileInfoLabel;
|
|
|
|
|
juce::Label buildInfoLabel;
|
|
|
|
|
juce::File currentLoadedFile;
|
2026-07-21 02:06:45 +02:00
|
|
|
|
|
|
|
|
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<SliderAttachment>& attachment);
|
|
|
|
|
|
|
|
|
|
int keyClickNote = -1;
|
|
|
|
|
|
2026-07-22 00:33:03 +02:00
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MonoslicerEditor)
|
2026-07-21 02:06:45 +02:00
|
|
|
};
|