diff --git a/packages/superdough/README.md b/packages/superdough/README.md index 0c6e4f14..f5947d83 100644 --- a/packages/superdough/README.md +++ b/packages/superdough/README.md @@ -153,6 +153,7 @@ samples('github:tidalcycles/dirt-samples') The format is `github://`. +If `` and `` are not specified, they will default to `samples` and `main` respectively. It expects a `strudel.json` file to be present at the root of the given repository, which declares the sample paths in the repo. The format is also expected to be the same as explained above. diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index f229cd33..3232fa47 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -121,13 +121,20 @@ function githubPath(base, subpath = '') { if (!base.startsWith('github:')) { throw new Error('expected "github:" at the start of pseudoUrl'); } - let [_, path] = base.split('github:'); + let path = base.slice('github:'.length); path = path.endsWith('/') ? path.slice(0, -1) : path; - if (path.split('/').length === 2) { - // assume main as default branch if none set - path += '/main'; + + let components = path.split('/'); + let user = components[0]; + let repo = components.length >= 2 ? components[1] : 'samples'; + let branch = components.length >= 3 ? components[2] : 'main'; + let other = components.slice(3); + if (subpath) { + other.push(subpath); } - return `https://raw.githubusercontent.com/${path}/${subpath}`; + other = other.join('/'); + + return `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${other}`; } export const processSampleMap = (sampleMap, fn, baseUrl = sampleMap._base || '') => { diff --git a/packages/superdough/wavetable.mjs b/packages/superdough/wavetable.mjs index da6abcf7..01d4eb83 100644 --- a/packages/superdough/wavetable.mjs +++ b/packages/superdough/wavetable.mjs @@ -231,12 +231,12 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) { begin: t, end: envEnd, frequency, - detune: value.detune, + freqspread: value.detune, position: value.wt, warp: value.warp, warpMode: warpmode, voices: Math.max(value.unison ?? 1, 1), - spread: value.spread, + panspread: value.spread, phaserand: (value.wtphaserand ?? value.unison > 1) ? 1 : 0, }, { outputChannelCount: [2] }, diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index a40e7a1b..7d7ef8e2 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -1050,14 +1050,15 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { return [ { 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.18 }, - { name: 'position', defaultValue: 0, minValue: 0, maxValue: 1 }, - { name: 'warp', defaultValue: 0, minValue: 0, maxValue: 1 }, + { name: 'frequency', defaultValue: 440, min: Number.EPSILON }, + { name: 'detune', defaultValue: 0 }, + { name: 'freqspread', defaultValue: 0.18, min: 0 }, + { name: 'position', defaultValue: 0, min: 0, max: 1 }, + { name: 'warp', defaultValue: 0, min: 0, max: 1 }, { name: 'warpMode', defaultValue: 0 }, - { name: 'voices', defaultValue: 1, minValue: 1, maxValue: 32 }, - { name: 'spread', defaultValue: 0.7, minValue: 0, maxValue: 1 }, - { name: 'phaserand', defaultValue: 0, minValue: 0, maxValue: 1 }, + { name: 'voices', defaultValue: 1, min: 1 }, + { name: 'panspread', defaultValue: 0.7, min: 0, max: 1 }, + { name: 'phaserand', defaultValue: 0, min: 0, max: 1 }, ]; } @@ -1235,10 +1236,12 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { _sampleFrame(frame, phase) { const len = frame.length; const pos = phase * len; - const i = pos | 0; + let i = pos | 0; + if (i >= len) i = 0; // fast wrap const frac = pos - i; const a = frame[i]; - const i1 = i + 1 < len ? i + 1 : 0; // fast wrap + let i1 = i + 1; + if (i1 >= len) i1 = 0; const b = frame[i1]; return a + (b - a) * frac; } @@ -1268,6 +1271,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { } for (let i = 0; i < outL.length; i++) { const detune = pv(parameters.detune, i); + const freqspread = pv(parameters.freqspread, i); const tablePos = clamp(pv(parameters.position, i), 0, 1); const idx = tablePos * (this.numFrames - 1); const fIdx = idx | 0; @@ -1276,9 +1280,9 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { const warpMode = pv(parameters.warpMode, i); const voices = pv(parameters.voices, i); const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1); - const spread = voices > 1 ? clamp(pv(parameters.spread, i), 0, 1) : 0; - const gain1 = Math.sqrt(0.5 - 0.5 * spread); - const gain2 = Math.sqrt(0.5 + 0.5 * spread); + const panspread = voices > 1 ? clamp(pv(parameters.panspread, i), 0, 1) : 0; + const gain1 = Math.sqrt(0.5 - 0.5 * panspread); + const gain2 = Math.sqrt(0.5 + 0.5 * panspread); let f = pv(parameters.frequency, i); f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune const normalizer = 1 / Math.sqrt(voices); @@ -1291,7 +1295,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { gainL = gain2; gainR = gain1; } - const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, detune, n)); // voice detune + const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, freqspread, n)); // voice detune const dPhase = fVoice * this.invSR; const level = this._chooseMip(dPhase); const table = this.tables[level]; diff --git a/website/src/pages/learn/code.mdx b/website/src/pages/learn/code.mdx index f9c6b3a2..c454dcce 100644 --- a/website/src/pages/learn/code.mdx +++ b/website/src/pages/learn/code.mdx @@ -60,6 +60,23 @@ Strudel makes heavy use of chained functions. Here is a more sophisticated examp .room(0.5)`} /> +## Write your own chained function + +You can write your own chained function using `register`. Here's the above chain but registered as a reusable, chained function. + + pat + .s("sawtooth") + .cutoff(500) + //.delay(0.5) + .room(0.5) + ) +note("a3 c#4 e4 a4").effectChain()`} +/> + +Try adding `.rev()` after `effectChain()` to hear further effects added. + # Comments The `//` in the example above is a line comment, resulting in the `delay` function being ignored. diff --git a/website/src/pages/learn/samples.mdx b/website/src/pages/learn/samples.mdx index eb79cccf..201d57df 100644 --- a/website/src/pages/learn/samples.mdx +++ b/website/src/pages/learn/samples.mdx @@ -381,7 +381,7 @@ Sampler effects are functions that can be used to change the behaviour of sample ### scrub -{' '} + ### speed