sync without cyclist changes
This commit is contained in:
parent
077556e7cb
commit
d9e44dcda6
8 changed files with 261 additions and 82 deletions
|
|
@ -448,13 +448,23 @@ export const { coarse } = registerControl('coarse');
|
||||||
* modulate the amplitude of a sound with a continuous waveform
|
* modulate the amplitude of a sound with a continuous waveform
|
||||||
*
|
*
|
||||||
* @name am
|
* @name am
|
||||||
* @synonyms tremelo
|
* @param {number | Pattern} speed modulation speed in HZ
|
||||||
* @param {number | Pattern} speed modulation speed in cycles
|
|
||||||
* @example
|
* @example
|
||||||
* s("triangle").am("2").amshape("<tri saw ramp square>").amdepth(.5)
|
* s("triangle").am("2").amshape("<tri saw ramp square>").amdepth(.5)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { am, tremolo } = registerControl(['am', 'amdepth', 'amskew', 'amphase'], 'tremolo');
|
export const { am, } = registerControl(['am', 'amdepth', 'amskew', 'amphase'],);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* modulate the amplitude of a sound with a continuous waveform
|
||||||
|
*
|
||||||
|
* @name amsync
|
||||||
|
* @param {number | Pattern} cycles modulation speed in cycles
|
||||||
|
* @example
|
||||||
|
* s("triangle").am("2").amshape("<tri saw ramp square>").amdepth(.5)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const { amsync } = registerControl(['amsync', 'amdepth', 'amskew', 'amphase']);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* depth of amplitude modulation
|
* depth of amplitude modulation
|
||||||
|
|
|
||||||
140
packages/core/cyclist copy.mjs
Normal file
140
packages/core/cyclist copy.mjs
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
cyclist.mjs - event scheduler for a single strudel instance. for multi-instance scheduler, see - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/neocyclist.mjs>
|
||||||
|
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/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,
|
||||||
|
setInterval,
|
||||||
|
clearInterval,
|
||||||
|
beforeStart,
|
||||||
|
}) {
|
||||||
|
this.started = false;
|
||||||
|
this.beforeStart = beforeStart;
|
||||||
|
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_at_cps_change = 0;
|
||||||
|
this.seconds_at_cps_change; // clock phase when cps was changed
|
||||||
|
this.onToggle = onToggle;
|
||||||
|
this.latency = latency; // fixed trigger time offset
|
||||||
|
this.clock = createClock(
|
||||||
|
getTime,
|
||||||
|
// called slightly before each cycle
|
||||||
|
(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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (phase < t) {
|
||||||
|
// avoid querying haps that are in the past anyway
|
||||||
|
console.log(`skip query: too late`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// query the pattern for events
|
||||||
|
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps });
|
||||||
|
|
||||||
|
haps.forEach((hap) => {
|
||||||
|
if (hap.hasOnset()) {
|
||||||
|
const targetTime =
|
||||||
|
(hap.whole.begin - this.num_cycles_at_cps_change) / this.cps + this.seconds_at_cps_change + latency;
|
||||||
|
const duration = hap.duration / this.cps;
|
||||||
|
// the following line is dumb and only here for backwards compatibility
|
||||||
|
// see https://codeberg.org/uzu/strudel/pulls/1004
|
||||||
|
const deadline = targetTime - phase;
|
||||||
|
// this onTrigger has another signature
|
||||||
|
onTrigger?.(hap, deadline, duration, this.cps, targetTime);
|
||||||
|
if (hap.value.cps !== undefined && this.cps != hap.value.cps) {
|
||||||
|
this.cps = hap.value.cps;
|
||||||
|
this.num_ticks_since_cps_change = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger(`[cyclist] error: ${e.message}`);
|
||||||
|
onError?.(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interval, // duration of each cycle
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
setInterval,
|
||||||
|
clearInterval,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
now() {
|
||||||
|
if (!this.started) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
async setPattern(pat, autostart = false) {
|
||||||
|
this.pattern = pat;
|
||||||
|
if (autostart && !this.started) {
|
||||||
|
await 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('')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
cyclist.mjs - event scheduler for a single strudel instance. for multi-instance scheduler, see - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/neocyclist.mjs>
|
cyclist.mjs - event scheduler for a single strudel instance. for multi-instance scheduler, see - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/neocyclist.mjs>
|
||||||
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/cyclist.mjs>
|
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/>.
|
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/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -8,77 +8,50 @@ import createClock from './zyklus.mjs';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
|
|
||||||
export class Cyclist {
|
export class Cyclist {
|
||||||
constructor({
|
constructor({ interval = 0.05, onTrigger, onToggle, onError, getTime, latency = 0.1, setInterval, clearInterval }) {
|
||||||
interval,
|
|
||||||
onTrigger,
|
|
||||||
onToggle,
|
|
||||||
onError,
|
|
||||||
getTime,
|
|
||||||
latency = 0.1,
|
|
||||||
setInterval,
|
|
||||||
clearInterval,
|
|
||||||
beforeStart,
|
|
||||||
}) {
|
|
||||||
this.started = false;
|
this.started = false;
|
||||||
this.beforeStart = beforeStart;
|
|
||||||
this.cps = 0.5;
|
this.cps = 0.5;
|
||||||
this.num_ticks_since_cps_change = 0;
|
this.time_at_last_tick_message = 0;
|
||||||
this.lastTick = 0; // absolute time when last tick (clock callback) happened
|
this.cycle = 0;
|
||||||
this.lastBegin = 0; // query begin of last tick
|
|
||||||
this.lastEnd = 0; // query end of last tick
|
|
||||||
this.getTime = getTime; // get absolute time
|
this.getTime = getTime; // get absolute time
|
||||||
this.num_cycles_at_cps_change = 0;
|
this.num_cycles_at_cps_change = 0;
|
||||||
this.seconds_at_cps_change; // clock phase when cps was changed
|
this.seconds_at_cps_change; // clock phase when cps was changed
|
||||||
|
this.num_ticks_since_cps_change = 0;
|
||||||
this.onToggle = onToggle;
|
this.onToggle = onToggle;
|
||||||
this.latency = latency; // fixed trigger time offset
|
this.latency = latency; // fixed trigger time offset
|
||||||
|
|
||||||
|
this.interval = interval;
|
||||||
|
|
||||||
this.clock = createClock(
|
this.clock = createClock(
|
||||||
getTime,
|
getTime,
|
||||||
// called slightly before each cycle
|
// called slightly before each cycle
|
||||||
(phase, duration, _, t) => {
|
(lastTick, duration, _, time) => {
|
||||||
if (this.num_ticks_since_cps_change === 0) {
|
if (this.started === false) {
|
||||||
this.num_cycles_at_cps_change = this.lastEnd;
|
return;
|
||||||
this.seconds_at_cps_change = phase;
|
|
||||||
}
|
}
|
||||||
this.num_ticks_since_cps_change++;
|
const num_seconds_since_cps_change = this.num_ticks_since_cps_change * duration;
|
||||||
const seconds_since_cps_change = this.num_ticks_since_cps_change * duration;
|
const tickdeadline = lastTick - time;
|
||||||
const num_cycles_since_cps_change = seconds_since_cps_change * this.cps;
|
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;
|
||||||
|
|
||||||
try {
|
//account for latency and tick duration when using cycle calculations for audio downstream
|
||||||
const begin = this.lastEnd;
|
const cycle_gap = (this.latency - duration) * this.cps;
|
||||||
this.lastBegin = begin;
|
|
||||||
const end = this.num_cycles_at_cps_change + num_cycles_since_cps_change;
|
|
||||||
this.lastEnd = end;
|
|
||||||
this.lastTick = phase;
|
|
||||||
|
|
||||||
if (phase < t) {
|
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps });
|
||||||
// avoid querying haps that are in the past anyway
|
haps.forEach((hap) => {
|
||||||
console.log(`skip query: too late`);
|
if (hap.hasOnset()) {
|
||||||
return;
|
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);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
// query the pattern for events
|
this.time_at_last_tick_message = time;
|
||||||
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps });
|
this.num_ticks_since_cps_change++;
|
||||||
|
|
||||||
haps.forEach((hap) => {
|
|
||||||
if (hap.hasOnset()) {
|
|
||||||
const targetTime =
|
|
||||||
(hap.whole.begin - this.num_cycles_at_cps_change) / this.cps + this.seconds_at_cps_change + latency;
|
|
||||||
const duration = hap.duration / this.cps;
|
|
||||||
// the following line is dumb and only here for backwards compatibility
|
|
||||||
// see https://codeberg.org/uzu/strudel/pulls/1004
|
|
||||||
const deadline = targetTime - phase;
|
|
||||||
// this onTrigger has another signature
|
|
||||||
onTrigger?.(hap, deadline, duration, this.cps, targetTime);
|
|
||||||
if (hap.value.cps !== undefined && this.cps != hap.value.cps) {
|
|
||||||
this.cps = hap.value.cps;
|
|
||||||
this.num_ticks_since_cps_change = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
logger(`[cyclist] error: ${e.message}`);
|
|
||||||
onError?.(e);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
interval, // duration of each cycle
|
interval, // duration of each cycle
|
||||||
0.1,
|
0.1,
|
||||||
|
|
@ -91,17 +64,24 @@ export class Cyclist {
|
||||||
if (!this.started) {
|
if (!this.started) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const secondsSinceLastTick = this.getTime() - this.lastTick - this.clock.duration;
|
const gap = (this.getTime() - this.time_at_last_tick_message) * this.cps;
|
||||||
return this.lastBegin + secondsSinceLastTick * this.cps; // + this.clock.minLatency;
|
return this.cycle + gap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setCycle = (cycle) => {
|
||||||
|
this.num_ticks_since_cps_change = 0;
|
||||||
|
this.num_cycles_at_cps_change = cycle;
|
||||||
|
};
|
||||||
setStarted(v) {
|
setStarted(v) {
|
||||||
this.started = v;
|
this.started = v;
|
||||||
|
|
||||||
|
this.setCycle(0);
|
||||||
this.onToggle?.(v);
|
this.onToggle?.(v);
|
||||||
}
|
}
|
||||||
async start() {
|
start() {
|
||||||
await this.beforeStart?.();
|
|
||||||
this.num_ticks_since_cps_change = 0;
|
this.num_ticks_since_cps_change = 0;
|
||||||
this.num_cycles_at_cps_change = 0;
|
this.num_cycles_at_cps_change = 0;
|
||||||
|
|
||||||
if (!this.pattern) {
|
if (!this.pattern) {
|
||||||
throw new Error('Scheduler: no pattern set! call .setPattern first.');
|
throw new Error('Scheduler: no pattern set! call .setPattern first.');
|
||||||
}
|
}
|
||||||
|
|
@ -120,16 +100,18 @@ export class Cyclist {
|
||||||
this.lastEnd = 0;
|
this.lastEnd = 0;
|
||||||
this.setStarted(false);
|
this.setStarted(false);
|
||||||
}
|
}
|
||||||
async setPattern(pat, autostart = false) {
|
setPattern(pat, autostart = false) {
|
||||||
this.pattern = pat;
|
this.pattern = pat;
|
||||||
if (autostart && !this.started) {
|
if (autostart && !this.started) {
|
||||||
await this.start();
|
this.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setCps(cps = 0.5) {
|
setCps(cps = 0.5) {
|
||||||
if (this.cps === cps) {
|
if (this.cps === cps) {
|
||||||
return;
|
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.cps = cps;
|
||||||
this.num_ticks_since_cps_change = 0;
|
this.num_ticks_since_cps_change = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -137,4 +119,4 @@ export class Cyclist {
|
||||||
const onsets = haps.filter((h) => h.hasOnset());
|
const onsets = haps.filter((h) => h.hasOnset());
|
||||||
console.log(`${begin.toFixed(4)} - ${end.toFixed(4)} ${Array(onsets.length).fill('I').join('')}`);
|
console.log(`${begin.toFixed(4)} - ${end.toFixed(4)} ${Array(onsets.length).fill('I').join('')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -45,7 +45,7 @@ export class NeoCyclist {
|
||||||
const timeUntilTrigger = cycleToSeconds(hap.whole.begin - this.cycle, this.cps);
|
const timeUntilTrigger = cycleToSeconds(hap.whole.begin - this.cycle, this.cps);
|
||||||
const targetTime = timeUntilTrigger + currentTime + this.latency;
|
const targetTime = timeUntilTrigger + currentTime + this.latency;
|
||||||
const duration = cycleToSeconds(hap.duration, this.cps);
|
const duration = cycleToSeconds(hap.duration, this.cps);
|
||||||
onTrigger?.(hap, 0, duration, this.cps, targetTime);
|
onTrigger?.(hap, 0, duration, this.cps, targetTime, this.cycle);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -244,16 +244,16 @@ export function repl({
|
||||||
|
|
||||||
export const getTrigger =
|
export const getTrigger =
|
||||||
({ getTime, defaultOutput }) =>
|
({ getTime, defaultOutput }) =>
|
||||||
async (hap, deadline, duration, cps, t) => {
|
async (hap, deadline, duration, cps, t, cycle = 0) => {
|
||||||
// ^ this signature is different from hap.context.onTrigger, as set by Pattern.onTrigger(onTrigger)
|
// ^ this signature is different from hap.context.onTrigger, as set by Pattern.onTrigger(onTrigger)
|
||||||
// TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004
|
// TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004
|
||||||
try {
|
try {
|
||||||
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
|
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
|
||||||
await defaultOutput(hap, deadline, duration, cps, t);
|
await defaultOutput(hap, deadline, duration, cps, t, cycle);
|
||||||
}
|
}
|
||||||
if (hap.context.onTrigger) {
|
if (hap.context.onTrigger) {
|
||||||
// call signature of output / onTrigger is different...
|
// call signature of output / onTrigger is different...
|
||||||
await hap.context.onTrigger(hap, getTime(), cps, t);
|
await hap.context.onTrigger(hap, getTime(), cps, t, cycle);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger(`[cyclist] error: ${err.message}`, 'error');
|
logger(`[cyclist] error: ${err.message}`, 'error');
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ export function setGainCurve(newGainCurveFunc) {
|
||||||
gainCurveFunc = newGainCurveFunc;
|
gainCurveFunc = newGainCurveFunc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function aliasBankMap(aliasMap) {
|
function aliasBankMap(aliasMap) {
|
||||||
// Make all bank keys lower case for case insensitivity
|
// Make all bank keys lower case for case insensitivity
|
||||||
for (const key in aliasMap) {
|
for (const key in aliasMap) {
|
||||||
|
|
@ -325,12 +327,29 @@ function getDelay(orbit, delaytime, delayfeedback, t, channels) {
|
||||||
return delays[orbit];
|
return delays[orbit];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLfo(audioContext, time, end, properties = {}) {
|
export function getLfo(audioContext, begin, end, properties = {}) {
|
||||||
return getWorklet(audioContext, 'lfo-processor', {
|
return getWorklet(audioContext, 'lfo-processor', {
|
||||||
frequency: 1,
|
frequency: 1,
|
||||||
depth: 1,
|
depth: 1,
|
||||||
skew: 0,
|
skew: 0,
|
||||||
phaseoffset: 0,
|
phaseoffset: 0,
|
||||||
|
time: begin,
|
||||||
|
begin,
|
||||||
|
end,
|
||||||
|
shape: 1,
|
||||||
|
dcoffset: -0.5,
|
||||||
|
...properties,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSyncedLfo(audioContext, time, end, cps, cycle, properties = {}) {
|
||||||
|
const frequency = cycle/cps
|
||||||
|
|
||||||
|
return getWorklet(audioContext, 'lfo-processor', {
|
||||||
|
frequency,
|
||||||
|
depth: 1,
|
||||||
|
skew: 0,
|
||||||
|
phaseoffset: 0,
|
||||||
time,
|
time,
|
||||||
end,
|
end,
|
||||||
shape: 1,
|
shape: 1,
|
||||||
|
|
@ -450,9 +469,10 @@ function mapChannelNumbers(channels) {
|
||||||
return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
|
return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const superdough = async (value, t, hapDuration, cps = 0.5) => {
|
export const superdough = async (value, t, hapDuration, cps = 0.5, cycle) => {
|
||||||
// new: t is always expected to be the absolute target onset time
|
// new: t is always expected to be the absolute target onset time
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
|
|
||||||
let { stretch } = value;
|
let { stretch } = value;
|
||||||
if (stretch != null) {
|
if (stretch != null) {
|
||||||
//account for phase vocoder latency
|
//account for phase vocoder latency
|
||||||
|
|
@ -707,11 +727,12 @@ export const superdough = async (value, t, hapDuration, cps = 0.5) => {
|
||||||
coarse !== undefined && chain.push(getWorklet(ac, 'coarse-processor', { coarse }));
|
coarse !== undefined && chain.push(getWorklet(ac, 'coarse-processor', { coarse }));
|
||||||
crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush }));
|
crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush }));
|
||||||
shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape, postgain: shapevol }));
|
shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape, postgain: shapevol }));
|
||||||
distort !== undefined && chain.push(getWorklet(ac, 'distort-processor', { distort, postgain: distortvol }));
|
// distort !== undefined && chain.push(getWorklet(ac, 'distort-processor', { distort, postgain: distortvol }));
|
||||||
// am !== undefined &&
|
// am !== undefined &&
|
||||||
// chain.push(
|
// chain.push(
|
||||||
// getWorklet(ac, 'am-processor', {
|
// getWorklet(ac, 'am-processor', {
|
||||||
// speed: am,
|
// speed: am,
|
||||||
|
// begin: t,
|
||||||
// depth: amdepth,
|
// depth: amdepth,
|
||||||
// skew: amskew,
|
// skew: amskew,
|
||||||
// phaseoffset: amphase,
|
// phaseoffset: amphase,
|
||||||
|
|
@ -724,12 +745,21 @@ export const superdough = async (value, t, hapDuration, cps = 0.5) => {
|
||||||
|
|
||||||
if (am !== undefined) {
|
if (am !== undefined) {
|
||||||
const amGain = new GainNode(ac, { gain: 1 });
|
const amGain = new GainNode(ac, { gain: 1 });
|
||||||
const frequency = cycleToSeconds(am, cps)
|
const frequency = cps / am
|
||||||
const phaseoffset = cycleToSeconds(amphase, cps)
|
const phaseoffset = cycleToSeconds(amphase, cps)
|
||||||
|
|
||||||
|
|
||||||
|
const time = cycle / cps
|
||||||
|
|
||||||
|
// console.info(cycle, time, frequency)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const lfo = getLfo(ac, t, t + hapDuration, {
|
const lfo = getLfo(ac, t, t + hapDuration, {
|
||||||
skew: amskew,
|
skew: amskew,
|
||||||
frequency: am * cps,
|
frequency,
|
||||||
depth: amdepth,
|
depth: amdepth,
|
||||||
|
time,
|
||||||
// dcoffset: 0,
|
// dcoffset: 0,
|
||||||
shape: amshape,
|
shape: amshape,
|
||||||
phaseoffset
|
phaseoffset
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ const waveShapeNames = Object.keys(waveshapes);
|
||||||
class LFOProcessor extends AudioWorkletProcessor {
|
class LFOProcessor extends AudioWorkletProcessor {
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [
|
return [
|
||||||
|
{ name: 'begin', defaultValue: 0 },
|
||||||
{ name: 'time', defaultValue: 0 },
|
{ name: 'time', defaultValue: 0 },
|
||||||
{ name: 'end', defaultValue: 0 },
|
{ name: 'end', defaultValue: 0 },
|
||||||
{ name: 'frequency', defaultValue: 0.5 },
|
{ name: 'frequency', defaultValue: 0.5 },
|
||||||
|
|
@ -109,10 +110,14 @@ class LFOProcessor extends AudioWorkletProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
process(inputs, outputs, parameters) {
|
process(inputs, outputs, parameters) {
|
||||||
|
const begin = parameters['begin'][0]
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
if (currentTime >= parameters.end[0]) {
|
if (currentTime >= parameters.end[0]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (currentTime <= begin) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const output = outputs[0];
|
const output = outputs[0];
|
||||||
const frequency = parameters['frequency'][0];
|
const frequency = parameters['frequency'][0];
|
||||||
|
|
@ -159,6 +164,8 @@ class CoarseProcessor extends AudioWorkletProcessor {
|
||||||
const input = inputs[0];
|
const input = inputs[0];
|
||||||
const output = outputs[0];
|
const output = outputs[0];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const hasInput = !(input[0] === undefined);
|
const hasInput = !(input[0] === undefined);
|
||||||
if (this.started && !hasInput) {
|
if (this.started && !hasInput) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -899,6 +906,7 @@ registerProcessor('byte-beat-processor', ByteBeatProcessor);
|
||||||
class AMProcessor extends AudioWorkletProcessor {
|
class AMProcessor extends AudioWorkletProcessor {
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [
|
return [
|
||||||
|
{ name: 'begin', defaultValue: 0 },
|
||||||
{ name: 'cps', defaultValue: 0.5 },
|
{ name: 'cps', defaultValue: 0.5 },
|
||||||
{ name: 'speed', defaultValue: 0.5 },
|
{ name: 'speed', defaultValue: 0.5 },
|
||||||
{ name: 'cycle', defaultValue: 0 },
|
{ name: 'cycle', defaultValue: 0 },
|
||||||
|
|
@ -925,10 +933,15 @@ class AMProcessor extends AudioWorkletProcessor {
|
||||||
const input = inputs[0];
|
const input = inputs[0];
|
||||||
const output = outputs[0];
|
const output = outputs[0];
|
||||||
const hasInput = !(input[0] === undefined);
|
const hasInput = !(input[0] === undefined);
|
||||||
|
|
||||||
|
|
||||||
if (this.started && !hasInput) {
|
if (this.started && !hasInput) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.started = hasInput;
|
this.started = hasInput;
|
||||||
|
if (currentTime <= parameters.begin[0]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const speed = parameters['speed'][0];
|
const speed = parameters['speed'][0];
|
||||||
const cps = parameters['cps'][0];
|
const cps = parameters['cps'][0];
|
||||||
|
|
@ -937,7 +950,7 @@ class AMProcessor extends AudioWorkletProcessor {
|
||||||
const skew = parameters['skew'][0];
|
const skew = parameters['skew'][0];
|
||||||
const phaseoffset = parameters['phaseoffset'][0];
|
const phaseoffset = parameters['phaseoffset'][0];
|
||||||
|
|
||||||
const frequency = speed * cps;
|
const frequency = cps / speed
|
||||||
if (this.phase == null) {
|
if (this.phase == null) {
|
||||||
const secondsPassed = cycle / cps;
|
const secondsPassed = cycle / cps;
|
||||||
this.phase = _mod(secondsPassed * frequency + phaseoffset, 1);
|
this.phase = _mod(secondsPassed * frequency + phaseoffset, 1);
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,12 @@ const hap2value = (hap) => {
|
||||||
|
|
||||||
// uses more precise, absolute t if available, see https://github.com/tidalcycles/strudel/pull/1004
|
// uses more precise, absolute t if available, see https://github.com/tidalcycles/strudel/pull/1004
|
||||||
// TODO: refactor output callbacks to eliminate deadline
|
// TODO: refactor output callbacks to eliminate deadline
|
||||||
export const webaudioOutput = (hap, deadline, hapDuration, cps, t) => {
|
export const webaudioOutput = (hap, deadline, hapDuration, cps, t, cycle) => {
|
||||||
return superdough(hap2value(hap), t, hapDuration, cps);
|
|
||||||
|
return superdough(hap2value(hap), t, hapDuration, cps,
|
||||||
|
hap.whole.begin.valueOf()
|
||||||
|
// cycle
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function webaudioRepl(options = {}) {
|
export function webaudioRepl(options = {}) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue