Compare commits

...

5 Commits

Author SHA1 Message Date
Felix Roos 5a42adc366 Merge remote-tracking branch 'origin/main' into objectify-midi 2022-10-29 17:55:53 +02:00
Felix Roos cd3765b8fa Merge remote-tracking branch 'origin/main' into objectify-midi 2022-09-25 21:17:29 +02:00
Felix Roos 787ba9872f Merge remote-tracking branch 'origin/main' into objectify-midi 2022-09-22 19:20:31 +02:00
Felix Roos fd2c7fb4bb allow non notes 2022-08-16 23:12:49 +02:00
Felix Roos 50c84a973c object support for midi #192 2022-08-16 22:31:04 +02:00
3 changed files with 54 additions and 46 deletions
+2 -4
View File
@@ -1287,7 +1287,7 @@ export class Pattern {
} }
// sets absolute duration of haps // sets absolute duration of haps
_duration(value) { /* _duration(value) {
return this.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(value))); return this.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(value)));
} }
@@ -1315,6 +1315,7 @@ export class Pattern {
_velocity(velocity) { _velocity(velocity) {
return this._withContext((context) => ({ ...context, velocity: (context.velocity || 1) * velocity })); return this._withContext((context) => ({ ...context, velocity: (context.velocity || 1) * velocity }));
} }
*/
/** /**
* Makes the sample fit the given number of cycles by changing the speed. * Makes the sample fit the given number of cycles by changing the speed.
@@ -1501,18 +1502,15 @@ Pattern.prototype.patternified = [
'chop', 'chop',
'color', 'color',
'cpm', 'cpm',
'duration',
'early', 'early',
'fast', 'fast',
'jux', 'jux',
'late', 'late',
'legato',
'linger', 'linger',
'ply', 'ply',
'segment', 'segment',
'striate', 'striate',
'slow', 'slow',
'velocity',
]; ];
// aliases // aliases
+7
View File
@@ -130,3 +130,10 @@ export function curry(func, overload) {
} }
return fn; return fn;
} }
export function objectify(value) {
if (typeof value === 'object' && !Array.isArray(value)) {
return value; // do nothing if its already an object
}
return { value };
}
+21 -18
View File
@@ -4,10 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { isNote } from 'tone';
import _WebMidi from 'webmidi'; import _WebMidi from 'webmidi';
import { Pattern, isPattern } from '@strudel.cycles/core'; import { Pattern, isPattern, isNote, getPlayableNoteValue, objectify } from '@strudel.cycles/core';
import { Tone } from '@strudel.cycles/tone';
// 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:
export const WebMidi = _WebMidi; export const WebMidi = _WebMidi;
@@ -32,21 +30,21 @@ const outputByName = (name) => WebMidi.getOutputByName(name);
// Pattern.prototype.midi = function (output: string | number, channel = 1) { // Pattern.prototype.midi = function (output: string | number, channel = 1) {
Pattern.prototype.midi = function (output, channel = 1) { Pattern.prototype.midi = function (output, channel = 1) {
if (isPattern(output?.constructor?.name)) { if (output?.constructor?.name && isPattern(output?.constructor?.name)) {
throw new Error( throw new Error(
`.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${ `.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' WebMidi.outputs?.[0]?.name || 'IAC Driver Bus 1'
}')`, }')`,
); );
} }
return this._withHap((hap) => { return this.onTrigger((time, hap, currentTime) => {
// const onTrigger = (time: number, hap: any) => { let note;
const onTrigger = (time, hap) => { try {
let note = hap.value; note = getPlayableNoteValue(hap);
const velocity = hap.context?.velocity ?? 0.9; } catch (err) {
if (!isNote(note)) { // dont care if no note is found as we can still send cvs
throw new Error('not a note: ' + note);
} }
const { velocity, nrpnn, nrpv, ccn, ccv, legato, duration: durationValue } = objectify(hap.value);
if (!WebMidi.enabled) { if (!WebMidi.enabled) {
throw new Error(`🎹 WebMidi is not enabled. Supported Browsers: https://caniuse.com/?search=webmidi`); throw new Error(`🎹 WebMidi is not enabled. Supported Browsers: https://caniuse.com/?search=webmidi`);
} }
@@ -69,16 +67,21 @@ Pattern.prototype.midi = function (output, channel = 1) {
); );
} }
// console.log('midi', value, output); // console.log('midi', value, output);
const timingOffset = WebMidi.time - Tone.getContext().currentTime * 1000; const deadline = (time - currentTime) * 1000;
time = time * 1000 + timingOffset; time = WebMidi.time + deadline;
// const inMs = '+' + (time - Tone.getContext().currentTime) * 1000; /* const timingOffset = WebMidi.time - currentTime * 1000;
// await enableWebMidi() time = time * 1000 + timingOffset; */
const duration = (durationValue ?? hap.duration.valueOf()) * (legato ?? 1) * 1000 - 5;
if (note) {
device.playNote(note, channel, { device.playNote(note, channel, {
time, time,
duration: hap.duration.valueOf() * 1000 - 5, duration,
velocity, velocity,
}); });
}; }
return hap.setContext({ ...hap.context, onTrigger }); if (ccn && ccv) {
device.sendControlChange(ccn, ccv, channel, { time });
}
}); });
}; };