diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 23a1dfc3b..b58465965 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -192,7 +192,7 @@ export const { wtPosSkew } = registerControl('wtPosSkew'); * 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'); /** * Attack time of the wavetable oscillator's warp envelope diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index d3b6ca6c7..859329b20 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -107,7 +107,6 @@ function getModulationShapeInput(val) { 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, diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index 9188c17c3..b195ba79c 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 { 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); } @@ -79,14 +56,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 +244,12 @@ 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) => { + registerSampleSource(key, bank, { baseUrl, prebake, tag }); + }, baseUrl, ); }; @@ -366,3 +339,20 @@ 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); + } +} diff --git a/packages/superdough/util.mjs b/packages/superdough/util.mjs index 764ebb43e..475c05f63 100644 --- a/packages/superdough/util.mjs +++ b/packages/superdough/util.mjs @@ -76,3 +76,32 @@ 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 }; +} diff --git a/packages/superdough/wavetable.mjs b/packages/superdough/wavetable.mjs index dede7a2e5..095bd079e 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 } from './util.mjs'; import { destroyAudioWorkletNode, getADSRValues, @@ -39,11 +39,11 @@ export const WarpMode = Object.freeze({ FLIP: 21, }); -async function loadWavetableFrames(url, label, frameLen = 256) { +async function loadWavetableFrames(url, label, frameLen = 2048) { const buf = await loadBuffer(url, 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,16 +86,6 @@ 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 }; -} - // Extract the sample rate of a .wav file function parseWavSampleRate(arrBuf) { const dv = new DataView(arrBuf); @@ -181,19 +171,19 @@ const _processTables = (json, baseUrl, frameLen, options = {}) => { 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, - }); + registerWaveTable(key, tables, { baseUrl, frameLen }); } }); }; +export function registerWaveTable(key, tables, params) { + registerSound(key, (t, hapValue, onended) => onTriggerSynth(t, hapValue, onended, tables, params?.frameLen ?? 2048), { + type: 'wavetable', + tables, + ...params, + }); +} + /** * Loads a collection of wavetables to use with `s` * @@ -224,7 +214,7 @@ export const tables = async (url, frameLen, json, options = {}) => { }); }; -async function onTriggerSynth(t, value, onended, tables, frameLen) { +export 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]); @@ -233,8 +223,8 @@ async function onTriggerSynth(t, value, onended, tables, frameLen) { wtWarpMode = WarpMode[wtWarpMode.toUpperCase()] ?? WarpMode.NONE; } const frequency = getFrequencyFromValue(value); - const { tableUrl, label } = getTableInfo(value, tables); - const payload = await loadWavetableFrames(tableUrl, label, frameLen); + const { url, label } = getCommonSampleInfo(value, tables); + const payload = await loadWavetableFrames(url, label, frameLen); const holdEnd = t + duration; const endWithRelease = holdEnd + release; const envEnd = endWithRelease + 0.01; @@ -265,6 +255,7 @@ async function onTriggerSynth(t, value, onended, tables, frameLen) { 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'); @@ -278,6 +269,7 @@ async function onTriggerSynth(t, value, onended, tables, frameLen) { }); 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'); @@ -304,8 +296,8 @@ async function onTriggerSynth(t, value, onended, tables, frameLen) { destroyAudioWorkletNode(source); vibratoOscillator?.stop(); node.disconnect(); - warpLFO.disconnect(); - posLFO.disconnect(); + posLFO?.disconnect(); + warpLFO?.disconnect(); onended(); }, t, diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index a83ad9e4f..0a2d40d91 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: 0.18 }, { name: 'position', defaultValue: 0, minValue: 0, maxValue: 1 }, { name: 'warp', defaultValue: 0, minValue: 0, maxValue: 1 }, { name: 'warpMode', defaultValue: 0 }, @@ -1239,6 +1239,7 @@ 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); diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index a05975580..bfa4ae40e 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -11993,6 +11993,234 @@ exports[`runs examples > example "withValue" example index 0 1`] = ` ] `; +exports[`runs examples > example "wtPhaseRand" 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 ]", +] +`; + +exports[`runs examples > example "wtPos" 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 ]", +] +`; + 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 000000000..d9e0eeb90 --- /dev/null +++ b/website/public/uzu-wavetables.json @@ -0,0 +1,39 @@ +{ + "_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" + ], + "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"] +} \ 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 ff64fc18e..363f6b98a 100644 --- a/website/src/repl/components/panel/SoundsTab.jsx +++ b/website/src/repl/components/panel/SoundsTab.jsx @@ -57,6 +57,9 @@ export function SoundsTab() { // holds mutable ref to current triggered sound const trigRef = useRef(); + // Used to cycle through sound previews on banks with multiple sounds + let soundPreviewIdx = 0; + // stop current sound on mouseup useEvent('mouseup', () => { const t = trigRef.current; @@ -114,11 +117,13 @@ export function SoundsTab() { const params = { note: ['synth', 'soundfont'].includes(data.type) ? 'a3' : undefined, s: name, + n: soundPreviewIdx, clip: 1, release: 0.5, sustain: 1, duration: 0.5, }; + soundPreviewIdx++; const time = ctx.currentTime + 0.05; const onended = () => trigRef.current?.node?.disconnect(); trigRef.current = Promise.resolve(onTrigger(time, params, onended)); @@ -129,7 +134,8 @@ export function SoundsTab() { > {' '} {name} - {data?.type === 'sample' || data?.type === 'wavetable' ? `(${getSamples(data.samples)})` : ''} + {data?.type === 'sample' ? `(${getSamples(data.samples)})` : ''} + {data?.type === 'wavetable' ? `(${getSamples(data.tables)})` : ''} {data?.type === 'soundfont' ? `(${data.fonts.length})` : ''} ); diff --git a/website/src/repl/idbutils.mjs b/website/src/repl/idbutils.mjs index f26ee0479..d87d649c2 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,7 @@ 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'); diff --git a/website/src/repl/prebake.mjs b/website/src/repl/prebake.mjs index fac6f5bb6..1fbc84021 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( {