From 4cb93ddcded1d14794787de057c1a59d1efbb1c1 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 13 Sep 2023 12:59:51 +0200 Subject: [PATCH 1/7] fix: eval scope --- website/src/repl/Repl.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 173bb4556..eb61a216c 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -33,7 +33,7 @@ const supabase = createClient( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', ); -const modules = [ +let modules = [ import('@strudel.cycles/core'), import('@strudel.cycles/tonal'), import('@strudel.cycles/mini'), @@ -45,13 +45,13 @@ const modules = [ import('@strudel.cycles/csound'), ]; if (isTauri()) { - modules.concat([ + modules = modules.concat([ import('@strudel/desktopbridge/loggerbridge.mjs'), import('@strudel/desktopbridge/midibridge.mjs'), import('@strudel/desktopbridge/oscbridge.mjs'), ]); } else { - modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); + modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); } const modulesLoading = evalScope( From 0a8874180cf5b22c85d8a844c7035623e7230573 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 13 Sep 2023 13:48:33 +0200 Subject: [PATCH 2/7] generalize getDevice + begin midiIn implementation --- packages/midi/midi.mjs | 49 ++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 98509b461..831c3afde 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -8,6 +8,7 @@ import * as _WebMidi from 'webmidi'; import { Pattern, isPattern, logger } from '@strudel.cycles/core'; import { noteToMidi } from '@strudel.cycles/core'; import { Note } from 'webmidi'; +import EventEmitter from 'events'; // if you use WebMidi from outside of this package, make sure to import that instance: export const { WebMidi } = _WebMidi; @@ -15,8 +16,8 @@ function supportsMidi() { return typeof navigator.requestMIDIAccess === 'function'; } -function getMidiDeviceNamesString(outputs) { - return outputs.map((o) => `'${o.name}'`).join(' | '); +function getMidiDeviceNamesString(devices) { + return devices.map((o) => `'${o.name}'`).join(' | '); } export function enableWebMidi(options = {}) { @@ -52,26 +53,24 @@ export function enableWebMidi(options = {}) { }); }); } -// const outputByName = (name: string) => WebMidi.getOutputByName(name); -const outputByName = (name) => WebMidi.getOutputByName(name); -// output?: string | number, outputs: typeof WebMidi.outputs -function getDevice(output, outputs) { - if (!outputs.length) { +function getDevice(indexOrName, devices) { + if (!devices.length) { throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`); } - if (typeof output === 'number') { - return outputs[output]; + if (typeof indexOrName === 'number') { + return devices[indexOrName]; } - if (typeof output === 'string') { - return outputByName(output); + const byName = (name) => devices.find((output) => output.name.includes(name)); + if (typeof indexOrName === 'string') { + return byName(indexOrName); } // attempt to default to first IAC device if none is specified - const IACOutput = outputs.find((output) => output.name.includes('IAC')); - const device = IACOutput ?? outputs[0]; + const IACOutput = byName('IAC'); + const device = IACOutput ?? devices[0]; if (!device) { throw new Error( - `🔌 MIDI device '${output ? output : ''}' not found. Use one of ${getMidiDeviceNamesString(WebMidi.outputs)}`, + `🔌 MIDI device '${device ? device : ''}' not found. Use one of ${getMidiDeviceNamesString(devices)}`, ); } @@ -137,3 +136,25 @@ Pattern.prototype.midi = function (output) { } }); }; + +export async function midiIn(input) { + console.log('midi in...'); + const initial = await enableWebMidi(); // only returns on first init + const device = getDevice(input, WebMidi.inputs); + + if (initial) { + const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name); + logger( + `Midi enabled! Using "${device.name}". ${ + otherInputs?.length ? `Also available: ${getMidiDeviceNamesString(otherInputs)}` : '' + }`, + ); + } + return (fn) => { + device.addListener(EventEmitter.ANY_EVENT, (...args) => { + console.log('event!', args); + fn(args); + }); + return; + }; +} From 0f72729f0de9fedd7e4bdccf8c717746351942c8 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Sep 2023 09:36:06 +0200 Subject: [PATCH 3/7] midi cc input poc --- packages/core/pattern.mjs | 6 ++++++ packages/midi/midi.mjs | 24 ++++++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 7e694f498..d29fde3f3 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -2336,3 +2336,9 @@ export const fit = register('fit', (pat) => export const { loopAtCps, loopatcps } = register(['loopAtCps', 'loopatcps'], function (factor, cps, pat) { return _loopAt(factor, pat, cps); }); + +/** exposes a custom value at query time. basically allows mutating state without evaluation */ +export const ref = (accessor) => + pure(1) + .withValue(() => reify(accessor())) + .innerJoin(); diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 831c3afde..95af837ec 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -8,7 +8,6 @@ import * as _WebMidi from 'webmidi'; import { Pattern, isPattern, logger } from '@strudel.cycles/core'; import { noteToMidi } from '@strudel.cycles/core'; import { Note } from 'webmidi'; -import EventEmitter from 'events'; // if you use WebMidi from outside of this package, make sure to import that instance: export const { WebMidi } = _WebMidi; @@ -137,7 +136,10 @@ Pattern.prototype.midi = function (output) { }); }; -export async function midiIn(input) { +let listeners = {}; +const refs = {}; + +export async function midin(input) { console.log('midi in...'); const initial = await enableWebMidi(); // only returns on first init const device = getDevice(input, WebMidi.inputs); @@ -149,12 +151,18 @@ export async function midiIn(input) { otherInputs?.length ? `Also available: ${getMidiDeviceNamesString(otherInputs)}` : '' }`, ); + refs[input] = {}; } - return (fn) => { - device.addListener(EventEmitter.ANY_EVENT, (...args) => { - console.log('event!', args); - fn(args); - }); - return; + const cc = (cc) => ref(() => refs[input][cc] || 0); + + listeners[input] && device.removeListener('midimessage', listeners[input]); + listeners[input] = (e) => { + const cc = e.dataBytes[0]; + const v = e.dataBytes[1]; + console.log(cc, v); + refs[input][cc] = v / 127; }; + device.addListener('midimessage', listeners[input]); + //return { cc }; + return cc; } From 3a69fd50bb7d0c314dae7f961e7beae1f6c674a1 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Sep 2023 09:39:31 +0200 Subject: [PATCH 4/7] fix: linting errors --- packages/midi/midi.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 95af837ec..bc65497a4 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th */ import * as _WebMidi from 'webmidi'; -import { Pattern, isPattern, logger } from '@strudel.cycles/core'; +import { Pattern, isPattern, logger, ref } from '@strudel.cycles/core'; import { noteToMidi } from '@strudel.cycles/core'; import { Note } from 'webmidi'; // if you use WebMidi from outside of this package, make sure to import that instance: @@ -73,7 +73,7 @@ function getDevice(indexOrName, devices) { ); } - return IACOutput ?? outputs[0]; + return IACOutput ?? devices[0]; } Pattern.prototype.midi = function (output) { From 30d96dcb65fd176ea6c515e9145d4181358de04a Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Sep 2023 16:28:47 +0200 Subject: [PATCH 5/7] remove log --- packages/midi/midi.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index bc65497a4..688b7a9b9 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -159,7 +159,6 @@ export async function midin(input) { listeners[input] = (e) => { const cc = e.dataBytes[0]; const v = e.dataBytes[1]; - console.log(cc, v); refs[input][cc] = v / 127; }; device.addListener('midimessage', listeners[input]); From 6c3a3b9e29123286f686c00851c4ff44a28e142a Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 28 Sep 2023 10:40:44 +0200 Subject: [PATCH 6/7] dedupe --- packages/core/controls.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index d1a9ce7f5..6cac6e540 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1066,7 +1066,6 @@ const generic_params = [ */ ['waveloss'], // TODO: midi effects? - ['midicmd'], ['dur'], // ['modwheel'], ['expression'], From 0dcc55ee167a6dd26cf4a671ae73e326e62ae997 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 28 Sep 2023 10:48:31 +0200 Subject: [PATCH 7/7] prevent error --- packages/midi/midi.mjs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 1d89b6dcb..ab59c2e42 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -167,7 +167,6 @@ let listeners = {}; const refs = {}; export async function midin(input) { - console.log('midi in...'); const initial = await enableWebMidi(); // only returns on first init const device = getDevice(input, WebMidi.inputs); @@ -186,9 +185,8 @@ export async function midin(input) { listeners[input] = (e) => { const cc = e.dataBytes[0]; const v = e.dataBytes[1]; - refs[input][cc] = v / 127; + refs[input] && (refs[input][cc] = v / 127); }; device.addListener('midimessage', listeners[input]); - //return { cc }; return cc; }