2026-07-21 02:06:45 +02:00
|
|
|
#include "PluginEditor.h"
|
|
|
|
|
|
|
|
|
|
MonoSlicerEditor::MonoSlicerEditor(MonoSlicerProcessor& p)
|
|
|
|
|
: AudioProcessorEditor(p),
|
|
|
|
|
processorRef(p),
|
|
|
|
|
waveformDisplay(p.getSliceManager())
|
|
|
|
|
{
|
|
|
|
|
setSize(baseWidth, baseHeight);
|
|
|
|
|
|
|
|
|
|
// Title bar buttons
|
|
|
|
|
auto setupTitleBtn = [this](juce::Button& btn, const juce::String& tip) {
|
|
|
|
|
addAndMakeVisible(btn);
|
|
|
|
|
btn.addListener(this);
|
|
|
|
|
btn.setTooltip(tip);
|
|
|
|
|
btn.setColour(juce::TextButton::buttonColourId, juce::Colour(0xff2a2a4a));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
setupTitleBtn(loadSampleButton, "Load audio file");
|
|
|
|
|
setupTitleBtn(prevSampleButton, "Previous sample in folder");
|
|
|
|
|
setupTitleBtn(nextSampleButton, "Next sample in folder");
|
|
|
|
|
setupTitleBtn(undoButton, "Undo");
|
|
|
|
|
setupTitleBtn(redoButton, "Redo");
|
|
|
|
|
setupTitleBtn(zoomInButton, "Zoom In");
|
|
|
|
|
setupTitleBtn(zoomOutButton, "Zoom Out");
|
|
|
|
|
setupTitleBtn(zoomResetButton, "Reset Zoom");
|
|
|
|
|
|
|
|
|
|
addAndMakeVisible(scaleComboBox);
|
|
|
|
|
scaleComboBox.addItem("75%", 1);
|
|
|
|
|
scaleComboBox.addItem("100%", 2);
|
|
|
|
|
scaleComboBox.addItem("125%", 3);
|
|
|
|
|
scaleComboBox.addItem("150%", 4);
|
|
|
|
|
scaleComboBox.addItem("175%", 5);
|
|
|
|
|
scaleComboBox.addItem("200%", 6);
|
|
|
|
|
scaleComboBox.addListener(this);
|
|
|
|
|
scaleComboBox.setTooltip("UI Scale");
|
|
|
|
|
scaleAttachment = std::make_unique<ComboBoxAttachment>(processorRef.getAPVTS(), "uiScale", scaleComboBox);
|
|
|
|
|
|
2026-07-21 15:20:49 +02:00
|
|
|
addAndMakeVisible(octaveComboBox);
|
|
|
|
|
for (int i = -3; i <= 3; ++i)
|
|
|
|
|
octaveComboBox.addItem((i >= 0 ? "+" : "") + juce::String(i) + " oct", i + 4);
|
|
|
|
|
octaveComboBox.setSelectedId(4, juce::dontSendNotification);
|
|
|
|
|
octaveComboBox.setColour(juce::ComboBox::backgroundColourId, juce::Colour(0xff2a2a4a));
|
|
|
|
|
octaveComboBox.setColour(juce::ComboBox::textColourId, juce::Colours::white);
|
|
|
|
|
octaveComboBox.setColour(juce::ComboBox::outlineColourId, juce::Colour(0xff444466));
|
|
|
|
|
octaveComboBox.setColour(juce::ComboBox::arrowColourId, juce::Colours::white);
|
|
|
|
|
octaveComboBox.setTooltip("Octave offset for MIDI input");
|
|
|
|
|
octaveAttachment = std::make_unique<ComboBoxAttachment>(processorRef.getAPVTS(), "octaveOffset", octaveComboBox);
|
|
|
|
|
|
2026-07-21 02:06:45 +02:00
|
|
|
// Bottom panel buttons
|
|
|
|
|
addAndMakeVisible(autoSliceButton);
|
|
|
|
|
autoSliceButton.addListener(this);
|
|
|
|
|
addAndMakeVisible(clearSlicesButton);
|
|
|
|
|
clearSlicesButton.addListener(this);
|
|
|
|
|
|
|
|
|
|
addAndMakeVisible(waveformDisplay);
|
|
|
|
|
|
|
|
|
|
// Slice knobs
|
|
|
|
|
attachSlider(bassSlider, bassLabel, "Bass", "bass", bassAttachment);
|
|
|
|
|
attachSlider(trebleSlider, trebleLabel, "Treble", "treble", trebleAttachment);
|
|
|
|
|
attachSlider(sensitivitySlider, sensitivityLabel, "Sens.", "sensitivity", sensitivityAttachment);
|
|
|
|
|
|
|
|
|
|
// Amp ADSR
|
|
|
|
|
attachSlider(ampAttackSlider, ampAttackLabel, "A Atk", "ampAttack", ampAttackAttachment);
|
|
|
|
|
attachSlider(ampDecaySlider, ampDecayLabel, "A Dcy", "ampDecay", ampDecayAttachment);
|
|
|
|
|
attachSlider(ampSustainSlider, ampSustainLabel, "A Sus", "ampSustain", ampSustainAttachment);
|
|
|
|
|
attachSlider(ampReleaseSlider, ampReleaseLabel, "A Rel", "ampRelease", ampReleaseAttachment);
|
|
|
|
|
|
|
|
|
|
// Filter knobs
|
|
|
|
|
attachSlider(filterCutoffSlider, filterCutoffLabel, "Cutoff", "filterCutoff", filterCutoffAttachment);
|
|
|
|
|
attachSlider(filterResoSlider, filterResoLabel, "Reso", "filterReso", filterResoAttachment);
|
|
|
|
|
attachSlider(filterEnvDepthSlider, filterEnvDepthLabel, "Env Dph", "filterEnvDepth", filterEnvDepthAttachment);
|
|
|
|
|
|
|
|
|
|
// Filter type combo
|
|
|
|
|
addAndMakeVisible(filterTypeComboBox);
|
|
|
|
|
filterTypeComboBox.addItem("LP12", 1);
|
|
|
|
|
filterTypeComboBox.addItem("LP24", 2);
|
|
|
|
|
filterTypeComboBox.addItem("HP", 3);
|
|
|
|
|
filterTypeComboBox.addItem("BP", 4);
|
|
|
|
|
filterTypeComboBox.addItem("Notch", 5);
|
|
|
|
|
filterTypeAttachment = std::make_unique<ComboBoxAttachment>(processorRef.getAPVTS(), "filterType", filterTypeComboBox);
|
|
|
|
|
filterTypeComboBox.setColour(juce::ComboBox::backgroundColourId, juce::Colour(0xff2a2a4a));
|
|
|
|
|
filterTypeComboBox.setColour(juce::ComboBox::textColourId, juce::Colours::white);
|
|
|
|
|
filterTypeComboBox.setColour(juce::ComboBox::outlineColourId, juce::Colour(0xff444466));
|
|
|
|
|
filterTypeComboBox.setColour(juce::ComboBox::arrowColourId, juce::Colours::white);
|
|
|
|
|
|
|
|
|
|
// Filter ADSR
|
|
|
|
|
attachSlider(filterAttackSlider, filterAttackLabel, "F Atk", "filterAttack", filterAttackAttachment);
|
|
|
|
|
attachSlider(filterDecaySlider, filterDecayLabel, "F Dcy", "filterDecay", filterDecayAttachment);
|
|
|
|
|
attachSlider(filterSustainSlider, filterSustainLabel, "F Sus", "filterSustain", filterSustainAttachment);
|
|
|
|
|
attachSlider(filterReleaseSlider, filterReleaseLabel, "F Rel", "filterRelease", filterReleaseAttachment);
|
|
|
|
|
|
|
|
|
|
// Time-stretch controls
|
|
|
|
|
addAndMakeVisible(bpmSlider);
|
|
|
|
|
bpmSlider.setSliderStyle(juce::Slider::IncDecButtons);
|
|
|
|
|
bpmSlider.setTextBoxStyle(juce::Slider::TextBoxLeft, false, 50, 20);
|
|
|
|
|
bpmSlider.setRange(20.0, 300.0, 1.0);
|
|
|
|
|
bpmSlider.setColour(juce::Slider::thumbColourId, juce::Colours::transparentBlack);
|
|
|
|
|
bpmSlider.setColour(juce::Slider::rotarySliderFillColourId, juce::Colour(0xffee8833));
|
|
|
|
|
bpmSlider.setColour(juce::Slider::textBoxTextColourId, juce::Colours::white);
|
|
|
|
|
bpmSlider.setColour(juce::Slider::textBoxBackgroundColourId, juce::Colour(0xff2a2a4a));
|
|
|
|
|
bpmSlider.setColour(juce::Slider::textBoxOutlineColourId, juce::Colour(0xff444466));
|
|
|
|
|
bpmSlider.setColour(juce::Slider::backgroundColourId, juce::Colour(0xff2a2a4a));
|
|
|
|
|
bpmSlider.setColour(juce::Slider::trackColourId, juce::Colour(0xffee8833));
|
|
|
|
|
stretchBpmAttachment = std::make_unique<SliderAttachment>(processorRef.getAPVTS(), "stretchBpm", bpmSlider);
|
|
|
|
|
|
|
|
|
|
addAndMakeVisible(stretchBeatsComboBox);
|
|
|
|
|
stretchBeatsComboBox.addItem("1 beat", 1);
|
|
|
|
|
stretchBeatsComboBox.addItem("2 beats", 2);
|
|
|
|
|
stretchBeatsComboBox.addItem("4 beats", 3);
|
|
|
|
|
stretchBeatsComboBox.addItem("8 beats", 4);
|
|
|
|
|
stretchBeatsComboBox.addItem("16 beats", 5);
|
|
|
|
|
stretchBeatsComboBox.addItem("32 beats", 6);
|
|
|
|
|
stretchBeatsComboBox.setSelectedId(3, juce::dontSendNotification); // default 4 beats
|
|
|
|
|
stretchBeatsComboBox.setColour(juce::ComboBox::backgroundColourId, juce::Colour(0xff2a2a4a));
|
|
|
|
|
stretchBeatsComboBox.setColour(juce::ComboBox::textColourId, juce::Colours::white);
|
|
|
|
|
stretchBeatsComboBox.setColour(juce::ComboBox::outlineColourId, juce::Colour(0xff444466));
|
|
|
|
|
stretchBeatsComboBox.setColour(juce::ComboBox::arrowColourId, juce::Colours::white);
|
|
|
|
|
stretchBeatsAttachment = std::make_unique<ComboBoxAttachment>(processorRef.getAPVTS(), "stretchBeats", stretchBeatsComboBox);
|
|
|
|
|
|
|
|
|
|
addAndMakeVisible(stretchButton);
|
|
|
|
|
stretchButton.addListener(this);
|
|
|
|
|
stretchButton.setColour(juce::TextButton::buttonColourId, juce::Colour(0xff2a2a4a));
|
|
|
|
|
|
|
|
|
|
// Key shift combo
|
|
|
|
|
addAndMakeVisible(keyShiftComboBox);
|
|
|
|
|
for (int i = -12; i <= 12; ++i)
|
|
|
|
|
keyShiftComboBox.addItem((i >= 0 ? "+" : "") + juce::String(i) + " st", i + 13);
|
|
|
|
|
keyShiftComboBox.setSelectedId(13, juce::dontSendNotification); // 0 semitones
|
|
|
|
|
keyShiftComboBox.setColour(juce::ComboBox::backgroundColourId, juce::Colour(0xff2a2a4a));
|
|
|
|
|
keyShiftComboBox.setColour(juce::ComboBox::textColourId, juce::Colours::white);
|
|
|
|
|
keyShiftComboBox.setColour(juce::ComboBox::outlineColourId, juce::Colour(0xff444466));
|
|
|
|
|
keyShiftComboBox.setColour(juce::ComboBox::arrowColourId, juce::Colours::white);
|
|
|
|
|
addAndMakeVisible(keyShiftLabel);
|
|
|
|
|
keyShiftLabel.setText("Shift", juce::dontSendNotification);
|
|
|
|
|
keyShiftLabel.setJustificationType(juce::Justification::centred);
|
|
|
|
|
keyShiftLabel.setFont(juce::Font(juce::FontOptions(11.0f)));
|
|
|
|
|
keyShiftLabel.attachToComponent(&keyShiftComboBox, false);
|
|
|
|
|
keyShiftAttachment = std::make_unique<ComboBoxAttachment>(processorRef.getAPVTS(), "keyShift", keyShiftComboBox);
|
|
|
|
|
|
|
|
|
|
auto allKnobs = { &bassSlider, &trebleSlider, &sensitivitySlider,
|
|
|
|
|
&AttackSlider, &DecaySlider, &SustainSlider, &ReleaseSlider,
|
|
|
|
|
&filterCutoffSlider, &filterResoSlider, &filterEnvDepthSlider,
|
|
|
|
|
&filterAttackSlider, &filterDecaySlider, &filterSustainSlider, &filterReleaseSlider };
|
|
|
|
|
|
|
|
|
|
for (auto* slider : allKnobs)
|
|
|
|
|
{
|
|
|
|
|
slider->setSliderStyle(juce::Slider::RotaryVerticalDrag);
|
|
|
|
|
slider->setTextBoxStyle(juce::Slider::TextBoxBelow, false, 40, 14);
|
|
|
|
|
slider->setColour(juce::Slider::thumbColourId, juce::Colours::transparentBlack);
|
|
|
|
|
slider->setColour(juce::Slider::rotarySliderFillColourId, juce::Colour(0xffee8833));
|
|
|
|
|
slider->setColour(juce::Slider::rotarySliderOutlineColourId, juce::Colour(0xff332211));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (processorRef.getSampleBuffer().getNumSamples() > 0)
|
|
|
|
|
{
|
|
|
|
|
waveformDisplay.setSampleBuffer(&processorRef.getSampleBuffer(), processorRef.getSampleRateLoaded());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
waveformDisplay.setOnEmptyAreaClick([this] {
|
|
|
|
|
buttonClicked(&loadSampleButton);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
waveformDisplay.setOnKeyClick([this](int midiNote) {
|
|
|
|
|
keyClickNote = midiNote;
|
|
|
|
|
int pitchClass = midiNote % 12;
|
|
|
|
|
int sliceIdx = -1;
|
|
|
|
|
const auto& slices = processorRef.getSliceManager().getSlices();
|
|
|
|
|
for (int i = 0; i < static_cast<int>(slices.size()); ++i)
|
|
|
|
|
{
|
|
|
|
|
if (slices[static_cast<size_t>(i)].midiNote == pitchClass)
|
|
|
|
|
{
|
|
|
|
|
sliceIdx = i;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (sliceIdx >= 0)
|
|
|
|
|
processorRef.triggerSlice(sliceIdx, 0.8f);
|
|
|
|
|
else
|
|
|
|
|
processorRef.triggerSlice(0, 0.8f);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
startTimerHz(30);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MonoSlicerEditor::~MonoSlicerEditor()
|
|
|
|
|
{
|
|
|
|
|
autoSliceButton.removeListener(this);
|
|
|
|
|
clearSlicesButton.removeListener(this);
|
|
|
|
|
scaleComboBox.removeListener(this);
|
|
|
|
|
loadSampleButton.removeListener(this);
|
|
|
|
|
prevSampleButton.removeListener(this);
|
|
|
|
|
nextSampleButton.removeListener(this);
|
|
|
|
|
undoButton.removeListener(this);
|
|
|
|
|
redoButton.removeListener(this);
|
|
|
|
|
zoomInButton.removeListener(this);
|
|
|
|
|
zoomOutButton.removeListener(this);
|
|
|
|
|
zoomResetButton.removeListener(this);
|
|
|
|
|
stretchButton.removeListener(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MonoSlicerEditor::setupSlider(juce::Slider& slider, juce::Label& label, const juce::String& name)
|
|
|
|
|
{
|
|
|
|
|
addAndMakeVisible(slider);
|
|
|
|
|
addAndMakeVisible(label);
|
|
|
|
|
label.setText(name, juce::dontSendNotification);
|
|
|
|
|
label.attachToComponent(&slider, false);
|
|
|
|
|
label.setJustificationType(juce::Justification::centred);
|
|
|
|
|
label.setFont(juce::Font(juce::FontOptions(13.0f)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MonoSlicerEditor::attachSlider(juce::Slider& slider, juce::Label& label, const juce::String& name,
|
|
|
|
|
const juce::String& paramId, std::unique_ptr<SliderAttachment>& attachment)
|
|
|
|
|
{
|
|
|
|
|
setupSlider(slider, label, name);
|
|
|
|
|
attachment = std::make_unique<SliderAttachment>(processorRef.getAPVTS(), paramId, slider);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MonoSlicerEditor::paint(juce::Graphics& g)
|
|
|
|
|
{
|
|
|
|
|
g.fillAll(juce::Colour(0xff0f0f1a));
|
|
|
|
|
|
|
|
|
|
auto titleBar = getLocalBounds().removeFromTop(40);
|
|
|
|
|
g.setColour(juce::Colour(0xff1a1a30));
|
|
|
|
|
g.fillRect(titleBar);
|
|
|
|
|
|
|
|
|
|
g.setColour(juce::Colours::white);
|
|
|
|
|
g.setFont(juce::Font(juce::FontOptions(15.0f, juce::Font::bold)));
|
|
|
|
|
g.drawText("MONOSLICER", juce::Rectangle<int>(10, titleBar.getY(), 110, titleBar.getHeight()),
|
|
|
|
|
juce::Justification::centredLeft);
|
|
|
|
|
|
|
|
|
|
g.setColour(juce::Colour(0xff333355));
|
|
|
|
|
g.drawHorizontalLine(40, 0.0f, static_cast<float>(getWidth()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MonoSlicerEditor::resized()
|
|
|
|
|
{
|
|
|
|
|
float scaleX = static_cast<float>(getWidth()) / static_cast<float>(baseWidth);
|
|
|
|
|
float scaleY = static_cast<float>(getHeight()) / static_cast<float>(baseHeight);
|
|
|
|
|
setTransform(juce::AffineTransform::scale(juce::jmin(scaleX, scaleY)));
|
|
|
|
|
|
|
|
|
|
auto bounds = getLocalBounds();
|
|
|
|
|
auto titleBar = bounds.removeFromTop(40);
|
|
|
|
|
|
|
|
|
|
int tx = 120;
|
|
|
|
|
|
|
|
|
|
auto placeBtn = [&](juce::Button& btn, int w) {
|
|
|
|
|
btn.setBounds(tx, titleBar.getY() + 8, w, 24);
|
|
|
|
|
tx += w + 3;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
placeBtn(loadSampleButton, 44);
|
|
|
|
|
placeBtn(prevSampleButton, 24);
|
|
|
|
|
placeBtn(nextSampleButton, 24);
|
|
|
|
|
|
|
|
|
|
tx += 10;
|
|
|
|
|
placeBtn(undoButton, 44);
|
|
|
|
|
placeBtn(redoButton, 44);
|
|
|
|
|
|
|
|
|
|
tx += 10;
|
|
|
|
|
placeBtn(zoomOutButton, 28);
|
|
|
|
|
placeBtn(zoomResetButton, 32);
|
|
|
|
|
placeBtn(zoomInButton, 28);
|
|
|
|
|
|
|
|
|
|
scaleComboBox.setBounds(titleBar.getRight() - 100, titleBar.getY() + 8, 90, 24);
|
2026-07-21 15:20:49 +02:00
|
|
|
octaveComboBox.setBounds(titleBar.getRight() - 176, titleBar.getY() + 8, 72, 24);
|
2026-07-21 02:06:45 +02:00
|
|
|
|
|
|
|
|
auto bottomPanel = bounds.removeFromBottom(200);
|
|
|
|
|
auto bottomRow = bottomPanel.reduced(8);
|
|
|
|
|
|
|
|
|
|
// Bottom panel: buttons + stretch | amp ADSR | filter ADSR | filter knobs
|
|
|
|
|
auto leftArea = bottomRow.removeFromLeft(200);
|
|
|
|
|
{
|
|
|
|
|
auto btnArea = leftArea.removeFromTop(28);
|
|
|
|
|
autoSliceButton.setBounds(btnArea.getX(), btnArea.getY(), btnArea.getWidth() / 2 - 2, 24);
|
|
|
|
|
clearSlicesButton.setBounds(btnArea.getX() + btnArea.getWidth() / 2 + 2, btnArea.getY(), btnArea.getWidth() / 2 - 2, 24);
|
|
|
|
|
leftArea.removeFromTop(4);
|
|
|
|
|
// Stretch controls
|
|
|
|
|
int stretchW = leftArea.getWidth();
|
|
|
|
|
int col1W = stretchW / 2 - 2;
|
|
|
|
|
int col2W = stretchW / 2 - 2;
|
|
|
|
|
bpmSlider.setBounds(leftArea.getX(), leftArea.getY(), col1W, 20);
|
|
|
|
|
stretchBeatsComboBox.setBounds(leftArea.getX() + col1W + 4, leftArea.getY(), col2W, 20);
|
|
|
|
|
leftArea.removeFromTop(24);
|
|
|
|
|
stretchButton.setBounds(leftArea.getX(), leftArea.getY(), stretchW, 22);
|
|
|
|
|
leftArea.removeFromTop(26);
|
|
|
|
|
leftArea.removeFromTop(20);
|
|
|
|
|
keyShiftComboBox.setBounds(leftArea.getX(), leftArea.getY(), stretchW, 20);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bottomRow.removeFromLeft(8);
|
|
|
|
|
|
|
|
|
|
auto ampSection = bottomRow.removeFromLeft(230);
|
|
|
|
|
{
|
|
|
|
|
int kw = ampSection.getWidth() / 4;
|
|
|
|
|
int k = 0;
|
|
|
|
|
for (auto* s : { &AttackSlider, &DecaySlider, &SustainSlider, &ReleaseSlider })
|
|
|
|
|
{
|
|
|
|
|
s->setBounds(ampSection.getX() + k * kw, ampSection.getY(), kw, ampSection.getHeight());
|
|
|
|
|
++k;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bottomRow.removeFromLeft(8);
|
|
|
|
|
|
|
|
|
|
auto fltAdsSection = bottomRow.removeFromLeft(230);
|
|
|
|
|
{
|
|
|
|
|
int kw = fltAdsSection.getWidth() / 4;
|
|
|
|
|
int k = 0;
|
|
|
|
|
for (auto* s : { &filterAttackSlider, &filterDecaySlider, &filterSustainSlider, &filterReleaseSlider })
|
|
|
|
|
{
|
|
|
|
|
s->setBounds(fltAdsSection.getX() + k * kw, fltAdsSection.getY(), kw, fltAdsSection.getHeight());
|
|
|
|
|
++k;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bottomRow.removeFromLeft(8);
|
|
|
|
|
|
|
|
|
|
auto filterArea = bottomRow;
|
|
|
|
|
{
|
|
|
|
|
int comboW = 56;
|
|
|
|
|
int gap = 4;
|
|
|
|
|
int knobAreaX = filterArea.getX() + comboW + gap;
|
|
|
|
|
int knobAreaW = filterArea.getWidth() - comboW - gap;
|
|
|
|
|
int kw = knobAreaW / 3;
|
|
|
|
|
int topRowH = 80;
|
|
|
|
|
int bottomRowGap = 24;
|
|
|
|
|
int bottomRowH = filterArea.getHeight() - topRowH - bottomRowGap;
|
|
|
|
|
filterTypeComboBox.setBounds(filterArea.getX(), filterArea.getY() + 2, comboW, 20);
|
|
|
|
|
filterCutoffSlider.setBounds(knobAreaX, filterArea.getY(), kw, topRowH);
|
|
|
|
|
filterResoSlider.setBounds(knobAreaX + kw, filterArea.getY(), kw, topRowH);
|
|
|
|
|
filterEnvDepthSlider.setBounds(knobAreaX + kw * 2, filterArea.getY(), kw, topRowH);
|
|
|
|
|
int bottomY = filterArea.getY() + topRowH + bottomRowGap;
|
|
|
|
|
bassSlider.setBounds(knobAreaX, bottomY, kw, bottomRowH);
|
|
|
|
|
trebleSlider.setBounds(knobAreaX + kw, bottomY, kw, bottomRowH);
|
|
|
|
|
sensitivitySlider.setBounds(knobAreaX + kw * 2, bottomY, kw, bottomRowH);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto mainArea = bounds.reduced(8);
|
|
|
|
|
waveformDisplay.setBounds(mainArea);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MonoSlicerEditor::timerCallback()
|
|
|
|
|
{
|
2026-07-21 15:20:49 +02:00
|
|
|
if (processorRef.stateRestored.exchange(false))
|
|
|
|
|
{
|
|
|
|
|
waveformDisplay.setSampleBuffer(&processorRef.getSampleBuffer(), processorRef.getSampleRateLoaded());
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-21 02:06:45 +02:00
|
|
|
waveformDisplay.setPlaybackPosition(processorRef.currentPlaybackSample.load());
|
|
|
|
|
waveformDisplay.setActiveSliceIndex(processorRef.currentActiveSlice.load());
|
|
|
|
|
waveformDisplay.setSelectedSlice(processorRef.selectedSlice.load());
|
|
|
|
|
|
|
|
|
|
// Release key click note when mouse is released
|
|
|
|
|
if (keyClickNote >= 0)
|
|
|
|
|
{
|
|
|
|
|
auto& editor = *this;
|
|
|
|
|
if (!editor.isMouseButtonDown())
|
|
|
|
|
{
|
|
|
|
|
processorRef.releaseSlice();
|
|
|
|
|
keyClickNote = -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MonoSlicerEditor::buttonClicked(juce::Button* button)
|
|
|
|
|
{
|
|
|
|
|
if (button == &loadSampleButton)
|
|
|
|
|
{
|
|
|
|
|
fileChooser.launchAsync(juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectFiles,
|
|
|
|
|
[this](const juce::FileChooser& fc)
|
|
|
|
|
{
|
|
|
|
|
auto file = fc.getResult();
|
|
|
|
|
if (file.existsAsFile())
|
|
|
|
|
{
|
|
|
|
|
processorRef.loadAudioFile(file);
|
|
|
|
|
waveformDisplay.setSampleBuffer(&processorRef.getSampleBuffer(), processorRef.getSampleRateLoaded());
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else if (button == &prevSampleButton)
|
|
|
|
|
{
|
|
|
|
|
processorRef.loadPreviousSample();
|
|
|
|
|
waveformDisplay.setSampleBuffer(&processorRef.getSampleBuffer(), processorRef.getSampleRateLoaded());
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
else if (button == &nextSampleButton)
|
|
|
|
|
{
|
|
|
|
|
processorRef.loadNextSample();
|
|
|
|
|
waveformDisplay.setSampleBuffer(&processorRef.getSampleBuffer(), processorRef.getSampleRateLoaded());
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
else if (button == &autoSliceButton)
|
|
|
|
|
{
|
|
|
|
|
processorRef.getSliceManager().autoSlice();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
else if (button == &clearSlicesButton)
|
|
|
|
|
{
|
|
|
|
|
processorRef.getSliceManager().clearSlices();
|
|
|
|
|
}
|
|
|
|
|
else if (button == &undoButton)
|
|
|
|
|
{
|
|
|
|
|
processorRef.getSliceManager().undo();
|
|
|
|
|
}
|
|
|
|
|
else if (button == &redoButton)
|
|
|
|
|
{
|
|
|
|
|
processorRef.getSliceManager().redo();
|
|
|
|
|
}
|
|
|
|
|
else if (button == &stretchButton)
|
|
|
|
|
{
|
|
|
|
|
int beatsId = stretchBeatsComboBox.getSelectedId();
|
|
|
|
|
int numBeats = 4;
|
|
|
|
|
switch (beatsId)
|
|
|
|
|
{
|
|
|
|
|
case 1: numBeats = 1; break;
|
|
|
|
|
case 2: numBeats = 2; break;
|
|
|
|
|
case 3: numBeats = 4; break;
|
|
|
|
|
case 4: numBeats = 8; break;
|
|
|
|
|
case 5: numBeats = 16; break;
|
|
|
|
|
case 6: numBeats = 32; break;
|
|
|
|
|
default: numBeats = 4; break;
|
|
|
|
|
}
|
|
|
|
|
float bpm = static_cast<float>(bpmSlider.getValue());
|
|
|
|
|
processorRef.stretchToBeats(numBeats, bpm);
|
|
|
|
|
waveformDisplay.setSampleBuffer(&processorRef.getSampleBuffer(), processorRef.getSampleRateLoaded());
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
else if (button == &zoomInButton)
|
|
|
|
|
{
|
|
|
|
|
waveformDisplay.zoomIn();
|
|
|
|
|
}
|
|
|
|
|
else if (button == &zoomOutButton)
|
|
|
|
|
{
|
|
|
|
|
waveformDisplay.zoomOut();
|
|
|
|
|
}
|
|
|
|
|
else if (button == &zoomResetButton)
|
|
|
|
|
{
|
|
|
|
|
waveformDisplay.zoomReset();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MonoSlicerEditor::comboBoxChanged(juce::ComboBox* comboBoxThatHasChanged)
|
|
|
|
|
{
|
|
|
|
|
if (comboBoxThatHasChanged == &scaleComboBox)
|
|
|
|
|
{
|
|
|
|
|
static const float scales[] = { 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f };
|
|
|
|
|
int id = scaleComboBox.getSelectedId();
|
|
|
|
|
if (id >= 1 && id <= 6)
|
|
|
|
|
{
|
|
|
|
|
float scale = scales[id - 1];
|
|
|
|
|
setSize(static_cast<int>(baseWidth * scale), static_cast<int>(baseHeight * scale));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MonoSlicerEditor::isInterestedInFileDrag(const juce::StringArray& files)
|
|
|
|
|
{
|
|
|
|
|
for (const auto& f : files)
|
|
|
|
|
if (f.endsWithIgnoreCase(".wav") || f.endsWithIgnoreCase(".aiff") ||
|
|
|
|
|
f.endsWithIgnoreCase(".flac") || f.endsWithIgnoreCase(".ogg"))
|
|
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MonoSlicerEditor::filesDropped(const juce::StringArray& files, int, int)
|
|
|
|
|
{
|
|
|
|
|
for (const auto& f : files)
|
|
|
|
|
{
|
|
|
|
|
juce::File file(f);
|
|
|
|
|
if (file.existsAsFile())
|
|
|
|
|
{
|
|
|
|
|
processorRef.loadAudioFile(file);
|
|
|
|
|
waveformDisplay.setSampleBuffer(&processorRef.getSampleBuffer(), processorRef.getSampleRateLoaded());
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|