From 2b3b6389431f9b38f0d26da37f0eac202c38f39f Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Fri, 26 Sep 2025 21:47:45 -0500 Subject: [PATCH 01/17] sample source for WT --- packages/superdough/sampler.mjs | 38 ++++++++++++++++++++++--------- packages/superdough/util.mjs | 1 + packages/superdough/wavetable.mjs | 17 ++++++++------ packages/superdough/worklets.mjs | 5 ++-- website/src/repl/idbutils.mjs | 11 +++------ 5 files changed, 44 insertions(+), 28 deletions(-) diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index 9188c17c..0e9633d4 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -1,5 +1,5 @@ import { noteToMidi, valueToMidi, getSoundIndex } from './util.mjs'; -import { getAudioContext, registerSound } from './index.mjs'; +import { getAudioContext, registerSound, registerWaveTable } from './index.mjs'; import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs'; import { logger } from './logger.mjs'; @@ -79,14 +79,14 @@ export const getSampleBufferSource = async (hapValue, bank, resolveUrl) => { bufferSource.buffer = buffer; bufferSource.playbackRate.value = playbackRate; - const { s, loopBegin = 0, loopEnd = 1, begin = 0, end = 1 } = hapValue; + const { loopBegin = 0, loopEnd = 1, begin = 0, end = 1 } = hapValue; // "The computation of the offset into the sound is performed using the sound buffer's natural sample rate, // rather than the current playback rate, so even if the sound is playing at twice its normal speed, // the midway point through a 10-second audio buffer is still 5." const offset = begin * bufferSource.buffer.duration; - const loop = s.startsWith('wt_') ? 1 : hapValue.loop; + const loop = hapValue.loop; if (loop) { bufferSource.loop = true; bufferSource.loopStart = loopBegin * bufferSource.buffer.duration - offset; @@ -267,16 +267,13 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option return samples(json, baseUrl || base, options); } const { prebake, tag } = options; + + processSampleMap( sampleMap, - (key, bank) => - registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, bank), { - type: 'sample', - samples: bank, - baseUrl, - prebake, - tag, - }), + (key, bank) => { + registerSample(key, bank, { baseUrl, prebake, tag }) + }, baseUrl, ); }; @@ -366,3 +363,22 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { return handle; } + + +function registerSample(key, bank, params) { + registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, bank), { + type: 'sample', + samples: bank, + ...params + }) +} + +export function registerSampleSource(key, bank, params) { + const isWavetable = key.startsWith('wt_'); + if (isWavetable) { + registerWaveTable(key,bank, params) + } else { + registerSample(key, bank, params) + } + +} \ No newline at end of file diff --git a/packages/superdough/util.mjs b/packages/superdough/util.mjs index 764ebb43..80dd31a9 100644 --- a/packages/superdough/util.mjs +++ b/packages/superdough/util.mjs @@ -76,3 +76,4 @@ export function cycleToSeconds(cycle, cps) { export function secondsToCycle(t, cps) { return t * cps; } + diff --git a/packages/superdough/wavetable.mjs b/packages/superdough/wavetable.mjs index c628549e..06765604 100644 --- a/packages/superdough/wavetable.mjs +++ b/packages/superdough/wavetable.mjs @@ -140,15 +140,18 @@ const _processTables = (json, baseUrl, frameLen) => { baseUrl = githubPath(baseUrl, ''); } value = value.map((v) => baseUrl + v); - registerSound(key, (t, hapValue, onended) => onTriggerSynth(t, hapValue, onended, value, frameLen), { - type: 'wavetable', - tables: value, - baseUrl, - frameLen, - }); + registerWaveTable(key,value, {baseUrl, frameLen}) }); }; +export function registerWaveTable(key, bank, params) { + registerSound(key, (t, hapValue, onended) => onTriggerSynth(t, hapValue, onended, bank, params?.frameLen ?? 2048), { + type: 'wavetable', + tables: bank, + ...params + }); +} + /** * Loads a collection of wavetables to use with `s` * @@ -179,7 +182,7 @@ export const tables = async (url, frameLen, json) => { }); }; -async function onTriggerSynth(t, value, onended, bank, frameLen) { +export async function onTriggerSynth(t, value, onended, bank, frameLen) { const { s, n = 0, duration } = value; const ac = getAudioContext(); const [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]); diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index af8a4a63..14093d09 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -1239,6 +1239,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { } const outL = outputs[0][0]; const outR = outputs[0][1] || outputs[0][0]; + const gainAdjustment = .15; if (!this.tables) { outL.fill(0); @@ -1284,8 +1285,8 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) { s = -s; } - outL[i] += (s * gainL) / Math.sqrt(voices); - outR[i] += (s * gainR) / Math.sqrt(voices); + outL[i] += ((s * gainL) / Math.sqrt(voices)) * gainAdjustment; + outR[i] += ((s * gainR) / Math.sqrt(voices)) * gainAdjustment; this.phase[n] = wrapPhase(this.phase[n] + dPhase); } } diff --git a/website/src/repl/idbutils.mjs b/website/src/repl/idbutils.mjs index f26ee047..5ac60455 100644 --- a/website/src/repl/idbutils.mjs +++ b/website/src/repl/idbutils.mjs @@ -1,4 +1,4 @@ -import { registerSound, onTriggerSample } from '@strudel/webaudio'; +import { registerSampleSource } from '@strudel/webaudio'; import { isAudioFile } from './files.mjs'; import { logger } from '@strudel/core'; @@ -76,13 +76,8 @@ export function registerSamplesFromDB(config = userSamplesDBConfig, onComplete = }) .map((title) => titlePathMap.get(title)); - registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), { - type: 'sample', - samples: value, - baseUrl: undefined, - prebake: false, - tag: undefined, - }); + registerSampleSource(key,value, {prebake: false}) + }); logger('imported sounds registered!', 'success'); From 97beaec25ae69d5ec146df1dfa77eaddd0d4fbed Mon Sep 17 00:00:00 2001 From: Aria Date: Fri, 26 Sep 2025 20:46:10 -0700 Subject: [PATCH 02/17] Added examples, fixed samplerate issue on import, added to settings tab, fixed spread, added default wavetables, change default phaserand to 0 --- packages/core/controls.mjs | 10 +- packages/superdough/wavetable.mjs | 90 +++++++--- packages/superdough/worklets.mjs | 23 +-- test/__snapshots__/examples.test.mjs.snap | 159 ++++++++++++++++++ website/public/uzu-wavetables.json | 54 ++++++ .../src/repl/components/panel/SoundsTab.jsx | 6 +- website/src/repl/prebake.mjs | 6 +- website/src/settings.mjs | 1 + 8 files changed, 311 insertions(+), 38 deletions(-) create mode 100644 website/public/uzu-wavetables.json diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 9b551970..3e5e9e63 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -93,7 +93,8 @@ export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound'); * @name wtPos * @param {number | Pattern} position Position in the wavetable from 0 to 1 * @synonyms wavetablePosition - * + * @example + * s("squelch").seg(8).note("F1").wtPos("0 0.25 0.5 0.75 1") */ export const { wtPos, wavetablePosition } = registerControl('wtPos', 'wavetablePosition'); @@ -103,7 +104,9 @@ export const { wtPos, wavetablePosition } = registerControl('wtPos', 'wavetableP * @name wtWarp * @param {number | Pattern} amount Warp of the wavetable from 0 to 1 * @synonyms wavetableWarp - * + * @example + * s("basique").seg(8).note("F1").wtWarp("0 0.25 0.5 0.75 1") + * .wtWarpMode("spin") */ export const { wtWarp, wavetableWarp } = registerControl('wtWarp', 'wavetableWarp'); @@ -116,6 +119,9 @@ export const { wtWarp, wavetableWarp } = registerControl('wtWarp', 'wavetableWar * @name wtWarpMode * @param {number | string | Pattern} mode Warp mode * @synonyms wavetableWarpMode + * @example + * s("morgana").seg(8).note("F1").wtWarp("0 0.25 0.5 0.75 1") + * .wtWarpMode("*2") * */ export const { wtWarpMode, wavetableWarpMode } = registerControl('wtWarpMode', 'wavetableWarpMode'); diff --git a/packages/superdough/wavetable.mjs b/packages/superdough/wavetable.mjs index c628549e..14706f92 100644 --- a/packages/superdough/wavetable.mjs +++ b/packages/superdough/wavetable.mjs @@ -39,8 +39,7 @@ export const WarpMode = Object.freeze({ }); async function loadWavetableFrames(url, label, frameLen = 256) { - const ac = getAudioContext(); - const buf = await loadBuffer(url, ac, label); + const buf = await loadBuffer(url, label); const ch0 = buf.getChannelData(0); const total = ch0.length; const numFrames = Math.floor(total / frameLen); @@ -96,7 +95,37 @@ export function getTableInfo(hapValue, tableUrls) { return { transpose, tableUrl, index, midi, label }; } -const loadBuffer = (url, ac, label) => { +// Extract the sample rate of a .wav file +function parseWavSampleRate(arrBuf) { + const dv = new DataView(arrBuf); + // Header is "RIFFWAVE", so 12 bytes + let p = 12; + // Look through chunks for the format header + // (they will always have an 8 byte header (id and size) followed by a payload) + while (p + 8 <= dv.byteLength) { + // Parse id + const id = String.fromCharCode(dv.getUint8(p), dv.getUint8(p + 1), dv.getUint8(p + 2), dv.getUint8(p + 3)); + // Parse chunk size + const size = dv.getUint32(p + 4, true); + if (id === 'fmt ') { + // The format chunk contains the sample rate after + // 8 bytes of header, 2 bytes of format tag, 2 bytes of num channels + // (for a total of 12) + return dv.getUint32(p + 12, true); + } + // Advance to next chunk + p += 8 + size + (size & 1); + } + return null; +} + +async function decodeAtNativeRate(arr) { + const sr = parseWavSampleRate(arr) || 44100; + const tempAC = new OfflineAudioContext(1, 1, sr); + return await tempAC.decodeAudioData(arr); +} + +const loadBuffer = (url, label) => { url = url.replace('#', '%23'); if (!loadCache[url]) { logger(`[wavetable] load table ${label}..`, 'load-table', { url }); @@ -107,7 +136,7 @@ const loadBuffer = (url, ac, label) => { const took = Date.now() - timestamp; const size = humanFileSize(res.byteLength); logger(`[wavetable] load table ${label}... done! loaded ${size} in ${took}ms`, 'loaded-table', { url }); - const decoded = await ac.decodeAudioData(res); + const decoded = await decodeAtNativeRate(res); return decoded; }); } @@ -127,25 +156,40 @@ function githubPath(base, subpath = '') { return `https://raw.githubusercontent.com/${path}/${subpath}`; } -const _processTables = (json, baseUrl, frameLen) => { - return Object.entries(json).forEach(([key, value]) => { - if (typeof value === 'string') { - value = [value]; +const _processTables = (json, baseUrl, frameLen, options = {}) => { + baseUrl = json._base || baseUrl; + return Object.entries(json).forEach(([key, tables]) => { + if (key === '_base') return false; + if (typeof tables === 'string') { + tables = [tables]; } - if (typeof value !== 'object') { + if (typeof tables !== 'object') { throw new Error('wrong json format for ' + key); } - baseUrl = value._base || baseUrl; - if (baseUrl.startsWith('github:')) { - baseUrl = githubPath(baseUrl, ''); + let resolvedUrl = baseUrl; + if (resolvedUrl.startsWith('github:')) { + resolvedUrl = githubPath(resolvedUrl, ''); + } + tables = tables + .map((t) => resolvedUrl + t) + .filter((t) => { + if (!t.toLowerCase().endsWith('.wav')) { + logger(`[wavetable] skipping ${t} -- wavetables must be ".wav" format`); + return false; + } + return true; + }); + if (tables.length) { + const { prebake, tag } = options; + registerSound(key, (t, hapValue, onended) => onTriggerSynth(t, hapValue, onended, tables, frameLen), { + type: 'wavetable', + tables, + baseUrl, + frameLen, + prebake, + tag, + }); } - value = value.map((v) => baseUrl + v); - registerSound(key, (t, hapValue, onended) => onTriggerSynth(t, hapValue, onended, value, frameLen), { - type: 'wavetable', - tables: value, - baseUrl, - frameLen, - }); }); }; @@ -154,7 +198,7 @@ const _processTables = (json, baseUrl, frameLen) => { * * @name tables */ -export const tables = async (url, frameLen, json) => { +export const tables = async (url, frameLen, json, options = {}) => { if (json !== undefined) return _processTables(json, url, frameLen); if (url.startsWith('github:')) { url = githubPath(url, 'strudel.json'); @@ -172,14 +216,14 @@ export const tables = async (url, frameLen, json) => { } return fetch(url) .then((res) => res.json()) - .then((json) => _processTables(json, url, frameLen)) + .then((json) => _processTables(json, url, frameLen, options)) .catch((error) => { console.error(error); throw new Error(`error loading "${url}"`); }); }; -async function onTriggerSynth(t, value, onended, bank, frameLen) { +async function onTriggerSynth(t, value, onended, tables, frameLen) { const { s, n = 0, duration } = value; const ac = getAudioContext(); const [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]); @@ -188,7 +232,7 @@ async function onTriggerSynth(t, value, onended, bank, frameLen) { wtWarpMode = WarpMode[wtWarpMode.toUpperCase()] ?? WarpMode.NONE; } const frequency = getFrequencyFromValue(value); - const { tableUrl, label } = getTableInfo(value, bank); + const { tableUrl, label } = getTableInfo(value, tables); const payload = await loadWavetableFrames(tableUrl, label, frameLen); const holdEnd = t + duration; const envEnd = holdEnd + release + 0.01; diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index daf19e53..dbfb78f8 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -988,7 +988,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { { name: 'warpMode', defaultValue: 0 }, { name: 'voices', defaultValue: 1, minValue: 1, maxValue: 32 }, { name: 'spread', defaultValue: 0, minValue: 0, maxValue: 1 }, - { name: 'phaserand', defaultValue: 1, minValue: 0, maxValue: 1 }, + { name: 'phaserand', defaultValue: 0, minValue: 0, maxValue: 1 }, ]; } @@ -1155,10 +1155,10 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { } _sampleFrame(frame, phase) { - const pos = phase * (frame.length - 1); + const pos = phase * frame.length; const i = pos | 0; const frac = pos - i; - const a = frame[i]; + const a = frame[i % frame.length]; const b = frame[(i + 1) % frame.length]; return a + (b - a) * frac; } @@ -1181,7 +1181,6 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { for (let i = 0; i < outL.length; i++) { const detune = pv(parameters.detune, i); - const spread = pv(parameters.spread, i) * 0.5 + 0.5; const tablePos = pv(parameters.position, i); const idx = tablePos * (this.numFrames - 1); const fIdx = idx | 0; @@ -1189,11 +1188,13 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { const warpAmount = pv(parameters.warp, i); const warpMode = pv(parameters.warpMode, i); const voices = pv(parameters.voices, i); + const spread = voices > 1 ? pv(parameters.spread, i) : 0; const phaseRand = pv(parameters.phaserand, i); - const gain1 = Math.sqrt(1 - spread); - const gain2 = Math.sqrt(spread); + const gain1 = Math.sqrt(0.5 - 0.5 * spread); + const gain2 = Math.sqrt(0.5 + 0.5 * spread); let f = pv(parameters.frequency, i); f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune + const normalizer = 0.3 / Math.sqrt(voices); for (let n = 0; n < voices; n++) { const isOdd = (n & 1) == 1; let gainL = gain1; @@ -1206,19 +1207,19 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, detune, n)); // voice detune const dPhase = fVoice / sampleRate; const level = this._chooseMip(dPhase); - const bank = this.tables[level]; + const table = this.tables[level]; // warp phase then sample this.phase[n] = this.phase[n] ?? Math.random() * phaseRand; const ph = this._warpPhase(this.phase[n], warpAmount, warpMode); - const s0 = this._sampleFrame(bank[fIdx], ph); - const s1 = this._sampleFrame(bank[Math.min(this.numFrames - 1, fIdx + 1)], ph); + const s0 = this._sampleFrame(table[fIdx], ph); + const s1 = this._sampleFrame(table[Math.min(this.numFrames - 1, fIdx + 1)], ph); let s = s0 + (s1 - s0) * frac; if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) { s = -s; } - outL[i] += (s * gainL) / Math.sqrt(voices); - outR[i] += (s * gainR) / Math.sqrt(voices); + outL[i] += s * gainL * normalizer; + outR[i] += s * gainR * normalizer; this.phase[n] = wrapPhase(this.phase[n] + dPhase); } } diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index f91503ca..3305def1 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -11981,6 +11981,165 @@ exports[`runs examples > example "withValue" example index 0 1`] = ` ] `; +exports[`runs examples > example "wtPos" example index 0 1`] = ` +[ + "[ 0/1 → 1/8 | s:squelch note:F1 wtPos:0 ]", + "[ (1/8 → 1/5) ⇝ 1/4 | s:squelch note:F1 wtPos:0 ]", + "[ 1/8 ⇜ (1/5 → 1/4) | s:squelch note:F1 wtPos:0.25 ]", + "[ 1/4 → 3/8 | s:squelch note:F1 wtPos:0.25 ]", + "[ (3/8 → 2/5) ⇝ 1/2 | s:squelch note:F1 wtPos:0.25 ]", + "[ 3/8 ⇜ (2/5 → 1/2) | s:squelch note:F1 wtPos:0.5 ]", + "[ (1/2 → 3/5) ⇝ 5/8 | s:squelch note:F1 wtPos:0.5 ]", + "[ 1/2 ⇜ (3/5 → 5/8) | s:squelch note:F1 wtPos:0.75 ]", + "[ 5/8 → 3/4 | s:squelch note:F1 wtPos:0.75 ]", + "[ (3/4 → 4/5) ⇝ 7/8 | s:squelch note:F1 wtPos:0.75 ]", + "[ 3/4 ⇜ (4/5 → 7/8) | s:squelch note:F1 wtPos:1 ]", + "[ 7/8 → 1/1 | s:squelch note:F1 wtPos:1 ]", + "[ 1/1 → 9/8 | s:squelch note:F1 wtPos:0 ]", + "[ (9/8 → 6/5) ⇝ 5/4 | s:squelch note:F1 wtPos:0 ]", + "[ 9/8 ⇜ (6/5 → 5/4) | s:squelch note:F1 wtPos:0.25 ]", + "[ 5/4 → 11/8 | s:squelch note:F1 wtPos:0.25 ]", + "[ (11/8 → 7/5) ⇝ 3/2 | s:squelch note:F1 wtPos:0.25 ]", + "[ 11/8 ⇜ (7/5 → 3/2) | s:squelch note:F1 wtPos:0.5 ]", + "[ (3/2 → 8/5) ⇝ 13/8 | s:squelch note:F1 wtPos:0.5 ]", + "[ 3/2 ⇜ (8/5 → 13/8) | s:squelch note:F1 wtPos:0.75 ]", + "[ 13/8 → 7/4 | s:squelch note:F1 wtPos:0.75 ]", + "[ (7/4 → 9/5) ⇝ 15/8 | s:squelch note:F1 wtPos:0.75 ]", + "[ 7/4 ⇜ (9/5 → 15/8) | s:squelch note:F1 wtPos:1 ]", + "[ 15/8 → 2/1 | s:squelch note:F1 wtPos:1 ]", + "[ 2/1 → 17/8 | s:squelch note:F1 wtPos:0 ]", + "[ (17/8 → 11/5) ⇝ 9/4 | s:squelch note:F1 wtPos:0 ]", + "[ 17/8 ⇜ (11/5 → 9/4) | s:squelch note:F1 wtPos:0.25 ]", + "[ 9/4 → 19/8 | s:squelch note:F1 wtPos:0.25 ]", + "[ (19/8 → 12/5) ⇝ 5/2 | s:squelch note:F1 wtPos:0.25 ]", + "[ 19/8 ⇜ (12/5 → 5/2) | s:squelch note:F1 wtPos:0.5 ]", + "[ (5/2 → 13/5) ⇝ 21/8 | s:squelch note:F1 wtPos:0.5 ]", + "[ 5/2 ⇜ (13/5 → 21/8) | s:squelch note:F1 wtPos:0.75 ]", + "[ 21/8 → 11/4 | s:squelch note:F1 wtPos:0.75 ]", + "[ (11/4 → 14/5) ⇝ 23/8 | s:squelch note:F1 wtPos:0.75 ]", + "[ 11/4 ⇜ (14/5 → 23/8) | s:squelch note:F1 wtPos:1 ]", + "[ 23/8 → 3/1 | s:squelch note:F1 wtPos:1 ]", + "[ 3/1 → 25/8 | s:squelch note:F1 wtPos:0 ]", + "[ (25/8 → 16/5) ⇝ 13/4 | s:squelch note:F1 wtPos:0 ]", + "[ 25/8 ⇜ (16/5 → 13/4) | s:squelch note:F1 wtPos:0.25 ]", + "[ 13/4 → 27/8 | s:squelch note:F1 wtPos:0.25 ]", + "[ (27/8 → 17/5) ⇝ 7/2 | s:squelch note:F1 wtPos:0.25 ]", + "[ 27/8 ⇜ (17/5 → 7/2) | s:squelch note:F1 wtPos:0.5 ]", + "[ (7/2 → 18/5) ⇝ 29/8 | s:squelch note:F1 wtPos:0.5 ]", + "[ 7/2 ⇜ (18/5 → 29/8) | s:squelch note:F1 wtPos:0.75 ]", + "[ 29/8 → 15/4 | s:squelch note:F1 wtPos:0.75 ]", + "[ (15/4 → 19/5) ⇝ 31/8 | s:squelch note:F1 wtPos:0.75 ]", + "[ 15/4 ⇜ (19/5 → 31/8) | s:squelch note:F1 wtPos:1 ]", + "[ 31/8 → 4/1 | s:squelch note:F1 wtPos:1 ]", +] +`; + +exports[`runs examples > example "wtWarp" example index 0 1`] = ` +[ + "[ 0/1 → 1/8 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]", + "[ (1/8 → 1/5) ⇝ 1/4 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]", + "[ 1/8 ⇜ (1/5 → 1/4) | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", + "[ 1/4 → 3/8 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", + "[ (3/8 → 2/5) ⇝ 1/2 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", + "[ 3/8 ⇜ (2/5 → 1/2) | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]", + "[ (1/2 → 3/5) ⇝ 5/8 | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]", + "[ 1/2 ⇜ (3/5 → 5/8) | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", + "[ 5/8 → 3/4 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", + "[ (3/4 → 4/5) ⇝ 7/8 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", + "[ 3/4 ⇜ (4/5 → 7/8) | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]", + "[ 7/8 → 1/1 | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]", + "[ 1/1 → 9/8 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]", + "[ (9/8 → 6/5) ⇝ 5/4 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]", + "[ 9/8 ⇜ (6/5 → 5/4) | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", + "[ 5/4 → 11/8 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", + "[ (11/8 → 7/5) ⇝ 3/2 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", + "[ 11/8 ⇜ (7/5 → 3/2) | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]", + "[ (3/2 → 8/5) ⇝ 13/8 | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]", + "[ 3/2 ⇜ (8/5 → 13/8) | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", + "[ 13/8 → 7/4 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", + "[ (7/4 → 9/5) ⇝ 15/8 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", + "[ 7/4 ⇜ (9/5 → 15/8) | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]", + "[ 15/8 → 2/1 | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]", + "[ 2/1 → 17/8 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]", + "[ (17/8 → 11/5) ⇝ 9/4 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]", + "[ 17/8 ⇜ (11/5 → 9/4) | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", + "[ 9/4 → 19/8 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", + "[ (19/8 → 12/5) ⇝ 5/2 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", + "[ 19/8 ⇜ (12/5 → 5/2) | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]", + "[ (5/2 → 13/5) ⇝ 21/8 | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]", + "[ 5/2 ⇜ (13/5 → 21/8) | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", + "[ 21/8 → 11/4 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", + "[ (11/4 → 14/5) ⇝ 23/8 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", + "[ 11/4 ⇜ (14/5 → 23/8) | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]", + "[ 23/8 → 3/1 | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]", + "[ 3/1 → 25/8 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]", + "[ (25/8 → 16/5) ⇝ 13/4 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]", + "[ 25/8 ⇜ (16/5 → 13/4) | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", + "[ 13/4 → 27/8 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", + "[ (27/8 → 17/5) ⇝ 7/2 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", + "[ 27/8 ⇜ (17/5 → 7/2) | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]", + "[ (7/2 → 18/5) ⇝ 29/8 | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]", + "[ 7/2 ⇜ (18/5 → 29/8) | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", + "[ 29/8 → 15/4 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", + "[ (15/4 → 19/5) ⇝ 31/8 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", + "[ 15/4 ⇜ (19/5 → 31/8) | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]", + "[ 31/8 → 4/1 | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]", +] +`; + +exports[`runs examples > example "wtWarpMode" example index 0 1`] = ` +[ + "[ 0/1 → 1/8 | s:morgana note:F1 wtWarp:0 wtWarpMode:asym ]", + "[ (1/8 → 1/5) ⇝ 1/4 | s:morgana note:F1 wtWarp:0 wtWarpMode:asym ]", + "[ 1/8 ⇜ (1/5 → 1/4) | s:morgana note:F1 wtWarp:0.25 wtWarpMode:asym ]", + "[ 1/4 → 3/8 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:asym ]", + "[ (3/8 → 2/5) ⇝ 1/2 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:asym ]", + "[ 3/8 ⇜ (2/5 → 1/2) | s:morgana note:F1 wtWarp:0.5 wtWarpMode:asym ]", + "[ (1/2 → 3/5) ⇝ 5/8 | s:morgana note:F1 wtWarp:0.5 wtWarpMode:bendp ]", + "[ 1/2 ⇜ (3/5 → 5/8) | s:morgana note:F1 wtWarp:0.75 wtWarpMode:bendp ]", + "[ 5/8 → 3/4 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:bendp ]", + "[ (3/4 → 4/5) ⇝ 7/8 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:bendp ]", + "[ 3/4 ⇜ (4/5 → 7/8) | s:morgana note:F1 wtWarp:1 wtWarpMode:bendp ]", + "[ 7/8 → 1/1 | s:morgana note:F1 wtWarp:1 wtWarpMode:bendp ]", + "[ 1/1 → 9/8 | s:morgana note:F1 wtWarp:0 wtWarpMode:spin ]", + "[ (9/8 → 6/5) ⇝ 5/4 | s:morgana note:F1 wtWarp:0 wtWarpMode:spin ]", + "[ 9/8 ⇜ (6/5 → 5/4) | s:morgana note:F1 wtWarp:0.25 wtWarpMode:spin ]", + "[ 5/4 → 11/8 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:spin ]", + "[ (11/8 → 7/5) ⇝ 3/2 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:spin ]", + "[ 11/8 ⇜ (7/5 → 3/2) | s:morgana note:F1 wtWarp:0.5 wtWarpMode:spin ]", + "[ (3/2 → 8/5) ⇝ 13/8 | s:morgana note:F1 wtWarp:0.5 wtWarpMode:logistic ]", + "[ 3/2 ⇜ (8/5 → 13/8) | s:morgana note:F1 wtWarp:0.75 wtWarpMode:logistic ]", + "[ 13/8 → 7/4 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:logistic ]", + "[ (7/4 → 9/5) ⇝ 15/8 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:logistic ]", + "[ 7/4 ⇜ (9/5 → 15/8) | s:morgana note:F1 wtWarp:1 wtWarpMode:logistic ]", + "[ 15/8 → 2/1 | s:morgana note:F1 wtWarp:1 wtWarpMode:logistic ]", + "[ 2/1 → 17/8 | s:morgana note:F1 wtWarp:0 wtWarpMode:sync ]", + "[ (17/8 → 11/5) ⇝ 9/4 | s:morgana note:F1 wtWarp:0 wtWarpMode:sync ]", + "[ 17/8 ⇜ (11/5 → 9/4) | s:morgana note:F1 wtWarp:0.25 wtWarpMode:sync ]", + "[ 9/4 → 19/8 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:sync ]", + "[ (19/8 → 12/5) ⇝ 5/2 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:sync ]", + "[ 19/8 ⇜ (12/5 → 5/2) | s:morgana note:F1 wtWarp:0.5 wtWarpMode:sync ]", + "[ (5/2 → 13/5) ⇝ 21/8 | s:morgana note:F1 wtWarp:0.5 wtWarpMode:wormhole ]", + "[ 5/2 ⇜ (13/5 → 21/8) | s:morgana note:F1 wtWarp:0.75 wtWarpMode:wormhole ]", + "[ 21/8 → 11/4 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:wormhole ]", + "[ (11/4 → 14/5) ⇝ 23/8 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:wormhole ]", + "[ 11/4 ⇜ (14/5 → 23/8) | s:morgana note:F1 wtWarp:1 wtWarpMode:wormhole ]", + "[ 23/8 → 3/1 | s:morgana note:F1 wtWarp:1 wtWarpMode:wormhole ]", + "[ 3/1 → 25/8 | s:morgana note:F1 wtWarp:0 wtWarpMode:brownian ]", + "[ (25/8 → 16/5) ⇝ 13/4 | s:morgana note:F1 wtWarp:0 wtWarpMode:brownian ]", + "[ 25/8 ⇜ (16/5 → 13/4) | s:morgana note:F1 wtWarp:0.25 wtWarpMode:brownian ]", + "[ 13/4 → 27/8 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:brownian ]", + "[ (27/8 → 17/5) ⇝ 7/2 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:brownian ]", + "[ 27/8 ⇜ (17/5 → 7/2) | s:morgana note:F1 wtWarp:0.5 wtWarpMode:brownian ]", + "[ (7/2 → 18/5) ⇝ 29/8 | s:morgana note:F1 wtWarp:0.5 wtWarpMode:asym ]", + "[ 7/2 ⇜ (18/5 → 29/8) | s:morgana note:F1 wtWarp:0.75 wtWarpMode:asym ]", + "[ 29/8 → 15/4 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:asym ]", + "[ (15/4 → 19/5) ⇝ 31/8 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:asym ]", + "[ 15/4 ⇜ (19/5 → 31/8) | s:morgana note:F1 wtWarp:1 wtWarpMode:asym ]", + "[ 31/8 → 4/1 | s:morgana note:F1 wtWarp:1 wtWarpMode:asym ]", +] +`; + exports[`runs examples > example "xfade" example index 0 1`] = ` [ "[ 0/1 → 1/8 | s:hh gain:0 ]", diff --git a/website/public/uzu-wavetables.json b/website/public/uzu-wavetables.json new file mode 100644 index 00000000..4497791f --- /dev/null +++ b/website/public/uzu-wavetables.json @@ -0,0 +1,54 @@ +{ + "_base": "http://localhost:5432", + "Bad Day": [ + "/Bad Day.wav" + ], + "Basique": [ + "/Basique.wav" + ], + "Crickets": [ + "/Crickets.wav" + ], + "Curses": [ + "/Curses.wav" + ], + "Earl Grey": [ + "/Earl Grey.wav" + ], + "Echoes": [ + "/Echoes.wav" + ], + "Glimmer": [ + "/Glimmer.wav" + ], + "Majick": [ + "/Majick.wav" + ], + "Meditation": [ + "/Meditation.wav" + ], + "Morgana": [ + "/Morgana.wav" + ], + "Red Alert": [ + "/Red Alert.wav" + ], + "Sad Piano": [ + "/Sad Piano.wav" + ], + "Shook": [ + "/Shook.wav" + ], + "Sludge": [ + "/Sludge.wav" + ], + "Squelch": [ + "/Squelch.wav" + ], + "Summers Day": [ + "/Summers Day.wav" + ], + "Wasp": [ + "/Wasp.wav" + ] +} \ No newline at end of file diff --git a/website/src/repl/components/panel/SoundsTab.jsx b/website/src/repl/components/panel/SoundsTab.jsx index 0484da02..ff64fc18 100644 --- a/website/src/repl/components/panel/SoundsTab.jsx +++ b/website/src/repl/components/panel/SoundsTab.jsx @@ -44,6 +44,9 @@ export function SoundsTab() { if (soundsFilter === soundFilterType.SYNTHS) { return filtered.filter(([_, { data }]) => ['synth', 'soundfont'].includes(data.type)); } + if (soundsFilter === soundFilterType.WAVETABLES) { + return filtered.filter(([_, { data }]) => data.type === 'wavetable'); + } //TODO: tidy this up, it does not need to be saved in settings if (soundsFilter === 'importSounds') { return []; @@ -74,6 +77,7 @@ export function SoundsTab() { samples: 'samples', drums: 'drum-machines', synths: 'Synths', + wavetables: 'Wavetables', user: 'User', importSounds: 'import-sounds', }} @@ -125,7 +129,7 @@ export function SoundsTab() { > {' '} {name} - {data?.type === 'sample' ? `(${getSamples(data.samples)})` : ''} + {data?.type === 'sample' || data?.type === 'wavetable' ? `(${getSamples(data.samples)})` : ''} {data?.type === 'soundfont' ? `(${data.fonts.length})` : ''} ); diff --git a/website/src/repl/prebake.mjs b/website/src/repl/prebake.mjs index fac6f5bb..855798fd 100644 --- a/website/src/repl/prebake.mjs +++ b/website/src/repl/prebake.mjs @@ -1,5 +1,5 @@ import { Pattern, noteToMidi, valueToMidi } from '@strudel/core'; -import { aliasBank, registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio'; +import { aliasBank, registerSynthSounds, registerZZFXSounds, samples, tables } from '@strudel/webaudio'; import { registerSamplesFromDB } from './idbutils.mjs'; import './piano.mjs'; import './files.mjs'; @@ -32,6 +32,10 @@ export async function prebake() { prebake: true, tag: 'drum-machines', }), + tables(`${baseNoTrailing}/uzu-wavetables.json`, 2048, undefined, { + prebake: true, + tag: 'wavetables', + }), samples(`${baseNoTrailing}/mridangam.json`, undefined, { prebake: true, tag: 'drum-machines' }), samples( { diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 9c3d7814..9365a6db 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -13,6 +13,7 @@ export const soundFilterType = { DRUMS: 'drums', SAMPLES: 'samples', SYNTHS: 'synths', + WAVETABLES: 'wavetables', ALL: 'all', }; From 4bd103e61200fe888196807b7c43a9ff206cb38c Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 27 Sep 2025 11:31:43 -0400 Subject: [PATCH 03/17] gain adj --- packages/superdough/worklets.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 14093d09..826e9610 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -1049,7 +1049,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { { name: 'begin', defaultValue: 0, min: 0, max: Number.POSITIVE_INFINITY }, { name: 'end', defaultValue: 0, min: 0, max: Number.POSITIVE_INFINITY }, { name: 'frequency', defaultValue: 220, minValue: 0.01, maxValue: 20000 }, - { name: 'detune', defaultValue: 0 }, + { name: 'detune', defaultValue: .18 }, { name: 'position', defaultValue: 0, minValue: 0, maxValue: 1 }, { name: 'warp', defaultValue: 0, minValue: 0, maxValue: 1 }, { name: 'warpMode', defaultValue: 0 }, From a9957b45e5ce101fe3142d9dbeb6112cacc188a8 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 27 Sep 2025 13:22:21 -0400 Subject: [PATCH 04/17] suppoert existing wt_ api --- packages/superdough/sampler.mjs | 2 +- website/public/uzu-wavetables.json | 22 ++++++++++++++++++++++ website/src/repl/prebake.mjs | 3 +++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 website/public/uzu-wavetables.json diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index 0e9633d4..ef4bdc54 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -272,7 +272,7 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option processSampleMap( sampleMap, (key, bank) => { - registerSample(key, bank, { baseUrl, prebake, tag }) + registerSampleSource(key, bank, { baseUrl, prebake, tag }) }, baseUrl, ); diff --git a/website/public/uzu-wavetables.json b/website/public/uzu-wavetables.json new file mode 100644 index 00000000..be8a4e16 --- /dev/null +++ b/website/public/uzu-wavetables.json @@ -0,0 +1,22 @@ +{ + "_base": "https://raw.githubusercontent.com/tidalcycles/uzu-wavetables/main/", + "wt_digital": [ + "wt_digital/wt_bad_day.wav", + "wt_digital/wt_basique.wav", + "wt_digital/wt_crickets.wav", + "wt_digital/wt_curses.wav", + "wt_digital/wt_earl_grey.wav", + "wt_digital/wt_echoes.wav", + "wt_digital/wt_glimmer.wav", + "wt_digital/wt_majick.wav", + "wt_digital/wt_meditation.wav", + "wt_digital/wt_morgana.wav", + "wt_digital/wt_red_alert.wav", + "wt_digital/wt_sad_piano.wav", + "wt_digital/wt_shook.wav", + "wt_digital/wt_sludge.wav", + "wt_digital/wt_squelch.wav", + "wt_digital/wt_summer.wav", + "wt_digital/wt_wasp.wav" + ] +} \ No newline at end of file diff --git a/website/src/repl/prebake.mjs b/website/src/repl/prebake.mjs index fac6f5bb..0b552b87 100644 --- a/website/src/repl/prebake.mjs +++ b/website/src/repl/prebake.mjs @@ -32,6 +32,9 @@ export async function prebake() { prebake: true, tag: 'drum-machines', }), + samples(`${baseNoTrailing}/uzu-wavetables.json`, undefined, { + prebake: true, + }), samples(`${baseNoTrailing}/mridangam.json`, undefined, { prebake: true, tag: 'drum-machines' }), samples( { From a8918d55fb35cb66279c64f1721c7b8a5d0d693b Mon Sep 17 00:00:00 2001 From: Aria Date: Sat, 27 Sep 2025 10:45:30 -0700 Subject: [PATCH 05/17] Actually update normalizer --- packages/superdough/worklets.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 8c40c276..dc0cf807 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -1261,7 +1261,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { const gain2 = Math.sqrt(0.5 + 0.5 * spread); let f = pv(parameters.frequency, i); f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune - const normalizer = 0.3 / Math.sqrt(voices); + const normalizer = 0.3 / voices; for (let n = 0; n < voices; n++) { const isOdd = (n & 1) == 1; let gainL = gain1; From c5c05ebadc749824de9d77a9ad5640f519ac161f Mon Sep 17 00:00:00 2001 From: Aria Date: Sat, 27 Sep 2025 11:02:57 -0700 Subject: [PATCH 06/17] Clean up to align with other PR --- packages/core/controls.mjs | 8 +- packages/superdough/worklets.mjs | 2 +- test/__snapshots__/examples.test.mjs.snap | 159 ---------------------- website/public/uzu-wavetables.json | 54 -------- website/src/repl/prebake.mjs | 4 - 5 files changed, 6 insertions(+), 221 deletions(-) delete mode 100644 website/public/uzu-wavetables.json diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 73e44473..fef0e45e 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -94,7 +94,7 @@ export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound'); * @param {number | Pattern} position Position in the wavetable from 0 to 1 * @synonyms wavetablePosition * @example - * s("squelch").seg(8).note("F1").wtPos("0 0.25 0.5 0.75 1") + * s("squelch").bank("wt_digital").seg(8).note("F1").wtPos("0 0.25 0.5 0.75 1") */ export const { wtPos, wavetablePosition } = registerControl('wtPos', 'wavetablePosition'); @@ -105,7 +105,7 @@ export const { wtPos, wavetablePosition } = registerControl('wtPos', 'wavetableP * @param {number | Pattern} amount Warp of the wavetable from 0 to 1 * @synonyms wavetableWarp * @example - * s("basique").seg(8).note("F1").wtWarp("0 0.25 0.5 0.75 1") + * s("basique").bank("wt_digital").seg(8).note("F1").wtWarp("0 0.25 0.5 0.75 1") * .wtWarpMode("spin") */ export const { wtWarp, wavetableWarp } = registerControl('wtWarp', 'wavetableWarp'); @@ -120,7 +120,7 @@ export const { wtWarp, wavetableWarp } = registerControl('wtWarp', 'wavetableWar * @param {number | string | Pattern} mode Warp mode * @synonyms wavetableWarpMode * @example - * s("morgana").seg(8).note("F1").wtWarp("0 0.25 0.5 0.75 1") + * s("morgana").bank("wt_digital").seg(8).note("F1").wtWarp("0 0.25 0.5 0.75 1") * .wtWarpMode("*2") * */ @@ -132,6 +132,8 @@ export const { wtWarpMode, wavetableWarpMode } = registerControl('wtWarpMode', ' * @name wtPhaseRand * @param {number | Pattern} amount Randomness of the initial phase. Between 0 (not random) and 1 (fully random) * @synonyms wavetablePhaseRand + * @example + * s("basique").bank("wt_digital").seg(16).wtPhaseRand("<0 1>") * */ export const { wtPhaseRand, wavetablePhaseRand } = registerControl('wtPhaseRand', 'wavetablePhaseRand'); diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index dc0cf807..a83ad9e4 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -1054,7 +1054,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { { name: 'warp', defaultValue: 0, minValue: 0, maxValue: 1 }, { name: 'warpMode', defaultValue: 0 }, { name: 'voices', defaultValue: 1, minValue: 1, maxValue: 32 }, - { name: 'spread', defaultValue: 0, minValue: 0, maxValue: 1 }, + { name: 'spread', defaultValue: 0.18, minValue: 0, maxValue: 1 }, { name: 'phaserand', defaultValue: 0, minValue: 0, maxValue: 1 }, ]; } diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index c2135e10..a0597558 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -11993,165 +11993,6 @@ exports[`runs examples > example "withValue" example index 0 1`] = ` ] `; -exports[`runs examples > example "wtPos" example index 0 1`] = ` -[ - "[ 0/1 → 1/8 | s:squelch note:F1 wtPos:0 ]", - "[ (1/8 → 1/5) ⇝ 1/4 | s:squelch note:F1 wtPos:0 ]", - "[ 1/8 ⇜ (1/5 → 1/4) | s:squelch note:F1 wtPos:0.25 ]", - "[ 1/4 → 3/8 | s:squelch note:F1 wtPos:0.25 ]", - "[ (3/8 → 2/5) ⇝ 1/2 | s:squelch note:F1 wtPos:0.25 ]", - "[ 3/8 ⇜ (2/5 → 1/2) | s:squelch note:F1 wtPos:0.5 ]", - "[ (1/2 → 3/5) ⇝ 5/8 | s:squelch note:F1 wtPos:0.5 ]", - "[ 1/2 ⇜ (3/5 → 5/8) | s:squelch note:F1 wtPos:0.75 ]", - "[ 5/8 → 3/4 | s:squelch note:F1 wtPos:0.75 ]", - "[ (3/4 → 4/5) ⇝ 7/8 | s:squelch note:F1 wtPos:0.75 ]", - "[ 3/4 ⇜ (4/5 → 7/8) | s:squelch note:F1 wtPos:1 ]", - "[ 7/8 → 1/1 | s:squelch note:F1 wtPos:1 ]", - "[ 1/1 → 9/8 | s:squelch note:F1 wtPos:0 ]", - "[ (9/8 → 6/5) ⇝ 5/4 | s:squelch note:F1 wtPos:0 ]", - "[ 9/8 ⇜ (6/5 → 5/4) | s:squelch note:F1 wtPos:0.25 ]", - "[ 5/4 → 11/8 | s:squelch note:F1 wtPos:0.25 ]", - "[ (11/8 → 7/5) ⇝ 3/2 | s:squelch note:F1 wtPos:0.25 ]", - "[ 11/8 ⇜ (7/5 → 3/2) | s:squelch note:F1 wtPos:0.5 ]", - "[ (3/2 → 8/5) ⇝ 13/8 | s:squelch note:F1 wtPos:0.5 ]", - "[ 3/2 ⇜ (8/5 → 13/8) | s:squelch note:F1 wtPos:0.75 ]", - "[ 13/8 → 7/4 | s:squelch note:F1 wtPos:0.75 ]", - "[ (7/4 → 9/5) ⇝ 15/8 | s:squelch note:F1 wtPos:0.75 ]", - "[ 7/4 ⇜ (9/5 → 15/8) | s:squelch note:F1 wtPos:1 ]", - "[ 15/8 → 2/1 | s:squelch note:F1 wtPos:1 ]", - "[ 2/1 → 17/8 | s:squelch note:F1 wtPos:0 ]", - "[ (17/8 → 11/5) ⇝ 9/4 | s:squelch note:F1 wtPos:0 ]", - "[ 17/8 ⇜ (11/5 → 9/4) | s:squelch note:F1 wtPos:0.25 ]", - "[ 9/4 → 19/8 | s:squelch note:F1 wtPos:0.25 ]", - "[ (19/8 → 12/5) ⇝ 5/2 | s:squelch note:F1 wtPos:0.25 ]", - "[ 19/8 ⇜ (12/5 → 5/2) | s:squelch note:F1 wtPos:0.5 ]", - "[ (5/2 → 13/5) ⇝ 21/8 | s:squelch note:F1 wtPos:0.5 ]", - "[ 5/2 ⇜ (13/5 → 21/8) | s:squelch note:F1 wtPos:0.75 ]", - "[ 21/8 → 11/4 | s:squelch note:F1 wtPos:0.75 ]", - "[ (11/4 → 14/5) ⇝ 23/8 | s:squelch note:F1 wtPos:0.75 ]", - "[ 11/4 ⇜ (14/5 → 23/8) | s:squelch note:F1 wtPos:1 ]", - "[ 23/8 → 3/1 | s:squelch note:F1 wtPos:1 ]", - "[ 3/1 → 25/8 | s:squelch note:F1 wtPos:0 ]", - "[ (25/8 → 16/5) ⇝ 13/4 | s:squelch note:F1 wtPos:0 ]", - "[ 25/8 ⇜ (16/5 → 13/4) | s:squelch note:F1 wtPos:0.25 ]", - "[ 13/4 → 27/8 | s:squelch note:F1 wtPos:0.25 ]", - "[ (27/8 → 17/5) ⇝ 7/2 | s:squelch note:F1 wtPos:0.25 ]", - "[ 27/8 ⇜ (17/5 → 7/2) | s:squelch note:F1 wtPos:0.5 ]", - "[ (7/2 → 18/5) ⇝ 29/8 | s:squelch note:F1 wtPos:0.5 ]", - "[ 7/2 ⇜ (18/5 → 29/8) | s:squelch note:F1 wtPos:0.75 ]", - "[ 29/8 → 15/4 | s:squelch note:F1 wtPos:0.75 ]", - "[ (15/4 → 19/5) ⇝ 31/8 | s:squelch note:F1 wtPos:0.75 ]", - "[ 15/4 ⇜ (19/5 → 31/8) | s:squelch note:F1 wtPos:1 ]", - "[ 31/8 → 4/1 | s:squelch note:F1 wtPos:1 ]", -] -`; - -exports[`runs examples > example "wtWarp" example index 0 1`] = ` -[ - "[ 0/1 → 1/8 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ (1/8 → 1/5) ⇝ 1/4 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ 1/8 ⇜ (1/5 → 1/4) | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 1/4 → 3/8 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ (3/8 → 2/5) ⇝ 1/2 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 3/8 ⇜ (2/5 → 1/2) | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ (1/2 → 3/5) ⇝ 5/8 | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ 1/2 ⇜ (3/5 → 5/8) | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ 5/8 → 3/4 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ (3/4 → 4/5) ⇝ 7/8 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ 3/4 ⇜ (4/5 → 7/8) | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]", - "[ 7/8 → 1/1 | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]", - "[ 1/1 → 9/8 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ (9/8 → 6/5) ⇝ 5/4 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ 9/8 ⇜ (6/5 → 5/4) | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 5/4 → 11/8 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ (11/8 → 7/5) ⇝ 3/2 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 11/8 ⇜ (7/5 → 3/2) | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ (3/2 → 8/5) ⇝ 13/8 | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ 3/2 ⇜ (8/5 → 13/8) | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ 13/8 → 7/4 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ (7/4 → 9/5) ⇝ 15/8 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ 7/4 ⇜ (9/5 → 15/8) | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]", - "[ 15/8 → 2/1 | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]", - "[ 2/1 → 17/8 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ (17/8 → 11/5) ⇝ 9/4 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ 17/8 ⇜ (11/5 → 9/4) | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 9/4 → 19/8 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ (19/8 → 12/5) ⇝ 5/2 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 19/8 ⇜ (12/5 → 5/2) | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ (5/2 → 13/5) ⇝ 21/8 | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ 5/2 ⇜ (13/5 → 21/8) | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ 21/8 → 11/4 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ (11/4 → 14/5) ⇝ 23/8 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ 11/4 ⇜ (14/5 → 23/8) | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]", - "[ 23/8 → 3/1 | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]", - "[ 3/1 → 25/8 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ (25/8 → 16/5) ⇝ 13/4 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ 25/8 ⇜ (16/5 → 13/4) | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 13/4 → 27/8 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ (27/8 → 17/5) ⇝ 7/2 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 27/8 ⇜ (17/5 → 7/2) | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ (7/2 → 18/5) ⇝ 29/8 | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ 7/2 ⇜ (18/5 → 29/8) | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ 29/8 → 15/4 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ (15/4 → 19/5) ⇝ 31/8 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ 15/4 ⇜ (19/5 → 31/8) | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]", - "[ 31/8 → 4/1 | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]", -] -`; - -exports[`runs examples > example "wtWarpMode" example index 0 1`] = ` -[ - "[ 0/1 → 1/8 | s:morgana note:F1 wtWarp:0 wtWarpMode:asym ]", - "[ (1/8 → 1/5) ⇝ 1/4 | s:morgana note:F1 wtWarp:0 wtWarpMode:asym ]", - "[ 1/8 ⇜ (1/5 → 1/4) | s:morgana note:F1 wtWarp:0.25 wtWarpMode:asym ]", - "[ 1/4 → 3/8 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:asym ]", - "[ (3/8 → 2/5) ⇝ 1/2 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:asym ]", - "[ 3/8 ⇜ (2/5 → 1/2) | s:morgana note:F1 wtWarp:0.5 wtWarpMode:asym ]", - "[ (1/2 → 3/5) ⇝ 5/8 | s:morgana note:F1 wtWarp:0.5 wtWarpMode:bendp ]", - "[ 1/2 ⇜ (3/5 → 5/8) | s:morgana note:F1 wtWarp:0.75 wtWarpMode:bendp ]", - "[ 5/8 → 3/4 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:bendp ]", - "[ (3/4 → 4/5) ⇝ 7/8 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:bendp ]", - "[ 3/4 ⇜ (4/5 → 7/8) | s:morgana note:F1 wtWarp:1 wtWarpMode:bendp ]", - "[ 7/8 → 1/1 | s:morgana note:F1 wtWarp:1 wtWarpMode:bendp ]", - "[ 1/1 → 9/8 | s:morgana note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ (9/8 → 6/5) ⇝ 5/4 | s:morgana note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ 9/8 ⇜ (6/5 → 5/4) | s:morgana note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 5/4 → 11/8 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ (11/8 → 7/5) ⇝ 3/2 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 11/8 ⇜ (7/5 → 3/2) | s:morgana note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ (3/2 → 8/5) ⇝ 13/8 | s:morgana note:F1 wtWarp:0.5 wtWarpMode:logistic ]", - "[ 3/2 ⇜ (8/5 → 13/8) | s:morgana note:F1 wtWarp:0.75 wtWarpMode:logistic ]", - "[ 13/8 → 7/4 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:logistic ]", - "[ (7/4 → 9/5) ⇝ 15/8 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:logistic ]", - "[ 7/4 ⇜ (9/5 → 15/8) | s:morgana note:F1 wtWarp:1 wtWarpMode:logistic ]", - "[ 15/8 → 2/1 | s:morgana note:F1 wtWarp:1 wtWarpMode:logistic ]", - "[ 2/1 → 17/8 | s:morgana note:F1 wtWarp:0 wtWarpMode:sync ]", - "[ (17/8 → 11/5) ⇝ 9/4 | s:morgana note:F1 wtWarp:0 wtWarpMode:sync ]", - "[ 17/8 ⇜ (11/5 → 9/4) | s:morgana note:F1 wtWarp:0.25 wtWarpMode:sync ]", - "[ 9/4 → 19/8 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:sync ]", - "[ (19/8 → 12/5) ⇝ 5/2 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:sync ]", - "[ 19/8 ⇜ (12/5 → 5/2) | s:morgana note:F1 wtWarp:0.5 wtWarpMode:sync ]", - "[ (5/2 → 13/5) ⇝ 21/8 | s:morgana note:F1 wtWarp:0.5 wtWarpMode:wormhole ]", - "[ 5/2 ⇜ (13/5 → 21/8) | s:morgana note:F1 wtWarp:0.75 wtWarpMode:wormhole ]", - "[ 21/8 → 11/4 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:wormhole ]", - "[ (11/4 → 14/5) ⇝ 23/8 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:wormhole ]", - "[ 11/4 ⇜ (14/5 → 23/8) | s:morgana note:F1 wtWarp:1 wtWarpMode:wormhole ]", - "[ 23/8 → 3/1 | s:morgana note:F1 wtWarp:1 wtWarpMode:wormhole ]", - "[ 3/1 → 25/8 | s:morgana note:F1 wtWarp:0 wtWarpMode:brownian ]", - "[ (25/8 → 16/5) ⇝ 13/4 | s:morgana note:F1 wtWarp:0 wtWarpMode:brownian ]", - "[ 25/8 ⇜ (16/5 → 13/4) | s:morgana note:F1 wtWarp:0.25 wtWarpMode:brownian ]", - "[ 13/4 → 27/8 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:brownian ]", - "[ (27/8 → 17/5) ⇝ 7/2 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:brownian ]", - "[ 27/8 ⇜ (17/5 → 7/2) | s:morgana note:F1 wtWarp:0.5 wtWarpMode:brownian ]", - "[ (7/2 → 18/5) ⇝ 29/8 | s:morgana note:F1 wtWarp:0.5 wtWarpMode:asym ]", - "[ 7/2 ⇜ (18/5 → 29/8) | s:morgana note:F1 wtWarp:0.75 wtWarpMode:asym ]", - "[ 29/8 → 15/4 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:asym ]", - "[ (15/4 → 19/5) ⇝ 31/8 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:asym ]", - "[ 15/4 ⇜ (19/5 → 31/8) | s:morgana note:F1 wtWarp:1 wtWarpMode:asym ]", - "[ 31/8 → 4/1 | s:morgana note:F1 wtWarp:1 wtWarpMode:asym ]", -] -`; - exports[`runs examples > example "xfade" example index 0 1`] = ` [ "[ 0/1 → 1/8 | s:hh gain:0 ]", diff --git a/website/public/uzu-wavetables.json b/website/public/uzu-wavetables.json deleted file mode 100644 index 4497791f..00000000 --- a/website/public/uzu-wavetables.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "_base": "http://localhost:5432", - "Bad Day": [ - "/Bad Day.wav" - ], - "Basique": [ - "/Basique.wav" - ], - "Crickets": [ - "/Crickets.wav" - ], - "Curses": [ - "/Curses.wav" - ], - "Earl Grey": [ - "/Earl Grey.wav" - ], - "Echoes": [ - "/Echoes.wav" - ], - "Glimmer": [ - "/Glimmer.wav" - ], - "Majick": [ - "/Majick.wav" - ], - "Meditation": [ - "/Meditation.wav" - ], - "Morgana": [ - "/Morgana.wav" - ], - "Red Alert": [ - "/Red Alert.wav" - ], - "Sad Piano": [ - "/Sad Piano.wav" - ], - "Shook": [ - "/Shook.wav" - ], - "Sludge": [ - "/Sludge.wav" - ], - "Squelch": [ - "/Squelch.wav" - ], - "Summers Day": [ - "/Summers Day.wav" - ], - "Wasp": [ - "/Wasp.wav" - ] -} \ No newline at end of file diff --git a/website/src/repl/prebake.mjs b/website/src/repl/prebake.mjs index 855798fd..79f30bfe 100644 --- a/website/src/repl/prebake.mjs +++ b/website/src/repl/prebake.mjs @@ -32,10 +32,6 @@ export async function prebake() { prebake: true, tag: 'drum-machines', }), - tables(`${baseNoTrailing}/uzu-wavetables.json`, 2048, undefined, { - prebake: true, - tag: 'wavetables', - }), samples(`${baseNoTrailing}/mridangam.json`, undefined, { prebake: true, tag: 'drum-machines' }), samples( { From 8c3ee6db6bb042a2cf234aeea5cd3501fc2eb7e5 Mon Sep 17 00:00:00 2001 From: Aria Date: Sat, 27 Sep 2025 11:07:04 -0700 Subject: [PATCH 07/17] Missed deletion --- website/src/repl/prebake.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/repl/prebake.mjs b/website/src/repl/prebake.mjs index 79f30bfe..fac6f5bb 100644 --- a/website/src/repl/prebake.mjs +++ b/website/src/repl/prebake.mjs @@ -1,5 +1,5 @@ import { Pattern, noteToMidi, valueToMidi } from '@strudel/core'; -import { aliasBank, registerSynthSounds, registerZZFXSounds, samples, tables } from '@strudel/webaudio'; +import { aliasBank, registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio'; import { registerSamplesFromDB } from './idbutils.mjs'; import './piano.mjs'; import './files.mjs'; From 626a99ba5bbaa7032bf5ef62fe2c2d9849ed911a Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 27 Sep 2025 15:22:54 -0400 Subject: [PATCH 08/17] supports old documented way of loading wavetables: --- packages/superdough/sampler.mjs | 33 +++++-------------------------- packages/superdough/util.mjs | 29 ++++++++++++++++++++++++++- packages/superdough/wavetable.mjs | 20 +++++++------------ 3 files changed, 40 insertions(+), 42 deletions(-) diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index ef4bdc54..02e1fb33 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -1,4 +1,4 @@ -import { noteToMidi, valueToMidi, getSoundIndex } from './util.mjs'; +import { noteToMidi, valueToMidi, getSoundIndex, getCommonSampleInfo } from './util.mjs'; import { getAudioContext, registerSound, registerWaveTable } from './index.mjs'; import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs'; import { logger } from './logger.mjs'; @@ -22,39 +22,16 @@ function humanFileSize(bytes, si) { return bytes.toFixed(1) + ' ' + units[u]; } -// deduces relevant info for sample loading from hap.value and sample definition -// it encapsulates the core sampler logic into a pure and synchronous function -// hapValue: Hap.value, bank: sample bank definition for sound "s" (values in strudel.json format) export function getSampleInfo(hapValue, bank) { - const { s, n = 0, speed = 1.0 } = hapValue; - let midi = valueToMidi(hapValue, 36); - let transpose = midi - 36; // C3 is middle C; - let sampleUrl; - let index = 0; - if (Array.isArray(bank)) { - index = getSoundIndex(n, bank.length); - sampleUrl = bank[index]; - } else { - const midiDiff = (noteA) => noteToMidi(noteA) - midi; - // object format will expect keys as notes - const closest = Object.keys(bank) - .filter((k) => !k.startsWith('_')) - .reduce( - (closest, key, j) => (!closest || Math.abs(midiDiff(key)) < Math.abs(midiDiff(closest)) ? key : closest), - null, - ); - transpose = -midiDiff(closest); // semitones to repitch - index = getSoundIndex(n, bank[closest].length); - sampleUrl = bank[closest][index]; - } - const label = `${s}:${index}`; + const { speed = 1.0 } = hapValue; + const {transpose, url, index, midi, label} = getCommonSampleInfo(hapValue, bank) let playbackRate = Math.abs(speed) * Math.pow(2, transpose / 12); - return { transpose, sampleUrl, index, midi, label, playbackRate }; + return { transpose, url, index, midi, label, playbackRate }; } // takes hapValue and returns buffer + playbackRate. export const getSampleBuffer = async (hapValue, bank, resolveUrl) => { - let { sampleUrl, label, playbackRate } = getSampleInfo(hapValue, bank); + let { url: sampleUrl, label, playbackRate } = getSampleInfo(hapValue, bank); if (resolveUrl) { sampleUrl = await resolveUrl(sampleUrl); } diff --git a/packages/superdough/util.mjs b/packages/superdough/util.mjs index 80dd31a9..1886c55e 100644 --- a/packages/superdough/util.mjs +++ b/packages/superdough/util.mjs @@ -76,4 +76,31 @@ export function cycleToSeconds(cycle, cps) { export function secondsToCycle(t, cps) { return t * cps; } - +// deduces relevant info for sample loading from hap.value and sample definition +// it encapsulates the core sampler logic into a pure and synchronous function +// hapValue: Hap.value, bank: sample bank definition for sound "s" (values in strudel.json format) +export function getCommonSampleInfo(hapValue, bank) { + const { s, n = 0 } = hapValue; + let midi = valueToMidi(hapValue, 36); + let transpose = midi - 36; // C3 is middle C; + let url; + let index = 0; + if (Array.isArray(bank)) { + index = getSoundIndex(n, bank.length); + url = bank[index]; + } else { + const midiDiff = (noteA) => noteToMidi(noteA) - midi; + // object format will expect keys as notes + const closest = Object.keys(bank) + .filter((k) => !k.startsWith('_')) + .reduce( + (closest, key, j) => (!closest || Math.abs(midiDiff(key)) < Math.abs(midiDiff(closest)) ? key : closest), + null, + ); + transpose = -midiDiff(closest); // semitones to repitch + index = getSoundIndex(n, bank[closest].length); + url = bank[closest][index]; + } + const label = `${s}:${index}`; + return { transpose, url, index, midi, label }; +} \ No newline at end of file diff --git a/packages/superdough/wavetable.mjs b/packages/superdough/wavetable.mjs index 06765604..a7e70804 100644 --- a/packages/superdough/wavetable.mjs +++ b/packages/superdough/wavetable.mjs @@ -1,5 +1,5 @@ import { getAudioContext, registerSound } from './index.mjs'; -import { getSoundIndex, valueToMidi } from './util.mjs'; +import { getCommonSampleInfo, getSoundIndex, valueToMidi } from './util.mjs'; import { destroyAudioWorkletNode, getADSRValues, @@ -38,12 +38,12 @@ export const WarpMode = Object.freeze({ FLIP: 21, }); -async function loadWavetableFrames(url, label, frameLen = 256) { +async function loadWavetableFrames(url, label, frameLen = 2048) { const ac = getAudioContext(); const buf = await loadBuffer(url, ac, label); const ch0 = buf.getChannelData(0); const total = ch0.length; - const numFrames = Math.floor(total / frameLen); + const numFrames = Math.max(1,Math.floor(total / frameLen)); const frames = new Array(numFrames); for (let i = 0; i < numFrames; i++) { const start = i * frameLen; @@ -86,14 +86,8 @@ function humanFileSize(bytes, si) { return bytes.toFixed(1) + ' ' + units[u]; } -export function getTableInfo(hapValue, tableUrls) { - const { s, n = 0 } = hapValue; - let midi = valueToMidi(hapValue, 36); - let transpose = midi - 36; // C3 is middle C; - const index = getSoundIndex(n, tableUrls.length); - const tableUrl = tableUrls[index]; - const label = `${s}:${index}`; - return { transpose, tableUrl, index, midi, label }; +export function getTableInfo(hapValue, urls) { + return getCommonSampleInfo(hapValue,urls) } const loadBuffer = (url, ac, label) => { @@ -191,8 +185,8 @@ export async function onTriggerSynth(t, value, onended, bank, frameLen) { wtWarpMode = WarpMode[wtWarpMode.toUpperCase()] ?? WarpMode.NONE; } const frequency = getFrequencyFromValue(value); - const { tableUrl, label } = getTableInfo(value, bank); - const payload = await loadWavetableFrames(tableUrl, label, frameLen); + const { url, label } = getTableInfo(value, bank); + const payload = await loadWavetableFrames(url, label, frameLen); const holdEnd = t + duration; const envEnd = holdEnd + release + 0.01; const source = getWorklet( From 774372a339d929d8826187efc90afe5ae7e5e337 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 27 Sep 2025 15:29:55 -0400 Subject: [PATCH 09/17] format --- packages/superdough/sampler.mjs | 19 ++++++++----------- packages/superdough/util.mjs | 4 ++-- packages/superdough/wavetable.mjs | 10 +++++----- packages/superdough/worklets.mjs | 4 ++-- website/src/repl/idbutils.mjs | 3 +-- website/src/repl/prebake.mjs | 2 +- 6 files changed, 19 insertions(+), 23 deletions(-) diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index 02e1fb33..b195ba79 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -23,8 +23,8 @@ function humanFileSize(bytes, si) { } export function getSampleInfo(hapValue, bank) { - const { speed = 1.0 } = hapValue; - const {transpose, url, index, midi, label} = getCommonSampleInfo(hapValue, bank) + const { speed = 1.0 } = hapValue; + const { transpose, url, index, midi, label } = getCommonSampleInfo(hapValue, bank); let playbackRate = Math.abs(speed) * Math.pow(2, transpose / 12); return { transpose, url, index, midi, label, playbackRate }; } @@ -245,11 +245,10 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option } const { prebake, tag } = options; - processSampleMap( sampleMap, (key, bank) => { - registerSampleSource(key, bank, { baseUrl, prebake, tag }) + registerSampleSource(key, bank, { baseUrl, prebake, tag }); }, baseUrl, ); @@ -341,21 +340,19 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { return handle; } - function registerSample(key, bank, params) { registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, bank), { type: 'sample', samples: bank, - ...params - }) + ...params, + }); } export function registerSampleSource(key, bank, params) { const isWavetable = key.startsWith('wt_'); if (isWavetable) { - registerWaveTable(key,bank, params) + registerWaveTable(key, bank, params); } else { - registerSample(key, bank, params) + registerSample(key, bank, params); } - -} \ No newline at end of file +} diff --git a/packages/superdough/util.mjs b/packages/superdough/util.mjs index 1886c55e..0ba09517 100644 --- a/packages/superdough/util.mjs +++ b/packages/superdough/util.mjs @@ -100,7 +100,7 @@ export function getCommonSampleInfo(hapValue, bank) { transpose = -midiDiff(closest); // semitones to repitch index = getSoundIndex(n, bank[closest].length); url = bank[closest][index]; - } + } const label = `${s}:${index}`; return { transpose, url, index, midi, label }; -} \ No newline at end of file +} diff --git a/packages/superdough/wavetable.mjs b/packages/superdough/wavetable.mjs index a7e70804..c54f3a88 100644 --- a/packages/superdough/wavetable.mjs +++ b/packages/superdough/wavetable.mjs @@ -43,7 +43,7 @@ async function loadWavetableFrames(url, label, frameLen = 2048) { const buf = await loadBuffer(url, ac, label); const ch0 = buf.getChannelData(0); const total = ch0.length; - const numFrames = Math.max(1,Math.floor(total / frameLen)); + const numFrames = Math.max(1, Math.floor(total / frameLen)); const frames = new Array(numFrames); for (let i = 0; i < numFrames; i++) { const start = i * frameLen; @@ -87,7 +87,7 @@ function humanFileSize(bytes, si) { } export function getTableInfo(hapValue, urls) { - return getCommonSampleInfo(hapValue,urls) + return getCommonSampleInfo(hapValue, urls); } const loadBuffer = (url, ac, label) => { @@ -134,15 +134,15 @@ const _processTables = (json, baseUrl, frameLen) => { baseUrl = githubPath(baseUrl, ''); } value = value.map((v) => baseUrl + v); - registerWaveTable(key,value, {baseUrl, frameLen}) + registerWaveTable(key, value, { baseUrl, frameLen }); }); }; -export function registerWaveTable(key, bank, params) { +export function registerWaveTable(key, bank, params) { registerSound(key, (t, hapValue, onended) => onTriggerSynth(t, hapValue, onended, bank, params?.frameLen ?? 2048), { type: 'wavetable', tables: bank, - ...params + ...params, }); } diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 826e9610..91eb6863 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -1049,7 +1049,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { { name: 'begin', defaultValue: 0, min: 0, max: Number.POSITIVE_INFINITY }, { name: 'end', defaultValue: 0, min: 0, max: Number.POSITIVE_INFINITY }, { name: 'frequency', defaultValue: 220, minValue: 0.01, maxValue: 20000 }, - { name: 'detune', defaultValue: .18 }, + { name: 'detune', defaultValue: 0.18 }, { name: 'position', defaultValue: 0, minValue: 0, maxValue: 1 }, { name: 'warp', defaultValue: 0, minValue: 0, maxValue: 1 }, { name: 'warpMode', defaultValue: 0 }, @@ -1239,7 +1239,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { } const outL = outputs[0][0]; const outR = outputs[0][1] || outputs[0][0]; - const gainAdjustment = .15; + const gainAdjustment = 0.3; if (!this.tables) { outL.fill(0); diff --git a/website/src/repl/idbutils.mjs b/website/src/repl/idbutils.mjs index 5ac60455..d87d649c 100644 --- a/website/src/repl/idbutils.mjs +++ b/website/src/repl/idbutils.mjs @@ -76,8 +76,7 @@ export function registerSamplesFromDB(config = userSamplesDBConfig, onComplete = }) .map((title) => titlePathMap.get(title)); - registerSampleSource(key,value, {prebake: false}) - + registerSampleSource(key, value, { prebake: false }); }); logger('imported sounds registered!', 'success'); diff --git a/website/src/repl/prebake.mjs b/website/src/repl/prebake.mjs index 0b552b87..1fbc8402 100644 --- a/website/src/repl/prebake.mjs +++ b/website/src/repl/prebake.mjs @@ -33,7 +33,7 @@ export async function prebake() { tag: 'drum-machines', }), samples(`${baseNoTrailing}/uzu-wavetables.json`, undefined, { - prebake: true, + prebake: true, }), samples(`${baseNoTrailing}/mridangam.json`, undefined, { prebake: true, tag: 'drum-machines' }), samples( From 917d01c47d9ab245fb09ce121af657a184c52c1f Mon Sep 17 00:00:00 2001 From: Aria Date: Sat, 27 Sep 2025 12:48:37 -0700 Subject: [PATCH 10/17] Add LFOs and envelopes --- packages/core/controls.mjs | 172 ++++++++++++++++++++++++++++- packages/superdough/helpers.mjs | 30 +++++ packages/superdough/superdough.mjs | 39 ++----- packages/superdough/synth.mjs | 3 +- packages/superdough/wavetable.mjs | 37 ++++++- 5 files changed, 247 insertions(+), 34 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index fef0e45e..23a1dfc3 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -98,6 +98,90 @@ export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound'); */ export const { wtPos, wavetablePosition } = registerControl('wtPos', 'wavetablePosition'); +/** + * Attack time of the wavetable oscillator's position envelope + * + * @name wtPosAttack + * @synonyms wtPosAtt + * @param {number | Pattern} time attack time in seconds + */ +export const { wtPosAttack, wtPosAtt } = registerControl('wtPosAttack', 'wtPosAtt'); + +/** + * Decay time of the wavetable oscillator's position envelope + * + * @name wtPosDecay + * @synonyms wtPosDec + * @param {number | Pattern} time decay time in seconds + */ +export const { wtPosDecay, wtPosDec } = registerControl('wtPosDecay', 'wtPosDec'); + +/** + * Sustain time of the wavetable oscillator's position envelope + * + * @name wtPosAttack + * @synonyms wtPosSus + * @param {number | Pattern} gain sustain level (0 to 1) + */ +export const { wtPosSustain, wtPosSus } = registerControl('wtPosSustain', 'wtPosSus'); + +/** + * Release time of the wavetable oscillator's position envelope + * + * @name wtPosRelease + * @synonyms wtPosRel + * @param {number | Pattern} time release time in seconds + */ +export const { wtPosRelease, wtPosRel } = registerControl('wtPosRelease', 'wtPosRel'); + +/** + * Rate of the LFO for the wavetable oscillator's position + * + * @name wtPosRate + * @param {number | Pattern} rate rate in hertz + */ +export const { wtPosRate } = registerControl('wtPosRate'); + +/** + * Depth of the LFO for the wavetable oscillator's position + * + * @name wtPosDepth + * @param {number | Pattern} depth depth of modulation + */ +export const { wtPosDepth } = registerControl('wtPosDepth'); + +/** + * Whether to sync the LFO for the wavetable oscillator's position + * + * @name wtPosSynced + * @param {number | Pattern} synced Whether to sync the lfo to CPM. > 0.5 will be synced + */ +export const { wtPosSynced } = registerControl('wtPosSynced'); + +/** + * Shape of the LFO for the wavetable oscillator's position + * + * @name wtPosShape + * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..) + */ +export const { wtPosShape } = registerControl('wtPosShape'); + +/** + * DC offset of the LFO for the wavetable oscillator's position + * + * @name wtPosDCOffset + * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar + */ +export const { wtPosDCOffset } = registerControl('wtPosDCOffset'); + +/** + * Skew of the LFO for the wavetable oscillator's position + * + * @name wtPosSkew + * @param {number | Pattern} skew How much to bend the LFO shape + */ +export const { wtPosSkew } = registerControl('wtPosSkew'); + /** * Amount of warp (alteration of the waveform) to apply to the wavetable oscillator * @@ -108,10 +192,94 @@ export const { wtPos, wavetablePosition } = registerControl('wtPos', 'wavetableP * s("basique").bank("wt_digital").seg(8).note("F1").wtWarp("0 0.25 0.5 0.75 1") * .wtWarpMode("spin") */ -export const { wtWarp, wavetableWarp } = registerControl('wtWarp', 'wavetableWarp'); +export const { wtWarp, wavetableWarp } = registerControl('wtWarp', 'wavetableWarp') /** - * Amount of warp (alteration of the waveform) to apply to the wavetable oscillator. + * Attack time of the wavetable oscillator's warp envelope + * + * @name wtWarpAttack + * @synonyms wtWarpAtt + * @param {number | Pattern} time attack time in seconds + */ +export const { wtWarpAttack, wtWarpAtt } = registerControl('wtWarpAttack', 'wtWarpAtt'); + +/** + * Decay time of the wavetable oscillator's warp envelope + * + * @name wtWarpDecay + * @synonyms wtWarpDec + * @param {number | Pattern} time decay time in seconds + */ +export const { wtWarpDecay, wtWarpDec } = registerControl('wtWarpDecay', 'wtWarpDec'); + +/** + * Sustain time of the wavetable oscillator's warp envelope + * + * @name wtWarpAttack + * @synonyms wtWarpSus + * @param {number | Pattern} gain sustain level (0 to 1) + */ +export const { wtWarpSustain, wtWarpSus } = registerControl('wtWarpSustain', 'wtWarpSus'); + +/** + * Release time of the wavetable oscillator's warp envelope + * + * @name wtWarpRelease + * @synonyms wtWarpRel + * @param {number | Pattern} time release time in seconds + */ +export const { wtWarpRelease, wtWarpRel } = registerControl('wtWarpRelease', 'wtWarpRel'); + +/** + * Rate of the LFO for the wavetable oscillator's warp + * + * @name wtWarpRate + * @param {number | Pattern} rate rate in hertz + */ +export const { wtWarpRate } = registerControl('wtWarpRate'); + +/** + * Depth of the LFO for the wavetable oscillator's warp + * + * @name wtWarpDepth + * @param {number | Pattern} depth depth of modulation + */ +export const { wtWarpDepth } = registerControl('wtWarpDepth'); + +/** + * Whether to sync the LFO for the wavetable oscillator's warp + * + * @name wtWarpSynced + * @param {number | Pattern} synced Whether to sync the lfo to CPM. > 0.5 will be synced + */ +export const { wtWarpSynced } = registerControl('wtWarpSynced'); + +/** + * Shape of the LFO for the wavetable oscillator's warp + * + * @name wtWarpShape + * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..) + */ +export const { wtWarpShape } = registerControl('wtWarpShape'); + +/** + * DC offset of the LFO for the wavetable oscillator's warp + * + * @name wtWarpDCOffset + * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar + */ +export const { wtWarpDCOffset } = registerControl('wtWarpDCOffset'); + +/** + * Skew of the LFO for the wavetable oscillator's warp + * + * @name wtWarpSkew + * @param {number | Pattern} skew How much to bend the LFO shape + */ +export const { wtWarpSkew } = registerControl('wtWarpSkew'); + +/** + * Type of warp (alteration of the waveform) to apply to the wavetable oscillator. * * The current options are: none, asym, bendp, bendm, bendmp, sync, quant, fold, pwm, orbit, * spin, chaos, primes, binary, brownian, reciprocal, wormhole, logistic, sigmoid, fractal, flip diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 4921d873..d3b6ca6c 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -97,6 +97,36 @@ export const getParamADSR = ( param[ramp](min, end + release); }; +function getModulationShapeInput(val) { + if (typeof val === 'number') { + return val % 5; + } + return { tri: 0, triangle: 0, sine: 1, ramp: 2, saw: 3, square: 4 }[val] ?? 0; +} + +export function getLfo(audioContext, begin, end, properties = {}) { + const { shape = 0, ...props } = properties; + const { dcoffset = -0.5, depth = 1 } = properties; + debugger; + const lfoprops = { + frequency: 1, + depth, + skew: 0.5, + phaseoffset: 0, + time: begin, + begin, + end, + shape: getModulationShapeInput(shape), + dcoffset, + min: dcoffset * depth, + max: dcoffset * depth + depth, + curve: 1, + ...props, + }; + + return getWorklet(audioContext, 'lfo-processor', lfoprops); +} + export function getCompressor(ac, threshold, ratio, knee, attack, release) { const options = { threshold: threshold ?? -3, diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 63ed7d30..48264ec0 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -9,7 +9,7 @@ import './reverb.mjs'; import './vowel.mjs'; import { nanFallback, _mod, cycleToSeconds } from './util.mjs'; import workletsUrl from './worklets.mjs?audioworklet'; -import { createFilter, gainNode, getCompressor, getWorklet, effectSend } from './helpers.mjs'; +import { createFilter, gainNode, getCompressor, getLfo, getWorklet, effectSend } from './helpers.mjs'; import { map } from 'nanostores'; import { logger } from './logger.mjs'; import { loadBuffer } from './sampler.mjs'; @@ -29,13 +29,6 @@ export function setMultiChannelOrbits(bool) { multiChannelOrbits = bool == true; } -function getModulationShapeInput(val) { - if (typeof val === 'number') { - return val % 5; - } - return { tri: 0, triangle: 0, sine: 1, ramp: 2, saw: 3, square: 4 }[val] ?? 0; -} - export const soundMap = map(); export function registerSound(key, onTrigger, data = {}) { @@ -314,28 +307,6 @@ export function connectToDestination(input, channels) { controller.output.connectToDestination(input, channels); } -export function getLfo(audioContext, begin, end, properties = {}) { - const { shape = 0, ...props } = properties; - const { dcoffset = -0.5, depth = 1 } = properties; - const lfoprops = { - frequency: 1, - depth, - skew: 0.5, - phaseoffset: 0, - time: begin, - begin, - end, - shape: getModulationShapeInput(shape), - dcoffset, - min: dcoffset * depth, - max: dcoffset * depth + depth, - curve: 1, - ...props, - }; - - return getWorklet(audioContext, 'lfo-processor', lfoprops); -} - function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000, sweep = 2000) { const ac = getAudioContext(); const lfoGain = getLfo(ac, time, end, { frequency, depth: sweep * 2 }); @@ -682,6 +653,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) tremolo = cps * tremolosync; } + if (value.wtPosSynced != null) { + value.wtPosRate /= cps; + } + + if (value.wtWarpSynced != null) { + value.wtWarpRate /= cps; + } + if (tremolo !== undefined) { // Allow clipping of modulator for more dynamic possiblities, and to prevent speaker overload // EX: a triangle waveform will clip like this /-\ when the depth is above 1 diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 5b1b4edf..5dc5dc08 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -1,11 +1,12 @@ import { clamp } from './util.mjs'; -import { registerSound, getAudioContext, soundMap, getLfo } from './superdough.mjs'; +import { registerSound, getAudioContext, soundMap } from './superdough.mjs'; import { applyFM, destroyAudioWorkletNode, gainNode, getADSRValues, getFrequencyFromValue, + getLfo, getParamADSR, getPitchEnvelope, getVibratoOscillator, diff --git a/packages/superdough/wavetable.mjs b/packages/superdough/wavetable.mjs index 14706f92..dede7a2e 100644 --- a/packages/superdough/wavetable.mjs +++ b/packages/superdough/wavetable.mjs @@ -4,6 +4,7 @@ import { destroyAudioWorkletNode, getADSRValues, getFrequencyFromValue, + getLfo, getParamADSR, getPitchEnvelope, getVibratoOscillator, @@ -235,7 +236,8 @@ async function onTriggerSynth(t, value, onended, tables, frameLen) { const { tableUrl, label } = getTableInfo(value, tables); const payload = await loadWavetableFrames(tableUrl, label, frameLen); const holdEnd = t + duration; - const envEnd = holdEnd + release + 0.01; + const endWithRelease = holdEnd + release; + const envEnd = endWithRelease + 0.01; const source = getWorklet( ac, 'wavetable-oscillator-processor', @@ -258,6 +260,37 @@ async function onTriggerSynth(t, value, onended, tables, frameLen) { logger(`[wavetable] still loading sound "${s}:${n}"`, 'highlight'); return; } + const posADSRParams = [value.wtPosAttack, value.wtPosDecay, value.wtPosSustain, value.wtPosRelease]; + const warpADSRParams = [value.wtPosAttack, value.wtPosDecay, value.wtPosSustain, value.wtPosRelease]; + const wtParams = source.parameters; + const positionParam = wtParams.get('position'); + const warpParam = wtParams.get('warp'); + if (posADSRParams.some((p) => p !== undefined)) { + const [pAttack, pDecay, pSustain, pRelease] = getADSRValues(posADSRParams); + getParamADSR(positionParam, pAttack, pDecay, pSustain, pRelease, 0, 1, t, holdEnd, 'linear'); + } else { + const posLFO = getLfo(ac, t, endWithRelease, { + frequency: value.wtPosRate, + depth: value.wtPosDepth, + shape: value.wtPosShape, + skew: value.wtPosSkew, + dcoffset: value.wtPosDCOffset ?? 0, + }); + posLFO.connect(positionParam); + } + if (posADSRParams.some((p) => p !== undefined)) { + const [wAttack, wDecay, wSustain, wRelease] = getADSRValues(warpADSRParams); + getParamADSR(warpParam, wAttack, wDecay, wSustain, wRelease, 0, 1, t, holdEnd, 'linear'); + } else { + const warpLFO = getLfo(ac, t, endWithRelease, { + frequency: value.wtWarpRate, + depth: value.wtWarpDepth, + shape: value.wtWarpShape, + skew: value.wtWarpSkew, + dcoffset: value.wtWarpDCOffset ?? 0, + }); + warpLFO.connect(warpParam); + } const vibratoOscillator = getVibratoOscillator(source.detune, value, t); const envGain = ac.createGain(); const node = source.connect(envGain); @@ -271,6 +304,8 @@ async function onTriggerSynth(t, value, onended, tables, frameLen) { destroyAudioWorkletNode(source); vibratoOscillator?.stop(); node.disconnect(); + warpLFO.disconnect(); + posLFO.disconnect(); onended(); }, t, From 23d4bfa9d9ec9b2f3bc7dd6469da113dfc794629 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sun, 28 Sep 2025 09:19:39 -0400 Subject: [PATCH 11/17] rename controls --- packages/core/controls.mjs | 162 ++++++++++++++++------------- packages/superdough/superdough.mjs | 2 +- packages/superdough/wavetable.mjs | 88 ++++++++++------ 3 files changed, 144 insertions(+), 108 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index b5846596..7e3c2a8e 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -90,193 +90,191 @@ export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound'); /** * Position in the wavetable of the wavetable oscillator * - * @name wtPos + * @name wt * @param {number | Pattern} position Position in the wavetable from 0 to 1 * @synonyms wavetablePosition * @example - * s("squelch").bank("wt_digital").seg(8).note("F1").wtPos("0 0.25 0.5 0.75 1") + * s("squelch").bank("wt_digital").seg(8).note("F1").wt("0 0.25 0.5 0.75 1") */ -export const { wtPos, wavetablePosition } = registerControl('wtPos', 'wavetablePosition'); +export const { wt, wavetablePosition } = registerControl('wt', 'wavetablePosition'); +/** + * Amount of envelope applied wavetable oscillator's position envelope + * + * @name wtenv + * @param {number | Pattern} amount between 0 and 1 + */ +export const { wtenv } = registerControl('wtenv'); /** * Attack time of the wavetable oscillator's position envelope * - * @name wtPosAttack - * @synonyms wtPosAtt + * @name wtattack + * @synonyms wtatt * @param {number | Pattern} time attack time in seconds */ -export const { wtPosAttack, wtPosAtt } = registerControl('wtPosAttack', 'wtPosAtt'); +export const { wtattack, wtatt } = registerControl('wtattack', 'wtatt'); /** * Decay time of the wavetable oscillator's position envelope * - * @name wtPosDecay - * @synonyms wtPosDec + * @name wtdecay + * @synonyms wtdec * @param {number | Pattern} time decay time in seconds */ -export const { wtPosDecay, wtPosDec } = registerControl('wtPosDecay', 'wtPosDec'); +export const { wtdecay, wtdec } = registerControl('wtdecay', 'wtdec'); /** * Sustain time of the wavetable oscillator's position envelope * - * @name wtPosAttack - * @synonyms wtPosSus + * @name wtsustain + * @synonyms wtsus * @param {number | Pattern} gain sustain level (0 to 1) */ -export const { wtPosSustain, wtPosSus } = registerControl('wtPosSustain', 'wtPosSus'); +export const { wtsustain, wtsus } = registerControl('wtsustain', 'wtsus'); /** * Release time of the wavetable oscillator's position envelope * - * @name wtPosRelease - * @synonyms wtPosRel + * @name wtrelease + * @synonyms wtrel * @param {number | Pattern} time release time in seconds */ -export const { wtPosRelease, wtPosRel } = registerControl('wtPosRelease', 'wtPosRel'); +export const { wtrelease, wtrel } = registerControl('wtrelease', 'wtrel'); /** * Rate of the LFO for the wavetable oscillator's position * - * @name wtPosRate + * @name wtrate * @param {number | Pattern} rate rate in hertz */ -export const { wtPosRate } = registerControl('wtPosRate'); +export const { wtrate } = registerControl('wtrate'); +/** + * cycle synced rate of the LFO for the wavetable oscillator's position + * + * @name wtsync + * @param {number | Pattern} rate rate in cycles + */ +export const { wtsync } = registerControl('wtsync'); /** * Depth of the LFO for the wavetable oscillator's position * - * @name wtPosDepth + * @name wtdepth * @param {number | Pattern} depth depth of modulation */ -export const { wtPosDepth } = registerControl('wtPosDepth'); - -/** - * Whether to sync the LFO for the wavetable oscillator's position - * - * @name wtPosSynced - * @param {number | Pattern} synced Whether to sync the lfo to CPM. > 0.5 will be synced - */ -export const { wtPosSynced } = registerControl('wtPosSynced'); +export const { wtdepth } = registerControl('wtdepth'); /** * Shape of the LFO for the wavetable oscillator's position * - * @name wtPosShape + * @name wtshape * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..) */ -export const { wtPosShape } = registerControl('wtPosShape'); +export const { wtshape } = registerControl('wtshape'); /** * DC offset of the LFO for the wavetable oscillator's position * - * @name wtPosDCOffset + * @name wtdc * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar */ -export const { wtPosDCOffset } = registerControl('wtPosDCOffset'); +export const { wtdc } = registerControl('wtdc'); /** * Skew of the LFO for the wavetable oscillator's position * - * @name wtPosSkew + * @name wtskew * @param {number | Pattern} skew How much to bend the LFO shape */ -export const { wtPosSkew } = registerControl('wtPosSkew'); +export const { wtskew } = registerControl('wtskew'); /** * Amount of warp (alteration of the waveform) to apply to the wavetable oscillator * - * @name wtWarp + * @name warp * @param {number | Pattern} amount Warp of the wavetable from 0 to 1 * @synonyms wavetableWarp * @example - * s("basique").bank("wt_digital").seg(8).note("F1").wtWarp("0 0.25 0.5 0.75 1") - * .wtWarpMode("spin") + * s("basique").bank("wt_digital").seg(8).note("F1").warp("0 0.25 0.5 0.75 1") + * .warpmode("spin") */ -export const { wtWarp, wavetableWarp } = registerControl('wtWarp', 'wavetableWarp'); +export const { warp, wavetableWarp } = registerControl('warp', 'wavetableWarp'); /** * Attack time of the wavetable oscillator's warp envelope * - * @name wtWarpAttack - * @synonyms wtWarpAtt + * @name warpattack + * @synonyms warpatt * @param {number | Pattern} time attack time in seconds */ -export const { wtWarpAttack, wtWarpAtt } = registerControl('wtWarpAttack', 'wtWarpAtt'); +export const { warpattack, warpatt } = registerControl('warpattack', 'warpatt'); /** * Decay time of the wavetable oscillator's warp envelope * - * @name wtWarpDecay - * @synonyms wtWarpDec + * @name warpdecay + * @synonyms warpdec * @param {number | Pattern} time decay time in seconds */ -export const { wtWarpDecay, wtWarpDec } = registerControl('wtWarpDecay', 'wtWarpDec'); +export const { warpdecay, warpdec } = registerControl('warpdecay', 'warpdec'); /** * Sustain time of the wavetable oscillator's warp envelope * - * @name wtWarpAttack - * @synonyms wtWarpSus + * @name warpsustain + * @synonyms warpsus * @param {number | Pattern} gain sustain level (0 to 1) */ -export const { wtWarpSustain, wtWarpSus } = registerControl('wtWarpSustain', 'wtWarpSus'); +export const { warpsustain, warpsus } = registerControl('warpsustain', 'warpsus'); /** * Release time of the wavetable oscillator's warp envelope * - * @name wtWarpRelease - * @synonyms wtWarpRel + * @name warprelease + * @synonyms warprel * @param {number | Pattern} time release time in seconds */ -export const { wtWarpRelease, wtWarpRel } = registerControl('wtWarpRelease', 'wtWarpRel'); +export const { warprelease, warprel } = registerControl('warprelease', 'warprel'); /** * Rate of the LFO for the wavetable oscillator's warp * - * @name wtWarpRate + * @name warprate * @param {number | Pattern} rate rate in hertz */ -export const { wtWarpRate } = registerControl('wtWarpRate'); +export const { warprate } = registerControl('warprate'); /** * Depth of the LFO for the wavetable oscillator's warp * - * @name wtWarpDepth + * @name warpdepth * @param {number | Pattern} depth depth of modulation */ -export const { wtWarpDepth } = registerControl('wtWarpDepth'); - -/** - * Whether to sync the LFO for the wavetable oscillator's warp - * - * @name wtWarpSynced - * @param {number | Pattern} synced Whether to sync the lfo to CPM. > 0.5 will be synced - */ -export const { wtWarpSynced } = registerControl('wtWarpSynced'); +export const { warpdepth } = registerControl('warpdepth'); /** * Shape of the LFO for the wavetable oscillator's warp * - * @name wtWarpShape + * @name warpshape * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..) */ -export const { wtWarpShape } = registerControl('wtWarpShape'); +export const { warpshape } = registerControl('warpshape'); /** * DC offset of the LFO for the wavetable oscillator's warp * - * @name wtWarpDCOffset + * @name warpdc * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar */ -export const { wtWarpDCOffset } = registerControl('wtWarpDCOffset'); +export const { warpdc } = registerControl('warpdc'); /** * Skew of the LFO for the wavetable oscillator's warp * - * @name wtWarpSkew + * @name warpskew * @param {number | Pattern} skew How much to bend the LFO shape */ -export const { wtWarpSkew } = registerControl('wtWarpSkew'); +export const { warpskew } = registerControl('warpskew'); /** * Type of warp (alteration of the waveform) to apply to the wavetable oscillator. @@ -284,27 +282,43 @@ export const { wtWarpSkew } = registerControl('wtWarpSkew'); * The current options are: none, asym, bendp, bendm, bendmp, sync, quant, fold, pwm, orbit, * spin, chaos, primes, binary, brownian, reciprocal, wormhole, logistic, sigmoid, fractal, flip * - * @name wtWarpMode + * @name warpmode * @param {number | string | Pattern} mode Warp mode * @synonyms wavetableWarpMode * @example - * s("morgana").bank("wt_digital").seg(8).note("F1").wtWarp("0 0.25 0.5 0.75 1") - * .wtWarpMode("*2") + * s("morgana").bank("wt_digital").seg(8).note("F1").warp("0 0.25 0.5 0.75 1") + * .warpmode("*2") * */ -export const { wtWarpMode, wavetableWarpMode } = registerControl('wtWarpMode', 'wavetableWarpMode'); +export const { warpmode, wavetableWarpMode } = registerControl('warpmode', 'wavetableWarpMode'); /** * Amount of randomness of the initial phase of the wavetable oscillator. * - * @name wtPhaseRand + * @name wtphaserand * @param {number | Pattern} amount Randomness of the initial phase. Between 0 (not random) and 1 (fully random) * @synonyms wavetablePhaseRand * @example - * s("basique").bank("wt_digital").seg(16).wtPhaseRand("<0 1>") + * s("basique").bank("wt_digital").seg(16).wtphaserand("<0 1>") * */ -export const { wtPhaseRand, wavetablePhaseRand } = registerControl('wtPhaseRand', 'wavetablePhaseRand'); +export const { wtphaserand, wavetablePhaseRand } = registerControl('wtphaserand', 'wavetablePhaseRand'); + +/** + * Amount of envelope applied wavetable oscillator's position envelope + * + * @name warpenv + * @param {number | Pattern} amount between 0 and 1 + */ +export const { warpenv } = registerControl('warpenv'); + +/** + * cycle synced rate of the LFO for the wavetable warp position + * + * @name warpsync + * @param {number | Pattern} rate rate in cycles + */ +export const { warpsync } = registerControl('warpsync'); /** * Define a custom webaudio node to use as a sound source. diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 48264ec0..52ed9bff 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -555,7 +555,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) audioNodes.forEach((n) => n?.disconnect()); activeSoundSources.delete(chainID); }; - const soundHandle = await onTrigger(t, value, onEnded); + const soundHandle = await onTrigger(t, value, onEnded, cps); if (soundHandle) { sourceNode = soundHandle.node; diff --git a/packages/superdough/wavetable.mjs b/packages/superdough/wavetable.mjs index 095bd079..eddc2e3e 100644 --- a/packages/superdough/wavetable.mjs +++ b/packages/superdough/wavetable.mjs @@ -14,7 +14,7 @@ import { import { logger } from './logger.mjs'; const WT_MAX_MIP_LEVELS = 6; -export const WarpMode = Object.freeze({ +export const Warpmode = Object.freeze({ NONE: 0, ASYM: 1, MIRROR: 2, @@ -177,11 +177,17 @@ const _processTables = (json, baseUrl, frameLen, options = {}) => { }; export function registerWaveTable(key, tables, params) { - registerSound(key, (t, hapValue, onended) => onTriggerSynth(t, hapValue, onended, tables, params?.frameLen ?? 2048), { - type: 'wavetable', - tables, - ...params, - }); + registerSound( + key, + (t, hapValue, onended, cps) => { + return onTriggerSynth(t, hapValue, onended, tables, cps, params?.frameLen ?? 2048); + }, + { + type: 'wavetable', + tables, + ...params, + }, + ); } /** @@ -214,13 +220,13 @@ export const tables = async (url, frameLen, json, options = {}) => { }); }; -export async function onTriggerSynth(t, value, onended, tables, frameLen) { - const { s, n = 0, duration } = value; +export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) { + const { s, n = 0, duration, wtenv } = value; const ac = getAudioContext(); const [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]); - let { wtWarpMode } = value; - if (typeof wtWarpMode === 'string') { - wtWarpMode = WarpMode[wtWarpMode.toUpperCase()] ?? WarpMode.NONE; + let { warpmode } = value; + if (typeof warpmode === 'string') { + warpmode = Warpmode[warpmode.toUpperCase()] ?? Warpmode.NONE; } const frequency = getFrequencyFromValue(value); const { url, label } = getCommonSampleInfo(value, tables); @@ -236,12 +242,12 @@ export async function onTriggerSynth(t, value, onended, tables, frameLen) { end: envEnd, frequency, detune: value.detune, - position: value.wtPos, - warp: value.wtWarp, - warpMode: wtWarpMode, + position: value.wt, + warp: value.warp, + warpMode: warpmode, voices: value.unison, spread: value.spread, - phaserand: value.wtPhaseRand, + phaserand: (value.wtphaserand ?? value.unison > 1) ? 1 : 0, }, { outputChannelCount: [2] }, ); @@ -250,36 +256,52 @@ export async function onTriggerSynth(t, value, onended, tables, frameLen) { logger(`[wavetable] still loading sound "${s}:${n}"`, 'highlight'); return; } - const posADSRParams = [value.wtPosAttack, value.wtPosDecay, value.wtPosSustain, value.wtPosRelease]; - const warpADSRParams = [value.wtPosAttack, value.wtPosDecay, value.wtPosSustain, value.wtPosRelease]; + const posADSRParams = [value.wtattack, value.wtdecay, value.wtsustain, value.wtrelease]; + const warpADSRParams = [value.warpattack, value.warpdecay, value.warpsustain, value.warprelease]; const wtParams = source.parameters; const positionParam = wtParams.get('position'); const warpParam = wtParams.get('warp'); let posLFO; - if (posADSRParams.some((p) => p !== undefined)) { - const [pAttack, pDecay, pSustain, pRelease] = getADSRValues(posADSRParams); - getParamADSR(positionParam, pAttack, pDecay, pSustain, pRelease, 0, 1, t, holdEnd, 'linear'); - } else { + if ([wtenv, ...posADSRParams].some((p) => p !== undefined)) { + const [pAttack, pDecay, pSustain, pRelease] = getADSRValues(posADSRParams, 'linear', [0, 0.5, 0, 0.1]); + const min = value.wt ?? 0; + const max = (wtenv ?? 0.5) + min; + getParamADSR(positionParam, pAttack, pDecay, pSustain, pRelease, min, max, t, holdEnd, 'linear'); + } + let wtrate = value.wtrate; + if (value.wtsync != null) { + wtrate = wtrate = cps * value.wtsync; + } + if ([wtrate, value.wtdepth, value.wtshape, value.wtskew, value.wtdc].some((p) => p != null)) { const posLFO = getLfo(ac, t, endWithRelease, { - frequency: value.wtPosRate, - depth: value.wtPosDepth, - shape: value.wtPosShape, - skew: value.wtPosSkew, - dcoffset: value.wtPosDCOffset ?? 0, + frequency: wtrate, + depth: value.wtdepth ?? 0.5, + shape: value.wtshape, + skew: value.wtskew, + dcoffset: value.wtdc ?? 0, }); posLFO.connect(positionParam); } let warpLFO; + if (posADSRParams.some((p) => p !== undefined)) { const [wAttack, wDecay, wSustain, wRelease] = getADSRValues(warpADSRParams); - getParamADSR(warpParam, wAttack, wDecay, wSustain, wRelease, 0, 1, t, holdEnd, 'linear'); - } else { + const min = value.warp ?? 0; + const max = (value.warpenv ?? 0.5) + min; + getParamADSR(warpParam, wAttack, wDecay, wSustain, wRelease, min, max, t, holdEnd, 'linear', [0, 0.5, 0, 0.1]); + } + let warprate = value.warprate; + if (value.warpsync != null) { + console.info(value.warpsync); + warprate = warprate = cps * value.warpsync; + } + if ([warprate, value.warpdepth, value.warpshape, value.warpskew, value.warpdc].some((p) => p != null)) { const warpLFO = getLfo(ac, t, endWithRelease, { - frequency: value.wtWarpRate, - depth: value.wtWarpDepth, - shape: value.wtWarpShape, - skew: value.wtWarpSkew, - dcoffset: value.wtWarpDCOffset ?? 0, + frequency: warprate, + depth: value.warpdepth ?? 0.5, + shape: value.warpshape, + skew: value.warpskew, + dcoffset: value.warpdc ?? 0, }); warpLFO.connect(warpParam); } From c689874bb473a58e1b7031a385227af675c4af08 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sun, 28 Sep 2025 14:57:12 -0400 Subject: [PATCH 12/17] simplify modulators --- packages/superdough/helpers.mjs | 35 +++++++++++++ packages/superdough/wavetable.mjs | 84 ++++++++++++++++--------------- 2 files changed, 78 insertions(+), 41 deletions(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 859329b2..c6ebd1b8 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -153,6 +153,41 @@ export const getADSRValues = (params, curve = 'linear', defaultValues) => { return [Math.max(a ?? 0, envmin), Math.max(d ?? 0, envmin), Math.min(sustain, envmax), Math.max(r ?? 0, releaseMin)]; }; +// helper utility for applying standard modulators to a parameter +export function applyParameterModulators(audioContext, param, start, end, envelopeValues, lfoValues) { + let { amount,offset,defaultAmount = 1, curve = 'linear', values, holdEnd, defaultValues } = envelopeValues; + + if (amount == null) { + const hasADSRParams = values.some(p => p != null); + amount = hasADSRParams ? defaultAmount : 0; + } + + const min = offset ?? 0; + const max = amount + min + const diff = Math.abs(max - min) + if (diff) { + const [attack, decay, sustain, release] = getADSRValues(values, curve, defaultValues); + getParamADSR(param, attack, decay, sustain, release, min, max, start, holdEnd, curve); + } + let lfo + let {defaultDepth = 1,depth, dcoffset, ...getLfoInputs} = lfoValues + + if (depth == null) { + const hasLFOParams = Object.values(getLfoInputs).some(v => v != null) + depth = hasLFOParams ? defaultDepth : 0; + } + if (depth) { + lfo = getLfo(audioContext, start, end, { + depth, + dcoffset, + ...getLfoInputs + }); + lfo.connect(param); + } + + return { lfo, disconnect: () => lfo?.disconnect() } +} + export function createFilter(context, type, frequency, Q, att, dec, sus, rel, fenv, start, end, fanchor, model, drive) { const curve = 'exponential'; const [attack, decay, sustain, release] = getADSRValues([att, dec, sus, rel], curve, [0.005, 0.14, 0, 0.1]); diff --git a/packages/superdough/wavetable.mjs b/packages/superdough/wavetable.mjs index eddc2e3e..bcf4ea46 100644 --- a/packages/superdough/wavetable.mjs +++ b/packages/superdough/wavetable.mjs @@ -1,6 +1,7 @@ import { getAudioContext, registerSound } from './index.mjs'; import { getCommonSampleInfo } from './util.mjs'; import { + applyParameterModulators, destroyAudioWorkletNode, getADSRValues, getFrequencyFromValue, @@ -220,8 +221,10 @@ export const tables = async (url, frameLen, json, options = {}) => { }); }; + + export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) { - const { s, n = 0, duration, wtenv } = value; + const { s, n = 0, duration } = value; const ac = getAudioContext(); const [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]); let { warpmode } = value; @@ -245,7 +248,7 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) { position: value.wt, warp: value.warp, warpMode: warpmode, - voices: value.unison, + voices: Math.max(value.unison ?? 1, 1), spread: value.spread, phaserand: (value.wtphaserand ?? value.unison > 1) ? 1 : 0, }, @@ -261,55 +264,54 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) { const wtParams = source.parameters; const positionParam = wtParams.get('position'); const warpParam = wtParams.get('warp'); - let posLFO; - if ([wtenv, ...posADSRParams].some((p) => p !== undefined)) { - const [pAttack, pDecay, pSustain, pRelease] = getADSRValues(posADSRParams, 'linear', [0, 0.5, 0, 0.1]); - const min = value.wt ?? 0; - const max = (wtenv ?? 0.5) + min; - getParamADSR(positionParam, pAttack, pDecay, pSustain, pRelease, min, max, t, holdEnd, 'linear'); - } + let wtrate = value.wtrate; if (value.wtsync != null) { - wtrate = wtrate = cps * value.wtsync; + wtrate = cps * value.wtsync; } - if ([wtrate, value.wtdepth, value.wtshape, value.wtskew, value.wtdc].some((p) => p != null)) { - const posLFO = getLfo(ac, t, endWithRelease, { - frequency: wtrate, - depth: value.wtdepth ?? 0.5, - shape: value.wtshape, - skew: value.wtskew, - dcoffset: value.wtdc ?? 0, - }); - posLFO.connect(positionParam); - } - let warpLFO; - if (posADSRParams.some((p) => p !== undefined)) { - const [wAttack, wDecay, wSustain, wRelease] = getADSRValues(warpADSRParams); - const min = value.warp ?? 0; - const max = (value.warpenv ?? 0.5) + min; - getParamADSR(warpParam, wAttack, wDecay, wSustain, wRelease, min, max, t, holdEnd, 'linear', [0, 0.5, 0, 0.1]); - } + const wtPosModulators = applyParameterModulators(ac, positionParam, t, endWithRelease, { + offset: value.wt, + amount: value.wtenv, + defaultAmount: 0.5, + shape: 'linear', + values: posADSRParams, + holdEnd, + defaultValues: [0, 0.5, 0, 0.1], + }, { + frequency: wtrate, + depth: value.wtdepth, + defaultDepth: 0.5, + shape: value.wtshape, + skew: value.wtskew, + dcoffset: value.wtdc ?? 0, + }); + let warprate = value.warprate; if (value.warpsync != null) { - console.info(value.warpsync); warprate = warprate = cps * value.warpsync; } - if ([warprate, value.warpdepth, value.warpshape, value.warpskew, value.warpdc].some((p) => p != null)) { - const warpLFO = getLfo(ac, t, endWithRelease, { - frequency: warprate, - depth: value.warpdepth ?? 0.5, - shape: value.warpshape, - skew: value.warpskew, - dcoffset: value.warpdc ?? 0, - }); - warpLFO.connect(warpParam); - } + const wtWarpModulators = applyParameterModulators(ac, warpParam, t, endWithRelease, { + offset: value.warp, + amount: value.warpenv, + defaultAmount: 0.5, + shape: 'linear', + values: warpADSRParams, + holdEnd, + defaultValues: [0, 0.5, 0, 0.1], + }, { + frequency: warprate, + depth: value.warpdepth, + defaultDepth: 0.5, + shape: value.warpshape, + skew: value.warpskew, + dcoffset: value.warpdc ?? 0, + }); const vibratoOscillator = getVibratoOscillator(source.detune, value, t); const envGain = ac.createGain(); const node = source.connect(envGain); getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear'); - getPitchEnvelope(source.detune, value, t, holdEnd); + getPitchEnvelope(source.parameters.get('detune'), value, t, holdEnd); const handle = { node, source }; const timeoutNode = webAudioTimeout( ac, @@ -318,8 +320,8 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) { destroyAudioWorkletNode(source); vibratoOscillator?.stop(); node.disconnect(); - posLFO?.disconnect(); - warpLFO?.disconnect(); + wtPosModulators?.disconnect(); + wtWarpModulators?.disconnect(); onended(); }, t, From 54ebc97ddc3531c4d814e800887e22afc6308d09 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sun, 28 Sep 2025 15:07:35 -0400 Subject: [PATCH 13/17] fix tests --- packages/superdough/helpers.mjs | 22 +- packages/superdough/wavetable.mjs | 80 ++-- packages/superdough/worklets.mjs | 2 +- test/__snapshots__/examples.test.mjs.snap | 440 +++++++++++----------- 4 files changed, 278 insertions(+), 266 deletions(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index c6ebd1b8..47bca330 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -155,37 +155,37 @@ export const getADSRValues = (params, curve = 'linear', defaultValues) => { // helper utility for applying standard modulators to a parameter export function applyParameterModulators(audioContext, param, start, end, envelopeValues, lfoValues) { - let { amount,offset,defaultAmount = 1, curve = 'linear', values, holdEnd, defaultValues } = envelopeValues; - + let { amount, offset, defaultAmount = 1, curve = 'linear', values, holdEnd, defaultValues } = envelopeValues; + if (amount == null) { - const hasADSRParams = values.some(p => p != null); + const hasADSRParams = values.some((p) => p != null); amount = hasADSRParams ? defaultAmount : 0; } const min = offset ?? 0; - const max = amount + min - const diff = Math.abs(max - min) + const max = amount + min; + const diff = Math.abs(max - min); if (diff) { const [attack, decay, sustain, release] = getADSRValues(values, curve, defaultValues); getParamADSR(param, attack, decay, sustain, release, min, max, start, holdEnd, curve); } - let lfo - let {defaultDepth = 1,depth, dcoffset, ...getLfoInputs} = lfoValues - + let lfo; + let { defaultDepth = 1, depth, dcoffset, ...getLfoInputs } = lfoValues; + if (depth == null) { - const hasLFOParams = Object.values(getLfoInputs).some(v => v != null) + const hasLFOParams = Object.values(getLfoInputs).some((v) => v != null); depth = hasLFOParams ? defaultDepth : 0; } if (depth) { lfo = getLfo(audioContext, start, end, { depth, dcoffset, - ...getLfoInputs + ...getLfoInputs, }); lfo.connect(param); } - return { lfo, disconnect: () => lfo?.disconnect() } + return { lfo, disconnect: () => lfo?.disconnect() }; } export function createFilter(context, type, frequency, Q, att, dec, sus, rel, fenv, start, end, fanchor, model, drive) { diff --git a/packages/superdough/wavetable.mjs b/packages/superdough/wavetable.mjs index bcf4ea46..4a21cabe 100644 --- a/packages/superdough/wavetable.mjs +++ b/packages/superdough/wavetable.mjs @@ -221,8 +221,6 @@ export const tables = async (url, frameLen, json, options = {}) => { }); }; - - export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) { const { s, n = 0, duration } = value; const ac = getAudioContext(); @@ -270,43 +268,57 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) { wtrate = cps * value.wtsync; } - const wtPosModulators = applyParameterModulators(ac, positionParam, t, endWithRelease, { - offset: value.wt, - amount: value.wtenv, - defaultAmount: 0.5, - shape: 'linear', - values: posADSRParams, - holdEnd, - defaultValues: [0, 0.5, 0, 0.1], - }, { - frequency: wtrate, - depth: value.wtdepth, - defaultDepth: 0.5, - shape: value.wtshape, - skew: value.wtskew, - dcoffset: value.wtdc ?? 0, - }); + const wtPosModulators = applyParameterModulators( + ac, + positionParam, + t, + endWithRelease, + { + offset: value.wt, + amount: value.wtenv, + defaultAmount: 0.5, + shape: 'linear', + values: posADSRParams, + holdEnd, + defaultValues: [0, 0.5, 0, 0.1], + }, + { + frequency: wtrate, + depth: value.wtdepth, + defaultDepth: 0.5, + shape: value.wtshape, + skew: value.wtskew, + dcoffset: value.wtdc ?? 0, + }, + ); let warprate = value.warprate; if (value.warpsync != null) { warprate = warprate = cps * value.warpsync; } - const wtWarpModulators = applyParameterModulators(ac, warpParam, t, endWithRelease, { - offset: value.warp, - amount: value.warpenv, - defaultAmount: 0.5, - shape: 'linear', - values: warpADSRParams, - holdEnd, - defaultValues: [0, 0.5, 0, 0.1], - }, { - frequency: warprate, - depth: value.warpdepth, - defaultDepth: 0.5, - shape: value.warpshape, - skew: value.warpskew, - dcoffset: value.warpdc ?? 0, - }); + const wtWarpModulators = applyParameterModulators( + ac, + warpParam, + t, + endWithRelease, + { + offset: value.warp, + amount: value.warpenv, + defaultAmount: 0.5, + shape: 'linear', + values: warpADSRParams, + holdEnd, + defaultValues: [0, 0.5, 0, 0.1], + }, + { + frequency: warprate, + depth: value.warpdepth, + defaultDepth: 0.5, + shape: value.warpshape, + skew: value.warpskew, + dcoffset: value.warpdc ?? 0, + }, + ); const vibratoOscillator = getVibratoOscillator(source.detune, value, t); const envGain = ac.createGain(); const node = source.connect(envGain); diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 0a2d40d9..cea5a1ea 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -1054,7 +1054,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { { name: 'warp', defaultValue: 0, minValue: 0, maxValue: 1 }, { name: 'warpMode', defaultValue: 0 }, { name: 'voices', defaultValue: 1, minValue: 1, maxValue: 32 }, - { name: 'spread', defaultValue: 0.18, minValue: 0, maxValue: 1 }, + { name: 'spread', defaultValue: 0.4, minValue: 0, maxValue: 1 }, { name: 'phaserand', defaultValue: 0, minValue: 0, maxValue: 1 }, ]; } diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index bfa4ae40..73278947 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -11789,6 +11789,112 @@ exports[`runs examples > example "vowel" example index 1 1`] = ` ] `; +exports[`runs examples > example "warp" example index 0 1`] = ` +[ + "[ 0/1 → 1/8 | s:basique bank:wt_digital note:F1 warp:0 warpmode:spin ]", + "[ (1/8 → 1/5) ⇝ 1/4 | s:basique bank:wt_digital note:F1 warp:0 warpmode:spin ]", + "[ 1/8 ⇜ (1/5 → 1/4) | s:basique bank:wt_digital note:F1 warp:0.25 warpmode:spin ]", + "[ 1/4 → 3/8 | s:basique bank:wt_digital note:F1 warp:0.25 warpmode:spin ]", + "[ (3/8 → 2/5) ⇝ 1/2 | s:basique bank:wt_digital note:F1 warp:0.25 warpmode:spin ]", + "[ 3/8 ⇜ (2/5 → 1/2) | s:basique bank:wt_digital note:F1 warp:0.5 warpmode:spin ]", + "[ (1/2 → 3/5) ⇝ 5/8 | s:basique bank:wt_digital note:F1 warp:0.5 warpmode:spin ]", + "[ 1/2 ⇜ (3/5 → 5/8) | s:basique bank:wt_digital note:F1 warp:0.75 warpmode:spin ]", + "[ 5/8 → 3/4 | s:basique bank:wt_digital note:F1 warp:0.75 warpmode:spin ]", + "[ (3/4 → 4/5) ⇝ 7/8 | s:basique bank:wt_digital note:F1 warp:0.75 warpmode:spin ]", + "[ 3/4 ⇜ (4/5 → 7/8) | s:basique bank:wt_digital note:F1 warp:1 warpmode:spin ]", + "[ 7/8 → 1/1 | s:basique bank:wt_digital note:F1 warp:1 warpmode:spin ]", + "[ 1/1 → 9/8 | s:basique bank:wt_digital note:F1 warp:0 warpmode:spin ]", + "[ (9/8 → 6/5) ⇝ 5/4 | s:basique bank:wt_digital note:F1 warp:0 warpmode:spin ]", + "[ 9/8 ⇜ (6/5 → 5/4) | s:basique bank:wt_digital note:F1 warp:0.25 warpmode:spin ]", + "[ 5/4 → 11/8 | s:basique bank:wt_digital note:F1 warp:0.25 warpmode:spin ]", + "[ (11/8 → 7/5) ⇝ 3/2 | s:basique bank:wt_digital note:F1 warp:0.25 warpmode:spin ]", + "[ 11/8 ⇜ (7/5 → 3/2) | s:basique bank:wt_digital note:F1 warp:0.5 warpmode:spin ]", + "[ (3/2 → 8/5) ⇝ 13/8 | s:basique bank:wt_digital note:F1 warp:0.5 warpmode:spin ]", + "[ 3/2 ⇜ (8/5 → 13/8) | s:basique bank:wt_digital note:F1 warp:0.75 warpmode:spin ]", + "[ 13/8 → 7/4 | s:basique bank:wt_digital note:F1 warp:0.75 warpmode:spin ]", + "[ (7/4 → 9/5) ⇝ 15/8 | s:basique bank:wt_digital note:F1 warp:0.75 warpmode:spin ]", + "[ 7/4 ⇜ (9/5 → 15/8) | s:basique bank:wt_digital note:F1 warp:1 warpmode:spin ]", + "[ 15/8 → 2/1 | s:basique bank:wt_digital note:F1 warp:1 warpmode:spin ]", + "[ 2/1 → 17/8 | s:basique bank:wt_digital note:F1 warp:0 warpmode:spin ]", + "[ (17/8 → 11/5) ⇝ 9/4 | s:basique bank:wt_digital note:F1 warp:0 warpmode:spin ]", + "[ 17/8 ⇜ (11/5 → 9/4) | s:basique bank:wt_digital note:F1 warp:0.25 warpmode:spin ]", + "[ 9/4 → 19/8 | s:basique bank:wt_digital note:F1 warp:0.25 warpmode:spin ]", + "[ (19/8 → 12/5) ⇝ 5/2 | s:basique bank:wt_digital note:F1 warp:0.25 warpmode:spin ]", + "[ 19/8 ⇜ (12/5 → 5/2) | s:basique bank:wt_digital note:F1 warp:0.5 warpmode:spin ]", + "[ (5/2 → 13/5) ⇝ 21/8 | s:basique bank:wt_digital note:F1 warp:0.5 warpmode:spin ]", + "[ 5/2 ⇜ (13/5 → 21/8) | s:basique bank:wt_digital note:F1 warp:0.75 warpmode:spin ]", + "[ 21/8 → 11/4 | s:basique bank:wt_digital note:F1 warp:0.75 warpmode:spin ]", + "[ (11/4 → 14/5) ⇝ 23/8 | s:basique bank:wt_digital note:F1 warp:0.75 warpmode:spin ]", + "[ 11/4 ⇜ (14/5 → 23/8) | s:basique bank:wt_digital note:F1 warp:1 warpmode:spin ]", + "[ 23/8 → 3/1 | s:basique bank:wt_digital note:F1 warp:1 warpmode:spin ]", + "[ 3/1 → 25/8 | s:basique bank:wt_digital note:F1 warp:0 warpmode:spin ]", + "[ (25/8 → 16/5) ⇝ 13/4 | s:basique bank:wt_digital note:F1 warp:0 warpmode:spin ]", + "[ 25/8 ⇜ (16/5 → 13/4) | s:basique bank:wt_digital note:F1 warp:0.25 warpmode:spin ]", + "[ 13/4 → 27/8 | s:basique bank:wt_digital note:F1 warp:0.25 warpmode:spin ]", + "[ (27/8 → 17/5) ⇝ 7/2 | s:basique bank:wt_digital note:F1 warp:0.25 warpmode:spin ]", + "[ 27/8 ⇜ (17/5 → 7/2) | s:basique bank:wt_digital note:F1 warp:0.5 warpmode:spin ]", + "[ (7/2 → 18/5) ⇝ 29/8 | s:basique bank:wt_digital note:F1 warp:0.5 warpmode:spin ]", + "[ 7/2 ⇜ (18/5 → 29/8) | s:basique bank:wt_digital note:F1 warp:0.75 warpmode:spin ]", + "[ 29/8 → 15/4 | s:basique bank:wt_digital note:F1 warp:0.75 warpmode:spin ]", + "[ (15/4 → 19/5) ⇝ 31/8 | s:basique bank:wt_digital note:F1 warp:0.75 warpmode:spin ]", + "[ 15/4 ⇜ (19/5 → 31/8) | s:basique bank:wt_digital note:F1 warp:1 warpmode:spin ]", + "[ 31/8 → 4/1 | s:basique bank:wt_digital note:F1 warp:1 warpmode:spin ]", +] +`; + +exports[`runs examples > example "warpmode" example index 0 1`] = ` +[ + "[ 0/1 → 1/8 | s:morgana bank:wt_digital note:F1 warp:0 warpmode:asym ]", + "[ (1/8 → 1/5) ⇝ 1/4 | s:morgana bank:wt_digital note:F1 warp:0 warpmode:asym ]", + "[ 1/8 ⇜ (1/5 → 1/4) | s:morgana bank:wt_digital note:F1 warp:0.25 warpmode:asym ]", + "[ 1/4 → 3/8 | s:morgana bank:wt_digital note:F1 warp:0.25 warpmode:asym ]", + "[ (3/8 → 2/5) ⇝ 1/2 | s:morgana bank:wt_digital note:F1 warp:0.25 warpmode:asym ]", + "[ 3/8 ⇜ (2/5 → 1/2) | s:morgana bank:wt_digital note:F1 warp:0.5 warpmode:asym ]", + "[ (1/2 → 3/5) ⇝ 5/8 | s:morgana bank:wt_digital note:F1 warp:0.5 warpmode:bendp ]", + "[ 1/2 ⇜ (3/5 → 5/8) | s:morgana bank:wt_digital note:F1 warp:0.75 warpmode:bendp ]", + "[ 5/8 → 3/4 | s:morgana bank:wt_digital note:F1 warp:0.75 warpmode:bendp ]", + "[ (3/4 → 4/5) ⇝ 7/8 | s:morgana bank:wt_digital note:F1 warp:0.75 warpmode:bendp ]", + "[ 3/4 ⇜ (4/5 → 7/8) | s:morgana bank:wt_digital note:F1 warp:1 warpmode:bendp ]", + "[ 7/8 → 1/1 | s:morgana bank:wt_digital note:F1 warp:1 warpmode:bendp ]", + "[ 1/1 → 9/8 | s:morgana bank:wt_digital note:F1 warp:0 warpmode:spin ]", + "[ (9/8 → 6/5) ⇝ 5/4 | s:morgana bank:wt_digital note:F1 warp:0 warpmode:spin ]", + "[ 9/8 ⇜ (6/5 → 5/4) | s:morgana bank:wt_digital note:F1 warp:0.25 warpmode:spin ]", + "[ 5/4 → 11/8 | s:morgana bank:wt_digital note:F1 warp:0.25 warpmode:spin ]", + "[ (11/8 → 7/5) ⇝ 3/2 | s:morgana bank:wt_digital note:F1 warp:0.25 warpmode:spin ]", + "[ 11/8 ⇜ (7/5 → 3/2) | s:morgana bank:wt_digital note:F1 warp:0.5 warpmode:spin ]", + "[ (3/2 → 8/5) ⇝ 13/8 | s:morgana bank:wt_digital note:F1 warp:0.5 warpmode:logistic ]", + "[ 3/2 ⇜ (8/5 → 13/8) | s:morgana bank:wt_digital note:F1 warp:0.75 warpmode:logistic ]", + "[ 13/8 → 7/4 | s:morgana bank:wt_digital note:F1 warp:0.75 warpmode:logistic ]", + "[ (7/4 → 9/5) ⇝ 15/8 | s:morgana bank:wt_digital note:F1 warp:0.75 warpmode:logistic ]", + "[ 7/4 ⇜ (9/5 → 15/8) | s:morgana bank:wt_digital note:F1 warp:1 warpmode:logistic ]", + "[ 15/8 → 2/1 | s:morgana bank:wt_digital note:F1 warp:1 warpmode:logistic ]", + "[ 2/1 → 17/8 | s:morgana bank:wt_digital note:F1 warp:0 warpmode:sync ]", + "[ (17/8 → 11/5) ⇝ 9/4 | s:morgana bank:wt_digital note:F1 warp:0 warpmode:sync ]", + "[ 17/8 ⇜ (11/5 → 9/4) | s:morgana bank:wt_digital note:F1 warp:0.25 warpmode:sync ]", + "[ 9/4 → 19/8 | s:morgana bank:wt_digital note:F1 warp:0.25 warpmode:sync ]", + "[ (19/8 → 12/5) ⇝ 5/2 | s:morgana bank:wt_digital note:F1 warp:0.25 warpmode:sync ]", + "[ 19/8 ⇜ (12/5 → 5/2) | s:morgana bank:wt_digital note:F1 warp:0.5 warpmode:sync ]", + "[ (5/2 → 13/5) ⇝ 21/8 | s:morgana bank:wt_digital note:F1 warp:0.5 warpmode:wormhole ]", + "[ 5/2 ⇜ (13/5 → 21/8) | s:morgana bank:wt_digital note:F1 warp:0.75 warpmode:wormhole ]", + "[ 21/8 → 11/4 | s:morgana bank:wt_digital note:F1 warp:0.75 warpmode:wormhole ]", + "[ (11/4 → 14/5) ⇝ 23/8 | s:morgana bank:wt_digital note:F1 warp:0.75 warpmode:wormhole ]", + "[ 11/4 ⇜ (14/5 → 23/8) | s:morgana bank:wt_digital note:F1 warp:1 warpmode:wormhole ]", + "[ 23/8 → 3/1 | s:morgana bank:wt_digital note:F1 warp:1 warpmode:wormhole ]", + "[ 3/1 → 25/8 | s:morgana bank:wt_digital note:F1 warp:0 warpmode:brownian ]", + "[ (25/8 → 16/5) ⇝ 13/4 | s:morgana bank:wt_digital note:F1 warp:0 warpmode:brownian ]", + "[ 25/8 ⇜ (16/5 → 13/4) | s:morgana bank:wt_digital note:F1 warp:0.25 warpmode:brownian ]", + "[ 13/4 → 27/8 | s:morgana bank:wt_digital note:F1 warp:0.25 warpmode:brownian ]", + "[ (27/8 → 17/5) ⇝ 7/2 | s:morgana bank:wt_digital note:F1 warp:0.25 warpmode:brownian ]", + "[ 27/8 ⇜ (17/5 → 7/2) | s:morgana bank:wt_digital note:F1 warp:0.5 warpmode:brownian ]", + "[ (7/2 → 18/5) ⇝ 29/8 | s:morgana bank:wt_digital note:F1 warp:0.5 warpmode:asym ]", + "[ 7/2 ⇜ (18/5 → 29/8) | s:morgana bank:wt_digital note:F1 warp:0.75 warpmode:asym ]", + "[ 29/8 → 15/4 | s:morgana bank:wt_digital note:F1 warp:0.75 warpmode:asym ]", + "[ (15/4 → 19/5) ⇝ 31/8 | s:morgana bank:wt_digital note:F1 warp:0.75 warpmode:asym ]", + "[ 15/4 ⇜ (19/5 → 31/8) | s:morgana bank:wt_digital note:F1 warp:1 warpmode:asym ]", + "[ 31/8 → 4/1 | s:morgana bank:wt_digital note:F1 warp:1 warpmode:asym ]", +] +`; + exports[`runs examples > example "wchoose" example index 0 1`] = ` [ "[ 0/1 → 1/5 | note:c2 s:sine ]", @@ -11993,231 +12099,125 @@ exports[`runs examples > example "withValue" example index 0 1`] = ` ] `; -exports[`runs examples > example "wtPhaseRand" example index 0 1`] = ` +exports[`runs examples > example "wt" example index 0 1`] = ` [ - "[ 0/1 → 1/16 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 1/16 → 1/8 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 1/8 → 3/16 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 3/16 → 1/4 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 1/4 → 5/16 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 5/16 → 3/8 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 3/8 → 7/16 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 7/16 → 1/2 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 1/2 → 9/16 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 9/16 → 5/8 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 5/8 → 11/16 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 11/16 → 3/4 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 3/4 → 13/16 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 13/16 → 7/8 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 7/8 → 15/16 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 15/16 → 1/1 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 1/1 → 17/16 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 17/16 → 9/8 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 9/8 → 19/16 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 19/16 → 5/4 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 5/4 → 21/16 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 21/16 → 11/8 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 11/8 → 23/16 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 23/16 → 3/2 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 3/2 → 25/16 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 25/16 → 13/8 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 13/8 → 27/16 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 27/16 → 7/4 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 7/4 → 29/16 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 29/16 → 15/8 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 15/8 → 31/16 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 31/16 → 2/1 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 2/1 → 33/16 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 33/16 → 17/8 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 17/8 → 35/16 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 35/16 → 9/4 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 9/4 → 37/16 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 37/16 → 19/8 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 19/8 → 39/16 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 39/16 → 5/2 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 5/2 → 41/16 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 41/16 → 21/8 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 21/8 → 43/16 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 43/16 → 11/4 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 11/4 → 45/16 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 45/16 → 23/8 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 23/8 → 47/16 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 47/16 → 3/1 | s:basique bank:wt_digital wtPhaseRand:0 ]", - "[ 3/1 → 49/16 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 49/16 → 25/8 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 25/8 → 51/16 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 51/16 → 13/4 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 13/4 → 53/16 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 53/16 → 27/8 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 27/8 → 55/16 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 55/16 → 7/2 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 7/2 → 57/16 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 57/16 → 29/8 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 29/8 → 59/16 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 59/16 → 15/4 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 15/4 → 61/16 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 61/16 → 31/8 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 31/8 → 63/16 | s:basique bank:wt_digital wtPhaseRand:1 ]", - "[ 63/16 → 4/1 | s:basique bank:wt_digital wtPhaseRand:1 ]", + "[ 0/1 → 1/8 | s:squelch bank:wt_digital note:F1 wt:0 ]", + "[ (1/8 → 1/5) ⇝ 1/4 | s:squelch bank:wt_digital note:F1 wt:0 ]", + "[ 1/8 ⇜ (1/5 → 1/4) | s:squelch bank:wt_digital note:F1 wt:0.25 ]", + "[ 1/4 → 3/8 | s:squelch bank:wt_digital note:F1 wt:0.25 ]", + "[ (3/8 → 2/5) ⇝ 1/2 | s:squelch bank:wt_digital note:F1 wt:0.25 ]", + "[ 3/8 ⇜ (2/5 → 1/2) | s:squelch bank:wt_digital note:F1 wt:0.5 ]", + "[ (1/2 → 3/5) ⇝ 5/8 | s:squelch bank:wt_digital note:F1 wt:0.5 ]", + "[ 1/2 ⇜ (3/5 → 5/8) | s:squelch bank:wt_digital note:F1 wt:0.75 ]", + "[ 5/8 → 3/4 | s:squelch bank:wt_digital note:F1 wt:0.75 ]", + "[ (3/4 → 4/5) ⇝ 7/8 | s:squelch bank:wt_digital note:F1 wt:0.75 ]", + "[ 3/4 ⇜ (4/5 → 7/8) | s:squelch bank:wt_digital note:F1 wt:1 ]", + "[ 7/8 → 1/1 | s:squelch bank:wt_digital note:F1 wt:1 ]", + "[ 1/1 → 9/8 | s:squelch bank:wt_digital note:F1 wt:0 ]", + "[ (9/8 → 6/5) ⇝ 5/4 | s:squelch bank:wt_digital note:F1 wt:0 ]", + "[ 9/8 ⇜ (6/5 → 5/4) | s:squelch bank:wt_digital note:F1 wt:0.25 ]", + "[ 5/4 → 11/8 | s:squelch bank:wt_digital note:F1 wt:0.25 ]", + "[ (11/8 → 7/5) ⇝ 3/2 | s:squelch bank:wt_digital note:F1 wt:0.25 ]", + "[ 11/8 ⇜ (7/5 → 3/2) | s:squelch bank:wt_digital note:F1 wt:0.5 ]", + "[ (3/2 → 8/5) ⇝ 13/8 | s:squelch bank:wt_digital note:F1 wt:0.5 ]", + "[ 3/2 ⇜ (8/5 → 13/8) | s:squelch bank:wt_digital note:F1 wt:0.75 ]", + "[ 13/8 → 7/4 | s:squelch bank:wt_digital note:F1 wt:0.75 ]", + "[ (7/4 → 9/5) ⇝ 15/8 | s:squelch bank:wt_digital note:F1 wt:0.75 ]", + "[ 7/4 ⇜ (9/5 → 15/8) | s:squelch bank:wt_digital note:F1 wt:1 ]", + "[ 15/8 → 2/1 | s:squelch bank:wt_digital note:F1 wt:1 ]", + "[ 2/1 → 17/8 | s:squelch bank:wt_digital note:F1 wt:0 ]", + "[ (17/8 → 11/5) ⇝ 9/4 | s:squelch bank:wt_digital note:F1 wt:0 ]", + "[ 17/8 ⇜ (11/5 → 9/4) | s:squelch bank:wt_digital note:F1 wt:0.25 ]", + "[ 9/4 → 19/8 | s:squelch bank:wt_digital note:F1 wt:0.25 ]", + "[ (19/8 → 12/5) ⇝ 5/2 | s:squelch bank:wt_digital note:F1 wt:0.25 ]", + "[ 19/8 ⇜ (12/5 → 5/2) | s:squelch bank:wt_digital note:F1 wt:0.5 ]", + "[ (5/2 → 13/5) ⇝ 21/8 | s:squelch bank:wt_digital note:F1 wt:0.5 ]", + "[ 5/2 ⇜ (13/5 → 21/8) | s:squelch bank:wt_digital note:F1 wt:0.75 ]", + "[ 21/8 → 11/4 | s:squelch bank:wt_digital note:F1 wt:0.75 ]", + "[ (11/4 → 14/5) ⇝ 23/8 | s:squelch bank:wt_digital note:F1 wt:0.75 ]", + "[ 11/4 ⇜ (14/5 → 23/8) | s:squelch bank:wt_digital note:F1 wt:1 ]", + "[ 23/8 → 3/1 | s:squelch bank:wt_digital note:F1 wt:1 ]", + "[ 3/1 → 25/8 | s:squelch bank:wt_digital note:F1 wt:0 ]", + "[ (25/8 → 16/5) ⇝ 13/4 | s:squelch bank:wt_digital note:F1 wt:0 ]", + "[ 25/8 ⇜ (16/5 → 13/4) | s:squelch bank:wt_digital note:F1 wt:0.25 ]", + "[ 13/4 → 27/8 | s:squelch bank:wt_digital note:F1 wt:0.25 ]", + "[ (27/8 → 17/5) ⇝ 7/2 | s:squelch bank:wt_digital note:F1 wt:0.25 ]", + "[ 27/8 ⇜ (17/5 → 7/2) | s:squelch bank:wt_digital note:F1 wt:0.5 ]", + "[ (7/2 → 18/5) ⇝ 29/8 | s:squelch bank:wt_digital note:F1 wt:0.5 ]", + "[ 7/2 ⇜ (18/5 → 29/8) | s:squelch bank:wt_digital note:F1 wt:0.75 ]", + "[ 29/8 → 15/4 | s:squelch bank:wt_digital note:F1 wt:0.75 ]", + "[ (15/4 → 19/5) ⇝ 31/8 | s:squelch bank:wt_digital note:F1 wt:0.75 ]", + "[ 15/4 ⇜ (19/5 → 31/8) | s:squelch bank:wt_digital note:F1 wt:1 ]", + "[ 31/8 → 4/1 | s:squelch bank:wt_digital note:F1 wt:1 ]", ] `; -exports[`runs examples > example "wtPos" example index 0 1`] = ` +exports[`runs examples > example "wtphaserand" example index 0 1`] = ` [ - "[ 0/1 → 1/8 | s:squelch bank:wt_digital note:F1 wtPos:0 ]", - "[ (1/8 → 1/5) ⇝ 1/4 | s:squelch bank:wt_digital note:F1 wtPos:0 ]", - "[ 1/8 ⇜ (1/5 → 1/4) | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]", - "[ 1/4 → 3/8 | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]", - "[ (3/8 → 2/5) ⇝ 1/2 | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]", - "[ 3/8 ⇜ (2/5 → 1/2) | s:squelch bank:wt_digital note:F1 wtPos:0.5 ]", - "[ (1/2 → 3/5) ⇝ 5/8 | s:squelch bank:wt_digital note:F1 wtPos:0.5 ]", - "[ 1/2 ⇜ (3/5 → 5/8) | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]", - "[ 5/8 → 3/4 | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]", - "[ (3/4 → 4/5) ⇝ 7/8 | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]", - "[ 3/4 ⇜ (4/5 → 7/8) | s:squelch bank:wt_digital note:F1 wtPos:1 ]", - "[ 7/8 → 1/1 | s:squelch bank:wt_digital note:F1 wtPos:1 ]", - "[ 1/1 → 9/8 | s:squelch bank:wt_digital note:F1 wtPos:0 ]", - "[ (9/8 → 6/5) ⇝ 5/4 | s:squelch bank:wt_digital note:F1 wtPos:0 ]", - "[ 9/8 ⇜ (6/5 → 5/4) | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]", - "[ 5/4 → 11/8 | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]", - "[ (11/8 → 7/5) ⇝ 3/2 | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]", - "[ 11/8 ⇜ (7/5 → 3/2) | s:squelch bank:wt_digital note:F1 wtPos:0.5 ]", - "[ (3/2 → 8/5) ⇝ 13/8 | s:squelch bank:wt_digital note:F1 wtPos:0.5 ]", - "[ 3/2 ⇜ (8/5 → 13/8) | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]", - "[ 13/8 → 7/4 | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]", - "[ (7/4 → 9/5) ⇝ 15/8 | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]", - "[ 7/4 ⇜ (9/5 → 15/8) | s:squelch bank:wt_digital note:F1 wtPos:1 ]", - "[ 15/8 → 2/1 | s:squelch bank:wt_digital note:F1 wtPos:1 ]", - "[ 2/1 → 17/8 | s:squelch bank:wt_digital note:F1 wtPos:0 ]", - "[ (17/8 → 11/5) ⇝ 9/4 | s:squelch bank:wt_digital note:F1 wtPos:0 ]", - "[ 17/8 ⇜ (11/5 → 9/4) | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]", - "[ 9/4 → 19/8 | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]", - "[ (19/8 → 12/5) ⇝ 5/2 | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]", - "[ 19/8 ⇜ (12/5 → 5/2) | s:squelch bank:wt_digital note:F1 wtPos:0.5 ]", - "[ (5/2 → 13/5) ⇝ 21/8 | s:squelch bank:wt_digital note:F1 wtPos:0.5 ]", - "[ 5/2 ⇜ (13/5 → 21/8) | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]", - "[ 21/8 → 11/4 | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]", - "[ (11/4 → 14/5) ⇝ 23/8 | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]", - "[ 11/4 ⇜ (14/5 → 23/8) | s:squelch bank:wt_digital note:F1 wtPos:1 ]", - "[ 23/8 → 3/1 | s:squelch bank:wt_digital note:F1 wtPos:1 ]", - "[ 3/1 → 25/8 | s:squelch bank:wt_digital note:F1 wtPos:0 ]", - "[ (25/8 → 16/5) ⇝ 13/4 | s:squelch bank:wt_digital note:F1 wtPos:0 ]", - "[ 25/8 ⇜ (16/5 → 13/4) | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]", - "[ 13/4 → 27/8 | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]", - "[ (27/8 → 17/5) ⇝ 7/2 | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]", - "[ 27/8 ⇜ (17/5 → 7/2) | s:squelch bank:wt_digital note:F1 wtPos:0.5 ]", - "[ (7/2 → 18/5) ⇝ 29/8 | s:squelch bank:wt_digital note:F1 wtPos:0.5 ]", - "[ 7/2 ⇜ (18/5 → 29/8) | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]", - "[ 29/8 → 15/4 | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]", - "[ (15/4 → 19/5) ⇝ 31/8 | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]", - "[ 15/4 ⇜ (19/5 → 31/8) | s:squelch bank:wt_digital note:F1 wtPos:1 ]", - "[ 31/8 → 4/1 | s:squelch bank:wt_digital note:F1 wtPos:1 ]", -] -`; - -exports[`runs examples > example "wtWarp" example index 0 1`] = ` -[ - "[ 0/1 → 1/8 | s:basique bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ (1/8 → 1/5) ⇝ 1/4 | s:basique bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ 1/8 ⇜ (1/5 → 1/4) | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 1/4 → 3/8 | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ (3/8 → 2/5) ⇝ 1/2 | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 3/8 ⇜ (2/5 → 1/2) | s:basique bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ (1/2 → 3/5) ⇝ 5/8 | s:basique bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ 1/2 ⇜ (3/5 → 5/8) | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ 5/8 → 3/4 | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ (3/4 → 4/5) ⇝ 7/8 | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ 3/4 ⇜ (4/5 → 7/8) | s:basique bank:wt_digital note:F1 wtWarp:1 wtWarpMode:spin ]", - "[ 7/8 → 1/1 | s:basique bank:wt_digital note:F1 wtWarp:1 wtWarpMode:spin ]", - "[ 1/1 → 9/8 | s:basique bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ (9/8 → 6/5) ⇝ 5/4 | s:basique bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ 9/8 ⇜ (6/5 → 5/4) | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 5/4 → 11/8 | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ (11/8 → 7/5) ⇝ 3/2 | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 11/8 ⇜ (7/5 → 3/2) | s:basique bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ (3/2 → 8/5) ⇝ 13/8 | s:basique bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ 3/2 ⇜ (8/5 → 13/8) | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ 13/8 → 7/4 | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ (7/4 → 9/5) ⇝ 15/8 | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ 7/4 ⇜ (9/5 → 15/8) | s:basique bank:wt_digital note:F1 wtWarp:1 wtWarpMode:spin ]", - "[ 15/8 → 2/1 | s:basique bank:wt_digital note:F1 wtWarp:1 wtWarpMode:spin ]", - "[ 2/1 → 17/8 | s:basique bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ (17/8 → 11/5) ⇝ 9/4 | s:basique bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ 17/8 ⇜ (11/5 → 9/4) | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 9/4 → 19/8 | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ (19/8 → 12/5) ⇝ 5/2 | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 19/8 ⇜ (12/5 → 5/2) | s:basique bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ (5/2 → 13/5) ⇝ 21/8 | s:basique bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ 5/2 ⇜ (13/5 → 21/8) | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ 21/8 → 11/4 | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ (11/4 → 14/5) ⇝ 23/8 | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ 11/4 ⇜ (14/5 → 23/8) | s:basique bank:wt_digital note:F1 wtWarp:1 wtWarpMode:spin ]", - "[ 23/8 → 3/1 | s:basique bank:wt_digital note:F1 wtWarp:1 wtWarpMode:spin ]", - "[ 3/1 → 25/8 | s:basique bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ (25/8 → 16/5) ⇝ 13/4 | s:basique bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ 25/8 ⇜ (16/5 → 13/4) | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 13/4 → 27/8 | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ (27/8 → 17/5) ⇝ 7/2 | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 27/8 ⇜ (17/5 → 7/2) | s:basique bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ (7/2 → 18/5) ⇝ 29/8 | s:basique bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ 7/2 ⇜ (18/5 → 29/8) | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ 29/8 → 15/4 | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ (15/4 → 19/5) ⇝ 31/8 | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]", - "[ 15/4 ⇜ (19/5 → 31/8) | s:basique bank:wt_digital note:F1 wtWarp:1 wtWarpMode:spin ]", - "[ 31/8 → 4/1 | s:basique bank:wt_digital note:F1 wtWarp:1 wtWarpMode:spin ]", -] -`; - -exports[`runs examples > example "wtWarpMode" example index 0 1`] = ` -[ - "[ 0/1 → 1/8 | s:morgana bank:wt_digital note:F1 wtWarp:0 wtWarpMode:asym ]", - "[ (1/8 → 1/5) ⇝ 1/4 | s:morgana bank:wt_digital note:F1 wtWarp:0 wtWarpMode:asym ]", - "[ 1/8 ⇜ (1/5 → 1/4) | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:asym ]", - "[ 1/4 → 3/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:asym ]", - "[ (3/8 → 2/5) ⇝ 1/2 | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:asym ]", - "[ 3/8 ⇜ (2/5 → 1/2) | s:morgana bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:asym ]", - "[ (1/2 → 3/5) ⇝ 5/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:bendp ]", - "[ 1/2 ⇜ (3/5 → 5/8) | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:bendp ]", - "[ 5/8 → 3/4 | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:bendp ]", - "[ (3/4 → 4/5) ⇝ 7/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:bendp ]", - "[ 3/4 ⇜ (4/5 → 7/8) | s:morgana bank:wt_digital note:F1 wtWarp:1 wtWarpMode:bendp ]", - "[ 7/8 → 1/1 | s:morgana bank:wt_digital note:F1 wtWarp:1 wtWarpMode:bendp ]", - "[ 1/1 → 9/8 | s:morgana bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ (9/8 → 6/5) ⇝ 5/4 | s:morgana bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]", - "[ 9/8 ⇜ (6/5 → 5/4) | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 5/4 → 11/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ (11/8 → 7/5) ⇝ 3/2 | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]", - "[ 11/8 ⇜ (7/5 → 3/2) | s:morgana bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]", - "[ (3/2 → 8/5) ⇝ 13/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:logistic ]", - "[ 3/2 ⇜ (8/5 → 13/8) | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:logistic ]", - "[ 13/8 → 7/4 | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:logistic ]", - "[ (7/4 → 9/5) ⇝ 15/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:logistic ]", - "[ 7/4 ⇜ (9/5 → 15/8) | s:morgana bank:wt_digital note:F1 wtWarp:1 wtWarpMode:logistic ]", - "[ 15/8 → 2/1 | s:morgana bank:wt_digital note:F1 wtWarp:1 wtWarpMode:logistic ]", - "[ 2/1 → 17/8 | s:morgana bank:wt_digital note:F1 wtWarp:0 wtWarpMode:sync ]", - "[ (17/8 → 11/5) ⇝ 9/4 | s:morgana bank:wt_digital note:F1 wtWarp:0 wtWarpMode:sync ]", - "[ 17/8 ⇜ (11/5 → 9/4) | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:sync ]", - "[ 9/4 → 19/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:sync ]", - "[ (19/8 → 12/5) ⇝ 5/2 | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:sync ]", - "[ 19/8 ⇜ (12/5 → 5/2) | s:morgana bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:sync ]", - "[ (5/2 → 13/5) ⇝ 21/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:wormhole ]", - "[ 5/2 ⇜ (13/5 → 21/8) | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:wormhole ]", - "[ 21/8 → 11/4 | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:wormhole ]", - "[ (11/4 → 14/5) ⇝ 23/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:wormhole ]", - "[ 11/4 ⇜ (14/5 → 23/8) | s:morgana bank:wt_digital note:F1 wtWarp:1 wtWarpMode:wormhole ]", - "[ 23/8 → 3/1 | s:morgana bank:wt_digital note:F1 wtWarp:1 wtWarpMode:wormhole ]", - "[ 3/1 → 25/8 | s:morgana bank:wt_digital note:F1 wtWarp:0 wtWarpMode:brownian ]", - "[ (25/8 → 16/5) ⇝ 13/4 | s:morgana bank:wt_digital note:F1 wtWarp:0 wtWarpMode:brownian ]", - "[ 25/8 ⇜ (16/5 → 13/4) | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:brownian ]", - "[ 13/4 → 27/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:brownian ]", - "[ (27/8 → 17/5) ⇝ 7/2 | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:brownian ]", - "[ 27/8 ⇜ (17/5 → 7/2) | s:morgana bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:brownian ]", - "[ (7/2 → 18/5) ⇝ 29/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:asym ]", - "[ 7/2 ⇜ (18/5 → 29/8) | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:asym ]", - "[ 29/8 → 15/4 | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:asym ]", - "[ (15/4 → 19/5) ⇝ 31/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:asym ]", - "[ 15/4 ⇜ (19/5 → 31/8) | s:morgana bank:wt_digital note:F1 wtWarp:1 wtWarpMode:asym ]", - "[ 31/8 → 4/1 | s:morgana bank:wt_digital note:F1 wtWarp:1 wtWarpMode:asym ]", + "[ 0/1 → 1/16 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 1/16 → 1/8 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 1/8 → 3/16 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 3/16 → 1/4 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 1/4 → 5/16 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 5/16 → 3/8 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 3/8 → 7/16 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 7/16 → 1/2 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 1/2 → 9/16 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 9/16 → 5/8 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 5/8 → 11/16 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 11/16 → 3/4 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 3/4 → 13/16 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 13/16 → 7/8 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 7/8 → 15/16 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 15/16 → 1/1 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 1/1 → 17/16 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 17/16 → 9/8 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 9/8 → 19/16 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 19/16 → 5/4 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 5/4 → 21/16 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 21/16 → 11/8 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 11/8 → 23/16 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 23/16 → 3/2 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 3/2 → 25/16 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 25/16 → 13/8 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 13/8 → 27/16 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 27/16 → 7/4 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 7/4 → 29/16 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 29/16 → 15/8 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 15/8 → 31/16 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 31/16 → 2/1 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 2/1 → 33/16 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 33/16 → 17/8 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 17/8 → 35/16 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 35/16 → 9/4 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 9/4 → 37/16 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 37/16 → 19/8 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 19/8 → 39/16 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 39/16 → 5/2 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 5/2 → 41/16 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 41/16 → 21/8 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 21/8 → 43/16 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 43/16 → 11/4 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 11/4 → 45/16 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 45/16 → 23/8 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 23/8 → 47/16 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 47/16 → 3/1 | s:basique bank:wt_digital wtphaserand:0 ]", + "[ 3/1 → 49/16 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 49/16 → 25/8 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 25/8 → 51/16 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 51/16 → 13/4 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 13/4 → 53/16 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 53/16 → 27/8 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 27/8 → 55/16 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 55/16 → 7/2 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 7/2 → 57/16 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 57/16 → 29/8 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 29/8 → 59/16 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 59/16 → 15/4 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 15/4 → 61/16 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 61/16 → 31/8 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 31/8 → 63/16 | s:basique bank:wt_digital wtphaserand:1 ]", + "[ 63/16 → 4/1 | s:basique bank:wt_digital wtphaserand:1 ]", ] `; From bf3fe605d9a71543e9922f7983f03e2f89129be9 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sun, 28 Sep 2025 15:21:06 -0400 Subject: [PATCH 14/17] fix unison gain --- packages/superdough/worklets.mjs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index cea5a1ea..7f5cbcf2 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -1054,7 +1054,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { { name: 'warp', defaultValue: 0, minValue: 0, maxValue: 1 }, { name: 'warpMode', defaultValue: 0 }, { name: 'voices', defaultValue: 1, minValue: 1, maxValue: 32 }, - { name: 'spread', defaultValue: 0.4, minValue: 0, maxValue: 1 }, + { name: 'spread', defaultValue: 0.7, minValue: 0, maxValue: 1 }, { name: 'phaserand', defaultValue: 0, minValue: 0, maxValue: 1 }, ]; } @@ -1239,7 +1239,6 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { } const outL = outputs[0][0]; const outR = outputs[0][1] || outputs[0][0]; - const gainAdjustment = 0.3; if (!this.tables) { outL.fill(0); @@ -1256,13 +1255,13 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { const warpAmount = pv(parameters.warp, i); const warpMode = pv(parameters.warpMode, i); const voices = pv(parameters.voices, i); - const spread = voices > 1 ? pv(parameters.spread, i) : 0; const phaseRand = pv(parameters.phaserand, i); + const spread = voices > 1 ? pv(parameters.spread, i) : 0; const gain1 = Math.sqrt(0.5 - 0.5 * spread); const gain2 = Math.sqrt(0.5 + 0.5 * spread); let f = pv(parameters.frequency, i); f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune - const normalizer = 0.3 / voices; + const normalizer = 0.3 / Math.sqrt(voices); for (let n = 0; n < voices; n++) { const isOdd = (n & 1) == 1; let gainL = gain1; From d94f145649b3bf3ea77d49fe26246fc075b353e0 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sun, 28 Sep 2025 15:23:58 -0400 Subject: [PATCH 15/17] fix vib --- packages/superdough/wavetable.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/wavetable.mjs b/packages/superdough/wavetable.mjs index 4a21cabe..48b7e661 100644 --- a/packages/superdough/wavetable.mjs +++ b/packages/superdough/wavetable.mjs @@ -319,7 +319,7 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) { dcoffset: value.warpdc ?? 0, }, ); - const vibratoOscillator = getVibratoOscillator(source.detune, value, t); + const vibratoOscillator = getVibratoOscillator(source.parameters.get('detune'), value, t); const envGain = ac.createGain(); const node = source.connect(envGain); getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear'); From 8d45016ccf4ca0d63e5d903a63c21e54f85e8785 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sun, 28 Sep 2025 19:11:13 -0400 Subject: [PATCH 16/17] working --- website/public/uzu-wavetables.json | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/website/public/uzu-wavetables.json b/website/public/uzu-wavetables.json index d9e0eeb9..b2d662d5 100644 --- a/website/public/uzu-wavetables.json +++ b/website/public/uzu-wavetables.json @@ -5,35 +5,11 @@ "wt_digital/wt_basique.wav", "wt_digital/wt_crickets.wav", "wt_digital/wt_curses.wav", - "wt_digital/wt_earl_grey.wav", - "wt_digital/wt_echoes.wav", - "wt_digital/wt_glimmer.wav", - "wt_digital/wt_majick.wav", - "wt_digital/wt_meditation.wav", - "wt_digital/wt_morgana.wav", - "wt_digital/wt_red_alert.wav", - "wt_digital/wt_sad_piano.wav", - "wt_digital/wt_shook.wav", - "wt_digital/wt_sludge.wav", - "wt_digital/wt_squelch.wav", - "wt_digital/wt_summer.wav", - "wt_digital/wt_wasp.wav" + "wt_digital/wt_echoes.wav" ], "wt_digital_bad_day": ["wt_digital/wt_bad_day.wav"], "wt_digital_basique": ["wt_digital/wt_basique.wav"], "wt_digital_crickets": ["wt_digital/wt_crickets.wav"], "wt_digital_curses": ["wt_digital/wt_curses.wav"], - "wt_digital_earl_grey": ["wt_digital/wt_earl_grey.wav"], - "wt_digital_echoes": ["wt_digital/wt_echoes.wav"], - "wt_digital_glimmer": ["wt_digital/wt_glimmer.wav"], - "wt_digital_majick": ["wt_digital/wt_majick.wav"], - "wt_digital_meditation": ["wt_digital/wt_meditation.wav"], - "wt_digital_morgana": ["wt_digital/wt_morgana.wav"], - "wt_digital_red_alert": ["wt_digital/wt_red_alert.wav"], - "wt_digital_sad_piano": ["wt_digital/wt_sad_piano.wav"], - "wt_digital_shook": ["wt_digital/wt_shook.wav"], - "wt_digital_sludge": ["wt_digital/wt_sludge.wav"], - "wt_digital_squelch": ["wt_digital/wt_squelch.wav"], - "wt_digital_summer": ["wt_digital/wt_summer.wav"], - "wt_digital_wasp": ["wt_digital/wt_wasp.wav"] + "wt_digital_echoes": ["wt_digital/wt_echoes.wav"] } \ No newline at end of file From 48718daea8c6e60a5430a639a09159b795cf2e9b Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sun, 28 Sep 2025 19:57:19 -0400 Subject: [PATCH 17/17] add_vgame --- website/public/uzu-wavetables.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/website/public/uzu-wavetables.json b/website/public/uzu-wavetables.json index b2d662d5..375b1fad 100644 --- a/website/public/uzu-wavetables.json +++ b/website/public/uzu-wavetables.json @@ -11,5 +11,18 @@ "wt_digital_basique": ["wt_digital/wt_basique.wav"], "wt_digital_crickets": ["wt_digital/wt_crickets.wav"], "wt_digital_curses": ["wt_digital/wt_curses.wav"], - "wt_digital_echoes": ["wt_digital/wt_echoes.wav"] + "wt_digital_echoes": ["wt_digital/wt_echoes.wav"], + "wt_vgame": [ + "wt_vgame/wt_vgame10.wav", + "wt_vgame/wt_vgame11.wav", + "wt_vgame/wt_vgame12.wav", + "wt_vgame/wt_vgame13.wav", + "wt_vgame/wt_vgame14.wav", + "wt_vgame/wt_vgame15.wav", + "wt_vgame/wt_vgame16.wav", + "wt_vgame/wt_vgame17.wav", + "wt_vgame/wt_vgame18.wav", + "wt_vgame/wt_vgame19.wav", + "wt_vgame/wt_vgame20.wav" + ] } \ No newline at end of file