From c767b1f4d507f7e87303197ce173cbf3d428178a Mon Sep 17 00:00:00 2001 From: gergelybartha Date: Thu, 9 Jul 2026 15:21:15 +0200 Subject: [PATCH 1/2] Add system MIDI channel selection and handling logic --- .../panels/configuration/Configuration.svelte | 213 ++++++++++++++++-- 1 file changed, 200 insertions(+), 13 deletions(-) diff --git a/src/renderer/main/panels/configuration/Configuration.svelte b/src/renderer/main/panels/configuration/Configuration.svelte index 80fe2e66d..5125165bf 100644 --- a/src/renderer/main/panels/configuration/Configuration.svelte +++ b/src/renderer/main/panels/configuration/Configuration.svelte @@ -37,8 +37,17 @@ updateAction, } from "../../../runtime/operations"; import { isPasteActionsEnabled } from "./components/Toolbar"; - import { MeltRadio, MoltenInput, Toggle } from "@intechstudio/grid-uikit"; - import { EventType, EventTypeToNumber } from "@intechstudio/grid-protocol"; + import { + MeltRadio, + MoltenInput, + Toggle, + MeltSelect, + } from "@intechstudio/grid-uikit"; + import { + EventType, + EventTypeToNumber, + ElementType, + } from "@intechstudio/grid-protocol"; import { information as elementNameInformation, generateScript, @@ -63,10 +72,6 @@ $: handleElementNameChange(elementName); - $: if ($element) { - handleElementChange(element); - } - function handleElementNameChange(value: string | undefined) { if (typeof element === "undefined") { return; @@ -118,6 +123,31 @@ } else { elementName = ""; } + + // Read back MIDI channel for system element — always reset first + let readbackChannel: number | null = null; + + if (element.type === ElementType.SYSTEM && setup) { + const fstIndex = findMidiAutoChBlock(setup); + if (fstIndex !== -1) { + const fenIndex = setup.config.findIndex( + (a, i) => i > fstIndex && a.short === "fen", + ); + const endIndex = fenIndex !== -1 ? fenIndex : setup.config.length; + const cbAction = setup.config.find( + (a, i) => + i > fstIndex && + i < endIndex && + a.short === "cb" && + MIDI_AUTO_CH_CB_REGEX.test(a.script), + ); + const match = cbAction?.script?.match(MIDI_AUTO_CH_CB_REGEX); + readbackChannel = match ? Number(match[1]) + 1 : null; + } + } + + systemMidiChannel = readbackChannel; + setMidiDisplay(readbackChannel); } function handleContextChange( @@ -142,17 +172,159 @@ ui.eventtype, ); - if (typeof element !== "undefined" && !element.isLoaded()) { - element - .load() - .then((e) => {}) - .catch((err) => { - console.error("Failed to load element:", err); - }); + if (typeof element !== "undefined") { + if (element.isLoaded()) { + handleElementChange(element); + } else { + const loadingElement = element; + loadingElement + .load() + .then(() => { + // Only apply if the element hasn't changed while loading + if (element === loadingElement) { + handleElementChange(loadingElement); + } + }) + .catch((err) => { + console.error("Failed to load element:", err); + }); + } } } + // --- System MIDI Channel --- + const MIDI_AUTO_CH_FST_SCRIPT = "midi_auto_ch = function(self)"; // humanized form (spaces) + const MIDI_AUTO_CH_FST_SCRIPT_SHORT = "midi_auto_ch=function(self)"; // shortified form (no spaces, from hardware) + const MIDI_AUTO_CH_CB_REGEX = /^return (\d+)$/; + let containerWidth: number; + let systemMidiChannel: number | null = null; + + function findMidiAutoChBlock(setup: GridEvent): number { + return setup.config.findIndex( + (a) => + a.short === "fst" && + (a.script === MIDI_AUTO_CH_FST_SCRIPT || + a.script === MIDI_AUTO_CH_FST_SCRIPT_SHORT), + ); + } + + function handleSystemMidiChannelChange(value: number | null) { + if (typeof element === "undefined") return; + if (element.type !== ElementType.SYSTEM) return; + + const setup = element.findEvent(EventTypeToNumber(EventType.SETUP)); + if (!setup) return; + const fstIndex = findMidiAutoChBlock(setup); + + if (value === null) { + // Auto selected — remove fst + everything up to and including its fen + if (fstIndex !== -1) { + const fenIndex = setup.config.findIndex( + (a, i) => i > fstIndex && a.short === "fen", + ); + const endIndex = fenIndex !== -1 ? fenIndex + 1 : fstIndex + 1; + // Snapshot the actions to remove before modifying config + const actionsToRemove = [...setup.config.slice(fstIndex, endIndex)]; + // Remove one by one to avoid partial-remove rejections + const removeSequentially = async () => { + for (const action of actionsToRemove) { + // Re-check it's still in config (it may have already been removed) + if (setup.config.includes(action)) { + await setup.remove(action).catch(console.error); + } + } + await setup.sendToGrid(); + }; + removeSequentially(); + } + return; + } + + // channel is 1-based from dropdown, Lua return value is 0-based + const returnVal = value - 1; + const cbScript = `return ${returnVal}`; + + if (fstIndex !== -1) { + // Function block exists — find the first cb with our return pattern + // between fst and fen, or insert one right after fst + const fenIndex = setup.config.findIndex( + (a, i) => i > fstIndex && a.short === "fen", + ); + const endIndex = fenIndex !== -1 ? fenIndex : setup.config.length; + const cbIndex = setup.config.findIndex( + (a, i) => + i > fstIndex && + i < endIndex && + a.short === "cb" && + MIDI_AUTO_CH_CB_REGEX.test(a.script), + ); + if (cbIndex !== -1) { + // Matching cb found — update it + const cbAction = setup.config[cbIndex]; + const data = new ActionData("cb", cbScript, cbAction.name); + updateAction(cbAction, data, true); + } else { + // No matching cb — insert a new one right after fst + const newCb = new GridAction( + setup as GridEvent, + new ActionData("cb", cbScript), + ); + (setup as GridEvent) + .insert(fstIndex + 1, newCb) + .then(() => (setup as GridEvent).sendToGrid()) + .catch(console.error); + } + } else { + // Insert 3 new blocks at index 0 + const fstAction = new GridAction( + setup as GridEvent, + new ActionData("fst", MIDI_AUTO_CH_FST_SCRIPT), + ); + const cbAction = new GridAction( + setup as GridEvent, + new ActionData("cb", cbScript), + ); + const fenAction = new GridAction( + setup as GridEvent, + new ActionData("fen", "end"), + ); + (setup as GridEvent) + .insert(0, fstAction, cbAction, fenAction) + .then(() => (setup as GridEvent).sendToGrid()) + .catch(console.error); + } + } + + const MIDI_AUTO_SENTINEL = "auto"; + const midiChannelOptions = [ + { title: "Auto", value: MIDI_AUTO_SENTINEL }, + ...Array.from({ length: 16 }, (_, i) => ({ + title: `Channel ${i + 1}`, + value: String(i + 1), + })), + ]; + + // String target for MeltSelect — always a string, never null. + // Updated programmatically by setMidiDisplay(); mutated by MeltSelect on user pick. + let midiChannelDisplay: string = MIDI_AUTO_SENTINEL; + let _midiDisplayUpdating = false; + + function setMidiDisplay(value: number | null) { + _midiDisplayUpdating = true; + midiChannelDisplay = value === null ? MIDI_AUTO_SENTINEL : String(value); + _midiDisplayUpdating = false; + } + + // Fires whenever MeltSelect mutates midiChannelDisplay. + // Guard skips it when setMidiDisplay() is the one changing it. + $: if (!_midiDisplayUpdating) { + handleSystemMidiChannelChange( + midiChannelDisplay === MIDI_AUTO_SENTINEL + ? null + : Number(midiChannelDisplay), + ); + } $: handleIsMultiViewAutoUpdate( containerWidth, @@ -354,6 +526,21 @@ {/if} + {#if $element && $element.type === ElementType.SYSTEM} +
+ Module MIDI Channel +
+ +
+
+ {/if} + {#if !$appSettings.isMultiView}
From f0b586d398d9555e05ed9858f97390826fdfd770 Mon Sep 17 00:00:00 2001 From: sukuwc Date: Mon, 13 Jul 2026 17:08:21 +0200 Subject: [PATCH 2/2] SUKU element name and midi default channel fixes --- src/renderer/config-blocks/Midi.svelte | 30 +- .../config-blocks/MidiFourteenBit.svelte | 2 +- .../config-blocks/headers/MidiFace.svelte | 38 ++- src/renderer/lib/_utils.ts | 29 ++ .../panels/configuration/Configuration.svelte | 288 +++++++++--------- src/renderer/runtime/runtime.ts | 4 + src/renderer/runtime/system-midi-channel.ts | 164 ++++++++++ 7 files changed, 390 insertions(+), 165 deletions(-) create mode 100644 src/renderer/runtime/system-midi-channel.ts diff --git a/src/renderer/config-blocks/Midi.svelte b/src/renderer/config-blocks/Midi.svelte index 252785c6a..2bf72ec45 100644 --- a/src/renderer/config-blocks/Midi.svelte +++ b/src/renderer/config-blocks/Midi.svelte @@ -58,7 +58,14 @@ import { midiCC } from "./_midi.js"; import { Script, extractParam } from "./_script_parsers.js"; import { LocalDefinitions } from "../runtime/runtime.store"; - import { ActionData, GridAction, GridEvent } from "./../runtime/runtime"; + import { + ActionData, + GridAction, + GridElement, + GridEvent, + GridPage, + } from "./../runtime/runtime"; + import { moduleMidiChannelState } from "../runtime/system-midi-channel"; import SendFeedback from "../main/user-interface/SendFeedback.svelte"; import { MusicalNotes } from "../main/panels/MidiMonitor/MidiMonitor.store"; import { Validator } from "./validators"; @@ -67,6 +74,12 @@ export let action: GridAction; let event = action.parent as GridEvent; + // Reactive, config-derived module MIDI channel for this action's page — lets + // renderSuggestions re-run (and the "Auto (N)" label update) the moment the + // page's default channel changes. + const channelStore = moduleMidiChannelState( + (event.parent as GridElement)?.parent as GridPage | undefined, + ); let containerWidth = 0; $: isWide = containerWidth > 360; @@ -309,13 +322,16 @@ index, }); + // Recompute the channel "Auto (N)" label each run so it reflects the page's + // current module MIDI channel (getMidi reads it live), rather than the value + // captured once in baseSuggestions at mount. + const channelAuto = makeAuto( + `Auto (${Grid.Auto.getMidiChannelLabel(action)})`, + ); + // assemble suggestions with "auto" always first suggestions = [ - [ - baseSuggestions[0][0], - ...localDefinitions, - ...baseSuggestions[0].slice(1), - ], // channels + [channelAuto, ...localDefinitions, ...baseSuggestions[0].slice(1)], // channels [autoCommand, ...localDefinitions, ...baseSuggestions[1]], // commands param1.length > 0 ? [param1[0], ...localDefinitions, ...param1.slice(1)] @@ -328,7 +344,7 @@ ]; } - $: if ($event || mode !== undefined) { + $: if ($event || mode !== undefined || $channelStore) { renderSuggestions(); } diff --git a/src/renderer/config-blocks/MidiFourteenBit.svelte b/src/renderer/config-blocks/MidiFourteenBit.svelte index 161a3db3e..67f3342dd 100644 --- a/src/renderer/config-blocks/MidiFourteenBit.svelte +++ b/src/renderer/config-blocks/MidiFourteenBit.svelte @@ -171,7 +171,7 @@ suggestions[0] = [ { value: "-1", - info: `Auto (${Grid.Auto.getMidi(action, Grid.Auto.Value.MIDI_CHANNEL)})`, + info: `Auto (${Grid.Auto.getMidiChannelLabel(action)})`, key: "auto", }, ..._suggestions[0], diff --git a/src/renderer/config-blocks/headers/MidiFace.svelte b/src/renderer/config-blocks/headers/MidiFace.svelte index ea9be7c78..b53152990 100644 --- a/src/renderer/config-blocks/headers/MidiFace.svelte +++ b/src/renderer/config-blocks/headers/MidiFace.svelte @@ -1,7 +1,14 @@