mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-10 05:08:33 +00:00
* 0.5 default cps * 1 -> 0.5 cps defaults * start moving examples to 2Hz * more 2Hz doc edits * small tweaks * format * adapt cycles page * adapt pitch page * tonal page * accumulation * synth page * adapt conditional-modifiers * audio effects page * adapt signals doc * fix: errors for signals * adapt signals page * start time modifiers * adapt time modifiers * adapt factories * hydra + pattern intro * adapt mini notation page * start recipes * adapt recipes page * use code_v1 table * delete old dbdump + add new csv based tool * fix: tests * fix: cpm * shuffle featured patterns * fix: snapshot --------- Co-authored-by: Felix Roos <flix91@gmail.com>
111 lines
4.1 KiB
JavaScript
111 lines
4.1 KiB
JavaScript
/*
|
|
cyclist.mjs - <short description TODO>
|
|
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/cyclist.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/>.
|
|
*/
|
|
|
|
import createClock from './zyklus.mjs';
|
|
import { logger } from './logger.mjs';
|
|
|
|
export class Cyclist {
|
|
constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1 }) {
|
|
this.started = false;
|
|
this.cps = 0.5;
|
|
this.num_ticks_since_cps_change = 0;
|
|
this.lastTick = 0; // absolute time when last tick (clock callback) happened
|
|
this.lastBegin = 0; // query begin of last tick
|
|
this.lastEnd = 0; // query end of last tick
|
|
this.getTime = getTime; // get absolute time
|
|
this.num_cycles_since_last_cps_change = 0;
|
|
this.onToggle = onToggle;
|
|
this.latency = latency; // fixed trigger time offset
|
|
this.clock = createClock(
|
|
getTime,
|
|
// called slightly before each cycle
|
|
(phase, duration, tick) => {
|
|
if (tick === 0) {
|
|
this.origin = phase;
|
|
}
|
|
if (this.num_ticks_since_cps_change === 0) {
|
|
this.num_cycles_since_last_cps_change = this.lastEnd;
|
|
}
|
|
this.num_ticks_since_cps_change++;
|
|
try {
|
|
const time = getTime();
|
|
const begin = this.lastEnd;
|
|
this.lastBegin = begin;
|
|
|
|
//convert ticks to cycles, so you can query the pattern for events
|
|
const eventLength = duration * this.cps;
|
|
const end = this.num_cycles_since_last_cps_change + this.num_ticks_since_cps_change * eventLength;
|
|
this.lastEnd = end;
|
|
|
|
// query the pattern for events
|
|
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps });
|
|
|
|
const tickdeadline = phase - time; // time left until the phase is a whole number
|
|
this.lastTick = time + tickdeadline;
|
|
|
|
haps.forEach((hap) => {
|
|
if (hap.part.begin.equals(hap.whole.begin)) {
|
|
const deadline = (hap.whole.begin - begin) / this.cps + tickdeadline + latency;
|
|
const duration = hap.duration / this.cps;
|
|
onTrigger?.(hap, deadline, duration, this.cps);
|
|
}
|
|
});
|
|
} catch (e) {
|
|
logger(`[cyclist] error: ${e.message}`);
|
|
onError?.(e);
|
|
}
|
|
},
|
|
interval, // duration of each cycle
|
|
);
|
|
}
|
|
now() {
|
|
const secondsSinceLastTick = this.getTime() - this.lastTick - this.clock.duration;
|
|
return this.lastBegin + secondsSinceLastTick * this.cps; // + this.clock.minLatency;
|
|
}
|
|
setStarted(v) {
|
|
this.started = v;
|
|
this.onToggle?.(v);
|
|
}
|
|
start() {
|
|
this.num_ticks_since_cps_change = 0;
|
|
this.num_cycles_since_last_cps_change = 0;
|
|
if (!this.pattern) {
|
|
throw new Error('Scheduler: no pattern set! call .setPattern first.');
|
|
}
|
|
logger('[cyclist] start');
|
|
this.clock.start();
|
|
this.setStarted(true);
|
|
}
|
|
pause() {
|
|
logger('[cyclist] pause');
|
|
this.clock.pause();
|
|
this.setStarted(false);
|
|
}
|
|
stop() {
|
|
logger('[cyclist] stop');
|
|
this.clock.stop();
|
|
this.lastEnd = 0;
|
|
this.setStarted(false);
|
|
}
|
|
setPattern(pat, autostart = false) {
|
|
this.pattern = pat;
|
|
if (autostart && !this.started) {
|
|
this.start();
|
|
}
|
|
}
|
|
setCps(cps = 0.5) {
|
|
if (this.cps === cps) {
|
|
return;
|
|
}
|
|
this.cps = cps;
|
|
this.num_ticks_since_cps_change = 0;
|
|
}
|
|
log(begin, end, haps) {
|
|
const onsets = haps.filter((h) => h.hasOnset());
|
|
console.log(`${begin.toFixed(4)} - ${end.toFixed(4)} ${Array(onsets.length).fill('I').join('')}`);
|
|
}
|
|
}
|