From bc3481822caa557831eaa7c87a30969f4c224390 Mon Sep 17 00:00:00 2001 From: nkymut Date: Sun, 9 Feb 2025 16:38:15 +0800 Subject: [PATCH 1/3] allow midimaps to register arbitary controlnames --- packages/midi/midi.mjs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 8de6c447f..c46ff0a30 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th import * as _WebMidi from 'webmidi'; import { Pattern, getEventOffsetMs, isPattern, logger, ref } from '@strudel/core'; -import { noteToMidi, getControlName } from '@strudel/core'; +import { noteToMidi, getControlName, registerControl } from '@strudel/core'; import { Note } from 'webmidi'; // if you use WebMidi from outside of this package, make sure to import that instance: @@ -100,10 +100,23 @@ export const midicontrolMap = new Map(); function unifyMapping(mapping) { return Object.fromEntries( Object.entries(mapping).map(([key, mapping]) => { + // Convert number to object with ccn property if (typeof mapping === 'number') { mapping = { ccn: mapping }; } - return [getControlName(key), mapping]; + + const controlName = getControlName(key); + + // Register the control in the midicontrolMap if it doesn't exist in + if (!midicontrolMap.has(controlName)) { + try { + registerControl(controlName); + } catch (err) { + logger.error(`Failed to register control '${controlName}': ${err.message}`); + throw err; + } + } + return [controlName, mapping]; }), ); } From e075095e8bef4a28125321f1ff4f42705ae3db29 Mon Sep 17 00:00:00 2001 From: nkymut Date: Sun, 18 May 2025 14:48:53 +0800 Subject: [PATCH 2/3] Add warning when midimap overrides existing Strudel API --- packages/core/controls.mjs | 17 +++++++++++++++++ packages/midi/midi.mjs | 38 +++++++++++++++++++++++++++++--------- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index a6c650e03..589af709f 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1755,6 +1755,23 @@ export const { miditouch } = registerControl('miditouch'); // TODO: what is this? export const { polyTouch } = registerControl('polyTouch'); +/** + * Checks if a control name exists in the controlAlias map. + * @name hasControlName + * @param {string} alias The control name to check + * @returns {boolean} True if the control name exists, false otherwise + */ +export const hasControlName = (alias) => { + // Check if the name exists as a key (alias) or value (main control name) + return controlAlias.has(alias) || Array.from(controlAlias.values()).includes(alias); +}; + +/** + * Gets the control name from the controlAlias map. + * @name getControlName + * @param {string} alias The control name to get + * @returns {string} The control name + */ export const getControlName = (alias) => { if (controlAlias.has(alias)) { return controlAlias.get(alias); diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 18430e916..527f87238 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th import * as _WebMidi from 'webmidi'; import { Pattern, getEventOffsetMs, isPattern, logger, ref } from '@strudel/core'; -import { noteToMidi, getControlName, registerControl } from '@strudel/core'; +import { noteToMidi, hasControlName, getControlName, registerControl } from '@strudel/core'; import { Note } from 'webmidi'; // if you use WebMidi from outside of this package, make sure to import that instance: @@ -105,16 +105,27 @@ function unifyMapping(mapping) { mapping = { ccn: mapping }; } + // Get the non-aliased control name from the key const controlName = getControlName(key); - // Register the control in the midicontrolMap if it doesn't exist in + // Check if the key or controlName already exists in the controlAlias map + if (hasControlName(key) || hasControlName(controlName)) { + // Show warning and carry on. + logger(`[midimap] '[${key}, ${controlName}]' overwrites a Strudel API.`); + + // Throw error to stop the music + //throw new Error(`[midimap] '${key}' overwrites a Strudel API.`); + } + + // Register the control in the midicontrolMap if it doesn't exist if (!midicontrolMap.has(controlName)) { try { registerControl(controlName); } catch (err) { - logger.error(`Failed to register control '${controlName}': ${err.message}`); - throw err; + throw new Error(`[midimap] Failed to register midimap control '${controlName}': ${err.message}`); } + } else { + logger(`[midimap] '${controlName}' already registered as a midimap control. Skipping registration.`); } return [controlName, mapping]; }), @@ -175,7 +186,14 @@ export async function midimaps(map) { map = await loadCache[map]; } if (typeof map === 'object') { - Object.entries(map).forEach(([name, mapping]) => midicontrolMap.set(name, unifyMapping(mapping))); + Object.entries(map).forEach(([name, mapping]) => { + try { + midicontrolMap.set(name, unifyMapping(mapping)); + } catch (err) { + logger(`[midi] Error setting midimap '${name}': ${err.message}`); + throw err; + } + }); } } @@ -337,18 +355,18 @@ Pattern.prototype.midi = function (midiport, options = {}) { const device = getDevice(midiConfig.midiport, outputs); const otherOutputs = outputs.filter((o) => o.name !== device.name); logger( - `Midi enabled! Using "${device.name}". ${ + `[midi] Midi enabled! Using "${device.name}". ${ otherOutputs?.length ? `Also available: ${getMidiDeviceNamesString(otherOutputs)}` : '' }`, ); }, onDisconnected: ({ outputs }) => - logger(`Midi device disconnected! Available: ${getMidiDeviceNamesString(outputs)}`), + logger(`[midi] Midi device disconnected! Available: ${getMidiDeviceNamesString(outputs)}`), }); return this.onTrigger((time_deprecate, hap, currentTime, cps, targetTime) => { if (!WebMidi.enabled) { - logger('Midi not enabled'); + logger('[midi] Midi not enabled'); return; } hap.ensureObjectValue(); @@ -396,7 +414,9 @@ Pattern.prototype.midi = function (midiport, options = {}) { ccs.forEach(({ ccn, ccv }) => sendCC(ccn, ccv, device, midichan, timeOffsetString)); } else if (midimap !== 'default') { // Add warning when a non-existent midimap is specified - logger(`[midi] midimap "${midimap}" not found! Available maps: ${[...midicontrolMap.keys()].join(', ')}`); + throw new Error( + `[midimap] midimap "${midimap}" not found! Available maps: ${[...midicontrolMap.keys()].join(', ')}`, + ); } // Handle note From 28eedae7303e2081d8d2e62ca206a7ae567cb5d7 Mon Sep 17 00:00:00 2001 From: nkymut Date: Wed, 21 May 2025 00:00:35 +0800 Subject: [PATCH 3/3] docs: add tips for enabling MIDI in Chrome for local development --- packages/midi/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/midi/README.md b/packages/midi/README.md index aa90992da..c126df492 100644 --- a/packages/midi/README.md +++ b/packages/midi/README.md @@ -8,6 +8,14 @@ This package adds midi functionality to strudel Patterns. npm i @strudel/midi --save ``` +## Enabling MIDI for Local Development in Chrome + +1. Open Chrome and navigate to `chrome://flags` +2. Search for "Insecure origins treated as secure" +3. In the text field that appears, add your development origin (e.g., http://localhost:3000) +4. Enable the flag +5. Restart Chrome + ## Available Controls The following MIDI controls are available: