mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-26 07:16:25 -04:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 947dd76823 | |||
| 28eedae730 | |||
| e075095e8b | |||
| 84e3f12171 | |||
| 260c6e0783 | |||
| bc3481822c |
@@ -2320,6 +2320,23 @@ export const { miditouch } = registerControl('miditouch');
|
|||||||
// TODO: what is this?
|
// TODO: what is this?
|
||||||
export const { polyTouch } = registerControl('polyTouch');
|
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) => {
|
export const getControlName = (alias) => {
|
||||||
if (controlAlias.has(alias)) {
|
if (controlAlias.has(alias)) {
|
||||||
return controlAlias.get(alias);
|
return controlAlias.get(alias);
|
||||||
|
|||||||
@@ -8,6 +8,14 @@ This package adds midi functionality to strudel Patterns.
|
|||||||
npm i @strudel/midi --save
|
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
|
## Available Controls
|
||||||
|
|
||||||
The following MIDI controls are available:
|
The following MIDI controls are available:
|
||||||
|
|||||||
+40
-7
@@ -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 * as _WebMidi from 'webmidi';
|
||||||
import { Pattern, getEventOffsetMs, isPattern, logger, ref } from '@strudel/core';
|
import { Pattern, getEventOffsetMs, isPattern, logger, ref } from '@strudel/core';
|
||||||
import { noteToMidi, getControlName } from '@strudel/core';
|
import { noteToMidi, hasControlName, getControlName, registerControl } from '@strudel/core';
|
||||||
import { Note } from 'webmidi';
|
import { Note } from 'webmidi';
|
||||||
|
|
||||||
// if you use WebMidi from outside of this package, make sure to import that instance:
|
// if you use WebMidi from outside of this package, make sure to import that instance:
|
||||||
@@ -100,10 +100,34 @@ export const midicontrolMap = new Map();
|
|||||||
function unifyMapping(mapping) {
|
function unifyMapping(mapping) {
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
Object.entries(mapping).map(([key, mapping]) => {
|
Object.entries(mapping).map(([key, mapping]) => {
|
||||||
|
// Convert number to object with ccn property
|
||||||
if (typeof mapping === 'number') {
|
if (typeof mapping === 'number') {
|
||||||
mapping = { ccn: mapping };
|
mapping = { ccn: mapping };
|
||||||
}
|
}
|
||||||
return [getControlName(key), mapping];
|
|
||||||
|
// Get the non-aliased control name from the key
|
||||||
|
const controlName = getControlName(key);
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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];
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -162,7 +186,14 @@ export async function midimaps(map) {
|
|||||||
map = await loadCache[map];
|
map = await loadCache[map];
|
||||||
}
|
}
|
||||||
if (typeof map === 'object') {
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,18 +355,18 @@ Pattern.prototype.midi = function (midiport, options = {}) {
|
|||||||
const device = getDevice(midiConfig.midiport, outputs);
|
const device = getDevice(midiConfig.midiport, outputs);
|
||||||
const otherOutputs = outputs.filter((o) => o.name !== device.name);
|
const otherOutputs = outputs.filter((o) => o.name !== device.name);
|
||||||
logger(
|
logger(
|
||||||
`Midi enabled! Using "${device.name}". ${
|
`[midi] Midi enabled! Using "${device.name}". ${
|
||||||
otherOutputs?.length ? `Also available: ${getMidiDeviceNamesString(otherOutputs)}` : ''
|
otherOutputs?.length ? `Also available: ${getMidiDeviceNamesString(otherOutputs)}` : ''
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onDisconnected: ({ outputs }) =>
|
onDisconnected: ({ outputs }) =>
|
||||||
logger(`Midi device disconnected! Available: ${getMidiDeviceNamesString(outputs)}`),
|
logger(`[midi] Midi device disconnected! Available: ${getMidiDeviceNamesString(outputs)}`),
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.onTrigger((hap, currentTime, cps, targetTime) => {
|
return this.onTrigger((hap, currentTime, cps, targetTime) => {
|
||||||
if (!WebMidi.enabled) {
|
if (!WebMidi.enabled) {
|
||||||
logger('Midi not enabled');
|
logger('[midi] Midi not enabled');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hap.ensureObjectValue();
|
hap.ensureObjectValue();
|
||||||
@@ -383,7 +414,9 @@ Pattern.prototype.midi = function (midiport, options = {}) {
|
|||||||
ccs.forEach(({ ccn, ccv }) => sendCC(ccn, ccv, device, midichan, timeOffsetString));
|
ccs.forEach(({ ccn, ccv }) => sendCC(ccn, ccv, device, midichan, timeOffsetString));
|
||||||
} else if (midimap !== 'default') {
|
} else if (midimap !== 'default') {
|
||||||
// Add warning when a non-existent midimap is specified
|
// 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
|
// Handle note
|
||||||
|
|||||||
Reference in New Issue
Block a user