strudel/repl/src/midi.ts
2022-03-06 14:39:33 +01:00

102 lines
3.4 KiB
TypeScript

import { useEffect, useState } from 'react';
import { isNote } from 'tone';
import _WebMidi from 'webmidi';
import { Pattern as _Pattern } from '../../strudel.mjs';
import * as Tone from 'tone';
const WebMidi: any = _WebMidi;
const Pattern = _Pattern as any;
export default function enableWebMidi() {
return new Promise((resolve, reject) => {
if (WebMidi.enabled) {
// if already enabled, just resolve WebMidi
resolve(WebMidi);
return;
}
WebMidi.enable((err: any) => {
if (err) {
reject(err);
}
resolve(WebMidi);
});
});
}
const outputByName = (name: string) => WebMidi.getOutputByName(name);
Pattern.prototype.midi = function (output: string, channel = 1) {
if (output?.constructor?.name === 'Pattern') {
throw new Error(
`.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${
WebMidi.outputs?.[0]?.name || 'IAC Driver Bus 1'
}')`
);
}
return this._withEvent((event: any) => {
const onTrigger = (time: number, event: any) => {
let note = event.value;
const velocity = event.context?.velocity ?? 0.9;
if (!isNote(note)) {
throw new Error('not a note: ' + note);
}
if (!WebMidi.enabled) {
throw new Error(`🎹 WebMidi is not enabled. Supported Browsers: https://caniuse.com/?search=webmidi`);
}
if (!WebMidi.outputs.length) {
throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`);
}
const device = output ? outputByName(output) : WebMidi.outputs[0];
if (!device) {
throw new Error(
`🔌 MIDI device '${output ? output : ''}' not found. Use one of ${WebMidi.outputs
.map((o: any) => `'${o.name}'`)
.join(' | ')}`
);
}
// console.log('midi', value, output);
const timingOffset = WebMidi.time - Tone.context.currentTime * 1000;
time = time * 1000 + timingOffset;
// const inMs = '+' + (time - Tone.context.currentTime) * 1000;
// await enableWebMidi()
device.playNote(note, channel, {
time,
duration: event.duration * 1000 - 5,
velocity,
});
};
return event.setContext({ ...event.context, onTrigger });
});
};
export function useWebMidi(props?: any) {
const { ready, connected, disconnected } = props;
const [loading, setLoading] = useState(true);
const [outputs, setOutputs] = useState<any[]>(WebMidi?.outputs || []);
useEffect(() => {
enableWebMidi()
.then(() => {
// Reacting when a new device becomes available
WebMidi.addListener('connected', (e: any) => {
setOutputs([...WebMidi.outputs]);
connected?.(WebMidi, e);
});
// Reacting when a device becomes unavailable
WebMidi.addListener('disconnected', (e: any) => {
setOutputs([...WebMidi.outputs]);
disconnected?.(WebMidi, e);
});
ready?.(WebMidi);
setLoading(false);
})
.catch((err: Error) => {
if (err) {
//throw new Error("Web Midi could not be enabled...");
console.warn('Web Midi could not be enabled..');
return;
}
});
}, [ready, connected, disconnected, outputs]);
const outputByName = (name: string) => WebMidi.getOutputByName(name);
return { loading, outputs, outputByName };
}