diff --git a/packages/core/cyclist.mjs b/packages/core/cyclist.mjs index 072e97ab..f28dc604 100644 --- a/packages/core/cyclist.mjs +++ b/packages/core/cyclist.mjs @@ -22,44 +22,38 @@ export class Cyclist { this.started = false; this.beforeStart = beforeStart; this.cps = 0.5; - this.time_at_last_tick_message = 0; - this.cycle = 0; + 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_at_cps_change = 0; this.seconds_at_cps_change; // clock phase when cps was changed - this.num_ticks_since_cps_change = 0; this.onToggle = onToggle; this.latency = latency; // fixed trigger time offset - - this.interval = interval; - this.clock = createClock( getTime, // called slightly before each cycle - (phase, duration, _, time) => { - if (this.started === false) { - return; + (phase, duration, _, t) => { + if (this.num_ticks_since_cps_change === 0) { + this.num_cycles_at_cps_change = this.lastEnd; + this.seconds_at_cps_change = phase; } - const num_seconds_since_cps_change = this.num_ticks_since_cps_change * duration; - const tickdeadline = phase - time; - const lastTick = time + tickdeadline; - const num_cycles_since_cps_change = num_seconds_since_cps_change * this.cps; - const begin = this.num_cycles_at_cps_change + num_cycles_since_cps_change; - const secondsSinceLastTick = time - lastTick - duration; - const eventLength = duration * this.cps; - const end = begin + eventLength; - this.cycle = begin + secondsSinceLastTick * this.cps; + this.num_ticks_since_cps_change++; + const seconds_since_cps_change = this.num_ticks_since_cps_change * duration; + const num_cycles_since_cps_change = seconds_since_cps_change * this.cps; - //account for latency and tick duration when using cycle calculations for audio downstream - const cycle_gap = (this.latency - duration) * this.cps; + try { + const begin = this.lastEnd; + this.lastBegin = begin; + const end = this.num_cycles_at_cps_change + num_cycles_since_cps_change; + this.lastEnd = end; + this.lastTick = phase; - const haps = this.pattern.queryArc(begin, end, { _cps: this.cps }); - haps.forEach((hap) => { - if (hap.hasOnset()) { - let targetTime = (hap.whole.begin - this.num_cycles_at_cps_change) / this.cps; - targetTime = targetTime + this.latency + tickdeadline + time - num_seconds_since_cps_change; - const duration = hap.duration / this.cps; - onTrigger?.(hap, tickdeadline, duration, this.cps, targetTime, this.cycle - cycle_gap); + if (phase < t) { + // avoid querying haps that are in the past anyway + console.log(`skip query: too late`); + return; } // query the pattern for events @@ -96,25 +90,17 @@ export class Cyclist { if (!this.started) { return 0; } - const gap = (this.getTime() - this.time_at_last_tick_message) * this.cps; - return this.cycle + gap; + const secondsSinceLastTick = this.getTime() - this.lastTick - this.clock.duration; + return this.lastBegin + secondsSinceLastTick * this.cps; // + this.clock.minLatency; } - - setCycle = (cycle) => { - this.num_ticks_since_cps_change = 0; - this.num_cycles_at_cps_change = cycle; - }; setStarted(v) { this.started = v; - - this.setCycle(0); this.onToggle?.(v); } async start() { await this.beforeStart?.(); this.num_ticks_since_cps_change = 0; this.num_cycles_at_cps_change = 0; - if (!this.pattern) { throw new Error('Scheduler: no pattern set! call .setPattern first.'); } @@ -143,8 +129,6 @@ export class Cyclist { if (this.cps === cps) { return; } - const num_seconds_since_cps_change = this.num_ticks_since_cps_change * this.interval; - this.num_cycles_at_cps_change = this.num_cycles_at_cps_change + num_seconds_since_cps_change * cps; this.cps = cps; this.num_ticks_since_cps_change = 0; } diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index d5cab857..dcc5fcba 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -248,11 +248,11 @@ export const getTrigger = // TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004 try { if (!hap.context.onTrigger || !hap.context.dominantTrigger) { - await defaultOutput(hap, deadline, duration, cps, t, cycle); + await defaultOutput(hap, deadline, duration, cps, t); } if (hap.context.onTrigger) { // call signature of output / onTrigger is different... - await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps, t, cycle); + await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps, t); } } catch (err) { logger(`[cyclist] error: ${err.message}`, 'error'); diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index c59a5c0d..e4296c6b 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -7,7 +7,7 @@ This program is free software: you can redistribute it and/or modify it under th import './feedbackdelay.mjs'; import './reverb.mjs'; import './vowel.mjs'; -import { clamp, nanFallback, _mod, cycleToSeconds } from './util.mjs'; +import { clamp, nanFallback, _mod, cycleToSeconds, secondsToCycle } from './util.mjs'; import workletsUrl from './worklets.mjs?audioworklet'; import { createFilter, gainNode, getCompressor, getWorklet } from './helpers.mjs'; import { map } from 'nanostores'; @@ -482,6 +482,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5) => { amdepth = 1, amskew = 0.5, amphase = 0, + amshape = 1, s = getDefaultValue('s'), bank, source, @@ -707,19 +708,35 @@ export const superdough = async (value, t, hapDuration, cps = 0.5) => { crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush })); shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape, postgain: shapevol })); distort !== undefined && chain.push(getWorklet(ac, 'distort-processor', { distort, postgain: distortvol })); - am !== undefined && - chain.push( - getWorklet(ac, 'am-processor', { - speed: am, - depth: amdepth, - skew: amskew, - phaseoffset: amphase, - // shape: amshape, + // am !== undefined && + // chain.push( + // getWorklet(ac, 'am-processor', { + // speed: am, + // depth: amdepth, + // skew: amskew, + // phaseoffset: amphase, + // // shape: amshape, - cps, - cycle, - }), - ); + // cps, + // cycle, + // }), + // ); + + if (am !== undefined) { + const amGain = new GainNode(ac, { gain: 1 }); + const frequency = cycleToSeconds(am, cps) + const phaseoffset = cycleToSeconds(amphase, cps) + const lfo = getLfo(ac, t, t + hapDuration, { + skew: amskew, + frequency: am * cps, + depth: amdepth, + // dcoffset: 0, + shape: amshape, + phaseoffset + }) + lfo.connect(amGain.gain) + chain.push(amGain) + } compressorThreshold !== undefined && chain.push( diff --git a/packages/superdough/util.mjs b/packages/superdough/util.mjs index ea63a16f..f4d59024 100644 --- a/packages/superdough/util.mjs +++ b/packages/superdough/util.mjs @@ -72,3 +72,7 @@ export const getSoundIndex = (n, numSounds) => { export function cycleToSeconds(cycle, cps) { return cycle / cps; } + +export function secondsToCycle(t, cps) { + return t * cps; +}