move scheduler to core + move audioctx to userland

This commit is contained in:
Felix Roos 2022-08-17 21:26:00 +02:00
parent 1cd31cfce9
commit 7af6188a8a
5 changed files with 55 additions and 43 deletions

View File

@ -1,6 +1,6 @@
/*
clockworker.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/webaudio/clockworker.mjs>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/clockworker.mjs>
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/>.
*/

View File

@ -1,8 +1,8 @@
<div style="position: absolute; top: 0; right: 0; padding: 4px">
<button id="start" style="margin-bottom: 4px; font-size: 2em">start</button><br />
<button id="stop" style="margin-bottom: 4px; font-size: 2em">stop</button><br />
<button id="slower" style="font-size: 2em">-</button>
<button id="faster" style="font-size: 2em">+</button>
<div style="position: absolute; bottom: 0; right: 0; padding: 4px; width: 100vw; display: flex">
<button id="start" style="font-size: 2em">start</button>
<button id="stop" style="font-size: 2em">stop</button>
<button id="slower" style="font-size: 2em">slower</button>
<button id="faster" style="font-size: 2em">faster</button>
</div>
<textarea
style="font-size: 2em; background: #052b49; color: #fff; height: 100%; width: 100%; outline: none; border: 0"
@ -14,31 +14,47 @@ Loading...</textarea
<script type="module">
document.body.style = 'margin: 0';
import * as strudel from '@strudel.cycles/core';
import * as util from '@strudel.cycles/core/util.mjs';
import '@strudel.cycles/core/euclid.mjs';
import { Scheduler, getAudioContext } from '../index.mjs';
const { cat, State, TimeSpan } = strudel;
const { cat, State, TimeSpan, Scheduler, getPlayableNoteValue, getFreq } = strudel;
Object.assign(window, strudel); // add strudel to eval scope
const ctx = new AudioContext();
const scheduler = new Scheduler({
audioContext: getAudioContext(),
// audioContext: getAudioContext(),
interval: 0.1,
onEvent: (e) => {
e.context?.createAudioNode?.(e);
onTrigger: (hap, time, duration) => {
const freq = getFrequency(hap);
const osc = ctx.createOscillator();
const gain = 0.2;
osc.frequency.value = freq;
osc.type = 'triangle';
const onset = ctx.currentTime + time;
const offset = onset + duration;
const attack = 0.05;
const release = 0.05;
osc.start(onset);
osc.stop(offset + release);
const g = ctx.createGain();
g.gain.setValueAtTime(gain, onset);
g.gain.linearRampToValueAtTime(gain, onset + attack);
g.gain.setValueAtTime(gain, offset - release);
g.gain.linearRampToValueAtTime(0, offset);
osc.connect(g);
g.connect(ctx.destination);
},
});
let initialCode = `sequence(1,2).mul(55/2) // frequencies
.mul(slowcat(1,2))
.mul(slowcat(1,3/2,4/3,5/3).slow(8))
.fast(3)
.freq()
.velocity(.5)
.s('sawtooth')
.cutoff(800)
.out()
`;
.mul(slowcat(1,2))
.mul(slowcat(1,3/2,4/3,5/3).slow(8))
.fast(3)
.freq()
.velocity(.5)
.s('sawtooth')
.cutoff(800)
.out()
`;
try {
const base64 = decodeURIComponent(window.location.href.split('#')[1]);
@ -59,8 +75,10 @@ Loading...</textarea
};
evaluate();
input.addEventListener('input', () => evaluate());
document.getElementById('start').addEventListener('click', () => scheduler.start());
document.getElementById('start').addEventListener('click', () => {
ctx.resume();
scheduler.start();
});
document.getElementById('stop').addEventListener('click', () => scheduler.stop());
document.getElementById('slower').addEventListener('click', () => scheduler.setCps(scheduler.cps - 0.1));
document.getElementById('faster').addEventListener('click', () => scheduler.setCps(scheduler.cps + 0.1));

View File

@ -15,6 +15,8 @@ export * from './state.mjs';
export * from './timespan.mjs';
export * from './util.mjs';
export * from './speak.mjs';
export * from './clockworker.mjs';
export * from './scheduler.mjs';
export { default as gist } from './gist.js';
// below won't work with runtime.mjs (json import fails)
/* import * as p from './package.json';

View File

@ -1,6 +1,6 @@
/*
scheduler.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/webaudio/scheduler.mjs>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/scheduler.mjs>
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/>.
*/
@ -10,29 +10,24 @@ export class Scheduler {
worker;
pattern;
phase;
audioContext;
cps = 1;
constructor({ audioContext, interval = 0.1, onEvent, latency = 0.1 }) {
this.audioContext = audioContext;
this.worker = new ClockWorker((tick, interval) => {
constructor({ interval = 0.1, onTrigger, latency = 0.1 }) {
this.worker = new ClockWorker((_, interval) => {
const begin = this.phase;
const end = this.phase + interval * this.cps;
this.phase = end;
const haps = this.pattern.queryArc(begin, end);
haps.forEach((e) => {
if (typeof e.value?.cps === 'number') {
this.setCps(e.value?.cps);
haps.forEach((hap) => {
if (typeof hap.value?.cps === 'number') {
this.setCps(hap.value?.cps);
}
if (!e.part.begin.equals(e.whole.begin)) {
if (!hap.part.begin.equals(hap.whole.begin)) {
return;
}
if (e.context.onTrigger) {
const ctxTime = (e.whole.begin - begin) / this.cps + this.audioContext.currentTime + latency;
e.context.onTrigger(ctxTime, e, this.audioContext.currentTime, this.cps);
}
if (onEvent) {
onEvent?.(e);
}
const deadline = (hap.whole.begin - begin) / this.cps + latency;
// TODO: use legato / duration of objectified value
const duration = hap.duration / this.cps;
onTrigger?.(hap, deadline, duration);
});
}, interval);
}
@ -40,7 +35,6 @@ export class Scheduler {
if (!this.pattern) {
throw new Error('Scheduler: no pattern set! call .setPattern first.');
}
this.audioContext.resume();
this.phase = 0;
this.worker.start();
}

View File

@ -4,7 +4,5 @@ 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/>.
*/
export * from './clockworker.mjs';
export * from './scheduler.mjs';
export * from './webaudio.mjs';
export * from './sampler.mjs';