#include "Editor.h" XtcJuceEditor::XtcJuceEditor(XtcJuceProcessor& owner) : AudioProcessorEditor(owner),engine(owner) { const std::array ids{{"gain","cutoff","room","wet"}}; const std::array names{{"OUTPUT / dB","TONE / Hz","ROOM SIZE","REVERB MIX"}}; for (size_t i=0;i(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(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(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(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(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(state.getSize())) ? "Preset loaded." : "Invalid preset; current settings retained."; else safe->presetStatus="Could not read preset (limit 64 KiB)."; } } safe->chooser.reset(); }); }