From 50c84a973caea3e45bb008012229ae4624a00ac3 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 16 Aug 2022 22:31:04 +0200 Subject: [PATCH] object support for midi #192 --- packages/core/pattern.mjs | 8 ++-- packages/core/util.mjs | 7 ++++ packages/midi/midi.mjs | 81 ++++++++++++++++++++------------------- 3 files changed, 51 insertions(+), 45 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 48e52643a..bfa99b2fa 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -1024,18 +1024,19 @@ export class Pattern { } // sets absolute duration of haps - _duration(value) { + /* _duration(value) { return this.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(value))); } // sets hap relative duration of haps _legato(value) { return this.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value)))); - } + } _velocity(velocity) { return this._withContext((context) => ({ ...context, velocity: (context.velocity || 1) * velocity })); } + */ // move this to controls? (speed and unit are controls) _loopAt(factor, cps = 1) { @@ -1171,18 +1172,15 @@ Pattern.prototype.patternified = [ 'chop', 'color', 'cpm', - 'duration', 'early', 'fast', 'jux', 'late', - 'legato', 'linger', 'ply', 'segment', 'striate', 'slow', - 'velocity', ]; // methods that create patterns, which are added to patternified Pattern methods Pattern.prototype.factories = { diff --git a/packages/core/util.mjs b/packages/core/util.mjs index 728fa92bc..0baea3372 100644 --- a/packages/core/util.mjs +++ b/packages/core/util.mjs @@ -124,3 +124,10 @@ export function curry(func, overload) { } return fn; } + +export function objectify(value) { + if (typeof value === 'object' && !Array.isArray(value)) { + return value; // do nothing if its already an object + } + return { value }; +} diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index a9cf4df17..52bfae170 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -4,10 +4,8 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { isNote } from 'tone'; import _WebMidi from 'webmidi'; -import { Pattern, isPattern } from '@strudel.cycles/core'; -import { Tone } from '@strudel.cycles/tone'; +import { Pattern, isPattern, isNote, getPlayableNoteValue, objectify } from '@strudel.cycles/core'; // if you use WebMidi from outside of this package, make sure to import that instance: export const WebMidi = _WebMidi; @@ -39,46 +37,49 @@ Pattern.prototype.midi = function (output, channel = 1) { }')`, ); } - return this._withHap((hap) => { - // const onTrigger = (time: number, hap: any) => { - const onTrigger = (time, hap) => { - let note = hap.value; - const velocity = hap.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.`); - } - let device; - if (typeof output === 'number') { - device = WebMidi.outputs[output]; - } else if (typeof output === 'string') { - device = outputByName(output); - } else { - device = WebMidi.outputs[0]; - } - if (!device) { - throw new Error( - `🔌 MIDI device '${output ? output : ''}' not found. Use one of ${WebMidi.outputs - .map((o) => `'${o.name}'`) - .join(' | ')}`, - ); - } - // console.log('midi', value, output); - const timingOffset = WebMidi.time - Tone.getContext().currentTime * 1000; - time = time * 1000 + timingOffset; - // const inMs = '+' + (time - Tone.getContext().currentTime) * 1000; - // await enableWebMidi() + return this.onTrigger((time, hap, currentTime) => { + let note = getPlayableNoteValue(hap); + const { velocity, nrpnn, nrpv, ccn, ccv, legato, duration: durationValue } = objectify(hap.value); + 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.`); + } + let device; + if (typeof output === 'number') { + device = WebMidi.outputs[output]; + } else if (typeof output === 'string') { + device = outputByName(output); + } else { + device = WebMidi.outputs[0]; + } + if (!device) { + throw new Error( + `🔌 MIDI device '${output ? output : ''}' not found. Use one of ${WebMidi.outputs + .map((o) => `'${o.name}'`) + .join(' | ')}`, + ); + } + // console.log('midi', value, output); + const deadline = (time - currentTime) * 1000; + time = WebMidi.time + deadline; + /* const timingOffset = WebMidi.time - currentTime * 1000; + time = time * 1000 + timingOffset; */ + const duration = (durationValue ?? hap.duration.valueOf()) * (legato ?? 1) * 1000 - 5; + + if (note) { device.playNote(note, channel, { time, - duration: hap.duration.valueOf() * 1000 - 5, + duration, velocity, }); - }; - return hap.setContext({ ...hap.context, onTrigger }); + } + if (ccn && ccv) { + device.sendControlChange(ccn, ccv, channel, { time }); + } }); };