mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-23 11:38:37 +00:00
refactor onTrigger
This commit is contained in:
parent
5fc8f10602
commit
b668a2c0d2
@ -32,11 +32,8 @@ function speak(words, lang, voice) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Pattern.prototype._speak = function (lang, voice) {
|
Pattern.prototype._speak = function (lang, voice) {
|
||||||
return this._withHap((hap) => {
|
return this.onTrigger((_, hap) => {
|
||||||
const onTrigger = (time, hap) => {
|
speak(hap.value, lang, voice);
|
||||||
speak(hap.value, lang, voice);
|
|
||||||
};
|
|
||||||
return hap.setContext({ ...hap.context, onTrigger });
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -65,46 +65,42 @@ Pattern.prototype.midi = async function (output, channel = 1) {
|
|||||||
}')`,
|
}')`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return this._withHap((hap) => {
|
return this.onTrigger((time, hap) => {
|
||||||
// const onTrigger = (time: number, hap: any) => {
|
let note = getPlayableNoteValue(hap);
|
||||||
const onTrigger = (time, hap) => {
|
const velocity = hap.context?.velocity ?? 0.9;
|
||||||
let note = getPlayableNoteValue(hap);
|
if (!isNote(note)) {
|
||||||
const velocity = hap.context?.velocity ?? 0.9;
|
throw new Error('not a note: ' + note);
|
||||||
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.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.`);
|
||||||
if (!WebMidi.outputs.length) {
|
}
|
||||||
throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`);
|
let device;
|
||||||
}
|
if (typeof output === 'number') {
|
||||||
let device;
|
device = WebMidi.outputs[output];
|
||||||
if (typeof output === 'number') {
|
} else if (typeof output === 'string') {
|
||||||
device = WebMidi.outputs[output];
|
device = outputByName(output);
|
||||||
} else if (typeof output === 'string') {
|
} else {
|
||||||
device = outputByName(output);
|
device = WebMidi.outputs[0];
|
||||||
} else {
|
}
|
||||||
device = WebMidi.outputs[0];
|
if (!device) {
|
||||||
}
|
throw new Error(
|
||||||
if (!device) {
|
`🔌 MIDI device '${output ? output : ''}' not found. Use one of ${WebMidi.outputs
|
||||||
throw new Error(
|
.map((o) => `'${o.name}'`)
|
||||||
`🔌 MIDI device '${output ? output : ''}' not found. Use one of ${WebMidi.outputs
|
.join(' | ')}`,
|
||||||
.map((o) => `'${o.name}'`)
|
);
|
||||||
.join(' | ')}`,
|
}
|
||||||
);
|
// console.log('midi', value, output);
|
||||||
}
|
const timingOffset = WebMidi.time - getAudioContext().currentTime * 1000;
|
||||||
// console.log('midi', value, output);
|
time = time * 1000 + timingOffset;
|
||||||
const timingOffset = WebMidi.time - getAudioContext().currentTime * 1000;
|
// const inMs = '+' + (time - Tone.getContext().currentTime) * 1000;
|
||||||
time = time * 1000 + timingOffset;
|
// await enableWebMidi()
|
||||||
// const inMs = '+' + (time - Tone.getContext().currentTime) * 1000;
|
device.playNote(note, channel, {
|
||||||
// await enableWebMidi()
|
time,
|
||||||
device.playNote(note, channel, {
|
duration: hap.duration.valueOf() * 1000 - 5,
|
||||||
time,
|
attack: velocity,
|
||||||
duration: hap.duration.valueOf() * 1000 - 5,
|
});
|
||||||
attack: velocity,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return hap.setContext({ ...hap.context, onTrigger });
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -15,12 +15,13 @@ function connect() {
|
|||||||
const osc = new OSC();
|
const osc = new OSC();
|
||||||
osc.open();
|
osc.open();
|
||||||
osc.on('open', () => {
|
osc.on('open', () => {
|
||||||
logger('OSC connected!');
|
const url = osc.options?.plugin?.socket?.url;
|
||||||
|
logger(`[osc] connected${url ? ` to ${url}` : ''}`);
|
||||||
resolve(osc);
|
resolve(osc);
|
||||||
});
|
});
|
||||||
osc.on('close', () => {
|
osc.on('close', () => {
|
||||||
connection = undefined; // allows new connection afterwards
|
connection = undefined; // allows new connection afterwards
|
||||||
console.log('osc connection closed');
|
console.log('[osc] disconnected');
|
||||||
reject('OSC connection closed');
|
reject('OSC connection closed');
|
||||||
});
|
});
|
||||||
osc.on('error', (err) => reject(err));
|
osc.on('error', (err) => reject(err));
|
||||||
@ -45,26 +46,23 @@ let startedAt = -1;
|
|||||||
*/
|
*/
|
||||||
Pattern.prototype.osc = async function () {
|
Pattern.prototype.osc = async function () {
|
||||||
const osc = await connect();
|
const osc = await connect();
|
||||||
return this._withHap((hap) => {
|
return this.onTrigger((time, hap, currentTime, cps = 1) => {
|
||||||
const onTrigger = (time, hap, currentTime, cps = 1) => {
|
const cycle = hap.wholeOrPart().begin.valueOf();
|
||||||
const cycle = hap.wholeOrPart().begin.valueOf();
|
const delta = hap.duration.valueOf();
|
||||||
const delta = hap.duration.valueOf();
|
// time should be audio time of onset
|
||||||
// time should be audio time of onset
|
// currentTime should be current time of audio context (slightly before time)
|
||||||
// currentTime should be current time of audio context (slightly before time)
|
if (startedAt < 0) {
|
||||||
if (startedAt < 0) {
|
startedAt = Date.now() - currentTime * 1000;
|
||||||
startedAt = Date.now() - currentTime * 1000;
|
}
|
||||||
}
|
const controls = Object.assign({}, { cps, cycle, delta }, hap.value);
|
||||||
const controls = Object.assign({}, { cps, cycle, delta }, hap.value);
|
// make sure n and note are numbers
|
||||||
// make sure n and note are numbers
|
controls.n && (controls.n = parseNumeral(controls.n));
|
||||||
controls.n && (controls.n = parseNumeral(controls.n));
|
controls.note && (controls.note = parseNumeral(controls.note));
|
||||||
controls.note && (controls.note = parseNumeral(controls.note));
|
const keyvals = Object.entries(controls).flat();
|
||||||
const keyvals = Object.entries(controls).flat();
|
const ts = Math.floor(startedAt + (time + latency) * 1000);
|
||||||
const ts = Math.floor(startedAt + (time + latency) * 1000);
|
const message = new OSC.Message('/dirt/play', ...keyvals);
|
||||||
const message = new OSC.Message('/dirt/play', ...keyvals);
|
const bundle = new OSC.Bundle([message], ts);
|
||||||
const bundle = new OSC.Bundle([message], ts);
|
bundle.timestamp(ts); // workaround for https://github.com/adzialocha/osc-js/issues/60
|
||||||
bundle.timestamp(ts); // workaround for https://github.com/adzialocha/osc-js/issues/60
|
osc.send(bundle);
|
||||||
osc.send(bundle);
|
|
||||||
};
|
|
||||||
return hap.setContext({ ...hap.context, onTrigger });
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { Pattern, isPattern } from '@strudel.cycles/core';
|
|||||||
var serialWriter;
|
var serialWriter;
|
||||||
var choosing = false;
|
var choosing = false;
|
||||||
|
|
||||||
export async function getWriter(br=38400) {
|
export async function getWriter(br = 38400) {
|
||||||
if (choosing) {
|
if (choosing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -24,11 +24,10 @@ export async function getWriter(br=38400) {
|
|||||||
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
|
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
|
||||||
const writer = textEncoder.writable.getWriter();
|
const writer = textEncoder.writable.getWriter();
|
||||||
serialWriter = function (message) {
|
serialWriter = function (message) {
|
||||||
writer.write(message)
|
writer.write(message);
|
||||||
}
|
};
|
||||||
}
|
} else {
|
||||||
else {
|
throw 'Webserial is not available in this browser.';
|
||||||
throw('Webserial is not available in this browser.')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +39,7 @@ Pattern.prototype.serial = function (...args) {
|
|||||||
getWriter(...args);
|
getWriter(...args);
|
||||||
}
|
}
|
||||||
const onTrigger = (time, hap, currentTime) => {
|
const onTrigger = (time, hap, currentTime) => {
|
||||||
var message = "";
|
var message = '';
|
||||||
if (typeof hap.value === 'object') {
|
if (typeof hap.value === 'object') {
|
||||||
if ('action' in hap.value) {
|
if ('action' in hap.value) {
|
||||||
message += hap.value['action'] + '(';
|
message += hap.value['action'] + '(';
|
||||||
@ -51,26 +50,23 @@ Pattern.prototype.serial = function (...args) {
|
|||||||
}
|
}
|
||||||
if (first) {
|
if (first) {
|
||||||
first = false;
|
first = false;
|
||||||
|
} else {
|
||||||
|
message += ',';
|
||||||
}
|
}
|
||||||
else {
|
message += `${key}:${val}`;
|
||||||
message +=',';
|
|
||||||
}
|
|
||||||
message += `${key}:${val}`
|
|
||||||
}
|
}
|
||||||
message += ')';
|
message += ')';
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
for (const [key, val] of Object.entries(hap.value)) {
|
for (const [key, val] of Object.entries(hap.value)) {
|
||||||
message += `${key}:${val};`
|
message += `${key}:${val};`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
message = hap.value;
|
message = hap.value;
|
||||||
}
|
}
|
||||||
const offset = (time - currentTime + latency) * 1000;
|
const offset = (time - currentTime + latency) * 1000;
|
||||||
window.setTimeout(serialWriter, offset, message);
|
window.setTimeout(serialWriter, offset, message);
|
||||||
};
|
};
|
||||||
return hap.setContext({ ...hap.context, onTrigger });
|
return hap.setContext({ ...hap.context, onTrigger, dominantTrigger: true });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -50,32 +50,29 @@ export const getDefaultSynth = () => {
|
|||||||
|
|
||||||
// with this function, you can play the pattern with any tone synth
|
// with this function, you can play the pattern with any tone synth
|
||||||
Pattern.prototype.tone = function (instrument) {
|
Pattern.prototype.tone = function (instrument) {
|
||||||
return this._withHap((hap) => {
|
return this.onTrigger((time, hap) => {
|
||||||
const onTrigger = (time, hap) => {
|
let note;
|
||||||
let note;
|
let velocity = hap.context?.velocity ?? 0.75;
|
||||||
let velocity = hap.context?.velocity ?? 0.75;
|
if (instrument instanceof PluckSynth) {
|
||||||
if (instrument instanceof PluckSynth) {
|
note = getPlayableNoteValue(hap);
|
||||||
note = getPlayableNoteValue(hap);
|
instrument.triggerAttack(note, time);
|
||||||
instrument.triggerAttack(note, time);
|
} else if (instrument instanceof NoiseSynth) {
|
||||||
} else if (instrument instanceof NoiseSynth) {
|
instrument.triggerAttackRelease(hap.duration.valueOf(), time); // noise has no value
|
||||||
instrument.triggerAttackRelease(hap.duration.valueOf(), time); // noise has no value
|
} else if (instrument instanceof Sampler) {
|
||||||
} else if (instrument instanceof Sampler) {
|
note = getPlayableNoteValue(hap);
|
||||||
note = getPlayableNoteValue(hap);
|
instrument.triggerAttackRelease(note, hap.duration.valueOf(), time, velocity);
|
||||||
instrument.triggerAttackRelease(note, hap.duration.valueOf(), time, velocity);
|
} else if (instrument instanceof Players) {
|
||||||
} else if (instrument instanceof Players) {
|
if (!instrument.has(hap.value)) {
|
||||||
if (!instrument.has(hap.value)) {
|
throw new Error(`name "${hap.value}" not defined for players`);
|
||||||
throw new Error(`name "${hap.value}" not defined for players`);
|
|
||||||
}
|
|
||||||
const player = instrument.player(hap.value);
|
|
||||||
// velocity ?
|
|
||||||
player.start(time);
|
|
||||||
player.stop(time + hap.duration.valueOf());
|
|
||||||
} else {
|
|
||||||
note = getPlayableNoteValue(hap);
|
|
||||||
instrument.triggerAttackRelease(note, hap.duration.valueOf(), time, velocity);
|
|
||||||
}
|
}
|
||||||
};
|
const player = instrument.player(hap.value);
|
||||||
return hap.setContext({ ...hap.context, instrument, onTrigger });
|
// velocity ?
|
||||||
|
player.start(time);
|
||||||
|
player.stop(time + hap.duration.valueOf());
|
||||||
|
} else {
|
||||||
|
note = getPlayableNoteValue(hap);
|
||||||
|
instrument.triggerAttackRelease(note, hap.duration.valueOf(), time, velocity);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -62,37 +62,34 @@ export function loadWebDirt(config) {
|
|||||||
*/
|
*/
|
||||||
Pattern.prototype.webdirt = function () {
|
Pattern.prototype.webdirt = function () {
|
||||||
// create a WebDirt object and initialize Web Audio context
|
// create a WebDirt object and initialize Web Audio context
|
||||||
return this._withHap((hap) => {
|
return this.onTrigger(async (time, e, currentTime) => {
|
||||||
const onTrigger = async (time, e, currentTime) => {
|
if (!webDirt) {
|
||||||
if (!webDirt) {
|
throw new Error('WebDirt not initialized!');
|
||||||
throw new Error('WebDirt not initialized!');
|
}
|
||||||
}
|
const deadline = time - currentTime;
|
||||||
const deadline = time - currentTime;
|
const { s, n = 0, ...rest } = e.value || {};
|
||||||
const { s, n = 0, ...rest } = e.value || {};
|
if (!s) {
|
||||||
if (!s) {
|
console.warn('Pattern.webdirt: no "s" was set!');
|
||||||
console.warn('Pattern.webdirt: no "s" was set!');
|
}
|
||||||
}
|
const samples = getLoadedSamples();
|
||||||
const samples = getLoadedSamples();
|
if (!samples?.[s]) {
|
||||||
if (!samples?.[s]) {
|
// try default samples
|
||||||
// try default samples
|
webDirt.playSample({ s, n, ...rest }, deadline);
|
||||||
webDirt.playSample({ s, n, ...rest }, deadline);
|
return;
|
||||||
return;
|
}
|
||||||
}
|
if (!samples?.[s]) {
|
||||||
if (!samples?.[s]) {
|
console.warn(`Pattern.webdirt: sample "${s}" not found in loaded samples`, samples);
|
||||||
console.warn(`Pattern.webdirt: sample "${s}" not found in loaded samples`, samples);
|
} else {
|
||||||
|
const bank = samples[s];
|
||||||
|
const sampleUrl = bank[n % bank.length];
|
||||||
|
const buffer = getLoadedBuffer(sampleUrl);
|
||||||
|
if (!buffer) {
|
||||||
|
console.log(`Pattern.webdirt: load ${s}:${n} from ${sampleUrl}`);
|
||||||
|
loadBuffer(sampleUrl, webDirt.ac);
|
||||||
} else {
|
} else {
|
||||||
const bank = samples[s];
|
const msg = { buffer: { buffer }, ...rest };
|
||||||
const sampleUrl = bank[n % bank.length];
|
webDirt.playSample(msg, deadline);
|
||||||
const buffer = getLoadedBuffer(sampleUrl);
|
|
||||||
if (!buffer) {
|
|
||||||
console.log(`Pattern.webdirt: load ${s}:${n} from ${sampleUrl}`);
|
|
||||||
loadBuffer(sampleUrl, webDirt.ac);
|
|
||||||
} else {
|
|
||||||
const msg = { buffer: { buffer }, ...rest };
|
|
||||||
webDirt.playSample(msg, deadline);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
return hap.setContext({ ...hap.context, onTrigger });
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user