mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-22 05:05:26 -04:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a42adc366 | |||
| cd3765b8fa | |||
| 787ba9872f | |||
| fd2c7fb4bb | |||
| 50c84a973c |
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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 });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user