From 7e8206dcbc67570c5001869e86251199c0fd5ad7 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 10 Jun 2025 12:31:21 +0200 Subject: [PATCH] pitched delay --- packages/core/controls.mjs | 19 ++++++++++++--- packages/supradough/dough.mjs | 45 +++++++++++++++++++++++------------ 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 21c183da9..9c9bdbc8c 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -964,14 +964,27 @@ export const { delay } = registerControl(['delay', 'delaytime', 'delayfeedback'] * */ export const { delayfeedback, delayfb, dfb } = registerControl('delayfeedback', 'delayfb', 'dfb'); + +/** + * Sets the level of the signal that is fed back into the delay. + * Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it + * + * @name delayfeedback + * @param {number | Pattern} feedback between 0 and 1 + * @synonyms delayfb, dfb + * @example + * s("bd").delay(.25).delayfeedback("<.25 .5 .75 1>") + * + */ +export const { delayspeed } = registerControl('delayspeed'); /** * Sets the time of the delay effect. * - * @name delaytime - * @param {number | Pattern} seconds between 0 and Infinity + * @name delayspeed + * @param {number | Pattern} delayspeed controls the pitch of the delay feedback * @synonyms delayt, dt * @example - * s("bd bd").delay(.25).delaytime("<.125 .25 .5 1>") + * note("d d a# a".fast(2)).s("sawtooth").delay(.8).delaytime(1/2).delayspeed("<2 .5 -1 -2>") * */ export const { delaytime, delayt, dt } = registerControl('delaytime', 'delayt', 'dt'); diff --git a/packages/supradough/dough.mjs b/packages/supradough/dough.mjs index 8a648a449..8e3544c9f 100644 --- a/packages/supradough/dough.mjs +++ b/packages/supradough/dough.mjs @@ -44,6 +44,7 @@ function polyBlep(t, dt) { // 0 otherwise return 0; } + export class SawOsc { constructor(props = {}) { this.phase = props.phase ?? 0; @@ -349,21 +350,31 @@ export class ADSR { .out()*/ const MAX_DELAY_TIME = 10; export class Delay { - writeIdx = 0; - readIdx = 0; - buffer = new Float32Array(MAX_DELAY_TIME * SAMPLE_RATE); //.fill(0) - write(s, delayTime) { - this.writeIdx = (this.writeIdx + 1) % this.buffer.length; - this.buffer[this.writeIdx] = s; - // Calculate how far in the past to read - let numSamples = Math.min(Math.floor(SAMPLE_RATE * delayTime), this.buffer.length - 1); - this.readIdx = this.writeIdx - numSamples; - // If past the start of the buffer, wrap around - if (this.readIdx < 0) this.readIdx += this.buffer.length; + constructor(_props = {}) { + this.buffer = new Float32Array(MAX_DELAY_TIME * SAMPLE_RATE); + this.writeIdx = 0; + this.readIdx = 0; + this.numSamples = 0; } - update(input, delayTime) { + write(s, delayTime) { + // Calculate how far in the past to read + this.numSamples = Math.min(Math.floor(SAMPLE_RATE * delayTime), this.buffer.length - 1); + this.writeIdx = (this.writeIdx + 1) % this.numSamples; + this.buffer[this.writeIdx] = s; + this.readIdx = this.writeIdx - this.numSamples + 1; + + // If past the start of the buffer, wrap around (Q: is this possible?) + if (this.readIdx < 0) this.readIdx += this.numSamples; + } + update(input, delayTime, speed = 1) { this.write(input, delayTime); - return this.buffer[this.readIdx]; + let index = this.readIdx; + if (speed < 0) { + index = this.numSamples - Math.floor(Math.abs(this.readIdx * speed) % this.numSamples); + } else { + index = Math.floor(this.readIdx * speed) % this.numSamples; + } + return this.buffer[index]; } } @@ -550,6 +561,7 @@ const defaultDefaultValues = { delay: 0, byteBeatExpression: '0', delayfeedback: 0.5, + delayspeed: 1, delaytime: 0.25, orbit: 1, i: 1, @@ -662,6 +674,7 @@ export class DoughVoice { // delay $.delay = applyGainCurve($.delay ?? getDefaultValue('delay')); $.delayfeedback = $.delayfeedback ?? getDefaultValue('delayfeedback'); + $.delayspeed = $.delayspeed ?? getDefaultValue('delayspeed'); $.delaytime = $.delaytime ?? getDefaultValue('delaytime'); // precalculated values @@ -822,6 +835,7 @@ export class Dough { delaysend = [0, 0]; delaytime = getDefaultValue('delaytime'); delayfeedback = getDefaultValue('delayfeedback'); + delayspeed = getDefaultValue('delayspeed'); t = 0; // sampleRate: number, currentTime: number (seconds) constructor(sampleRate = 48000, currentTime = 0) { @@ -897,12 +911,13 @@ export class Dough { this.delaysend[0] += this.voices[v].out[0] * this.voices[v].delay; this.delaysend[1] += this.voices[v].out[1] * this.voices[v].delay; this.delaytime = this.voices[v].delaytime; // we trust that these are initialized in the voice + this.delayspeed = this.voices[v].delayspeed; // we trust that these are initialized in the voice this.delayfeedback = this.voices[v].delayfeedback; } } // todo: how to change delaytime / delayfeedback from a voice? - const delayL = this._delayL.update(this.delaysend[0], this.delaytime); - const delayR = this._delayR.update(this.delaysend[1], this.delaytime); + const delayL = this._delayL.update(this.delaysend[0], this.delaytime, this.delayspeed); + const delayR = this._delayR.update(this.delaysend[1], this.delaytime, this.delayspeed); this.delaysend[0] = delayL * this.delayfeedback; this.delaysend[1] = delayR * this.delayfeedback; this.out[0] += delayL;