From 0b1bc74c3bd7ac85695df3209e66abd21aa3f29e Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 2 Feb 2025 22:01:53 +0100 Subject: [PATCH 01/11] feat: basic midimap handling --- packages/core/controls.mjs | 1 + packages/midi/midi.mjs | 70 ++++++++++++++++++++++++++++++++------ 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 3d3530d5..daf13328 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1514,6 +1514,7 @@ export const { binshift } = registerControl('binshift'); export const { hbrick } = registerControl('hbrick'); export const { lbrick } = registerControl('lbrick'); export const { midichan } = registerControl('midichan'); +export const { midimap } = registerControl('midimap'); export const { control } = registerControl('control'); export const { ccn } = registerControl('ccn'); export const { ccv } = registerControl('ccv'); diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 32e66f6c..8ad611fe 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 } from '@strudel/core'; +import { noteToMidi, getControlName } from '@strudel/core'; import { Note } from 'webmidi'; // if you use WebMidi from outside of this package, make sure to import that instance: export const { WebMidi } = _WebMidi; @@ -89,6 +89,57 @@ if (typeof window !== 'undefined') { }); } +// registry for midi mappings, converting control names to cc messages +export const midiMappings = new Map(); + +// takes midimap and converts each control key to the main control name +function unifyMapping(mapping) { + return Object.fromEntries(Object.entries(mapping).map(([key, mapping]) => [getControlName(key), mapping])); +} +// adds a midimap to the registry +export function addMidimap(name, mapping) { + midiMappings.set(name, unifyMapping(mapping)); +} +// adds multiple midimaps to the registry +export function midimaps(map) { + if (typeof map === 'object') { + Object.entries(midiMappings).forEach(([name, mapping]) => addMidimap(name, mapping)); + } +} + +// normalizes the given value from the given range and exponent +function normalize(value = 0, min = 0, max = 1, exp = 1) { + if (min === max) { + throw new Error('min and max cannot be the same value'); + } + let normalized = (value - min) / (max - min); + normalized = Math.min(1, Math.max(0, normalized)); + return Math.pow(normalized, exp); +} +function mapCC(mapping, value) { + const ccs = Array.isArray(value.cc) ? value.cc : []; + const matches = Object.entries(value).filter(([key]) => !!mapping[getControlName(key)]); + matches.forEach((match) => { + const control = match[0]; + const { ccn, min = 0, max = 1, exp = 1 } = mapping[control]; + const ccv = normalize(value[control], min, max, exp); + ccs.push({ ccn, ccv }); + }); + return ccs; +} + +// sends a cc message to the given device on the given channel +function sendCC(ccn, ccv, device, midichan, timeOffsetString) { + if (typeof ccv !== 'number' || ccv < 0 || ccv > 1) { + throw new Error('expected ccv to be a number between 0 and 1'); + } + if (!['string', 'number'].includes(typeof ccn)) { + throw new Error('expected ccn to be a number or a string'); + } + const scaled = Math.round(ccv * 127); + device.sendControlChange(ccn, scaled, midichan, { time: timeOffsetString }); +} + Pattern.prototype.midi = function (output) { if (isPattern(output)) { throw new Error( @@ -124,10 +175,16 @@ Pattern.prototype.midi = function (output) { // passing a string with a +num into the webmidi api adds an offset to the current time https://webmidijs.org/api/classes/Output const timeOffsetString = `+${getEventOffsetMs(targetTime, currentTime) + latencyMs}`; // destructure value - let { note, nrpnn, nrpv, ccn, ccv, midichan = 1, midicmd, gain = 1, velocity = 0.9 } = hap.value; + let { note, nrpnn, nrpv, ccn, ccv, midichan = 1, midicmd, gain = 1, velocity = 0.9, midimap } = hap.value; velocity = gain * velocity; + // if midimap is set, send a cc messages from defined controls + if (!!midimap && midiMappings.has(midimap)) { + const ccs = mapCC(midiMappings.get(midimap), hap.value); + ccs.forEach(({ ccn, ccv }) => sendCC(ccn, ccv, device, midichan, timeOffsetString)); + } + // note off messages will often a few ms arrive late, try to prevent glitching by subtracting from the duration length const duration = (hap.duration.valueOf() / cps) * 1000 - 10; if (note != null) { @@ -138,14 +195,7 @@ Pattern.prototype.midi = function (output) { }); } if (ccv !== undefined && ccn !== undefined) { - if (typeof ccv !== 'number' || ccv < 0 || ccv > 1) { - throw new Error('expected ccv to be a number between 0 and 1'); - } - if (!['string', 'number'].includes(typeof ccn)) { - throw new Error('expected ccn to be a number or a string'); - } - const scaled = Math.round(ccv * 127); - device.sendControlChange(ccn, scaled, midichan, { time: timeOffsetString }); + sendCC(ccn, ccv, device, midichan, timeOffsetString); } if (hap.whole.begin + 0 === 0) { // we need to start here because we have the timing info From 95b1548e5fdf0a7d9237bc5d4a937b31bb9e9d3b Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 2 Feb 2025 22:16:11 +0100 Subject: [PATCH 02/11] simplify --- packages/midi/midi.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 8ad611fe..9425beac 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -117,7 +117,7 @@ function normalize(value = 0, min = 0, max = 1, exp = 1) { return Math.pow(normalized, exp); } function mapCC(mapping, value) { - const ccs = Array.isArray(value.cc) ? value.cc : []; + const ccs = []; const matches = Object.entries(value).filter(([key]) => !!mapping[getControlName(key)]); matches.forEach((match) => { const control = match[0]; From b87b2aff9a06aa3f54e0c1c7ce65ae27f18f21ce Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 2 Feb 2025 22:18:56 +0100 Subject: [PATCH 03/11] simplify more --- packages/midi/midi.mjs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 9425beac..240e3844 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -117,15 +117,13 @@ function normalize(value = 0, min = 0, max = 1, exp = 1) { return Math.pow(normalized, exp); } function mapCC(mapping, value) { - const ccs = []; - const matches = Object.entries(value).filter(([key]) => !!mapping[getControlName(key)]); - matches.forEach((match) => { - const control = match[0]; - const { ccn, min = 0, max = 1, exp = 1 } = mapping[control]; - const ccv = normalize(value[control], min, max, exp); - ccs.push({ ccn, ccv }); - }); - return ccs; + return Object.keys(value) + .filter((key) => !!mapping[getControlName(key)]) + .map((key) => { + const { ccn, min = 0, max = 1, exp = 1 } = mapping[key]; + const ccv = normalize(value[key], min, max, exp); + return { ccn, ccv }; + }); } // sends a cc message to the given device on the given channel From b74d5becd5db8c42d5eb962a44d43d94fabdddef Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 2 Feb 2025 23:37:20 +0100 Subject: [PATCH 04/11] allow passing ccn numbers directly to midimapped control name --- packages/midi/midi.mjs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 240e3844..7da20498 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -94,7 +94,14 @@ export const midiMappings = new Map(); // takes midimap and converts each control key to the main control name function unifyMapping(mapping) { - return Object.fromEntries(Object.entries(mapping).map(([key, mapping]) => [getControlName(key), mapping])); + return Object.fromEntries( + Object.entries(mapping).map(([key, mapping]) => { + if (typeof mapping === 'number') { + mapping = { ccn: mapping }; + } + return [getControlName(key), mapping]; + }), + ); } // adds a midimap to the registry export function addMidimap(name, mapping) { From d86df33b8cdae139d85c88ba630dac2c811a54e6 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 2 Feb 2025 23:49:17 +0100 Subject: [PATCH 05/11] allow loading midimap from url --- packages/midi/midi.mjs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 7da20498..ddea2ff1 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -107,10 +107,34 @@ function unifyMapping(mapping) { export function addMidimap(name, mapping) { midiMappings.set(name, unifyMapping(mapping)); } + +function githubPath(base, subpath = '') { + if (!base.startsWith('github:')) { + throw new Error('expected "github:" at the start of pseudoUrl'); + } + let [_, path] = base.split('github:'); + path = path.endsWith('/') ? path.slice(0, -1) : path; + if (path.split('/').length === 2) { + // assume main as default branch if none set + path += '/main'; + } + return `https://raw.githubusercontent.com/${path}/${subpath}`; +} + +let loadCache = {}; // adds multiple midimaps to the registry -export function midimaps(map) { +export async function midimaps(map) { + if (typeof map === 'string') { + if (map.startsWith('github:')) { + map = githubPath(map, 'midimap.json'); + } + if (!loadCache[map]) { + loadCache[map] = fetch(map).then((res) => res.json()); + } + map = await loadCache[map]; + } if (typeof map === 'object') { - Object.entries(midiMappings).forEach(([name, mapping]) => addMidimap(name, mapping)); + Object.entries(map).forEach(([name, mapping]) => addMidimap(name, mapping)); } } From c810a02a79627e5caf6e83ebefcb183872b689d1 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 3 Feb 2025 21:55:57 +0100 Subject: [PATCH 06/11] - add defaultmidimap function - add midisounds function - add midiport control --- packages/core/controls.mjs | 1 + packages/midi/midi.mjs | 66 +++++++++++++++++++++++++++++++------- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index daf13328..1f2d3e70 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1515,6 +1515,7 @@ export const { hbrick } = registerControl('hbrick'); export const { lbrick } = registerControl('lbrick'); export const { midichan } = registerControl('midichan'); export const { midimap } = registerControl('midimap'); +export const { midiport } = registerControl('midiport'); export const { control } = registerControl('control'); export const { ccn } = registerControl('ccn'); export const { ccv } = registerControl('ccv'); diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index ddea2ff1..b7650c3b 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -90,7 +90,7 @@ if (typeof window !== 'undefined') { } // registry for midi mappings, converting control names to cc messages -export const midiMappings = new Map(); +export const midicontrolMap = new Map(); // takes midimap and converts each control key to the main control name function unifyMapping(mapping) { @@ -103,10 +103,6 @@ function unifyMapping(mapping) { }), ); } -// adds a midimap to the registry -export function addMidimap(name, mapping) { - midiMappings.set(name, unifyMapping(mapping)); -} function githubPath(base, subpath = '') { if (!base.startsWith('github:')) { @@ -121,6 +117,11 @@ function githubPath(base, subpath = '') { return `https://raw.githubusercontent.com/${path}/${subpath}`; } +// configures the default midimap, which is used when no "midimap" port is set +export function defaultmidimap(mapping) { + midicontrolMap.set('default', unifyMapping(mapping)); +} + let loadCache = {}; // adds multiple midimaps to the registry export async function midimaps(map) { @@ -134,10 +135,34 @@ export async function midimaps(map) { map = await loadCache[map]; } if (typeof map === 'object') { - Object.entries(map).forEach(([name, mapping]) => addMidimap(name, mapping)); + Object.entries(map).forEach(([name, mapping]) => midicontrolMap.set(name, unifyMapping(mapping))); } } +// registry for midi sounds, converting sound names to controls +export const midisoundMap = new Map(); +// adds multiple midimaps to the registry +export async function midisounds(map) { + if (typeof map === 'object') { + Object.entries(map).forEach(([name, mapping]) => midisoundMap.set(name, mapping)); + } +} + +function applyMidisounds(hapValue) { + const { s } = hapValue; + if (!midisoundMap.has(s)) { + return; + } + let controls = midisoundMap.get(hapValue.s); + if (Array.isArray(controls)) { + controls = controls[hapValue.n || 0]; + } + if (typeof controls === 'string') { + controls = { note: controls }; + } + Object.assign(hapValue, controls); +} + // normalizes the given value from the given range and exponent function normalize(value = 0, min = 0, max = 1, exp = 1) { if (min === max) { @@ -197,20 +222,39 @@ Pattern.prototype.midi = function (output) { console.log('not enabled'); return; } - const device = getDevice(output, WebMidi.outputs); hap.ensureObjectValue(); //magic number to get audio engine to line up, can probably be calculated somehow const latencyMs = 34; // passing a string with a +num into the webmidi api adds an offset to the current time https://webmidijs.org/api/classes/Output const timeOffsetString = `+${getEventOffsetMs(targetTime, currentTime) + latencyMs}`; + // convert s to midisounds preset (if set) + applyMidisounds(hap.value); + // destructure value - let { note, nrpnn, nrpv, ccn, ccv, midichan = 1, midicmd, gain = 1, velocity = 0.9, midimap } = hap.value; + let { + note, + ccn, + ccv, + midichan = 1, + midicmd, + gain = 1, + velocity = 0.9, + midimap = 'default', + midiport = output, + } = hap.value; + + const device = getDevice(midiport, WebMidi.outputs); + if (!device) { + logger( + `[midi] midiport "${midiport}" not found! available: ${WebMidi.outputs.map((output) => `'${output.name}'`).join(', ')}`, + ); + return; + } velocity = gain * velocity; - // if midimap is set, send a cc messages from defined controls - if (!!midimap && midiMappings.has(midimap)) { - const ccs = mapCC(midiMappings.get(midimap), hap.value); + if (midicontrolMap.has(midimap)) { + const ccs = mapCC(midicontrolMap.get(midimap), hap.value); ccs.forEach(({ ccn, ccv }) => sendCC(ccn, ccv, device, midichan, timeOffsetString)); } From e99f229b573a1e963fd2716ebaae6d9c21e58e5e Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 3 Feb 2025 22:37:59 +0100 Subject: [PATCH 07/11] doc: defaultmidimap + midimaps --- packages/midi/midi.mjs | 27 ++++++++++++++++++++++-- website/src/pages/learn/input-output.mdx | 10 +++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index b7650c3b..fe0829c9 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -117,13 +117,36 @@ function githubPath(base, subpath = '') { return `https://raw.githubusercontent.com/${path}/${subpath}`; } -// configures the default midimap, which is used when no "midimap" port is set +/** + * configures the default midimap, which is used when no "midimap" port is set + * @example + * defaultmidimap({ lpf: 74 }) + * $: note("c a f e").midi(); + * $: lpf(sine.slow(4).segment(16)).midi(); + */ export function defaultmidimap(mapping) { midicontrolMap.set('default', unifyMapping(mapping)); } let loadCache = {}; -// adds multiple midimaps to the registry + +/** + * Adds midimaps to the registry. Inside each midimap, control names (e.g. lpf) are mapped to cc numbers. + * @example + * midimaps({ mymap: { lpf: 74 } }) + * $: note("c a f e") + * .lpf(sine.slow(4)) + * .midimap('mymap') + * .midi() + * @example + * midimaps({ mymap: { + * lpf: { ccn: 74, min: 0, max: 20000, exp: 0.5 } + * }}) + * $: note("c a f e") + * .lpf(sine.slow(2).range(400,2000)) + * .midimap('mymap') + * .midi() + */ export async function midimaps(map) { if (typeof map === 'string') { if (map.startsWith('github:')) { diff --git a/website/src/pages/learn/input-output.mdx b/website/src/pages/learn/input-output.mdx index 14e46ba7..cec06a34 100644 --- a/website/src/pages/learn/input-output.mdx +++ b/website/src/pages/learn/input-output.mdx @@ -46,6 +46,16 @@ But you can also control cc messages separately like this: $: ccv(sine.segment(16).slow(4)).ccn(74).midi()`} /> +Instead of setting `ccn` and `ccv` directly, you can also create mappings with `midimaps`: + +## midimaps + + + +## defaultmidimap + + + # OSC/SuperDirt/StrudelDirt In TidalCycles, sound is usually generated using [SuperDirt](https://github.com/musikinformatik/SuperDirt/), which runs inside SuperCollider. Strudel also supports using SuperDirt, although it requires installing some additional software. From dc8becc610560ca8fa40ca0d8751e67521319244 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 3 Feb 2025 22:44:56 +0100 Subject: [PATCH 08/11] chore: delete old files --- .eslintignore | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index cbfa8917..00000000 --- a/.eslintignore +++ /dev/null @@ -1,26 +0,0 @@ -krill-parser.js -krill.pegjs -.eslintrc.json -server.js -tidal-sniffer.js -*.jsx -tunejs.js -out/** -postcss.config.js -postcss.config.cjs -tailwind.config.js -tailwind.config.cjs -vite.config.js -/**/dist/**/* -!**/*.mjs -**/*.tsx -**/*.ts -**/*.json -**/dev-dist -**/dist -/src-tauri/target/**/* -reverbGen.mjs -hydra.mjs -jsdoc-synonyms.js -packages/hs2js/src/hs2js.mjs -samples \ No newline at end of file From 88fb0b3b75224acfe629bf494e802d110aff9385 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 3 Feb 2025 22:45:19 +0100 Subject: [PATCH 09/11] fix: pnpm check --- packages/midi/midi.mjs | 2 +- test/examples.test.mjs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index fe0829c9..2979684a 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -133,7 +133,7 @@ let loadCache = {}; /** * Adds midimaps to the registry. Inside each midimap, control names (e.g. lpf) are mapped to cc numbers. * @example - * midimaps({ mymap: { lpf: 74 } }) + * midimaps({ mymap: { lpf: 74 } }) * $: note("c a f e") * .lpf(sine.slow(4)) * .midimap('mymap') diff --git a/test/examples.test.mjs b/test/examples.test.mjs index 997c1cf3..896a02f8 100644 --- a/test/examples.test.mjs +++ b/test/examples.test.mjs @@ -18,6 +18,8 @@ const skippedExamples = [ 'accelerationZ', 'accelerationY', 'accelerationX', + 'defaultmidimap', + 'midimaps', ]; describe('runs examples', () => { From 848edb1f693e3df76a61cafe582745f3c59e6bb2 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 3 Feb 2025 22:56:51 +0100 Subject: [PATCH 10/11] doc: midisounds --- packages/midi/midi.mjs | 12 +++++++++++- test/examples.test.mjs | 1 + website/src/pages/learn/input-output.mdx | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 2979684a..70df93e8 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -164,7 +164,17 @@ export async function midimaps(map) { // registry for midi sounds, converting sound names to controls export const midisoundMap = new Map(); -// adds multiple midimaps to the registry + +/** + * Maps a sound name to a set of controls: + * @example + * midisounds({ bd: { note: 'c2' } }) + * $: s("bd").midi() + * @example + * // notes can be set directly to simplify typical midi drum kit mappings + * midisounds({ bd: 'c2', rim: 'c#2' }) + * $: s("bd rim").midi() + **/ export async function midisounds(map) { if (typeof map === 'object') { Object.entries(map).forEach(([name, mapping]) => midisoundMap.set(name, mapping)); diff --git a/test/examples.test.mjs b/test/examples.test.mjs index 896a02f8..ccce6c2a 100644 --- a/test/examples.test.mjs +++ b/test/examples.test.mjs @@ -20,6 +20,7 @@ const skippedExamples = [ 'accelerationX', 'defaultmidimap', 'midimaps', + 'midisounds', ]; describe('runs examples', () => { diff --git a/website/src/pages/learn/input-output.mdx b/website/src/pages/learn/input-output.mdx index cec06a34..666a39f7 100644 --- a/website/src/pages/learn/input-output.mdx +++ b/website/src/pages/learn/input-output.mdx @@ -56,6 +56,10 @@ Instead of setting `ccn` and `ccv` directly, you can also create mappings with ` +## midisounds + + + # OSC/SuperDirt/StrudelDirt In TidalCycles, sound is usually generated using [SuperDirt](https://github.com/musikinformatik/SuperDirt/), which runs inside SuperCollider. Strudel also supports using SuperDirt, although it requires installing some additional software. From efd40716375a696190ceadd959d004cfdfc551fc Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 6 Feb 2025 13:51:53 +0100 Subject: [PATCH 11/11] remove midisounds for now --- packages/midi/midi.mjs | 33 ------------------------ test/examples.test.mjs | 1 - website/src/pages/learn/input-output.mdx | 4 --- 3 files changed, 38 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 70df93e8..2c1d1f20 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -165,37 +165,6 @@ export async function midimaps(map) { // registry for midi sounds, converting sound names to controls export const midisoundMap = new Map(); -/** - * Maps a sound name to a set of controls: - * @example - * midisounds({ bd: { note: 'c2' } }) - * $: s("bd").midi() - * @example - * // notes can be set directly to simplify typical midi drum kit mappings - * midisounds({ bd: 'c2', rim: 'c#2' }) - * $: s("bd rim").midi() - **/ -export async function midisounds(map) { - if (typeof map === 'object') { - Object.entries(map).forEach(([name, mapping]) => midisoundMap.set(name, mapping)); - } -} - -function applyMidisounds(hapValue) { - const { s } = hapValue; - if (!midisoundMap.has(s)) { - return; - } - let controls = midisoundMap.get(hapValue.s); - if (Array.isArray(controls)) { - controls = controls[hapValue.n || 0]; - } - if (typeof controls === 'string') { - controls = { note: controls }; - } - Object.assign(hapValue, controls); -} - // normalizes the given value from the given range and exponent function normalize(value = 0, min = 0, max = 1, exp = 1) { if (min === max) { @@ -260,8 +229,6 @@ Pattern.prototype.midi = function (output) { const latencyMs = 34; // passing a string with a +num into the webmidi api adds an offset to the current time https://webmidijs.org/api/classes/Output const timeOffsetString = `+${getEventOffsetMs(targetTime, currentTime) + latencyMs}`; - // convert s to midisounds preset (if set) - applyMidisounds(hap.value); // destructure value let { diff --git a/test/examples.test.mjs b/test/examples.test.mjs index ccce6c2a..896a02f8 100644 --- a/test/examples.test.mjs +++ b/test/examples.test.mjs @@ -20,7 +20,6 @@ const skippedExamples = [ 'accelerationX', 'defaultmidimap', 'midimaps', - 'midisounds', ]; describe('runs examples', () => { diff --git a/website/src/pages/learn/input-output.mdx b/website/src/pages/learn/input-output.mdx index 666a39f7..cec06a34 100644 --- a/website/src/pages/learn/input-output.mdx +++ b/website/src/pages/learn/input-output.mdx @@ -56,10 +56,6 @@ Instead of setting `ccn` and `ccv` directly, you can also create mappings with ` -## midisounds - - - # OSC/SuperDirt/StrudelDirt In TidalCycles, sound is usually generated using [SuperDirt](https://github.com/musikinformatik/SuperDirt/), which runs inside SuperCollider. Strudel also supports using SuperDirt, although it requires installing some additional software.