From 8b615f4f3725ae42700ccdf7c2583b3cbe7b6108 Mon Sep 17 00:00:00 2001 From: nuoun <10226424+nuoun@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:40:10 +0200 Subject: [PATCH] WTSE overview class refactor (#7755) * WTSE refactor - Changes WTSE overview class constructor to use similar style as FormulaModulatorEditor - Renames WavetableEquationEditor class to WavetableScriptEditor - Renames defaultWavetableFormula string to defaultWavetableScript - Adds preliminary Prelude to WTSE - Adds changes to WTSE UI - Adds WavetableScriptEditState struct to DAWExtraStateStorage and SurgePatch load_xml()/save_xml() - Increments XML file version to v25 * Fix missing override * Removes commented out code and adds description to DAWExtraStateStorage struct --- src/common/SurgePatch.cpp | 31 + src/common/SurgeStorage.h | 17 +- src/common/dsp/WavetableScriptEvaluator.cpp | 2 +- src/common/dsp/WavetableScriptEvaluator.h | 2 +- src/surge-xt/gui/SurgeGUIEditorOverlays.cpp | 15 +- src/surge-xt/gui/overlays/LuaEditors.cpp | 656 ++++++++++++++------ src/surge-xt/gui/overlays/LuaEditors.h | 79 +-- 7 files changed, 579 insertions(+), 223 deletions(-) diff --git a/src/common/SurgePatch.cpp b/src/common/SurgePatch.cpp index 3245efebc3e..5141cc21220 100644 --- a/src/common/SurgePatch.cpp +++ b/src/common/SurgePatch.cpp @@ -2875,6 +2875,26 @@ void SurgePatch::load_xml(const void *data, int datasize, bool is_preset) } } } + + for (int osc = 0; osc < n_oscs; osc++) + { + std::string wsns = + "wtse_state_" + std::to_string(sc) + "_" + std::to_string(osc); + auto wss = TINYXML_SAFE_TO_ELEMENT(p->FirstChild(wsns)); + + if (wss) + { + auto q = &(dawExtraState.editor.wavetableScriptEditState[sc][osc]); + int vv; + + q->codeOrPrelude = 0; + + if (wss->QueryIntAttribute("codeOrPrelude", &vv) == TIXML_SUCCESS) + { + q->codeOrPrelude = vv; + } + } + } } // end of scene loop { @@ -3718,6 +3738,17 @@ unsigned int SurgePatch::save_xml(void **data) // allocates mem, must be freed b eds.InsertEndChild(fss); } + for (int os = 0; os < n_oscs; ++os) + { + auto q = &(dawExtraState.editor.wavetableScriptEditState[sc][os]); + std::string wsns = "wtse_state_" + std::to_string(sc) + "_" + std::to_string(os); + TiXmlElement wss(wsns); + + wss.SetAttribute("codeOrPrelude", q->codeOrPrelude); + + eds.InsertEndChild(wss); + } + TiXmlElement modEd("modulation_editor"); modEd.SetAttribute("sortOrder", dawExtraState.editor.modulationEditorState.sortOrder); modEd.SetAttribute("filterOn", dawExtraState.editor.modulationEditorState.filterOn); diff --git a/src/common/SurgeStorage.h b/src/common/SurgeStorage.h index 72855935bae..f5b17b6b57c 100644 --- a/src/common/SurgeStorage.h +++ b/src/common/SurgeStorage.h @@ -136,9 +136,10 @@ const int FIRoffsetI16 = FIRipolI16_N >> 1; // added deform option for Release parameter of Filter/Amp EG, which only produces an open gate for the release stage // 23 -> 24 (XT 1.3.3 nightlies) added actually functioning extend mode to FM2 oscillator's M1/2 Offset parameter // (old patches load with extend disabled even if they had it enabled) +// 24 -> 25 (XT 1.3.4 nightlies) added storing of Wavetable Script Editor window state // clang-format on -const int ff_revision = 24; +const int ff_revision = 25; const int n_scene_params = 273; const int n_global_params = 11 + n_fx_slots * (n_fx_params + 1); // each param plus a type @@ -910,6 +911,11 @@ struct DAWExtraStateStorage int timeEditMode = 0; } msegEditState[n_scenes][n_lfos]; + /* + * Window state parameters for Formula Editor overlay + * codeOrPrelude: Code editor selected tab + * debuggerOpen: Debug panel toggle + */ struct FormulaEditState { int codeOrPrelude{0}; @@ -921,6 +927,15 @@ struct DAWExtraStateStorage bool hasCustomEditor = false; } oscExtraEditState[n_scenes][n_lfos]; + /* + * Window state parameters for WTSE overlay + * codeOrPrelude: Code editor selected tab + */ + struct WavetableScriptEditState + { + int codeOrPrelude{0}; + } wavetableScriptEditState[n_scenes][n_oscs]; + struct OverlayState { int whichOverlay{-1}; diff --git a/src/common/dsp/WavetableScriptEvaluator.cpp b/src/common/dsp/WavetableScriptEvaluator.cpp index a30771630b4..3e5e329802d 100644 --- a/src/common/dsp/WavetableScriptEvaluator.cpp +++ b/src/common/dsp/WavetableScriptEvaluator.cpp @@ -134,7 +134,7 @@ bool constructWavetable(SurgeStorage *storage, const std::string &eqn, int resol } return true; } -std::string defaultWavetableFormula() +std::string defaultWavetableScript() { return R"FN(function generate(config) -- This script serves as the default example for the wavetable script editor. Unlike the formula editor, which executes diff --git a/src/common/dsp/WavetableScriptEvaluator.h b/src/common/dsp/WavetableScriptEvaluator.h index 9d84ac59f0e..b20a82b3f04 100644 --- a/src/common/dsp/WavetableScriptEvaluator.h +++ b/src/common/dsp/WavetableScriptEvaluator.h @@ -46,7 +46,7 @@ std::vector evaluateScriptAtFrame(SurgeStorage *storage, const std::strin bool constructWavetable(SurgeStorage *storage, const std::string &eqn, int resolution, int frames, wt_header &wh, float **wavdata); -std::string defaultWavetableFormula(); +std::string defaultWavetableScript(); } // namespace WavetableScript } // namespace Surge diff --git a/src/surge-xt/gui/SurgeGUIEditorOverlays.cpp b/src/surge-xt/gui/SurgeGUIEditorOverlays.cpp index 07e3e1f7edb..63dcb1633b8 100644 --- a/src/surge-xt/gui/SurgeGUIEditorOverlays.cpp +++ b/src/surge-xt/gui/SurgeGUIEditorOverlays.cpp @@ -282,12 +282,6 @@ std::unique_ptr SurgeGUIEditor::createOverlay case WTSCRIPT_EDITOR: { - /* - int w = 800, h = 520; - auto px = (getWindowSizeX() - w) / 2; - auto py = (getWindowSizeY() - h) / 2; - auto r = juce::Rectangle(px, py, w, h); - */ auto os = &synth->storage.getPatch().scene[current_scene].osc[current_osc[current_scene]]; @@ -296,13 +290,14 @@ std::unique_ptr SurgeGUIEditor::createOverlay return nullptr; } - auto wtse = std::make_unique( - this, &(this->synth->storage), os, currentSkin); + auto wtse = std::make_unique( + this, &(this->synth->storage), os, current_osc[current_scene], current_scene, + currentSkin); - wtse->setSkin(currentSkin, bitmapStore); - // wtse->setEnclosingParentPosition(juce::Rectangle(px, py, w, h)); std::string title = "Wavetable Script Editor Osc "; title += std::to_string(current_osc[current_scene] + 1); + + wtse->setSkin(currentSkin, bitmapStore); wtse->setEnclosingParentTitle(title); wtse->setCanTearOut({true, Surge::Storage::WTScriptOverlayLocationTearOut, Surge::Storage::WTScriptOverlayTearOutAlwaysOnTop, diff --git a/src/surge-xt/gui/overlays/LuaEditors.cpp b/src/surge-xt/gui/overlays/LuaEditors.cpp index 3f209af09e4..d04b1ad7774 100644 --- a/src/surge-xt/gui/overlays/LuaEditors.cpp +++ b/src/surge-xt/gui/overlays/LuaEditors.cpp @@ -57,6 +57,7 @@ struct SurgeCodeEditorComponent : public juce::CodeEditorComponent } } }; + struct EditorColors { static void setColorsFromSkin(juce::CodeEditorComponent *comp, @@ -522,7 +523,6 @@ struct FormulaControlArea : public juce::Component, { int labelHeight = 12; int buttonHeight = 14; - int numfieldHeight = 12; int margin = 2; int xpos = 10; @@ -540,7 +540,6 @@ struct FormulaControlArea : public juce::Component, codeS = std::make_unique(); auto btnrect = juce::Rectangle(marginPos, ypos - 1, btnWidth, buttonHeight); - codeS->setBounds(btnrect); codeS->setStorage(overlay->storage); codeS->setTitle("Code Selection"); @@ -555,15 +554,13 @@ struct FormulaControlArea : public juce::Component, codeS->setValue(overlay->getEditState().codeOrPrelude); codeS->setSkin(skin, associatedBitmapStore); addAndMakeVisible(*codeS); - marginPos += btnWidth + margin; applyS = std::make_unique(); btnrect = juce::Rectangle(getWidth() / 2 - 30, ypos - 1, 60, buttonHeight); - applyS->setBounds(btnrect); + applyS->setStorage(overlay->storage); applyS->setTitle("Apply"); applyS->setDescription("Apply"); - applyS->setStorage(overlay->storage); applyS->setLabels({"Apply"}); applyS->addListener(this); applyS->setTag(tag_code_apply); @@ -574,7 +571,6 @@ struct FormulaControlArea : public juce::Component, applyS->setSkin(skin, associatedBitmapStore); applyS->setEnabled(false); addAndMakeVisible(*applyS); - xpos += 60 + 10; } // Debugger Controls from the left @@ -659,7 +655,6 @@ struct FormulaControlArea : public juce::Component, default: break; } - return 0; } @@ -704,7 +699,6 @@ struct FormulaControlArea : public juce::Component, stepS->setVisible(true); initS->setVisible(true); } - repaint(); } case tag_debugger_init: @@ -815,67 +809,6 @@ void FormulaModulatorEditor::applyCode() mainEditor->grabKeyboardFocus(); } -void FormulaModulatorEditor::updateDebuggerIfNeeded() -{ - { - if (debugPanel->isOpen) - { - bool anyUpdate{false}; - auto lfodata = lfos; - -#define CK(x) \ - { \ - auto &r = debugPanel->tp[lfodata->x.param_id_in_scene]; \ - \ - if (r.i != lfodata->x.val.i) \ - { \ - r.i = lfodata->x.val.i; \ - anyUpdate = true; \ - } \ - } - - CK(rate); - CK(magnitude); - CK(start_phase); - CK(deform); - - if (debugPanel->lfoDebugger->formulastate.tempo != storage->temposyncratio * 120) - { - anyUpdate = true; - } - -#undef CK - -#define CKENV(x, y) \ - { \ - auto &tgt = debugPanel->lfoDebugger->formulastate.x; \ - auto src = lfodata->y.value_to_normalized(lfodata->y.val.f); \ - \ - if (tgt != src) \ - { \ - tgt = src; \ - anyUpdate = true; \ - } \ - } - CKENV(del, delay); - CKENV(a, attack); - CKENV(h, hold); - CKENV(dec, decay); - CKENV(s, sustain); - CKENV(r, release); - -#undef CKENV - - if (anyUpdate) - { - debugPanel->refreshDebuggerView(); - editor->repaintFrame(); - } - } - } - updateDebuggerCounter = (updateDebuggerCounter + 1) & 31; -} - void FormulaModulatorEditor::forceRefresh() { mainDocument->replaceAllContent(formulastorage->formulaString); @@ -946,6 +879,67 @@ void FormulaModulatorEditor::escapeKeyPressed() } } +void FormulaModulatorEditor::updateDebuggerIfNeeded() +{ + { + if (debugPanel->isOpen) + { + bool anyUpdate{false}; + auto lfodata = lfos; + +#define CK(x) \ + { \ + auto &r = debugPanel->tp[lfodata->x.param_id_in_scene]; \ + \ + if (r.i != lfodata->x.val.i) \ + { \ + r.i = lfodata->x.val.i; \ + anyUpdate = true; \ + } \ + } + + CK(rate); + CK(magnitude); + CK(start_phase); + CK(deform); + + if (debugPanel->lfoDebugger->formulastate.tempo != storage->temposyncratio * 120) + { + anyUpdate = true; + } + +#undef CK + +#define CKENV(x, y) \ + { \ + auto &tgt = debugPanel->lfoDebugger->formulastate.x; \ + auto src = lfodata->y.value_to_normalized(lfodata->y.val.f); \ + \ + if (tgt != src) \ + { \ + tgt = src; \ + anyUpdate = true; \ + } \ + } + CKENV(del, delay); + CKENV(a, attack); + CKENV(h, hold); + CKENV(dec, decay); + CKENV(s, sustain); + CKENV(r, release); + +#undef CKENV + + if (anyUpdate) + { + debugPanel->refreshDebuggerView(); + editor->repaintFrame(); + } + } + } + updateDebuggerCounter = (updateDebuggerCounter + 1) & 31; +} + std::optional> FormulaModulatorEditor::getPreCloseChickenBoxMessage() { @@ -958,7 +952,7 @@ FormulaModulatorEditor::getPreCloseChickenBoxMessage() return std::nullopt; } -struct WavetablePreviewComponent : juce::Component +struct WavetablePreviewComponent : public juce::Component, public Surge::GUI::SkinConsumingComponent { WavetablePreviewComponent(SurgeStorage *s, OscillatorStorage *os, Surge::GUI::Skin::ptr_t skin) : storage(s), osc(os), skin(skin) @@ -967,25 +961,42 @@ struct WavetablePreviewComponent : juce::Component void paint(juce::Graphics &g) override { - g.fillAll(juce::Colour(0, 0, 0)); + g.fillAll(skin->getColor(Colors::MSEGEditor::Background)); g.setFont(skin->fontManager->getFiraMonoAtSize(9)); - g.setColour(juce::Colour(230, 230, 255)); // could be a skin->getColor of course - auto s1 = std::string("Frame : ") + std::to_string(frameNumber + 1); - auto s2 = std::string("Res : ") + std::to_string(points.size()); - g.drawSingleLineText(s1, 3, 18); - g.drawSingleLineText(s2, 3, 30); + g.setColour(skin->getColor(Colors::MSEGEditor::Text)); + auto s1 = std::string("Frame: ") + std::to_string(frameNumber + 1); + auto s2 = std::string("Res: ") + std::to_string(points.size()); + g.drawSingleLineText(s1, 5, 18); + g.drawSingleLineText(s2, 5, 30); - g.setColour(juce::Colour(160, 160, 160)); auto h = getHeight(); auto w = getWidth(); auto t = h * 0.05; auto m = h * 0.5; auto b = h * 0.95; - g.drawLine(0, t, w, t); + + g.setColour(skin->getColor(Colors::MSEGEditor::Grid::SecondaryHorizontal)); + for (float x : {0.25f, 0.75f}) + { + g.drawLine(0, h * x, w, h * x); + } + + g.setColour(skin->getColor(Colors::MSEGEditor::Grid::SecondaryVertical)); + for (float y : {0.25f, 0.5f, 0.75f}) + { + g.drawLine(w * y, 0, w * y, h); + } + + g.setColour(skin->getColor(Colors::MSEGEditor::Grid::Primary)); + g.drawLine(0, 0, w, 0); + g.drawLine(0, h, w, h); + + g.drawLine(0, 0, 0, h); + g.drawLine(w, 0, w, h); + g.drawLine(0, m, w, m); - g.drawLine(0, b, w, b); auto p = juce::Path(); auto dx = 1.0 / (points.size() - 1); @@ -998,7 +1009,7 @@ struct WavetablePreviewComponent : juce::Component else p.lineTo(xp, yp); } - g.setColour(juce::Colour(255, 180, 0)); + g.setColour(skin->getColor(Colors::MSEGEditor::Curve)); g.strokePath(p, juce::PathStrokeType(1.0)); } @@ -1010,154 +1021,451 @@ struct WavetablePreviewComponent : juce::Component Surge::GUI::Skin::ptr_t skin; }; -WavetableEquationEditor::WavetableEquationEditor(SurgeGUIEditor *ed, SurgeStorage *s, - OscillatorStorage *os, - Surge::GUI::Skin::ptr_t skin) - : CodeEditorContainerWithApply(ed, s, skin, true), osc(os) +struct WavetableScriptControlArea : public juce::Component, + public Surge::GUI::SkinConsumingComponent, + public Surge::GUI::IComponentTagValue::Listener, + public juce::ComboBox::Listener, + public juce::TextEditor::Listener { - if (osc->wavetable_formula == "") + enum tags + { + tag_select_tab = 0x597500, + tag_code_apply, + tag_generate_wt + }; + + WavetableScriptEditor *overlay{nullptr}; + SurgeGUIEditor *editor{nullptr}; + + WavetableScriptControlArea(WavetableScriptEditor *ol, SurgeGUIEditor *ed) + : overlay(ol), editor(ed) { - mainDocument->insertText(0, Surge::WavetableScript::defaultWavetableFormula()); + setAccessible(true); + setTitle("Controls"); + setDescription("Controls"); + setFocusContainerType(juce::Component::FocusContainerType::keyboardFocusContainer); } - else + + void resized() override { - mainDocument->insertText(0, osc->wavetable_formula); + if (skin) + { + rebuild(); + } } - resolutionLabel = std::make_unique("resLabl"); - resolutionLabel->setFont(skin->fontManager->getLatoAtSize(10)); - resolutionLabel->setText("Resolution:", juce::NotificationType::dontSendNotification); - addAndMakeVisible(resolutionLabel.get()); + void rebuild() + { + int labelHeight = 12; + int buttonHeight = 14; + int margin = 2; + int xpos = 10; + + removeAllChildren(); + auto h = getHeight(); + + { + int marginPos = xpos + margin; + int btnWidth = 100; + int ypos = 1 + labelHeight + margin; - resolution = std::make_unique("res"); - int id = 1, grid = 32; - while (grid <= 4096) + codeL = newL("Code"); + codeL->setBounds(xpos, 1, 100, labelHeight); + addAndMakeVisible(*codeL); + + codeS = std::make_unique(); + auto btnrect = juce::Rectangle(marginPos, ypos - 1, btnWidth, buttonHeight); + codeS->setBounds(btnrect); + codeS->setStorage(overlay->storage); + codeS->setTitle("Code Selection"); + codeS->setDescription("Code Selection"); + codeS->setLabels({"Modulator", "Prelude"}); + codeS->addListener(this); + codeS->setTag(tag_select_tab); + codeS->setHeightOfOneImage(buttonHeight); + codeS->setRows(1); + codeS->setColumns(2); + codeS->setDraggable(true); + codeS->setValue(overlay->getEditState().codeOrPrelude); + codeS->setSkin(skin, associatedBitmapStore); + addAndMakeVisible(*codeS); + + btnWidth = 60; + + applyS = std::make_unique(); + btnrect = juce::Rectangle(getWidth() / 2 - 30, ypos - 1, btnWidth, buttonHeight); + applyS->setBounds(btnrect); + applyS->setStorage(overlay->storage); + applyS->setTitle("Apply"); + applyS->setDescription("Apply"); + applyS->setLabels({"Apply"}); + applyS->addListener(this); + applyS->setTag(tag_code_apply); + applyS->setHeightOfOneImage(buttonHeight); + applyS->setRows(1); + applyS->setColumns(1); + applyS->setDraggable(true); + applyS->setSkin(skin, associatedBitmapStore); + applyS->setEnabled(false); + addAndMakeVisible(*applyS); + + int bpos = getWidth() - marginPos - 3 * btnWidth - 10; + + resolutionL = newL("Resolution"); + resolutionL->setBounds(bpos - 3, 1, 100, labelHeight); + addAndMakeVisible(*resolutionL); + + resolutionB = std::make_unique("res"); + btnrect = juce::Rectangle(bpos, ypos - 1, btnWidth, buttonHeight); + resolutionB->setBounds(btnrect); + int id = 1, grid = 32; + while (grid <= 4096) + { + resolutionB->addItem(std::to_string(grid), id); + id++; + grid *= 2; + } + resolutionB->setSelectedId(overlay->osc->wavetable_formula_res_base, + juce::NotificationType::dontSendNotification); + resolutionB->addListener(this); + addAndMakeVisible(*resolutionB); + + bpos += btnWidth + 5; + + framesL = newL("Frames"); + framesL->setBounds(bpos - 3, 1, 100, labelHeight); + addAndMakeVisible(*framesL); + + framesT = std::make_unique("frm"); + btnrect = juce::Rectangle(bpos, ypos - 1, btnWidth, buttonHeight); + framesT->setBounds(btnrect); + framesT->setFont(skin->fontManager->getLatoAtSize(10)); + framesT->setText(std::to_string(overlay->osc->wavetable_formula_nframes), + juce::NotificationType::dontSendNotification); + framesT->addListener(this); + addAndMakeVisible(*framesT); + + bpos += btnWidth + 5; + + generateS = std::make_unique(); + btnrect = juce::Rectangle(bpos, ypos - 1, btnWidth, buttonHeight); + generateS->setBounds(btnrect); + generateS->setStorage(overlay->storage); + generateS->setTitle("Generate"); + generateS->setDescription("Generate"); + generateS->setLabels({"Generate"}); + generateS->addListener(this); + generateS->setTag(tag_generate_wt); + generateS->setHeightOfOneImage(buttonHeight); + generateS->setRows(1); + generateS->setColumns(1); + generateS->setDraggable(false); + generateS->setSkin(skin, associatedBitmapStore); + generateS->setEnabled(true); + addAndMakeVisible(*generateS); + } + } + + std::unique_ptr newL(const std::string &s) { - resolution->addItem(std::to_string(grid), id); - id++; - grid *= 2; + auto res = std::make_unique(s, s); + res->setText(s, juce::dontSendNotification); + res->setFont(skin->fontManager->getLatoAtSize(9, juce::Font::bold)); + res->setColour(juce::Label::textColourId, skin->getColor(Colors::MSEGEditor::Text)); + return res; } - resolution->setSelectedId(os->wavetable_formula_res_base, - juce::NotificationType::dontSendNotification); - addAndMakeVisible(resolution.get()); - framesLabel = std::make_unique("frmLabl"); - framesLabel->setFont(skin->fontManager->getLatoAtSize(10)); - framesLabel->setText("Frames:", juce::NotificationType::dontSendNotification); - addAndMakeVisible(framesLabel.get()); + int32_t controlModifierClicked(GUI::IComponentTagValue *c, const juce::ModifierKeys &mods, + bool isDoubleClickEvent) override + { + auto tag = (tags)(c->getTag()); + + switch (tag) + { + case tag_select_tab: + case tag_code_apply: + case tag_generate_wt: + { + juce::PopupMenu contextMenu; + + auto msurl = editor->helpURLForSpecial("formula-editor"); + auto hurl = editor->fullyResolvedHelpURL(msurl); + + editor->addHelpHeaderTo("Formula Editor", hurl, contextMenu); + + contextMenu.showMenuAsync(editor->popupMenuOptions(this, false), + Surge::GUI::makeEndHoverCallback(c)); + } + break; + default: + break; + } + return 0; + } + + void valueChanged(GUI::IComponentTagValue *c) override + { + auto tag = (tags)(c->getTag()); + + switch (tag) + { + case tag_select_tab: + { + int m = c->getValue(); + + if (m > 0.5) + { + overlay->showPreludeCode(); + } + else + { + overlay->showModulatorCode(); + } + } + break; + case tag_code_apply: + { + overlay->applyCode(); + } + break; + case tag_generate_wt: + { + overlay->generateWavetable(); + } + break; + default: + break; + } + } + + void comboBoxChanged(juce::ComboBox *comboBoxThatHasChanged) override + { + overlay->osc->wavetable_formula_res_base = resolutionB->getSelectedId(); + overlay->rerenderFromUIState(); + } + + void textEditorReturnKeyPressed(juce::TextEditor &) override + { + overlay->osc->wavetable_formula_nframes = std::atoi(framesT->getText().toRawUTF8()); + overlay->rerenderFromUIState(); + } + + std::unique_ptr codeL, resolutionL, framesL; + std::unique_ptr codeS, applyS, generateS; + std::unique_ptr resolutionB; + std::unique_ptr framesT; + + void paint(juce::Graphics &g) override { g.fillAll(skin->getColor(Colors::MSEGEditor::Panel)); } + + void onSkinChanged() override { rebuild(); } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WavetableScriptControlArea); +}; + +WavetableScriptEditor::WavetableScriptEditor(SurgeGUIEditor *ed, SurgeStorage *s, + OscillatorStorage *os, int oid, int scene, + Surge::GUI::Skin::ptr_t skin) + + : CodeEditorContainerWithApply(ed, s, skin, false), osc(os), osc_id(oid), scene(scene), + editor(ed) +{ + mainEditor->setScrollbarThickness(8); + mainEditor->setTitle("Wavetable Code"); + mainEditor->setDescription("Wavetable Code"); + + if (osc->wavetable_formula == "") + { + mainDocument->insertText(0, Surge::WavetableScript::defaultWavetableScript()); + } + else + { + mainDocument->insertText(0, osc->wavetable_formula); + } - frames = std::make_unique("frm"); - frames->setFont(skin->fontManager->getLatoAtSize(10)); - frames->setText(std::to_string(osc->wavetable_formula_nframes), - juce::NotificationType::dontSendNotification); - addAndMakeVisible(frames.get()); + // FIXME: split prelude into Formula and WTSE + preludeDocument = std::make_unique(); + preludeDocument->insertText(0, Surge::LuaSupport::getSurgePrelude()); - generate = std::make_unique("gen"); - generate->setButtonText("Generate"); - generate->addListener(this); - addAndMakeVisible(generate.get()); + preludeDisplay = std::make_unique(*preludeDocument, tokenizer.get()); + preludeDisplay->setTabSize(4, true); + preludeDisplay->setReadOnly(true); + preludeDisplay->setScrollbarThickness(8); + preludeDisplay->setTitle("Wavetable Prelude Code"); + preludeDisplay->setDescription("Wavetable Prelude Code"); + EditorColors::setColorsFromSkin(preludeDisplay.get(), skin); - renderer = std::make_unique(storage, osc, skin); - addAndMakeVisible(renderer.get()); + controlArea = std::make_unique(this, editor); + addAndMakeVisible(*controlArea); + addAndMakeVisible(*mainEditor); + addChildComponent(*preludeDisplay); currentFrame = std::make_unique("currF"); currentFrame->setSliderStyle(juce::Slider::LinearVertical); currentFrame->setTextBoxStyle(juce::Slider::NoTextBox, true, 0, 0); currentFrame->setRange(0.0, 1.0); currentFrame->addListener(this); - addAndMakeVisible(currentFrame.get()); + addAndMakeVisible(*currentFrame); + + rendererComponent = std::make_unique(storage, osc, skin); + addAndMakeVisible(*rendererComponent); + + switch (getEditState().codeOrPrelude) + { + case 0: + showModulatorCode(); + break; + case 1: + showPreludeCode(); + break; + } } -WavetableEquationEditor::~WavetableEquationEditor() noexcept = default; +WavetableScriptEditor::~WavetableScriptEditor() = default; -void WavetableEquationEditor::resized() +DAWExtraStateStorage::EditorState::WavetableScriptEditState &WavetableScriptEditor::getEditState() { - auto w = getWidth() - 5; - auto h = getHeight() - 5; // this is a hack obvs - int m = 3; + return storage->getPatch().dawExtraState.editor.wavetableScriptEditState[scene][osc_id]; +} - int topH = 20; - int itemW = 100; +void WavetableScriptEditor::onSkinChanged() +{ + CodeEditorContainerWithApply::onSkinChanged(); + preludeDisplay->setFont(skin->getFont(Fonts::LuaEditor::Code)); + EditorColors::setColorsFromSkin(preludeDisplay.get(), skin); + controlArea->setSkin(skin, associatedBitmapStore); + rendererComponent->setSkin(skin, associatedBitmapStore); // FIXME +} - int renderH = 150; +void WavetableScriptEditor::applyCode() +{ + osc->wavetable_formula = mainDocument->getAllContent().toStdString(); + osc->wavetable_formula_res_base = controlArea->resolutionB->getSelectedId(); + osc->wavetable_formula_nframes = std::atoi(controlArea->framesT->getText().toRawUTF8()); - resolutionLabel->setBounds(m, m, itemW, topH); - resolution->setBounds(m * 2 + itemW, m, itemW, topH); - framesLabel->setBounds(m * 3 + 2 * itemW, m, itemW, topH); - frames->setBounds(m * 4 + 3 * itemW, m, itemW, topH); + editor->repaintFrame(); + rerenderFromUIState(); + setApplyEnabled(false); + mainEditor->grabKeyboardFocus(); +} - generate->setBounds(w - m - itemW, m, itemW, topH); - applyButton->setBounds(w - 2 * m - 2 * itemW, m, itemW, topH); +void WavetableScriptEditor::forceRefresh() +{ + mainDocument->replaceAllContent(osc->wavetable_formula); + editor->repaintFrame(); +} - mainEditor->setBounds(m, m * 2 + topH, w - 2 * m, h - topH - renderH - 3 * m); +void WavetableScriptEditor::setApplyEnabled(bool b) +{ + if (controlArea) + { + controlArea->applyS->setEnabled(b); + controlArea->applyS->repaint(); + } +} - currentFrame->setBounds(m, h - m - renderH, 32, renderH); - renderer->setBounds(m + 32, h - m - renderH, w - 2 * m - 32, renderH); +void WavetableScriptEditor::resized() +{ + auto t = getTransform().inverted(); + auto h = getHeight(); + auto w = getWidth(); + t.transformPoint(w, h); + + int itemWidth = 100; + int topHeight = 20; + int controlHeight = 35; + int rendererHeight = 150; + + auto edRect = juce::Rectangle(2, 2, w - 4, h - controlHeight - rendererHeight - 6); + mainEditor->setBounds(edRect); + preludeDisplay->setBounds(edRect); + controlArea->setBounds(0, h - controlHeight - rendererHeight - 2, w, + controlHeight + rendererHeight + 2); + + currentFrame->setBounds(2, h - rendererHeight - 2, 32, rendererHeight); + rendererComponent->setBounds(2 + 30, h - rendererHeight, w - 2 - 30, rendererHeight); rerenderFromUIState(); } -void WavetableEquationEditor::applyCode() +void WavetableScriptEditor::showModulatorCode() { - osc->wavetable_formula = mainDocument->getAllContent().toStdString(); - osc->wavetable_formula_res_base = resolution->getSelectedId(); - osc->wavetable_formula_nframes = std::atoi(frames->getText().toRawUTF8()); + preludeDisplay->setVisible(false); + mainEditor->setVisible(true); + getEditState().codeOrPrelude = 0; +} - applyButton->setEnabled(false); - rerenderFromUIState(); +void WavetableScriptEditor::showPreludeCode() +{ + preludeDisplay->setVisible(true); + mainEditor->setVisible(false); + getEditState().codeOrPrelude = 1; +} - editor->repaintFrame(); +void WavetableScriptEditor::escapeKeyPressed() +{ + auto c = getParentComponent(); + while (c) + { + if (auto olw = dynamic_cast(c)) + { + olw->onClose(); + return; + } + c = c->getParentComponent(); + } } -void WavetableEquationEditor::rerenderFromUIState() +void WavetableScriptEditor::rerenderFromUIState() { - auto resi = resolution->getSelectedId(); - auto nfr = std::atoi(frames->getText().toRawUTF8()); + auto resi = controlArea->resolutionB->getSelectedId(); + auto nfr = std::atoi(controlArea->framesT->getText().toRawUTF8()); auto cfr = (int)round((nfr - 1) * currentFrame->getValue()); // map slider to 0 .. nFrames - 1 auto respt = 32; for (int i = 1; i < resi; ++i) respt *= 2; - renderer->points = Surge::WavetableScript::evaluateScriptAtFrame( + rendererComponent->points = Surge::WavetableScript::evaluateScriptAtFrame( storage, mainDocument->getAllContent().toStdString(), respt, cfr, nfr); - renderer->frameNumber = cfr; - renderer->repaint(); + rendererComponent->frameNumber = cfr; + rendererComponent->repaint(); } -void WavetableEquationEditor::comboBoxChanged(juce::ComboBox *comboBoxThatHasChanged) +void WavetableScriptEditor::generateWavetable() { - rerenderFromUIState(); + auto resi = controlArea->resolutionB->getSelectedId(); + auto nfr = std::atoi(controlArea->framesT->getText().toRawUTF8()); + auto respt = 32; + for (int i = 1; i < resi; ++i) + respt *= 2; + std::cout << "Generating wavetable with " << respt << " samples and " << nfr << " frames" + << std::endl; + + wt_header wh; + float *wd = nullptr; + Surge::WavetableScript::constructWavetable(storage, mainDocument->getAllContent().toStdString(), + respt, nfr, wh, &wd); + storage->waveTableDataMutex.lock(); + osc->wt.BuildWT(wd, wh, wh.flags & wtf_is_sample); + osc->wavetable_display_name = "Scripted Wavetable"; + storage->waveTableDataMutex.unlock(); + + delete[] wd; + editor->repaintFrame(); } -void WavetableEquationEditor::sliderValueChanged(juce::Slider *slider) { rerenderFromUIState(); } -void WavetableEquationEditor::buttonClicked(juce::Button *button) +void WavetableScriptEditor::sliderValueChanged(juce::Slider *slider) { rerenderFromUIState(); } + +std::optional> +WavetableScriptEditor::getPreCloseChickenBoxMessage() { - if (button == generate.get()) + if (controlArea->applyS->isEnabled()) { - auto resi = resolution->getSelectedId(); - auto nfr = std::atoi(frames->getText().toRawUTF8()); - auto respt = 32; - for (int i = 1; i < resi; ++i) - respt *= 2; - std::cout << "Generating wavetable with " << respt << " samples and " << nfr << " frames" - << std::endl; - - wt_header wh; - float *wd = nullptr; - Surge::WavetableScript::constructWavetable( - storage, mainDocument->getAllContent().toStdString(), respt, nfr, wh, &wd); - storage->waveTableDataMutex.lock(); - osc->wt.BuildWT(wd, wh, wh.flags & wtf_is_sample); - osc->wavetable_display_name = "Scripted Wavetable"; - storage->waveTableDataMutex.unlock(); - - delete[] wd; - editor->repaintFrame(); - - return; + return std::make_pair("Close Wavetable Script Editor", + "Do you really want to close the wavetable editor? Any " + "changes that were not applied will be lost!"); } - CodeEditorContainerWithApply::buttonClicked(button); + return std::nullopt; } } // namespace Overlays diff --git a/src/surge-xt/gui/overlays/LuaEditors.h b/src/surge-xt/gui/overlays/LuaEditors.h index a47081cbc1d..58aebaf6ef2 100644 --- a/src/surge-xt/gui/overlays/LuaEditors.h +++ b/src/surge-xt/gui/overlays/LuaEditors.h @@ -88,33 +88,31 @@ struct FormulaModulatorEditor : public CodeEditorContainerWithApply, public Refr Surge::GUI::Skin::ptr_t sk); ~FormulaModulatorEditor(); - std::unique_ptr debugPanel; - std::unique_ptr controlArea; void resized() override; + void onSkinChanged() override; void applyCode() override; - + void forceRefresh() override; + void setApplyEnabled(bool b) override; void showModulatorCode(); void showPreludeCode(); - void escapeKeyPressed(); void updateDebuggerIfNeeded(); + std::unique_ptr preludeDocument; + std::unique_ptr preludeDisplay; + std::unique_ptr controlArea; + + std::unique_ptr debugPanel; + LFOStorage *lfos{nullptr}; FormulaModulatorStorage *formulastorage{nullptr}; SurgeGUIEditor *editor{nullptr}; int lfo_id, scene; - - void onSkinChanged() override; - void setApplyEnabled(bool b) override; - - void forceRefresh() override; + int32_t updateDebuggerCounter{0}; DAWExtraStateStorage::EditorState::FormulaEditState &getEditState(); - std::unique_ptr preludeDocument; - std::unique_ptr preludeDisplay; - bool shouldRepaintOnParamChange(const SurgePatch &patch, Parameter *p) override { return false; @@ -122,45 +120,54 @@ struct FormulaModulatorEditor : public CodeEditorContainerWithApply, public Refr std::optional> getPreCloseChickenBoxMessage() override; - int32_t updateDebuggerCounter{0}; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FormulaModulatorEditor); }; struct WavetablePreviewComponent; +struct WavetableScriptControlArea; -struct WavetableEquationEditor : public CodeEditorContainerWithApply, - public juce::Slider::Listener, - public juce::ComboBox::Listener +struct WavetableScriptEditor : public CodeEditorContainerWithApply, + public RefreshableOverlay, + public juce::Slider::Listener { - WavetableEquationEditor(SurgeGUIEditor *ed, SurgeStorage *s, OscillatorStorage *os, - Surge::GUI::Skin::ptr_t sk); - ~WavetableEquationEditor() noexcept; - - std::unique_ptr resolutionLabel; - std::unique_ptr resolution; - - std::unique_ptr framesLabel; - std::unique_ptr frames; - - std::unique_ptr currentFrame; - std::unique_ptr renderer; - - std::unique_ptr generate; - - void comboBoxChanged(juce::ComboBox *comboBoxThatHasChanged) override; - void sliderValueChanged(juce::Slider *slider) override; + WavetableScriptEditor(SurgeGUIEditor *ed, SurgeStorage *s, OscillatorStorage *os, int oscid, + int scene, Surge::GUI::Skin::ptr_t sk); + ~WavetableScriptEditor(); void resized() override; + void onSkinChanged() override; void applyCode() override; + void forceRefresh() override; + void setApplyEnabled(bool b) override; + void showModulatorCode(); + void showPreludeCode(); + void escapeKeyPressed(); + void sliderValueChanged(juce::Slider *slider) override; + void generateWavetable(); void rerenderFromUIState(); - void buttonClicked(juce::Button *button) override; + std::unique_ptr preludeDocument; + std::unique_ptr preludeDisplay; + std::unique_ptr controlArea; + + std::unique_ptr rendererComponent; + std::unique_ptr currentFrame; OscillatorStorage *osc; + SurgeGUIEditor *editor{nullptr}; + int osc_id, scene; + + DAWExtraStateStorage::EditorState::WavetableScriptEditState &getEditState(); + + bool shouldRepaintOnParamChange(const SurgePatch &patch, Parameter *p) override + { + return false; + } + + std::optional> getPreCloseChickenBoxMessage() override; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WavetableEquationEditor); + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WavetableScriptEditor); }; } // namespace Overlays