JUCE SDK integration.

This commit is contained in:
Johannes Findeisen 2026-11-06 18:29:35 +01:00
commit adcbcdfd5e
16 changed files with 1057 additions and 0 deletions

97
examples/juce/Editor.cpp Normal file
View file

@ -0,0 +1,97 @@
#include "Editor.h"
XtcJuceEditor::XtcJuceEditor(XtcJuceProcessor& owner)
: AudioProcessorEditor(owner),engine(owner)
{
const std::array<const char*,4> ids{{"gain","cutoff","room","wet"}};
const std::array<const char*,4> names{{"OUTPUT / dB","TONE / Hz","ROOM SIZE","REVERB MIX"}};
for (size_t i=0;i<sliders.size();++i) {
auto& slider=sliders[i];
slider.setSliderStyle(juce::Slider::RotaryHorizontalVerticalDrag);
slider.setTextBoxStyle(juce::Slider::TextBoxBelow,false,95,24);
slider.setColour(juce::Slider::rotarySliderFillColourId,juce::Colour(0xff74dbc1));
slider.setColour(juce::Slider::textBoxOutlineColourId,juce::Colours::transparentBlack);
addAndMakeVisible(slider);
labels[i].setText(names[i],juce::dontSendNotification);
labels[i].setJustificationType(juce::Justification::centred); addAndMakeVisible(labels[i]);
attachments[i]=std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(engine.parameters,ids[i],slider);
slider.textFromValueFunction=[i](double value) { return juce::String(value,i==1 ? 0 : 2); };
slider.updateText();
}
for (auto* button : {&save,&load,&panicButton}) addAndMakeVisible(*button);
addAndMakeVisible(effects); addAndMakeVisible(status); addAndMakeVisible(keyboard);
effectsAttachment=std::make_unique<juce::AudioProcessorValueTreeState::ButtonAttachment>(engine.parameters,"effects",effects);
keyboardState.addListener(this);
keyboard.setAvailableRange(36,96); keyboard.setLowestVisibleKey(48); keyboard.setKeyWidth(24);
keyboard.setColour(juce::MidiKeyboardComponent::keyDownOverlayColourId,juce::Colour(0xff74dbc1));
save.onClick=[this]{choosePreset(true);}; load.onClick=[this]{choosePreset(false);};
panicButton.onClick=[this]{keyboardState.allNotesOff(0);engine.panic();};
setSize(780,490); startTimerHz(30);
}
XtcJuceEditor::~XtcJuceEditor()
{
stopTimer(); keyboardState.removeListener(this); engine.panic();
}
void XtcJuceEditor::paint(juce::Graphics& graphics)
{
graphics.fillAll(juce::Colour(0xff101c23));
graphics.setColour(juce::Colour(0xff74dbc1));
graphics.setFont(juce::FontOptions(40.0f,juce::Font::bold)); graphics.drawText("xtc",24,15,100,55,juce::Justification::left);
graphics.setColour(juce::Colour(0xffedf5f5));
graphics.setFont(juce::FontOptions(23.0f)); graphics.drawText("Soft Keys",130,20,300,40,juce::Justification::left);
graphics.setColour(juce::Colour(0xffa4b7c1)); graphics.setFont(juce::FontOptions(14.0f));
graphics.drawText("Play. Shape. Explore.",132,58,420,25,juce::Justification::left);
graphics.setColour(juce::Colour(0xff283a44)); graphics.fillRoundedRectangle(590,38,160,9,4);
graphics.setColour(juce::Colour(0xff74dbc1)); graphics.fillRoundedRectangle(590,38,160*juce::jlimit(0.0f,1.0f,level),9,4);
}
void XtcJuceEditor::resized()
{
for (size_t i=0;i<sliders.size();++i) {
const int x=20+static_cast<int>(i)*190;
labels[i].setBounds(x,105,170,25); sliders[i].setBounds(x,135,170,150);
}
effects.setBounds(25,300,220,30); save.setBounds(380,300,120,30);
load.setBounds(510,300,120,30); panicButton.setBounds(640,300,110,30);
status.setBounds(25,340,725,30); keyboard.setBounds(20,385,740,85);
}
void XtcJuceEditor::timerCallback()
{
level=engine.peak(); repaint(580,30,180,25);
const juce::String text=engine.processingFailed() ? "Audio preparation or processing failed; reopen the device to retry."
: (engine.droppedEvents() ? "MIDI overflow: voices cleared. Reduce the event rate."
: "Use the keyboard below or select a MIDI input in the standalone Audio/MIDI settings.");
status.setText(presetStatus.isNotEmpty() ? presetStatus : text,juce::dontSendNotification);
}
void XtcJuceEditor::handleNoteOn(juce::MidiKeyboardState*,int channel,int note,float velocity)
{ engine.enqueueNote(true,channel,note,velocity); }
void XtcJuceEditor::handleNoteOff(juce::MidiKeyboardState*,int channel,int note,float velocity)
{ engine.enqueueNote(false,channel,note,velocity); }
void XtcJuceEditor::choosePreset(bool saving)
{
if (chooser) return;
chooser=std::make_unique<juce::FileChooser>(saving ? "Save xtc JUCE preset" : "Load xtc JUCE preset",
juce::File{},"*.xtcjuce");
const int browserFlags=juce::FileBrowserComponent::canSelectFiles |
(saving ? juce::FileBrowserComponent::saveMode | juce::FileBrowserComponent::warnAboutOverwriting
: juce::FileBrowserComponent::openMode);
chooser->launchAsync(browserFlags,[safe=juce::Component::SafePointer<XtcJuceEditor>(this),saving](const juce::FileChooser& dialog){
if (!safe) return;
const auto file=dialog.getResult();
if (file!=juce::File{}) {
if (saving) {
juce::MemoryBlock state; safe->engine.getStateInformation(state);
juce::TemporaryFile temporary(file);
if (!temporary.getFile().replaceWithData(state.getData(),state.getSize()) ||
!temporary.overwriteTargetFileWithTemporary())
safe->presetStatus="Could not save preset.";
else safe->presetStatus="Preset saved.";
} else {
juce::MemoryBlock state;
if (file.getSize()>0 && file.getSize()<=65536 && file.loadFileAsData(state))
safe->presetStatus=safe->engine.restorePreset(state.getData(),static_cast<int>(state.getSize()))
? "Preset loaded." : "Invalid preset; current settings retained.";
else safe->presetStatus="Could not read preset (limit 64 KiB).";
}
}
safe->chooser.reset();
});
}