From 38908baccd3ce9a3890f91e284cd1a0846284d5f Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 6 May 2025 01:06:37 -0400 Subject: [PATCH 01/74] working --- packages/core/controls.mjs | 43 ++++++++++++++++++++++++++++++ packages/superdough/superdough.mjs | 20 +++++++------- packages/superdough/synth.mjs | 25 ++++++++++++++--- 3 files changed, 73 insertions(+), 15 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index a6c650e03..2a655eed4 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -458,6 +458,49 @@ export const { drive } = registerControl('drive'); */ export const { channels, ch } = registerControl('channels', 'ch'); +/** + * controls the pulsewidth of the pulse oscillator + * + * @name pw + * @param {number | Pattern} pulsewidth + * @example + * note("{f a c e}%16").s("pulse").pw(".8:1:.2") + * @example + * n(run(8)).scale("D:pentatonic").s("pulse").pw("0 .75 .5 1") + */ +export const { pw } = registerControl( + ['pw', 'pwrate', 'pwsweep'], +); + +/** + * controls the lfo rate for the pulsewidth of the pulse oscillator + * + * @name pwrate + * @param {number | Pattern} rate + * @example + * n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>") + + * + */ +export const { pwrate } = registerControl( + 'pwrate' +); + + +/** + * controls the lfo sweep for the pulsewidth of the pulse oscillator + * + * @name pwsweep + * @param {number | Pattern} sweep + * @example + * n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>") + * + */ +export const { pwsweep } = registerControl( + 'pwsweep' +); + + /** * Phaser audio effect that approximates popular guitar pedals. * diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index a1dc30b7c..1bce0d793 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -307,16 +307,9 @@ function getDelay(orbit, delaytime, delayfeedback, t) { return delays[orbit]; } -function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000, sweep = 2000) { - //gain - const ac = getAudioContext(); - const lfoGain = ac.createGain(); - lfoGain.gain.value = sweep * 2; - // centerFrequency = centerFrequency * 2; - // sweep = sweep * 1.5; - - const lfo = getWorklet(ac, 'lfo-processor', { - frequency, +export function getLfo(audioContext, time, end, properties = {}) { + return getWorklet(audioContext, 'lfo-processor', { + frequency: 1, depth: 1, skew: 0, phaseoffset: 0, @@ -324,8 +317,13 @@ function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000 end, shape: 1, dcoffset: -0.5, + ...properties }); - lfo.connect(lfoGain); +} + +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 }) //filters const numStages = 2; //num of filters in series diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 8bb47bb7d..78712c4e1 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -1,5 +1,5 @@ import { clamp, midiToFreq, noteToMidi } from './util.mjs'; -import { registerSound, getAudioContext } from './superdough.mjs'; +import { registerSound, getAudioContext, getLfo } from './superdough.mjs'; import { applyFM, gainNode, @@ -145,7 +145,20 @@ export function registerSynthSounds() { 'pulse', (begin, value, onended) => { const ac = getAudioContext(); - let { duration, n: pulsewidth = 0.5 } = value; + let { pwrate, pwsweep } = value; + if (pwsweep == null) { + if (pwrate != null) { + pwsweep = 0.3 + } else { + pwsweep = 0 + } + } + + if (pwrate == null && pwsweep != null) { + pwrate = 1 + } + + let { duration, pw: pulsewidth = value.n ?? 0.5, } = value; const frequency = getFrequencyFromValue(value); const [attack, decay, sustain, release] = getADSRValues( @@ -153,10 +166,8 @@ export function registerSynthSounds() { 'linear', [0.001, 0.05, 0.6, 0.01], ); - const holdend = begin + duration; const end = holdend + release + 0.01; - let o = getWorklet( ac, 'pulse-oscillator', @@ -179,10 +190,16 @@ export function registerSynthSounds() { getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear'); + + if (pwsweep != 0) { + let lfo = getLfo(ac, begin, end, { frequency: pwrate, depth: pwsweep, }) + lfo.connect(o.parameters.get('pulsewidth')) + } let timeoutNode = webAudioTimeout( ac, () => { destroyAudioWorkletNode(o); + destroyAudioWorkletNode(lfo); envGain.disconnect(); onended(); fm?.stop(); From 38e4718826cf08d1121a2467bce3d167cdc5e550 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 6 May 2025 01:09:20 -0400 Subject: [PATCH 02/74] lint --- packages/core/controls.mjs | 14 +++----------- packages/superdough/superdough.mjs | 4 ++-- packages/superdough/synth.mjs | 19 +++++++++---------- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 2a655eed4..0b9821eaa 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -468,9 +468,7 @@ export const { channels, ch } = registerControl('channels', 'ch'); * @example * n(run(8)).scale("D:pentatonic").s("pulse").pw("0 .75 .5 1") */ -export const { pw } = registerControl( - ['pw', 'pwrate', 'pwsweep'], -); +export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']); /** * controls the lfo rate for the pulsewidth of the pulse oscillator @@ -482,10 +480,7 @@ export const { pw } = registerControl( * */ -export const { pwrate } = registerControl( - 'pwrate' -); - +export const { pwrate } = registerControl('pwrate'); /** * controls the lfo sweep for the pulsewidth of the pulse oscillator @@ -496,10 +491,7 @@ export const { pwrate } = registerControl( * n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>") * */ -export const { pwsweep } = registerControl( - 'pwsweep' -); - +export const { pwsweep } = registerControl('pwsweep'); /** * Phaser audio effect that approximates popular guitar pedals. diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 1bce0d793..0aa8cb621 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -317,13 +317,13 @@ export function getLfo(audioContext, time, end, properties = {}) { end, shape: 1, dcoffset: -0.5, - ...properties + ...properties, }); } 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 }) + const lfoGain = getLfo(ac, time, end, { frequency, depth: sweep * 2 }); //filters const numStages = 2; //num of filters in series diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 78712c4e1..50bb6acb6 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -148,17 +148,17 @@ export function registerSynthSounds() { let { pwrate, pwsweep } = value; if (pwsweep == null) { if (pwrate != null) { - pwsweep = 0.3 + pwsweep = 0.3; } else { - pwsweep = 0 + pwsweep = 0; } - } - - if (pwrate == null && pwsweep != null) { - pwrate = 1 } - let { duration, pw: pulsewidth = value.n ?? 0.5, } = value; + if (pwrate == null && pwsweep != null) { + pwrate = 1; + } + + let { duration, pw: pulsewidth = value.n ?? 0.5 } = value; const frequency = getFrequencyFromValue(value); const [attack, decay, sustain, release] = getADSRValues( @@ -190,10 +190,9 @@ export function registerSynthSounds() { getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear'); - if (pwsweep != 0) { - let lfo = getLfo(ac, begin, end, { frequency: pwrate, depth: pwsweep, }) - lfo.connect(o.parameters.get('pulsewidth')) + let lfo = getLfo(ac, begin, end, { frequency: pwrate, depth: pwsweep }); + lfo.connect(o.parameters.get('pulsewidth')); } let timeoutNode = webAudioTimeout( ac, From 98346261edb62cbe3f859fa2644c4809f88515c2 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 6 May 2025 01:13:50 -0400 Subject: [PATCH 03/74] lint --- packages/superdough/synth.mjs | 7 +- test/__snapshots__/examples.test.mjs.snap | 180 ++++++++++++++++++++++ 2 files changed, 185 insertions(+), 2 deletions(-) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 50bb6acb6..c08fd8c1c 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -26,6 +26,9 @@ const getFrequencyFromValue = (value) => { return Number(freq); }; function destroyAudioWorkletNode(node) { + if (node == null) { + return + } node.disconnect(); node.parameters.get('end')?.setValueAtTime(0, 0); } @@ -189,9 +192,9 @@ export function registerSynthSounds() { envGain = o.connect(envGain); getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear'); - + let lfo; if (pwsweep != 0) { - let lfo = getLfo(ac, begin, end, { frequency: pwrate, depth: pwsweep }); + lfo = getLfo(ac, begin, end, { frequency: pwrate, depth: pwsweep }); lfo.connect(o.parameters.get('pulsewidth')); } let timeoutNode = webAudioTimeout( diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 2d88d8355..a05d8aa6c 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -6721,6 +6721,186 @@ exports[`runs examples > example "pure" example index 0 1`] = ` ] `; +exports[`runs examples > example "pw" example index 0 1`] = ` +[ + "[ 0/1 → 1/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 1/16 → 1/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 1/8 → 3/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 3/16 → 1/4 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 1/4 → 5/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 5/16 → 3/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 3/8 → 7/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 7/16 → 1/2 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 1/2 → 9/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 9/16 → 5/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 5/8 → 11/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 11/16 → 3/4 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 3/4 → 13/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 13/16 → 7/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 7/8 → 15/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 15/16 → 1/1 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 1/1 → 17/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 17/16 → 9/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 9/8 → 19/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 19/16 → 5/4 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 5/4 → 21/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 21/16 → 11/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 11/8 → 23/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 23/16 → 3/2 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 3/2 → 25/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 25/16 → 13/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 13/8 → 27/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 27/16 → 7/4 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 7/4 → 29/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 29/16 → 15/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 15/8 → 31/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 31/16 → 2/1 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 2/1 → 33/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 33/16 → 17/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 17/8 → 35/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 35/16 → 9/4 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 9/4 → 37/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 37/16 → 19/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 19/8 → 39/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 39/16 → 5/2 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 5/2 → 41/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 41/16 → 21/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 21/8 → 43/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 43/16 → 11/4 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 11/4 → 45/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 45/16 → 23/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 23/8 → 47/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 47/16 → 3/1 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 3/1 → 49/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 49/16 → 25/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 25/8 → 51/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 51/16 → 13/4 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 13/4 → 53/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 53/16 → 27/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 27/8 → 55/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 55/16 → 7/2 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 7/2 → 57/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 57/16 → 29/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 29/8 → 59/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 59/16 → 15/4 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 15/4 → 61/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 61/16 → 31/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 31/8 → 63/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", + "[ 63/16 → 4/1 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]", +] +`; + +exports[`runs examples > example "pw" example index 1 1`] = ` +[ + "[ 0/1 → 1/8 | note:D3 s:pulse pw:0 ]", + "[ 1/8 → 1/4 | note:E3 s:pulse pw:0 ]", + "[ 1/4 → 3/8 | note:F#3 s:pulse pw:0.75 ]", + "[ 3/8 → 1/2 | note:A3 s:pulse pw:0.75 ]", + "[ 1/2 → 5/8 | note:B3 s:pulse pw:0.5 ]", + "[ 5/8 → 3/4 | note:D4 s:pulse pw:0.5 ]", + "[ 3/4 → 7/8 | note:E4 s:pulse pw:1 ]", + "[ 7/8 → 1/1 | note:F#4 s:pulse pw:1 ]", + "[ 1/1 → 9/8 | note:D3 s:pulse pw:0 ]", + "[ 9/8 → 5/4 | note:E3 s:pulse pw:0 ]", + "[ 5/4 → 11/8 | note:F#3 s:pulse pw:0.75 ]", + "[ 11/8 → 3/2 | note:A3 s:pulse pw:0.75 ]", + "[ 3/2 → 13/8 | note:B3 s:pulse pw:0.5 ]", + "[ 13/8 → 7/4 | note:D4 s:pulse pw:0.5 ]", + "[ 7/4 → 15/8 | note:E4 s:pulse pw:1 ]", + "[ 15/8 → 2/1 | note:F#4 s:pulse pw:1 ]", + "[ 2/1 → 17/8 | note:D3 s:pulse pw:0 ]", + "[ 17/8 → 9/4 | note:E3 s:pulse pw:0 ]", + "[ 9/4 → 19/8 | note:F#3 s:pulse pw:0.75 ]", + "[ 19/8 → 5/2 | note:A3 s:pulse pw:0.75 ]", + "[ 5/2 → 21/8 | note:B3 s:pulse pw:0.5 ]", + "[ 21/8 → 11/4 | note:D4 s:pulse pw:0.5 ]", + "[ 11/4 → 23/8 | note:E4 s:pulse pw:1 ]", + "[ 23/8 → 3/1 | note:F#4 s:pulse pw:1 ]", + "[ 3/1 → 25/8 | note:D3 s:pulse pw:0 ]", + "[ 25/8 → 13/4 | note:E3 s:pulse pw:0 ]", + "[ 13/4 → 27/8 | note:F#3 s:pulse pw:0.75 ]", + "[ 27/8 → 7/2 | note:A3 s:pulse pw:0.75 ]", + "[ 7/2 → 29/8 | note:B3 s:pulse pw:0.5 ]", + "[ 29/8 → 15/4 | note:D4 s:pulse pw:0.5 ]", + "[ 15/4 → 31/8 | note:E4 s:pulse pw:1 ]", + "[ 31/8 → 4/1 | note:F#4 s:pulse pw:1 ]", +] +`; + +exports[`runs examples > example "pwrate" example index 0 1`] = ` +[ + "[ 0/1 → 1/8 | note:D3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]", + "[ 1/8 → 1/4 | note:E3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]", + "[ 1/4 → 3/8 | note:F#3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]", + "[ 3/8 → 1/2 | note:A3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]", + "[ 1/2 → 5/8 | note:B3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]", + "[ 5/8 → 3/4 | note:D4 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]", + "[ 3/4 → 7/8 | note:E4 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]", + "[ 7/8 → 1/1 | note:F#4 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]", + "[ 1/1 → 9/8 | note:D3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]", + "[ 9/8 → 5/4 | note:E3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]", + "[ 5/4 → 11/8 | note:F#3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]", + "[ 11/8 → 3/2 | note:A3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]", + "[ 3/2 → 13/8 | note:B3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]", + "[ 13/8 → 7/4 | note:D4 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]", + "[ 7/4 → 15/8 | note:E4 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]", + "[ 15/8 → 2/1 | note:F#4 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]", + "[ 2/1 → 17/8 | note:D3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]", + "[ 17/8 → 9/4 | note:E3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]", + "[ 9/4 → 19/8 | note:F#3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]", + "[ 19/8 → 5/2 | note:A3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]", + "[ 5/2 → 21/8 | note:B3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]", + "[ 21/8 → 11/4 | note:D4 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]", + "[ 11/4 → 23/8 | note:E4 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]", + "[ 23/8 → 3/1 | note:F#4 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]", + "[ 3/1 → 25/8 | note:D3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]", + "[ 25/8 → 13/4 | note:E3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]", + "[ 13/4 → 27/8 | note:F#3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]", + "[ 27/8 → 7/2 | note:A3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]", + "[ 7/2 → 29/8 | note:B3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]", + "[ 29/8 → 15/4 | note:D4 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]", + "[ 15/4 → 31/8 | note:E4 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]", + "[ 31/8 → 4/1 | note:F#4 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]", +] +`; + +exports[`runs examples > example "pwsweep" example index 0 1`] = ` +[ + "[ 0/1 → 1/8 | note:D3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]", + "[ 1/8 → 1/4 | note:E3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]", + "[ 1/4 → 3/8 | note:F#3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]", + "[ 3/8 → 1/2 | note:A3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]", + "[ 1/2 → 5/8 | note:B3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]", + "[ 5/8 → 3/4 | note:D4 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]", + "[ 3/4 → 7/8 | note:E4 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]", + "[ 7/8 → 1/1 | note:F#4 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]", + "[ 1/1 → 9/8 | note:D3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]", + "[ 9/8 → 5/4 | note:E3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]", + "[ 5/4 → 11/8 | note:F#3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]", + "[ 11/8 → 3/2 | note:A3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]", + "[ 3/2 → 13/8 | note:B3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]", + "[ 13/8 → 7/4 | note:D4 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]", + "[ 7/4 → 15/8 | note:E4 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]", + "[ 15/8 → 2/1 | note:F#4 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]", + "[ 2/1 → 17/8 | note:D3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]", + "[ 17/8 → 9/4 | note:E3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]", + "[ 9/4 → 19/8 | note:F#3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]", + "[ 19/8 → 5/2 | note:A3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]", + "[ 5/2 → 21/8 | note:B3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]", + "[ 21/8 → 11/4 | note:D4 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]", + "[ 11/4 → 23/8 | note:E4 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]", + "[ 23/8 → 3/1 | note:F#4 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]", + "[ 3/1 → 25/8 | note:D3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]", + "[ 25/8 → 13/4 | note:E3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]", + "[ 13/4 → 27/8 | note:F#3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]", + "[ 27/8 → 7/2 | note:A3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]", + "[ 7/2 → 29/8 | note:B3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]", + "[ 29/8 → 15/4 | note:D4 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]", + "[ 15/4 → 31/8 | note:E4 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]", + "[ 31/8 → 4/1 | note:F#4 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]", +] +`; + exports[`runs examples > example "queryArc" example index 0 1`] = `[]`; exports[`runs examples > example "rand" example index 0 1`] = ` From 440ce0cd3b614eb593eb98c76c3a9305cc284eb3 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 6 May 2025 02:14:35 -0400 Subject: [PATCH 04/74] format --- packages/superdough/synth.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index c08fd8c1c..dd0481748 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -27,7 +27,7 @@ const getFrequencyFromValue = (value) => { }; function destroyAudioWorkletNode(node) { if (node == null) { - return + return; } node.disconnect(); node.parameters.get('end')?.setValueAtTime(0, 0); From 56d3436fdaf14cd6c2bd3615db42e08d77e8f9fc Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 7 May 2025 01:08:00 -0400 Subject: [PATCH 05/74] asconst --- packages/superdough/superdough.mjs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index a1dc30b7c..214b4d114 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -22,6 +22,12 @@ let maxPolyphony = DEFAULT_MAX_POLYPHONY; export function setMaxPolyphony(polyphony) { maxPolyphony = parseInt(polyphony) ?? DEFAULT_MAX_POLYPHONY; } + +let multiChannelOrbits = false +export function setMultiChannelOrbits(bool) { + multiChannelOrbits = (bool == true); +} + export const soundMap = map(); export function registerSound(key, onTrigger, data = {}) { @@ -195,7 +201,7 @@ function loadWorklets() { // this function should be called on first user interaction (to avoid console warning) export async function initAudio(options = {}) { - const { disableWorklets = false, maxPolyphony, audioDeviceName = DEFAULT_AUDIO_DEVICE_NAME } = options; + const { disableWorklets = false, maxPolyphony, audioDeviceName = DEFAULT_AUDIO_DEVICE_NAME, multiChannelOrbits = false } = options; setMaxPolyphony(maxPolyphony); if (typeof window === 'undefined') { return; @@ -277,7 +283,7 @@ export const connectToDestination = (input, channels = [0, 1]) => { }); stereoMix.connect(splitter); channels.forEach((ch, i) => { - splitter.connect(channelMerger, i % stereoMix.channelCount, clamp(ch, 0, ctx.destination.channelCount - 1)); + splitter.connect(channelMerger, i % stereoMix.channelCount, ch % ctx.destination.channelCount); }); }; @@ -290,7 +296,7 @@ export const panic = () => { channelMerger == null; }; -function getDelay(orbit, delaytime, delayfeedback, t) { +function getDelay(orbit, delaytime, delayfeedback, t, channels) { if (delayfeedback > maxfeedback) { //logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`); } @@ -299,7 +305,7 @@ function getDelay(orbit, delaytime, delayfeedback, t) { const ac = getAudioContext(); const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback); dly.start?.(t); // for some reason, this throws when audion extension is installed.. - connectToDestination(dly, [0, 1]); + connectToDestination(dly, channels); delays[orbit] = dly; } delays[orbit].delayTime.value !== delaytime && delays[orbit].delayTime.setValueAtTime(delaytime, t); @@ -356,12 +362,12 @@ function getFilterType(ftype) { let reverbs = {}; let hasChanged = (now, before) => now !== undefined && now !== before; -function getReverb(orbit, duration, fade, lp, dim, ir) { +function getReverb(orbit, duration, fade, lp, dim, ir, channels) { // If no reverb has been created for a given orbit, create one if (!reverbs[orbit]) { const ac = getAudioContext(); const reverb = ac.createReverb(duration, fade, lp, dim, ir); - connectToDestination(reverb, [0, 1]); + connectToDestination(reverb, channels); reverbs[orbit] = reverb; } if ( @@ -490,7 +496,7 @@ export const superdough = async (value, t, hapDuration) => { bpsustain, bprelease, bandq = getDefaultValue('bandq'), - channels = getDefaultValue('channels'), + //phaser phaserrate: phaser, phaserdepth = getDefaultValue('phaserdepth'), @@ -526,6 +532,9 @@ export const superdough = async (value, t, hapDuration) => { compressorRelease, } = value; + const orbitChannels = multiChannelOrbits ? [(orbit * 2) - 1, orbit * 2] : getDefaultValue('channels') + const channels = value.channels ?? orbitChannels; + gain = applyGainCurve(nanFallback(gain, 1)); postgain = applyGainCurve(postgain); shapevol = applyGainCurve(shapevol); @@ -699,7 +708,7 @@ export const superdough = async (value, t, hapDuration) => { // delay let delaySend; if (delay > 0 && delaytime > 0 && delayfeedback > 0) { - const delyNode = getDelay(orbit, delaytime, delayfeedback, t); + const delyNode = getDelay(orbit, delaytime, delayfeedback, t, orbitChannels); delaySend = effectSend(post, delyNode, delay); audioNodes.push(delaySend); } @@ -717,7 +726,7 @@ export const superdough = async (value, t, hapDuration) => { } roomIR = await loadBuffer(url, ac, ir, 0); } - const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, roomIR); + const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, roomIR, orbitChannels); reverbSend = effectSend(post, reverbNode, room); audioNodes.push(reverbSend); } From 59bdfd2cb8f360c6c5cc5e438f4576e6c3b0975e Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 7 May 2025 11:12:05 -0400 Subject: [PATCH 06/74] format --- packages/superdough/superdough.mjs | 28 +++++++++++++------ .../src/repl/components/panel/SettingsTab.jsx | 14 +++++++++- website/src/repl/useReplContext.jsx | 10 +++++-- website/src/settings.mjs | 4 ++- 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 214b4d114..90f0d3696 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -23,9 +23,9 @@ export function setMaxPolyphony(polyphony) { maxPolyphony = parseInt(polyphony) ?? DEFAULT_MAX_POLYPHONY; } -let multiChannelOrbits = false +let multiChannelOrbits = false; export function setMultiChannelOrbits(bool) { - multiChannelOrbits = (bool == true); + multiChannelOrbits = bool == true; } export const soundMap = map(); @@ -201,8 +201,15 @@ function loadWorklets() { // this function should be called on first user interaction (to avoid console warning) export async function initAudio(options = {}) { - const { disableWorklets = false, maxPolyphony, audioDeviceName = DEFAULT_AUDIO_DEVICE_NAME, multiChannelOrbits = false } = options; + const { + disableWorklets = false, + maxPolyphony, + audioDeviceName = DEFAULT_AUDIO_DEVICE_NAME, + multiChannelOrbits = false, + } = options; + setMaxPolyphony(maxPolyphony); + setMultiChannelOrbits(multiChannelOrbits); if (typeof window === 'undefined') { return; } @@ -496,7 +503,7 @@ export const superdough = async (value, t, hapDuration) => { bpsustain, bprelease, bandq = getDefaultValue('bandq'), - + //phaser phaserrate: phaser, phaserdepth = getDefaultValue('phaserdepth'), @@ -532,8 +539,14 @@ export const superdough = async (value, t, hapDuration) => { compressorRelease, } = value; - const orbitChannels = multiChannelOrbits ? [(orbit * 2) - 1, orbit * 2] : getDefaultValue('channels') - const channels = value.channels ?? orbitChannels; + //music programs/audio gear usually increments inputs/outputs from 1, we need to subtract 1 from the input because the webaudio API channels start at 0 + const orbitChannels = ( + multiChannelOrbits && orbit > 0 ? [orbit * 2 - 1, orbit * 2] : getDefaultValue('channels') + ).map((ch) => ch - 1); + const channels = + value.channels != null + ? (Array.isArray(value.channels) ? value.channels : [value.channels]).map((ch) => ch - 1) + : orbitChannels; gain = applyGainCurve(nanFallback(gain, 1)); postgain = applyGainCurve(postgain); @@ -556,9 +569,6 @@ export const superdough = async (value, t, hapDuration) => { activeSoundSources.delete(chainID); } - //music programs/audio gear usually increments inputs/outputs from 1, so imitate that behavior - channels = (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1); - let audioNodes = []; if (bank && s) { diff --git a/website/src/repl/components/panel/SettingsTab.jsx b/website/src/repl/components/panel/SettingsTab.jsx index c2faf07fa..dd3725557 100644 --- a/website/src/repl/components/panel/SettingsTab.jsx +++ b/website/src/repl/components/panel/SettingsTab.jsx @@ -6,7 +6,7 @@ import { ButtonGroup } from './Forms.jsx'; import { AudioDeviceSelector } from './AudioDeviceSelector.jsx'; import { AudioEngineTargetSelector } from './AudioEngineTargetSelector.jsx'; import { confirmDialog } from '../../util.mjs'; -import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony } from '@strudel/webaudio'; +import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony, setMultiChannelOrbits } from '@strudel/webaudio'; function Checkbox({ label, value, onChange, disabled = false }) { return ( @@ -108,6 +108,7 @@ export function SettingsTab({ started }) { audioEngineTarget, togglePanelTrigger, maxPolyphony, + multiChannelOrbits, } = useSettings(); const shouldAlwaysSync = isUdels(); const canChangeAudioDevice = AudioContext.prototype.setSinkId != null; @@ -162,6 +163,17 @@ export function SettingsTab({ started }) { value={maxPolyphony ?? ''} /> + + { + const val = cbEvent.target.checked; + settingsMap.setKey('multiChannelOrbits', val); + setMultiChannelOrbits(val); + }} + value={multiChannelOrbits} + /> + settingsMap.setKey('theme', theme)} /> diff --git a/website/src/repl/useReplContext.jsx b/website/src/repl/useReplContext.jsx index f0895aaa4..f88e5a6e2 100644 --- a/website/src/repl/useReplContext.jsx +++ b/website/src/repl/useReplContext.jsx @@ -18,7 +18,7 @@ import { setVersionDefaultsFrom } from './util.mjs'; import { StrudelMirror, defaultSettings } from '@strudel/codemirror'; import { clearHydra } from '@strudel/hydra'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { settingsMap, useSettings } from '../settings.mjs'; +import { parseBoolean, settingsMap, useSettings } from '../settings.mjs'; import { setActivePattern, setLatestCode, @@ -36,11 +36,15 @@ import './Repl.css'; import { setInterval, clearInterval } from 'worker-timers'; import { getMetadata } from '../metadata_parser'; -const { latestCode, maxPolyphony, audioDeviceName } = settingsMap.get(); +const { latestCode, maxPolyphony, audioDeviceName, multiChannelOrbits } = settingsMap.get(); let modulesLoading, presets, drawContext, clearCanvas, audioReady; if (typeof window !== 'undefined') { - audioReady = initAudioOnFirstClick({ maxPolyphony, audioDeviceName }); + audioReady = initAudioOnFirstClick({ + maxPolyphony, + audioDeviceName, + multiChannelOrbits: parseBoolean(multiChannelOrbits), + }); modulesLoading = loadModules(); presets = prebake(); drawContext = getDrawContext(); diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 1b4ba33f9..84b433141 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -38,6 +38,7 @@ export const defaultSettings = { isButtonRowHidden: false, isCSSAnimationDisabled: false, maxPolyphony: 128, + multiChannelOrbits: false, }; let search = null; @@ -50,7 +51,7 @@ const settings_key = `strudel-settings${instance > 0 ? instance : ''}`; export const settingsMap = persistentMap(settings_key, defaultSettings); -const parseBoolean = (booleanlike) => ([true, 'true'].includes(booleanlike) ? true : false); +export const parseBoolean = (booleanlike) => ([true, 'true'].includes(booleanlike) ? true : false); export function useSettings() { const state = useStore(settingsMap); @@ -81,6 +82,7 @@ export function useSettings() { isPanelPinned: parseBoolean(state.isPanelPinned), isPanelOpen: parseBoolean(state.isPanelOpen), userPatterns: userPatterns, + multiChannelOrbits: parseBoolean(state.multiChannelOrbits), }; } From 099d321dbd1489b885e2e1f8b639b41b39863d34 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 7 May 2025 11:25:34 -0400 Subject: [PATCH 07/74] format --- packages/superdough/superdough.mjs | 17 +++++++++-------- .../src/repl/components/panel/SettingsTab.jsx | 9 +++++++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 90f0d3696..f774ca603 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -441,6 +441,11 @@ export function resetGlobalEffects() { } let activeSoundSources = new Map(); +//music programs/audio gear usually increments inputs/outputs from 1, we need to subtract 1 from the input because the webaudio API channels start at 0 + +function mapChannelNumbers(channels) { + return (Array.isArray(channels) ? channels : [value.channels]).map((ch) => ch - 1); +} export const superdough = async (value, t, hapDuration) => { const ac = getAudioContext(); @@ -539,14 +544,10 @@ export const superdough = async (value, t, hapDuration) => { compressorRelease, } = value; - //music programs/audio gear usually increments inputs/outputs from 1, we need to subtract 1 from the input because the webaudio API channels start at 0 - const orbitChannels = ( - multiChannelOrbits && orbit > 0 ? [orbit * 2 - 1, orbit * 2] : getDefaultValue('channels') - ).map((ch) => ch - 1); - const channels = - value.channels != null - ? (Array.isArray(value.channels) ? value.channels : [value.channels]).map((ch) => ch - 1) - : orbitChannels; + const orbitChannels = mapChannelNumbers( + multiChannelOrbits && orbit > 0 ? [orbit * 2 - 1, orbit * 2] : getDefaultValue('channels'), + ); + const channels = value.channels != null ? mapChannelNumbers(value.channels) : orbitChannels; gain = applyGainCurve(nanFallback(gain, 1)); postgain = applyGainCurve(postgain); diff --git a/website/src/repl/components/panel/SettingsTab.jsx b/website/src/repl/components/panel/SettingsTab.jsx index dd3725557..5be9b602e 100644 --- a/website/src/repl/components/panel/SettingsTab.jsx +++ b/website/src/repl/components/panel/SettingsTab.jsx @@ -168,8 +168,13 @@ export function SettingsTab({ started }) { label="Multi Channel Orbits" onChange={(cbEvent) => { const val = cbEvent.target.checked; - settingsMap.setKey('multiChannelOrbits', val); - setMultiChannelOrbits(val); + confirmDialog(RELOAD_MSG).then((r) => { + if (r == true) { + settingsMap.setKey('multiChannelOrbits', val); + setMultiChannelOrbits(val); + return window.location.reload(); + } + }); }} value={multiChannelOrbits} /> From 64ebfb57c3d923acf0641b8391cc48b8243eda03 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 7 May 2025 11:30:42 -0400 Subject: [PATCH 08/74] lint --- packages/superdough/superdough.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index f774ca603..ffdb577c4 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -444,7 +444,7 @@ let activeSoundSources = new Map(); //music programs/audio gear usually increments inputs/outputs from 1, we need to subtract 1 from the input because the webaudio API channels start at 0 function mapChannelNumbers(channels) { - return (Array.isArray(channels) ? channels : [value.channels]).map((ch) => ch - 1); + return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1); } export const superdough = async (value, t, hapDuration) => { From 99779171f5f685957787f508c987ef2959e9c16e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henr=C3=A9=20Botha?= <5593874+henrebotha@users.noreply.github.com> Date: Tue, 13 May 2025 17:45:29 +0200 Subject: [PATCH 09/74] Fix typo (#1346) --- website/src/pages/functions/intro.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/pages/functions/intro.mdx b/website/src/pages/functions/intro.mdx index 412c086a1..f3e8bec4c 100644 --- a/website/src/pages/functions/intro.mdx +++ b/website/src/pages/functions/intro.mdx @@ -23,7 +23,7 @@ is equivalent to this Pattern without Mini Notation: Similarly, there is an equivalent function for every aspect of the mini notation. Which representation to use is a matter of context. As a rule of thumb, functions -are better suited in a larger context, while mini notation is more practical for individiual rhythms. +are better suited in a larger context, while mini notation is more practical for individual rhythms. ## Limits of Mini Notation From 760d1abec0408726dfeebc8465e68588b7484e95 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 14 May 2025 19:10:59 -0400 Subject: [PATCH 10/74] remove n --- packages/superdough/synth.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index dd0481748..9a1e4d150 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -161,7 +161,7 @@ export function registerSynthSounds() { pwrate = 1; } - let { duration, pw: pulsewidth = value.n ?? 0.5 } = value; + let { duration, pw: pulsewidth = 0.5 } = value; const frequency = getFrequencyFromValue(value); const [attack, decay, sustain, release] = getADSRValues( From 5c3501caa4345b4d12eb7686aeb4615d8cb6a7c9 Mon Sep 17 00:00:00 2001 From: Nachito Date: Sun, 18 May 2025 16:43:04 -0300 Subject: [PATCH 11/74] fix: typo on docs causing problems with autocompletion. (#1350) * fix: scramble and shuffle comments * fix: comments on themes,docs, tests and more --- packages/codemirror/themes/CutiePi.mjs | 4 +- packages/codemirror/themes/androidstudio.mjs | 4 +- packages/codemirror/themes/atomone.mjs | 4 +- packages/codemirror/themes/darcula.mjs | 5 +- packages/codemirror/themes/dracula.mjs | 3 +- packages/codemirror/themes/duotoneDark.mjs | 6 +- packages/codemirror/themes/duotoneLight.mjs | 6 +- packages/codemirror/themes/githubDark.mjs | 3 - packages/codemirror/themes/githubLight.mjs | 3 - packages/codemirror/themes/green-text.mjs | 4 +- packages/codemirror/themes/gruvboxDark.mjs | 7 +- packages/codemirror/themes/gruvboxLight.mjs | 7 +- packages/codemirror/themes/red-text.mjs | 4 +- packages/codemirror/themes/sonic-pink.mjs | 4 +- packages/core/controls.mjs | 2 +- packages/core/pick.mjs | 3 +- packages/core/signal.mjs | 4 +- test/__snapshots__/examples.test.mjs.snap | 75 ++++++++++++++++++-- 18 files changed, 100 insertions(+), 48 deletions(-) diff --git a/packages/codemirror/themes/CutiePi.mjs b/packages/codemirror/themes/CutiePi.mjs index f649a1174..64b38eaa7 100644 --- a/packages/codemirror/themes/CutiePi.mjs +++ b/packages/codemirror/themes/CutiePi.mjs @@ -1,5 +1,5 @@ -/** - * @name Cutie Pi +/* + * Cutie Pi * by Switch Angel */ import { tags as t } from '@lezer/highlight'; diff --git a/packages/codemirror/themes/androidstudio.mjs b/packages/codemirror/themes/androidstudio.mjs index 097f7951b..77c16c331 100644 --- a/packages/codemirror/themes/androidstudio.mjs +++ b/packages/codemirror/themes/androidstudio.mjs @@ -1,5 +1,5 @@ -/** - * @name androidstudio +/* + * androidstudio */ import { tags as t } from '@lezer/highlight'; import { createTheme } from './theme-helper.mjs'; diff --git a/packages/codemirror/themes/atomone.mjs b/packages/codemirror/themes/atomone.mjs index 4c9fdec6e..ab9cd3607 100644 --- a/packages/codemirror/themes/atomone.mjs +++ b/packages/codemirror/themes/atomone.mjs @@ -1,5 +1,5 @@ -/** - * @name Atom One +/* + * Atom One * Atom One dark syntax theme * * https://github.com/atom/one-dark-syntax diff --git a/packages/codemirror/themes/darcula.mjs b/packages/codemirror/themes/darcula.mjs index 176acbfe3..ead66e663 100644 --- a/packages/codemirror/themes/darcula.mjs +++ b/packages/codemirror/themes/darcula.mjs @@ -1,6 +1,5 @@ -/** - * @name darcula - * @author darcula +/* + * darcula * Name: IntelliJ IDEA darcula theme * From IntelliJ IDEA by JetBrains */ diff --git a/packages/codemirror/themes/dracula.mjs b/packages/codemirror/themes/dracula.mjs index 10147edaa..bdd9617de 100644 --- a/packages/codemirror/themes/dracula.mjs +++ b/packages/codemirror/themes/dracula.mjs @@ -1,6 +1,5 @@ -/** +/* * @name dracula - * @author dracula * Michael Kaminsky (http://github.com/mkaminsky11) * Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme) */ diff --git a/packages/codemirror/themes/duotoneDark.mjs b/packages/codemirror/themes/duotoneDark.mjs index 057302cb7..f54973e85 100644 --- a/packages/codemirror/themes/duotoneDark.mjs +++ b/packages/codemirror/themes/duotoneDark.mjs @@ -1,6 +1,6 @@ -/** - * @name duotone - * @author Bram de Haan +/* + * duotone + * author Bram de Haan * by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes) */ import { tags as t } from '@lezer/highlight'; diff --git a/packages/codemirror/themes/duotoneLight.mjs b/packages/codemirror/themes/duotoneLight.mjs index f7b0214b4..81aa34848 100644 --- a/packages/codemirror/themes/duotoneLight.mjs +++ b/packages/codemirror/themes/duotoneLight.mjs @@ -1,6 +1,6 @@ -/** - * @name duotone - * @author Bram de Haan +/* + * duotone + * author Bram de Haan * by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes) */ import { tags as t } from '@lezer/highlight'; diff --git a/packages/codemirror/themes/githubDark.mjs b/packages/codemirror/themes/githubDark.mjs index 5187dcc21..477e7d7b8 100644 --- a/packages/codemirror/themes/githubDark.mjs +++ b/packages/codemirror/themes/githubDark.mjs @@ -1,6 +1,3 @@ -/** - * @name github - */ import { tags as t } from '@lezer/highlight'; import { createTheme } from './theme-helper.mjs'; diff --git a/packages/codemirror/themes/githubLight.mjs b/packages/codemirror/themes/githubLight.mjs index 382c7ac66..2ccec93b6 100644 --- a/packages/codemirror/themes/githubLight.mjs +++ b/packages/codemirror/themes/githubLight.mjs @@ -1,6 +1,3 @@ -/** - * @name github - */ import { tags as t } from '@lezer/highlight'; import { createTheme } from './theme-helper.mjs'; diff --git a/packages/codemirror/themes/green-text.mjs b/packages/codemirror/themes/green-text.mjs index bd21f6d3a..08c7a4bbe 100644 --- a/packages/codemirror/themes/green-text.mjs +++ b/packages/codemirror/themes/green-text.mjs @@ -1,5 +1,5 @@ -/** - * @name Atom One +/* + * Atom One * Atom One dark syntax theme * * https://github.com/atom/one-dark-syntax diff --git a/packages/codemirror/themes/gruvboxDark.mjs b/packages/codemirror/themes/gruvboxDark.mjs index 31d7e42c1..67d8bffc1 100644 --- a/packages/codemirror/themes/gruvboxDark.mjs +++ b/packages/codemirror/themes/gruvboxDark.mjs @@ -1,7 +1,6 @@ -/** - * @name gruvbox-dark - * @author morhetz - * Name: Gruvbox +/* + * gruvbox-dark + * author morhetz * From github.com/codemirror/codemirror5/blob/master/theme/gruvbox-dark.css */ import { tags as t } from '@lezer/highlight'; diff --git a/packages/codemirror/themes/gruvboxLight.mjs b/packages/codemirror/themes/gruvboxLight.mjs index ffb4d716c..5d4b4ec01 100644 --- a/packages/codemirror/themes/gruvboxLight.mjs +++ b/packages/codemirror/themes/gruvboxLight.mjs @@ -1,7 +1,6 @@ -/** - * @name gruvbox-light - * @author morhetz - * Name: Gruvbox +/* + * gruvbox-light + * author morhetz * From github.com/codemirror/codemirror5/blob/master/theme/gruvbox-light.css */ import { tags as t } from '@lezer/highlight'; diff --git a/packages/codemirror/themes/red-text.mjs b/packages/codemirror/themes/red-text.mjs index 9fe7f516a..44dda5dc0 100644 --- a/packages/codemirror/themes/red-text.mjs +++ b/packages/codemirror/themes/red-text.mjs @@ -1,5 +1,5 @@ -/** - * @name Atom One +/* + * Atom One * Atom One dark syntax theme * * https://github.com/atom/one-dark-syntax diff --git a/packages/codemirror/themes/sonic-pink.mjs b/packages/codemirror/themes/sonic-pink.mjs index 976597ecc..5a19555ab 100644 --- a/packages/codemirror/themes/sonic-pink.mjs +++ b/packages/codemirror/themes/sonic-pink.mjs @@ -1,5 +1,5 @@ -/** - * @name Atom One +/* + * Atom One * Atom One dark syntax theme * * https://github.com/atom/one-dark-syntax diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 0b9821eaa..383c2ffa7 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1496,7 +1496,7 @@ export const { vowel } = registerControl('vowel'); * @name waveloss */ export const { waveloss } = registerControl('waveloss'); -/* +/** * Noise crackle density * * @name density diff --git a/packages/core/pick.mjs b/packages/core/pick.mjs index 334551e15..702201fa6 100644 --- a/packages/core/pick.mjs +++ b/packages/core/pick.mjs @@ -156,8 +156,7 @@ export const pickmodReset = register('pickmodReset', function (lookup, pat) { return _pick(lookup, pat, true).resetJoin(); }); -/** - /** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name). +/** Picks patterns (or plain values) either from a list (by index) or a lookup table (by name). * Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern. * @name inhabit * @synonyms pickSqueeze diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 66ac8a5a6..b6ba5d5cc 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -279,9 +279,9 @@ const _rearrangeWith = (ipat, n, pat) => { }; /** - * @name shuffle * Slices a pattern into the given number of parts, then plays those parts in random order. * Each part will be played exactly once per cycle. + * @name shuffle * @example * note("c d e f").sound("piano").shuffle(4) * @example @@ -292,9 +292,9 @@ export const shuffle = register('shuffle', (n, pat) => { }); /** - * @name scramble * Slices a pattern into the given number of parts, then plays those parts at random. Similar to `shuffle`, * but parts might be played more than once, or not at all, per cycle. + * @name scramble * @example * note("c d e f").sound("piano").scramble(4) * @example diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index a05d8aa6c..e7d6dbb18 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -2403,6 +2403,27 @@ exports[`runs examples > example "delaytime" example index 0 1`] = ` ] `; +exports[`runs examples > example "density" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | s:crackle density:0.01 ]", + "[ 1/4 → 1/2 | s:crackle density:0.01 ]", + "[ 1/2 → 3/4 | s:crackle density:0.01 ]", + "[ 3/4 → 1/1 | s:crackle density:0.01 ]", + "[ 1/1 → 5/4 | s:crackle density:0.01 ]", + "[ 5/4 → 3/2 | s:crackle density:0.01 ]", + "[ 3/2 → 7/4 | s:crackle density:0.01 ]", + "[ 7/4 → 2/1 | s:crackle density:0.01 ]", + "[ 2/1 → 9/4 | s:crackle density:0.01 ]", + "[ 9/4 → 5/2 | s:crackle density:0.01 ]", + "[ 5/2 → 11/4 | s:crackle density:0.01 ]", + "[ 11/4 → 3/1 | s:crackle density:0.01 ]", + "[ 3/1 → 13/4 | s:crackle density:0.01 ]", + "[ 13/4 → 7/2 | s:crackle density:0.01 ]", + "[ 7/2 → 15/4 | s:crackle density:0.01 ]", + "[ 15/4 → 4/1 | s:crackle density:0.01 ]", +] +`; + exports[`runs examples > example "detune" example index 0 1`] = ` [ "[ 0/1 → 1/12 | note:d s:supersaw detune:0.1 ]", @@ -8051,6 +8072,52 @@ but parts might be played more than once, or not at all, per cycle." example ind ] `; +exports[`runs examples > example "scramble" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:c s:piano ]", + "[ 1/4 → 1/2 | note:d s:piano ]", + "[ 1/2 → 3/4 | note:d s:piano ]", + "[ 3/4 → 1/1 | note:c s:piano ]", + "[ 1/1 → 5/4 | note:e s:piano ]", + "[ 5/4 → 3/2 | note:e s:piano ]", + "[ 3/2 → 7/4 | note:e s:piano ]", + "[ 7/4 → 2/1 | note:c s:piano ]", + "[ 2/1 → 9/4 | note:f s:piano ]", + "[ 9/4 → 5/2 | note:d s:piano ]", + "[ 5/2 → 11/4 | note:d s:piano ]", + "[ 11/4 → 3/1 | note:e s:piano ]", + "[ 3/1 → 13/4 | note:c s:piano ]", + "[ 13/4 → 7/2 | note:f s:piano ]", + "[ 7/2 → 15/4 | note:d s:piano ]", + "[ 15/4 → 4/1 | note:f s:piano ]", +] +`; + +exports[`runs examples > example "scramble" example index 1 1`] = ` +[ + "[ 0/1 → 1/8 | note:c s:piano ]", + "[ 1/8 → 1/4 | note:d s:piano ]", + "[ 1/4 → 3/8 | note:d s:piano ]", + "[ 3/8 → 1/2 | note:c s:piano ]", + "[ 1/2 → 1/1 | note:g s:piano ]", + "[ 1/1 → 9/8 | note:e s:piano ]", + "[ 9/8 → 5/4 | note:e s:piano ]", + "[ 5/4 → 11/8 | note:e s:piano ]", + "[ 11/8 → 3/2 | note:c s:piano ]", + "[ 3/2 → 2/1 | note:g s:piano ]", + "[ 2/1 → 17/8 | note:f s:piano ]", + "[ 17/8 → 9/4 | note:d s:piano ]", + "[ 9/4 → 19/8 | note:d s:piano ]", + "[ 19/8 → 5/2 | note:e s:piano ]", + "[ 5/2 → 3/1 | note:g s:piano ]", + "[ 3/1 → 25/8 | note:c s:piano ]", + "[ 25/8 → 13/4 | note:f s:piano ]", + "[ 13/4 → 27/8 | note:d s:piano ]", + "[ 27/8 → 7/2 | note:f s:piano ]", + "[ 7/2 → 4/1 | note:g s:piano ]", +] +`; + exports[`runs examples > example "scrub" example index 0 1`] = ` [ "[ 0/1 → 1/8 | s:swpad n:0 begin:0.1 speed:1 clip:1 ]", @@ -8481,9 +8548,7 @@ exports[`runs examples > example "shrink" example index 3 1`] = ` ] `; -exports[`runs examples > example "shuffle -Slices a pattern into the given number of parts, then plays those parts in random order. -Each part will be played exactly once per cycle." example index 0 1`] = ` +exports[`runs examples > example "shuffle" example index 0 1`] = ` [ "[ 0/1 → 1/4 | note:c s:piano ]", "[ 1/4 → 1/2 | note:d s:piano ]", @@ -8504,9 +8569,7 @@ Each part will be played exactly once per cycle." example index 0 1`] = ` ] `; -exports[`runs examples > example "shuffle -Slices a pattern into the given number of parts, then plays those parts in random order. -Each part will be played exactly once per cycle." example index 1 1`] = ` +exports[`runs examples > example "shuffle" example index 1 1`] = ` [ "[ 0/1 → 1/8 | note:c s:piano ]", "[ 1/8 → 1/4 | note:d s:piano ]", From 058cb36640e880b7818c9b556f1415655e7b6fd4 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 20 May 2025 20:28:52 +0100 Subject: [PATCH 12/74] structure --- packages/superdough/worklets.mjs | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 03d3a966f..66148ecbc 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -761,3 +761,55 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor { } registerProcessor('pulse-oscillator', PulseOscillatorProcessor); + + + +class ByteBeatProcessor extends AudioWorkletProcessor { + constructor() { + super(); + this.pi = _PI; + } + + static get parameterDescriptors() { + return [ + { + name: 'begin', + defaultValue: 0, + max: Number.POSITIVE_INFINITY, + min: 0, + }, + + { + name: 'end', + defaultValue: 0, + max: Number.POSITIVE_INFINITY, + min: 0, + }, + ]; + } + + process(inputs, outputs, params) { + if (this.disconnected) { + return false; + } + if (currentTime <= params.begin[0]) { + return true; + } + if (currentTime >= params.end[0]) { + return false; + } + const output = outputs[0]; + + for (let i = 0; i < (output[0].length ?? 0); i++) { + + for (let o = 0; o < output.length; o++) { + // Combination of both oscillators with envelope applied + output[o][i] = 0.15 * (out0 - out1) * this.envf; + } + } + + return true; // keep the audio processing going + } +} + +registerProcessor('byte-beat-processor', BeatBeatProcessor); From b8d2d024667e525d09c388fe8402437c23952d9f Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 20 May 2025 21:41:40 +0100 Subject: [PATCH 13/74] bb --- packages/core/controls.mjs | 14 ++++++++ packages/superdough/superdough.mjs | 2 ++ packages/superdough/synth.mjs | 57 ++++++++++++++++++++++++++++++ packages/superdough/worklets.mjs | 16 +++++++-- 4 files changed, 86 insertions(+), 3 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 383c2ffa7..94f034480 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -445,6 +445,20 @@ export const { coarse } = registerControl('coarse'); */ export const { drive } = registerControl('drive'); +/** + * Allows you to set the output channels on the interface + * + * @name byteBeatExpression + * @synonyms bbexpr + * + * @param {number | Pattern} byteBeatExpression pattern the output channels + * @example + * note("e a d b g").channels("3:4") + * + */ +export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr'); + + /** * Allows you to set the output channels on the interface * diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 55b72d165..0afbb3d67 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -137,6 +137,7 @@ const defaultDefaultValues = { shapevol: 1, distortvol: 1, delay: 0, + byteBeatExpression: '0', delayfeedback: 0.5, delaytime: 0.25, orbit: 1, @@ -475,6 +476,7 @@ export const superdough = async (value, t, hapDuration) => { let { s = getDefaultValue('s'), bank, + byteBeatExpression, source, gain = getDefaultValue('gain'), postgain = getDefaultValue('postgain'), diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 9a1e4d150..63c5a0b37 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -144,6 +144,63 @@ export function registerSynthSounds() { { prebake: true, type: 'synth' }, ); + + registerSound( + 'bytebeat', + (begin, value, onended) => { + const ac = getAudioContext(); + let { byteBeatExpression } = value; + + let { duration} = value; + const [attack, decay, sustain, release] = getADSRValues( + [value.attack, value.decay, value.sustain, value.release], + 'linear', + [0.001, 0.05, 0.6, 0.01], + ); + const holdend = begin + duration; + const end = holdend + release + 0.01; + let o = getWorklet( + ac, + 'byte-beat-processor', + { + + begin, + end, + }, + { + outputChannelCount: [2], + }, + ); + + + o.port.postMessage(byteBeatExpression); + + let envGain = gainNode(1); + envGain = o.connect(envGain); + + getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear'); + + let timeoutNode = webAudioTimeout( + ac, + () => { + destroyAudioWorkletNode(o); + envGain.disconnect(); + onended(); + }, + begin, + end, + ); + + return { + node: envGain, + stop: (time) => { + timeoutNode.stop(time); + }, + }; + }, + { prebake: true, type: 'synth' }, + ); + registerSound( 'pulse', (begin, value, onended) => { diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 66148ecbc..bd52e3233 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -767,7 +767,10 @@ registerProcessor('pulse-oscillator', PulseOscillatorProcessor); class ByteBeatProcessor extends AudioWorkletProcessor { constructor() { super(); - this.pi = _PI; + this.bb = '0' + this.port.onmessage = (event) => { + this.bb = event.data; + }; } static get parameterDescriptors() { @@ -798,13 +801,20 @@ class ByteBeatProcessor extends AudioWorkletProcessor { if (currentTime >= params.end[0]) { return false; } + const bb = this.bb + + const f = Function('t', 'return ' + bb) + const output = outputs[0]; for (let i = 0; i < (output[0].length ?? 0); i++) { + let t = currentTime + t *= sampleRate + const signal = ((f(t)&255)/127.5 - 1)/4 for (let o = 0; o < output.length; o++) { // Combination of both oscillators with envelope applied - output[o][i] = 0.15 * (out0 - out1) * this.envf; + output[o][i] = signal } } @@ -812,4 +822,4 @@ class ByteBeatProcessor extends AudioWorkletProcessor { } } -registerProcessor('byte-beat-processor', BeatBeatProcessor); +registerProcessor('byte-beat-processor', ByteBeatProcessor); From 65f1760fad44fdd8a452448a327dc6b9065780ca Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 20 May 2025 21:45:14 +0100 Subject: [PATCH 14/74] format --- packages/core/controls.mjs | 5 ++--- packages/superdough/synth.mjs | 7 ++----- packages/superdough/worklets.mjs | 17 +++++++---------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 94f034480..5e0c450d6 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -446,19 +446,18 @@ export const { coarse } = registerControl('coarse'); export const { drive } = registerControl('drive'); /** - * Allows you to set the output channels on the interface + * Create byte beats with custom expressions * * @name byteBeatExpression * @synonyms bbexpr * * @param {number | Pattern} byteBeatExpression pattern the output channels * @example - * note("e a d b g").channels("3:4") + * s("bytebeat").bbexpr('t*(t>>15^t>>66)') * */ export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr'); - /** * Allows you to set the output channels on the interface * diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 63c5a0b37..dd2c92451 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -144,14 +144,13 @@ export function registerSynthSounds() { { prebake: true, type: 'synth' }, ); - registerSound( 'bytebeat', (begin, value, onended) => { const ac = getAudioContext(); let { byteBeatExpression } = value; - let { duration} = value; + let { duration } = value; const [attack, decay, sustain, release] = getADSRValues( [value.attack, value.decay, value.sustain, value.release], 'linear', @@ -163,7 +162,6 @@ export function registerSynthSounds() { ac, 'byte-beat-processor', { - begin, end, }, @@ -172,14 +170,13 @@ export function registerSynthSounds() { }, ); - o.port.postMessage(byteBeatExpression); let envGain = gainNode(1); envGain = o.connect(envGain); getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear'); - + let timeoutNode = webAudioTimeout( ac, () => { diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index bd52e3233..8d14ac445 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -762,12 +762,10 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor { registerProcessor('pulse-oscillator', PulseOscillatorProcessor); - - class ByteBeatProcessor extends AudioWorkletProcessor { constructor() { super(); - this.bb = '0' + this.bb = '0'; this.port.onmessage = (event) => { this.bb = event.data; }; @@ -801,20 +799,19 @@ class ByteBeatProcessor extends AudioWorkletProcessor { if (currentTime >= params.end[0]) { return false; } - const bb = this.bb + const bb = this.bb; - const f = Function('t', 'return ' + bb) + const f = Function('t', 'return ' + bb); const output = outputs[0]; for (let i = 0; i < (output[0].length ?? 0); i++) { - - let t = currentTime - t *= sampleRate - const signal = ((f(t)&255)/127.5 - 1)/4 + let t = currentTime; + t *= sampleRate; + const signal = ((f(t) & 255) / 127.5 - 1) / 4; for (let o = 0; o < output.length; o++) { // Combination of both oscillators with envelope applied - output[o][i] = signal + output[o][i] = signal; } } From 45bccf13ffce223243879a847ec1916957964246 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 20 May 2025 21:47:43 +0100 Subject: [PATCH 15/74] add test --- test/__snapshots__/examples.test.mjs.snap | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index e7d6dbb18..b9cdb444b 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -1516,6 +1516,15 @@ exports[`runs examples > example "brandBy" example index 0 1`] = ` ] `; +exports[`runs examples > example "byteBeatExpression" example index 0 1`] = ` +[ + "[ 0/1 → 1/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]", + "[ 1/1 → 2/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]", + "[ 2/1 → 3/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]", + "[ 3/1 → 4/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]", +] +`; + exports[`runs examples > example "cat" example index 0 1`] = ` [ "[ 0/1 → 1/1 | note:e5 ]", From 5e4eb7fc53f995cbdad132ad71d6828d8b7c2e5c Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 20 May 2025 21:53:10 +0100 Subject: [PATCH 16/74] gain --- 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 8d14ac445..b4923d5aa 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -811,7 +811,7 @@ class ByteBeatProcessor extends AudioWorkletProcessor { const signal = ((f(t) & 255) / 127.5 - 1) / 4; for (let o = 0; o < output.length; o++) { // Combination of both oscillators with envelope applied - output[o][i] = signal; + output[o][i] = signal * .4; } } From 9bd7b6622830c357548576daecab86e780fa30f9 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 24 May 2025 11:30:02 +0200 Subject: [PATCH 17/74] Publish - @strudel/codemirror@1.2.1 - @strudel/core@1.2.1 - @strudel/csound@1.2.2 - @strudel/draw@1.2.1 - @strudel/gamepad@1.2.1 - @strudel/hydra@1.2.1 - @strudel/midi@1.2.2 - @strudel/mini@1.2.1 - @strudel/motion@1.2.1 - @strudel/mqtt@1.2.1 - @strudel/osc@1.2.1 - @strudel/repl@1.2.2 - @strudel/serial@1.2.1 - @strudel/soundfonts@1.2.2 - superdough@1.2.2 - @strudel/tonal@1.2.1 - @strudel/transpiler@1.2.1 - @strudel/web@1.2.2 - @strudel/webaudio@1.2.2 - @strudel/xen@1.2.1 --- packages/codemirror/package.json | 2 +- packages/core/package.json | 2 +- packages/csound/package.json | 2 +- packages/draw/package.json | 2 +- packages/gamepad/package.json | 2 +- packages/hydra/package.json | 2 +- packages/midi/package.json | 2 +- packages/mini/package.json | 2 +- packages/motion/package.json | 2 +- packages/mqtt/package.json | 2 +- packages/osc/package.json | 2 +- packages/repl/package.json | 2 +- packages/serial/package.json | 2 +- packages/soundfonts/package.json | 2 +- packages/superdough/package.json | 2 +- packages/tonal/package.json | 2 +- packages/transpiler/package.json | 2 +- packages/web/package.json | 2 +- packages/webaudio/package.json | 2 +- packages/xen/package.json | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index 1c3d93c7c..bc82fe380 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/codemirror", - "version": "1.2.0", + "version": "1.2.1", "description": "Codemirror Extensions for Strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/core/package.json b/packages/core/package.json index c6a686613..00438ee77 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/core", - "version": "1.2.0", + "version": "1.2.1", "description": "Port of Tidal Cycles to JavaScript", "main": "index.mjs", "type": "module", diff --git a/packages/csound/package.json b/packages/csound/package.json index dbd94d128..c9831f9ac 100644 --- a/packages/csound/package.json +++ b/packages/csound/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/csound", - "version": "1.2.1", + "version": "1.2.2", "description": "csound bindings for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/draw/package.json b/packages/draw/package.json index 3aec41ba4..3b97e53d9 100644 --- a/packages/draw/package.json +++ b/packages/draw/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/draw", - "version": "1.2.0", + "version": "1.2.1", "description": "Helpers for drawing with Strudel", "main": "index.mjs", "type": "module", diff --git a/packages/gamepad/package.json b/packages/gamepad/package.json index 101ea42b9..6332ee2ed 100644 --- a/packages/gamepad/package.json +++ b/packages/gamepad/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/gamepad", - "version": "1.2.0", + "version": "1.2.1", "description": "Gamepad Inputs for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/hydra/package.json b/packages/hydra/package.json index 68d744124..8a2aff50d 100644 --- a/packages/hydra/package.json +++ b/packages/hydra/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/hydra", - "version": "1.2.0", + "version": "1.2.1", "description": "Hydra integration for strudel", "main": "hydra.mjs", "type": "module", diff --git a/packages/midi/package.json b/packages/midi/package.json index f2c31d55b..2264ef969 100644 --- a/packages/midi/package.json +++ b/packages/midi/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/midi", - "version": "1.2.1", + "version": "1.2.2", "description": "Midi API for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/mini/package.json b/packages/mini/package.json index cd910d003..68ec683bf 100644 --- a/packages/mini/package.json +++ b/packages/mini/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/mini", - "version": "1.2.0", + "version": "1.2.1", "description": "Mini notation for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/motion/package.json b/packages/motion/package.json index ebd3d997d..da89b21fb 100644 --- a/packages/motion/package.json +++ b/packages/motion/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/motion", - "version": "1.2.0", + "version": "1.2.1", "description": "DeviceMotion API for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/mqtt/package.json b/packages/mqtt/package.json index f9d6be4f3..33ff4cdfb 100644 --- a/packages/mqtt/package.json +++ b/packages/mqtt/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/mqtt", - "version": "1.2.0", + "version": "1.2.1", "description": "MQTT API for strudel", "main": "mqtt.mjs", "type": "module", diff --git a/packages/osc/package.json b/packages/osc/package.json index ec05ac64c..a202ab606 100644 --- a/packages/osc/package.json +++ b/packages/osc/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/osc", - "version": "1.2.0", + "version": "1.2.1", "description": "OSC messaging for strudel", "main": "osc.mjs", "type": "module", diff --git a/packages/repl/package.json b/packages/repl/package.json index 6e8ad29d1..b75b87c45 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/repl", - "version": "1.2.1", + "version": "1.2.2", "description": "Strudel REPL as a Web Component", "module": "index.mjs", "publishConfig": { diff --git a/packages/serial/package.json b/packages/serial/package.json index 8a0467bef..fe7a23c82 100644 --- a/packages/serial/package.json +++ b/packages/serial/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/serial", - "version": "1.2.0", + "version": "1.2.1", "description": "Webserial API for strudel", "main": "serial.mjs", "type": "module", diff --git a/packages/soundfonts/package.json b/packages/soundfonts/package.json index cc97ae826..0d1bf200b 100644 --- a/packages/soundfonts/package.json +++ b/packages/soundfonts/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/soundfonts", - "version": "1.2.1", + "version": "1.2.2", "description": "Soundsfont support for strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/superdough/package.json b/packages/superdough/package.json index 6cdf8b312..c111b217c 100644 --- a/packages/superdough/package.json +++ b/packages/superdough/package.json @@ -1,6 +1,6 @@ { "name": "superdough", - "version": "1.2.1", + "version": "1.2.2", "description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.", "main": "index.mjs", "type": "module", diff --git a/packages/tonal/package.json b/packages/tonal/package.json index e3589c1f1..b183c79c7 100644 --- a/packages/tonal/package.json +++ b/packages/tonal/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/tonal", - "version": "1.2.0", + "version": "1.2.1", "description": "Tonal functions for strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/transpiler/package.json b/packages/transpiler/package.json index deb407617..cda9da6dc 100644 --- a/packages/transpiler/package.json +++ b/packages/transpiler/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/transpiler", - "version": "1.2.0", + "version": "1.2.1", "description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.", "main": "index.mjs", "type": "module", diff --git a/packages/web/package.json b/packages/web/package.json index 2554f89d3..d602e6281 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/web", - "version": "1.2.1", + "version": "1.2.2", "description": "Easy to setup, opiniated bundle of Strudel for the browser.", "module": "web.mjs", "publishConfig": { diff --git a/packages/webaudio/package.json b/packages/webaudio/package.json index 56e72e961..c4b61e96b 100644 --- a/packages/webaudio/package.json +++ b/packages/webaudio/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/webaudio", - "version": "1.2.1", + "version": "1.2.2", "description": "Web Audio helpers for Strudel", "main": "index.mjs", "type": "module", diff --git a/packages/xen/package.json b/packages/xen/package.json index e7e5f2cee..fc874a557 100644 --- a/packages/xen/package.json +++ b/packages/xen/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/xen", - "version": "1.2.0", + "version": "1.2.1", "description": "Xenharmonic API for strudel", "main": "index.mjs", "type": "module", From 608dfd515e4b305ae9152de7a114d313a7742563 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 27 May 2025 14:37:55 +0200 Subject: [PATCH 18/74] WIP oscillations --- .vscode/launch.json | 16 + packages/superdough/remove.html | 295 ++++++++++++++++++ packages/superdough/superdough.mjs | 4 +- packages/superdough/synth.mjs | 3 + packages/superdough/worklets.mjs | 460 ++++++++++++++++++++++++++++- 5 files changed, 763 insertions(+), 15 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 packages/superdough/remove.html diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..5bd1b92fe --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:4321", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/packages/superdough/remove.html b/packages/superdough/remove.html new file mode 100644 index 000000000..59476043c --- /dev/null +++ b/packages/superdough/remove.html @@ -0,0 +1,295 @@ +🌱 audio worklets +In the last post, we've been filling fixed length audio buffers. While this is very simple to implement, it's a bit annoying when the audio is always stopping. To calculate samples infinitely, we can use an AudioWorklet. Now we've arrived at a typical bytebeat/floatbeat editor: + +💡 hit ctrl+enter to update the code & ctrl+. to stop +((( // by stimmer (2011-10-03) +t*(4|t>>13&3)>>(~t>>11&1)&128|t*(t>>11&t>>13)*(~t>>9&3)&127 +) & 255) / 127.5 - 1)/4 + play | stop +The AudioWorklet is the lowest abstaction we get with the Web Audio API. It essentially gives us a function that fills a buffer of 128 samples over and over again, also running isolated from the rest of the page. At a sample rate of 44100/s, this means we fill a buffer every ~3ms (44100/128). + +Here are some examples to try: + +hello sine +slow sine (inaudible) +just intonation triad +hello saw +hello pulse +wandering sine +bytebeat 1 +bytebeat 2 +gladyouask - chirps +aks - TR-808-like hihat +show source (271 loc) + + + + 🌱 audio worklets + + + +

🌱 audio worklets

+

+ In the last post, we've been filling + fixed length audio buffers. While this is very simple to implement, it's a + bit annoying when the audio is always stopping. To calculate samples + infinitely, we can use an AudioWorklet. Now we've arrived at a typical + bytebeat/floatbeat editor: +

+
+ 💡 hit ctrl+enter to update the code & ctrl+. to stop +
+ + play | stop + +

+ The AudioWorklet is the lowest abstaction we get with the Web Audio API. + It essentially gives us a function that fills a buffer of 128 samples over + and over again, also running isolated from the rest of the page. At a + sample rate of 44100/s, this means we fill a buffer every ~3ms + (44100/128). +

+

Here are some examples to try:

+ + +
+ show page source +

+    
+

+ back to garten.salat +

+ + +back to garten.salat \ No newline at end of file diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 0afbb3d67..07ab08054 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -446,7 +446,7 @@ function mapChannelNumbers(channels) { return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1); } -export const superdough = async (value, t, hapDuration) => { +export const superdough = async (value, t, hapDuration, cps) => { const ac = getAudioContext(); t = typeof t === 'string' && t.startsWith('=') ? Number(t.slice(1)) : ac.currentTime + t; let { stretch } = value; @@ -580,7 +580,7 @@ export const superdough = async (value, t, hapDuration) => { // get source AudioNode let sourceNode; if (source) { - sourceNode = source(t, value, hapDuration); + sourceNode = source(t, value, hapDuration,cps); } else if (getSound(s)) { const { onTrigger } = getSound(s); const onEnded = () => { diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index dd2c92451..554bb1e9a 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -158,10 +158,13 @@ export function registerSynthSounds() { ); const holdend = begin + duration; const end = holdend + release + 0.01; + const frequency = getFrequencyFromValue(value); + let o = getWorklet( ac, 'byte-beat-processor', { + frequency, begin, end, }, diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index b4923d5aa..3d1bbccbe 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -295,7 +295,7 @@ class LadderProcessor extends AudioWorkletProcessor { cutoff = (cutoff * 2 * _PI) / sampleRate; cutoff = cutoff > 1 ? 1 : cutoff; - const k = Math.min(8, resonance * 0.4); + const k = Math.min(8, resonance * 0.13); // drive makeup * resonance volume loss makeup let makeupgain = (1 / drive) * Math.min(1.75, 1 + k); @@ -762,13 +762,29 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor { registerProcessor('pulse-oscillator', PulseOscillatorProcessor); + +function frequencyToDigit(freq, baseFreq) { + const d = Math.log2(freq / baseFreq); + return Math.max(d, 0) + Math.pow(2, Math.min(d, 0)); +} + class ByteBeatProcessor extends AudioWorkletProcessor { constructor() { super(); - this.bb = '0'; + this.codeText = '0'; this.port.onmessage = (event) => { - this.bb = event.data; + + this.codeText = event.data.trim().replace( + /^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/, + (match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%'))); + }; + this.virtualRate = 32000; // target sample rate + // this.nativeRate = sampleRate; // actual context rate, e.g., 48000 + // this.ratio = this.nativeRate / this.virtualRate; + this.t = null; + this.framebuffer = new Float32Array(Math.floor(sampleRate/60)); + this.func = null; } static get parameterDescriptors() { @@ -779,7 +795,17 @@ class ByteBeatProcessor extends AudioWorkletProcessor { max: Number.POSITIVE_INFINITY, min: 0, }, - + { + name: 'frequency', + defaultValue: 440, + min: Number.EPSILON, + }, + { + name: 'detune', + defaultValue: 0, + min: Number.NEGATIVE_INFINITY, + max: Number.POSITIVE_INFINITY, + }, { name: 'end', defaultValue: 0, @@ -799,24 +825,432 @@ class ByteBeatProcessor extends AudioWorkletProcessor { if (currentTime >= params.end[0]) { return false; } - const bb = this.bb; + if (this.t == null) { + this.t = params.begin[0] * this.virtualRate + } + + + let codeText = this.codeText; + + // const f = Function('t', 'return ' + codeText); + + + // const chyx = { + // /*bit*/ "bitC": function (x, y, z) { return x & y ? z : 0 }, + // /*bit reverse*/"br": function (x, size = 8) { + // if(size > 32) { throw new Error("br() Size cannot be greater than 32") } else { + // let result = 0; + // for (let idx = 0; idx < (size - 0); idx++) { + // result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1))) + // } + // return result + // } + // }, + // /*sin that loops every 128 "steps", instead of every pi steps*/"sinf": function (x) { return Math.sin(x / (128 / Math.PI)) }, + // /*cos that loops every 128 "steps", instead of every pi steps*/"cosf": function (x) { return Math.cos(x / (128 / Math.PI)) }, + // /*tan that loops every 128 "steps", instead of every pi steps*/"tanf": function (x) { return Math.tan(x / (128 / Math.PI)) }, + // /*converts t into a string composed of it's bits, regex's that*/"regG": function (t, X) { return X.test(t.toString(2)) } + // /*corrupt sound"crpt": function(x,y=8) {return chyx.br(chyx.br(x,y)+t,y)^chyx.br(t,y)}, + // decorrupt sound"decrpt": function(x,y=8) {return chyx.br(chyx.br(x^chyx.br(t,y),y)-t,y)},*/ + // } + // // Create shortened Math functions + // const mathParams = Object.getOwnPropertyNames(Math); + // const values = mathParams.map(k => Math[k]); + // const chyxNames = Object.getOwnPropertyNames(chyx); + // const chyxFuncs = chyxNames.map(k => chyx[k]); + // mathParams.push('int', 'window', ...chyxNames); + // values.push(Math.floor, globalThis, ...chyxFuncs); + + + + + // Optimize code like eval(unescape(escape`XXXX`.replace(/u(..)/g,"$1%"))) + // this.func = new Function(...mathParams, 't', `return 0,\n${ codeText || 0 };`).bind(globalThis, ...values); + this.func = Function('t', 'return ' + codeText); - const f = Function('t', 'return ' + bb); const output = outputs[0]; + const ct = params.begin[0] + const tIncrement = this.virtualRate / sampleRate + - for (let i = 0; i < (output[0].length ?? 0); i++) { - let t = currentTime; - t *= sampleRate; - const signal = ((f(t) & 255) / 127.5 - 1) / 4; - for (let o = 0; o < output.length; o++) { - // Combination of both oscillators with envelope applied - output[o][i] = signal * .4; + // console.info(ct, currentTime) + for (let i = 0; i < output[0].length; i++) { + let t = Math.floor(this.t) + const detune = getParamValue(i, params.detune); + const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100); + // t = t * (Math.log(freq / 440)) / Math.LN2; + + t = t * frequencyToDigit(freq, 440) + // console.info(t) + // let t = (currentTime * sampleRate) + i + // let t = this.t + + // const index = Math.floor(this.t) + const funcValue = this.func(t) + let signal = (funcValue & 255) / 127.5 - 1 + const out = signal; + for (let c = 0; c < output.length; c++) { + output[c][i] = out; } + this.t = this.t + tIncrement + // if (i % 8 === 0){ + // this.t = this.t + tIncrement + + // } + // this.framebuffer[this.t%this.framebuffer.length] = out; + // if(this.t % this.framebuffer.length === 0 && this.t > 0) { + // this.port.postMessage(this.framebuffer) + // } } + + + // for (let i = 0; i < (output[0].length ?? 0); i++) { + // let t = currentTime; + // t *= sampleRate; + // const signal = this.func(t) + // // const signal = ((this.func(t) + 128) & 255) / 127.5 - 1 + // // const signal = ((this.func(t) & 255) / 127.5 - 1) / 4; + // for (let o = 0; o < output.length; o++) { + // // Combination of both oscillators with envelope applied + // output[o][i] = signal * .4; + // } + // } return true; // keep the audio processing going } } registerProcessor('byte-beat-processor', ByteBeatProcessor); + + + +// class ByteBeatProcessor extends AudioWorkletProcessor { +// constructor(...args) { +// super(...args); +// this.audioSample = 0; +// this.byteSample = 0; +// // this.drawMode = 'Points'; +// // this.errorDisplayed = true; +// this.func = null; +// this.getValues = null; +// this.isFuncbeat = false; +// this.isPlaying = true; +// this.playbackSpeed = 1; +// this.divisorStorage = 0; +// this.lastTime = -1; +// this.lastFuncValue = [null, null]; +// this.lastByteValue = [0, 0]; +// this.outValue = [0, 0]; +// // this.sampleRate = 8000; +// // this.sampleRatio = 1; +// this.sampleDivisor/*PRO*/ = 1; +// this.soundMode = 'Bytebeat'; +// // Object.seal(this); +// // ByteBeatProcessor.deleteGlobals(); +// // ByteBeatProcessor.freezeGlobals(); +// this.port.addEventListener('message', e => this.receiveData(e.data)); +// this.port.start(); +// } + +// static get parameterDescriptors() { +// return [ +// { +// name: 'begin', +// defaultValue: 0, +// max: Number.POSITIVE_INFINITY, +// min: 0, +// }, + +// { +// name: 'end', +// defaultValue: 0, +// max: Number.POSITIVE_INFINITY, +// min: 0, +// }, +// ]; +// } +// // static deleteGlobals() { +// // // Delete single letter variables to prevent persistent variable errors (covers a good enough range) +// // for(let i = 0; i < 26; ++i) { +// // delete globalThis[String.fromCharCode(65 + i)]; +// // delete globalThis[String.fromCharCode(97 + i)]; +// // } +// // // Delete global variables +// // for(const name in globalThis) { +// // if(Object.prototype.hasOwnProperty.call(globalThis, name)) { +// // delete globalThis[name]; +// // } +// // } +// // } +// // static freezeGlobals() { +// // Object.getOwnPropertyNames(globalThis).forEach(name => { +// // const prop = globalThis[name]; +// // const type = typeof prop; +// // if((type === 'object' || type === 'function') && name !== 'globalThis') { +// // Object.freeze(prop); +// // } +// // if(type === 'function' && Object.prototype.hasOwnProperty.call(prop, 'prototype')) { +// // Object.freeze(prop.prototype); +// // } +// // Object.defineProperty(globalThis, name, { writable: false, configurable: false }); +// // }); +// // } + +// process(inputs, [outputData], params) { +// if (currentTime <= params.begin[0]) { +// return true; +// } +// if (currentTime >= params.end[0]) { +// return false; +// } + + + +// const chDataLen = outputData[0].length; +// if(!chDataLen || !this.isPlaying) { +// return true; +// } +// let time = currentTime; +// let { byteSample } = this; +// const drawBuffer = []; +// const isDiagram = this.drawMode === 'Combined' || this.drawMode === 'Diagram' || this.drawMode === 'Spectrogram'; + +// for(let i = 0; i < chDataLen; ++i) { +// time += this.sampleRatio; +// const CT = Math.floor(time); +// if(this.lastTime !== CT) { +// let funcValue; +// const currentSample = Math.floor(byteSample); + +// // long cascade of null handlers +// const inputs0 = inputs[0] ?? [ ]; +// const inputs00 = inputs0[0] ?? [ ]; +// const inputs01 = inputs0[1] ?? inputs00; +// const inputs00i = inputs00[i] ?? 0; +// const inputs01i = inputs01[i] ?? 0; +// const micSample = [inputs00i, inputs01i, inputs00i / 2 + inputs01i / 2]; + +// if(this.isFuncbeat) { +// funcValue = this.func(currentSample / this.sampleRate, this.sampleRate, +// currentSample, micSample); +// } else { +// funcValue = this.func(currentSample, micSample); +// } + +// funcValue = Array.isArray(funcValue) ? [funcValue[0], funcValue[1]] : [funcValue, funcValue]; +// let hasValue = false; +// let ch = 2; +// while(ch--) { +// try { +// funcValue[ch] = +funcValue[ch]; +// } catch(err) { +// funcValue[ch] = NaN; +// } +// if(isDiagram) { +// if(!isNaN(funcValue[ch])) { +// this.outValue[ch] = this.getValues(funcValue[ch], ch); +// } else { +// this.lastByteValue[ch] = NaN; +// } +// hasValue = true; +// continue; +// } +// if(funcValue[ch] === this.lastFuncValue[ch]) { +// continue; +// } else if(!isNaN(funcValue[ch])) { +// this.outValue[ch] = this.getValues(funcValue[ch], ch); +// hasValue = true; +// } else if(!isNaN(this.lastFuncValue[ch])) { +// this.lastByteValue[ch] = NaN; +// hasValue = true; +// } +// } +// if(hasValue) { +// drawBuffer.push({ t: currentSample, value: [...this.lastByteValue] }); +// } +// byteSample += CT - this.lastTime; +// this.lastFuncValue = funcValue; +// this.lastTime = CT; +// } +// outputData[0][i] = this.outValue[0]; +// outputData[1][i] = this.outValue[1]; +// } +// if(Math.abs(byteSample) > Number.MAX_SAFE_INTEGER) { +// this.resetTime(); +// return true; +// } +// this.audioSample += chDataLen; +// let isSend = false; +// const data = {}; +// if(byteSample !== this.byteSample) { +// isSend = true; +// data.byteSample = this.byteSample = byteSample; +// } +// if(drawBuffer.length) { +// isSend = true; +// data.drawBuffer = drawBuffer; +// } +// if(isSend) { +// this.sendData(data); +// } +// return true; +// } +// receiveData(data) { +// if(data.byteSample !== undefined) { +// this.byteSample = +data.byteSample || 0; +// this.resetValues(); +// } + + +// if(data.playbackSpeed !== undefined) { +// const sampleRatio = this.sampleRatio / this.playbackSpeed; +// this.playbackSpeed = data.playbackSpeed; +// this.setSampleRatio(sampleRatio); +// } +// if(data.mode !== undefined) { +// this.isFuncbeat = data.mode === 'Funcbeat'; +// switch (data.mode) { +// case 'Bytebeat': +// this.getValues = (funcValue, ch) => (this.lastByteValue[ch] = funcValue & 255) / 127.5 - 1; +// break; +// case 'Signed Bytebeat': +// this.getValues = (funcValue, ch) => +// (this.lastByteValue[ch] = (funcValue + 128) & 255) / 127.5 - 1; +// break; +// case 'Floatbeat': +// case 'Funcbeat': +// this.getValues = (funcValue, ch) => { +// const limited = Math.max(Math.min(funcValue, 1), -1); +// this.lastByteValue[ch] = limited * 127.5 + 127.5 | 0 +// return limited; +// }; +// break; +// case 'Bitbeat': +// this.getValues = (funcValue, ch) => { +// this.lastByteValue[ch] = funcValue & 1 ? 255 : 0; +// return (funcValue & 1) - 0.5; +// }; +// break; +// case '2048': +// this.getValues = (funcValue, ch) => { +// this.lastByteValue[ch] = funcValue / 8 & 255; +// return (funcValue & 2047) / 1023.5 - 1 +// }; +// break; +// case 'logmode': +// this.getValues = (funcValue, ch) => (this.lastByteValue[ch] = (Math.log2(funcValue) * 32) & 255) / 127.5 - 1; +// break; +// case 'logHack': +// this.getValues = (funcValue, ch) => { +// const neg = (funcValue < 0) ? -32 : 32; +// return (this.lastByteValue[ch] = (Math.log2(Math.abs(funcValue)) * neg) & 255) / 127.5 - 1; +// }; +// break; +// case 'logHack2': +// this.getValues = (funcValue, ch) => { +// const neg = funcValue < 0 +// return funcValue == 0 ? 0 : ((this.lastByteValue[ch] = ((Math.log2(Math.abs(funcValue)) * (neg ? -16 : 16)) + (neg ? -127 : 128)) & 255) / 127.5 - 1); +// }; +// break; + +// default: this.getValues = (_funcValue) => NaN; +// } +// } +// if(data.setFunction !== undefined) { +// this.setFunction(data.setFunction); +// } +// if(data.resetTime === true) { +// this.resetTime(); +// } +// if(data.sampleRate !== undefined) { +// this.sampleRate = data.sampleRate; +// } +// if(data.sampleRatio !== undefined) { +// this.setSampleRatio(data.sampleRatio); +// } +// if(data.divisor !== undefined) { +// this.sampleDivisor = data.divisor; +// } +// if(data.DMode !== undefined) { +// this.soundMode = data.DMode; +// } +// if(data.drawMode !== undefined) { +// this.drawMode = data.drawMode; +// } +// } +// sendData(data) { +// this.port.postMessage(data); +// } +// resetTime() { +// this.byteSample = 0; +// this.resetValues(); +// this.sendData({ byteSample: 0 }); +// } +// resetValues() { +// this.audioSample = 0; +// this.lastTime = -1; +// this.outValue = [0, 0]; +// this.lastFuncValue = [null,null]; +// } +// setFunction(codeText) { +// const chyx = { +// /*bit*/ "bitC": function (x, y, z) { return x & y ? z : 0 }, +// /*bit reverse*/"br": function (x, size = 8) { +// if(size > 32) { throw new Error("br() Size cannot be greater than 32") } else { +// let result = 0; +// for (let idx = 0; idx < (size - 0); idx++) { +// result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1))) +// } +// return result +// } +// }, +// /*sin that loops every 128 "steps", instead of every pi steps*/"sinf": function (x) { return Math.sin(x / (128 / Math.PI)) }, +// /*cos that loops every 128 "steps", instead of every pi steps*/"cosf": function (x) { return Math.cos(x / (128 / Math.PI)) }, +// /*tan that loops every 128 "steps", instead of every pi steps*/"tanf": function (x) { return Math.tan(x / (128 / Math.PI)) }, +// /*converts t into a string composed of it's bits, regex's that*/"regG": function (t, X) { return X.test(t.toString(2)) } +// /*corrupt sound"crpt": function(x,y=8) {return chyx.br(chyx.br(x,y)+t,y)^chyx.br(t,y)}, +// decorrupt sound"decrpt": function(x,y=8) {return chyx.br(chyx.br(x^chyx.br(t,y),y)-t,y)},*/ +// } +// // Create shortened Math functions +// const params = Object.getOwnPropertyNames(Math); +// const values = params.map(k => Math[k]); +// const chyxNames = Object.getOwnPropertyNames(chyx); +// const chyxFuncs = chyxNames.map(k => chyx[k]); +// params.push('int', 'window', ...chyxNames); +// values.push(Math.floor, globalThis, ...chyxFuncs); +// // ByteBeatProcessor.deleteGlobals(); +// // Code testing +// let isCompiled = false; +// const oldFunc = this.func; + + +// if(this.isFuncbeat) { +// this.func = new Function(...params, codeText).bind(globalThis, ...values); +// } else { +// // Optimize code like eval(unescape(escape`XXXX`.replace(/u(..)/g,"$1%"))) +// codeText = codeText.trim().replace( +// /^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/, +// (match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%'))); +// this.func = new Function(...params, 't', '_micSample', `return 0,\n${ codeText || 0 };`) +// .bind(globalThis, ...values); +// } +// isCompiled = true; +// if(this.isFuncbeat) { +// this.func = this.func(); +// this.func(0, this.sampleRate, 0, [0, 0, 0]); +// } else { +// this.func(0, [0, 0, 0]); +// } + + +// this.sendData({ error: { message: '', isCompiled }, updateUrl: true }); +// } +// setSampleRatio(sampleRatio) { +// const timeOffset = Math.floor(this.sampleRatio * this.audioSample) - this.lastTime; +// this.sampleRatio = sampleRatio * this.playbackSpeed; +// this.lastTime = Math.floor(this.sampleRatio * this.audioSample) - timeOffset; +// } +// } + +// registerProcessor('byte-beat-processor', ByteBeatProcessor); \ No newline at end of file From 366637026b14d3f26fe058b6bf13c5654e57ab8b Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 27 May 2025 17:34:53 +0200 Subject: [PATCH 19/74] bbs --- packages/superdough/remove.html | 295 --------------------- packages/superdough/synth.mjs | 8 +- packages/superdough/worklets.mjs | 422 +------------------------------ 3 files changed, 17 insertions(+), 708 deletions(-) delete mode 100644 packages/superdough/remove.html diff --git a/packages/superdough/remove.html b/packages/superdough/remove.html deleted file mode 100644 index 59476043c..000000000 --- a/packages/superdough/remove.html +++ /dev/null @@ -1,295 +0,0 @@ -🌱 audio worklets -In the last post, we've been filling fixed length audio buffers. While this is very simple to implement, it's a bit annoying when the audio is always stopping. To calculate samples infinitely, we can use an AudioWorklet. Now we've arrived at a typical bytebeat/floatbeat editor: - -💡 hit ctrl+enter to update the code & ctrl+. to stop -((( // by stimmer (2011-10-03) -t*(4|t>>13&3)>>(~t>>11&1)&128|t*(t>>11&t>>13)*(~t>>9&3)&127 -) & 255) / 127.5 - 1)/4 - play | stop -The AudioWorklet is the lowest abstaction we get with the Web Audio API. It essentially gives us a function that fills a buffer of 128 samples over and over again, also running isolated from the rest of the page. At a sample rate of 44100/s, this means we fill a buffer every ~3ms (44100/128). - -Here are some examples to try: - -hello sine -slow sine (inaudible) -just intonation triad -hello saw -hello pulse -wandering sine -bytebeat 1 -bytebeat 2 -gladyouask - chirps -aks - TR-808-like hihat -show source (271 loc) - - - - 🌱 audio worklets - - - -

🌱 audio worklets

-

- In the last post, we've been filling - fixed length audio buffers. While this is very simple to implement, it's a - bit annoying when the audio is always stopping. To calculate samples - infinitely, we can use an AudioWorklet. Now we've arrived at a typical - bytebeat/floatbeat editor: -

-
- 💡 hit ctrl+enter to update the code & ctrl+. to stop -
- - play | stop - -

- The AudioWorklet is the lowest abstaction we get with the Web Audio API. - It essentially gives us a function that fills a buffer of 128 samples over - and over again, also running isolated from the rest of the page. At a - sample rate of 44100/s, this means we fill a buffer every ~3ms - (44100/128). -

-

Here are some examples to try:

- - -
- show page source -

-    
-

- back to garten.salat -

- - -back to garten.salat \ No newline at end of file diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 554bb1e9a..abadec311 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -147,8 +147,14 @@ export function registerSynthSounds() { registerSound( 'bytebeat', (begin, value, onended) => { + const defaultBeats = [ + '(t%255 >= t/255%255)*255', + '(t*(t*8%60 <= 300)|(-t)*(t*4%512 < 256))+t/400', + 't', + ] + const { n } = value + const { byteBeatExpression = defaultBeats[n % defaultBeats.length] } = value const ac = getAudioContext(); - let { byteBeatExpression } = value; let { duration } = value; const [attack, decay, sustain, release] = getADSRValues( diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 3d1bbccbe..282c9a717 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -762,28 +762,22 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor { registerProcessor('pulse-oscillator', PulseOscillatorProcessor); - -function frequencyToDigit(freq, baseFreq) { - const d = Math.log2(freq / baseFreq); - return Math.max(d, 0) + Math.pow(2, Math.min(d, 0)); -} - class ByteBeatProcessor extends AudioWorkletProcessor { constructor() { super(); this.codeText = '0'; this.port.onmessage = (event) => { - this.codeText = event.data.trim().replace( + this.codeText = event.data.trim().replace( /^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/, (match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%'))); - + }; - this.virtualRate = 32000; // target sample rate + this.virtualRate = 112600; // target sample rate // this.nativeRate = sampleRate; // actual context rate, e.g., 48000 // this.ratio = this.nativeRate / this.virtualRate; this.t = null; - this.framebuffer = new Float32Array(Math.floor(sampleRate/60)); + this.framebuffer = new Float32Array(Math.floor(sampleRate / 60)); this.func = null; } @@ -829,64 +823,19 @@ class ByteBeatProcessor extends AudioWorkletProcessor { this.t = params.begin[0] * this.virtualRate } - let codeText = this.codeText; - - // const f = Function('t', 'return ' + codeText); - - - // const chyx = { - // /*bit*/ "bitC": function (x, y, z) { return x & y ? z : 0 }, - // /*bit reverse*/"br": function (x, size = 8) { - // if(size > 32) { throw new Error("br() Size cannot be greater than 32") } else { - // let result = 0; - // for (let idx = 0; idx < (size - 0); idx++) { - // result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1))) - // } - // return result - // } - // }, - // /*sin that loops every 128 "steps", instead of every pi steps*/"sinf": function (x) { return Math.sin(x / (128 / Math.PI)) }, - // /*cos that loops every 128 "steps", instead of every pi steps*/"cosf": function (x) { return Math.cos(x / (128 / Math.PI)) }, - // /*tan that loops every 128 "steps", instead of every pi steps*/"tanf": function (x) { return Math.tan(x / (128 / Math.PI)) }, - // /*converts t into a string composed of it's bits, regex's that*/"regG": function (t, X) { return X.test(t.toString(2)) } - // /*corrupt sound"crpt": function(x,y=8) {return chyx.br(chyx.br(x,y)+t,y)^chyx.br(t,y)}, - // decorrupt sound"decrpt": function(x,y=8) {return chyx.br(chyx.br(x^chyx.br(t,y),y)-t,y)},*/ - // } - // // Create shortened Math functions - // const mathParams = Object.getOwnPropertyNames(Math); - // const values = mathParams.map(k => Math[k]); - // const chyxNames = Object.getOwnPropertyNames(chyx); - // const chyxFuncs = chyxNames.map(k => chyx[k]); - // mathParams.push('int', 'window', ...chyxNames); - // values.push(Math.floor, globalThis, ...chyxFuncs); - - - - - // Optimize code like eval(unescape(escape`XXXX`.replace(/u(..)/g,"$1%"))) - // this.func = new Function(...mathParams, 't', `return 0,\n${ codeText || 0 };`).bind(globalThis, ...values); - this.func = Function('t', 'return ' + codeText); - - + this.func = Function('t', 'return ' + codeText); const output = outputs[0]; - const ct = params.begin[0] const tIncrement = this.virtualRate / sampleRate - - // console.info(ct, currentTime) + for (let i = 0; i < output[0].length; i++) { let t = Math.floor(this.t) const detune = getParamValue(i, params.detune); const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100); - // t = t * (Math.log(freq / 440)) / Math.LN2; - t = t * frequencyToDigit(freq, 440) - // console.info(t) - // let t = (currentTime * sampleRate) + i - // let t = this.t - - // const index = Math.floor(this.t) + t = t / (this.virtualRate / 256) * freq + const funcValue = this.func(t) let signal = (funcValue & 255) / 127.5 - 1 const out = signal; @@ -894,363 +843,12 @@ class ByteBeatProcessor extends AudioWorkletProcessor { output[c][i] = out; } this.t = this.t + tIncrement - // if (i % 8 === 0){ - // this.t = this.t + tIncrement - - // } - // this.framebuffer[this.t%this.framebuffer.length] = out; - // if(this.t % this.framebuffer.length === 0 && this.t > 0) { - // this.port.postMessage(this.framebuffer) - // } - } - - // for (let i = 0; i < (output[0].length ?? 0); i++) { - // let t = currentTime; - // t *= sampleRate; - // const signal = this.func(t) - // // const signal = ((this.func(t) + 128) & 255) / 127.5 - 1 - // // const signal = ((this.func(t) & 255) / 127.5 - 1) / 4; - // for (let o = 0; o < output.length; o++) { - // // Combination of both oscillators with envelope applied - // output[o][i] = signal * .4; - // } - // } + } + return true; // keep the audio processing going } } registerProcessor('byte-beat-processor', ByteBeatProcessor); - - - -// class ByteBeatProcessor extends AudioWorkletProcessor { -// constructor(...args) { -// super(...args); -// this.audioSample = 0; -// this.byteSample = 0; -// // this.drawMode = 'Points'; -// // this.errorDisplayed = true; -// this.func = null; -// this.getValues = null; -// this.isFuncbeat = false; -// this.isPlaying = true; -// this.playbackSpeed = 1; -// this.divisorStorage = 0; -// this.lastTime = -1; -// this.lastFuncValue = [null, null]; -// this.lastByteValue = [0, 0]; -// this.outValue = [0, 0]; -// // this.sampleRate = 8000; -// // this.sampleRatio = 1; -// this.sampleDivisor/*PRO*/ = 1; -// this.soundMode = 'Bytebeat'; -// // Object.seal(this); -// // ByteBeatProcessor.deleteGlobals(); -// // ByteBeatProcessor.freezeGlobals(); -// this.port.addEventListener('message', e => this.receiveData(e.data)); -// this.port.start(); -// } - -// static get parameterDescriptors() { -// return [ -// { -// name: 'begin', -// defaultValue: 0, -// max: Number.POSITIVE_INFINITY, -// min: 0, -// }, - -// { -// name: 'end', -// defaultValue: 0, -// max: Number.POSITIVE_INFINITY, -// min: 0, -// }, -// ]; -// } -// // static deleteGlobals() { -// // // Delete single letter variables to prevent persistent variable errors (covers a good enough range) -// // for(let i = 0; i < 26; ++i) { -// // delete globalThis[String.fromCharCode(65 + i)]; -// // delete globalThis[String.fromCharCode(97 + i)]; -// // } -// // // Delete global variables -// // for(const name in globalThis) { -// // if(Object.prototype.hasOwnProperty.call(globalThis, name)) { -// // delete globalThis[name]; -// // } -// // } -// // } -// // static freezeGlobals() { -// // Object.getOwnPropertyNames(globalThis).forEach(name => { -// // const prop = globalThis[name]; -// // const type = typeof prop; -// // if((type === 'object' || type === 'function') && name !== 'globalThis') { -// // Object.freeze(prop); -// // } -// // if(type === 'function' && Object.prototype.hasOwnProperty.call(prop, 'prototype')) { -// // Object.freeze(prop.prototype); -// // } -// // Object.defineProperty(globalThis, name, { writable: false, configurable: false }); -// // }); -// // } - -// process(inputs, [outputData], params) { -// if (currentTime <= params.begin[0]) { -// return true; -// } -// if (currentTime >= params.end[0]) { -// return false; -// } - - - -// const chDataLen = outputData[0].length; -// if(!chDataLen || !this.isPlaying) { -// return true; -// } -// let time = currentTime; -// let { byteSample } = this; -// const drawBuffer = []; -// const isDiagram = this.drawMode === 'Combined' || this.drawMode === 'Diagram' || this.drawMode === 'Spectrogram'; - -// for(let i = 0; i < chDataLen; ++i) { -// time += this.sampleRatio; -// const CT = Math.floor(time); -// if(this.lastTime !== CT) { -// let funcValue; -// const currentSample = Math.floor(byteSample); - -// // long cascade of null handlers -// const inputs0 = inputs[0] ?? [ ]; -// const inputs00 = inputs0[0] ?? [ ]; -// const inputs01 = inputs0[1] ?? inputs00; -// const inputs00i = inputs00[i] ?? 0; -// const inputs01i = inputs01[i] ?? 0; -// const micSample = [inputs00i, inputs01i, inputs00i / 2 + inputs01i / 2]; - -// if(this.isFuncbeat) { -// funcValue = this.func(currentSample / this.sampleRate, this.sampleRate, -// currentSample, micSample); -// } else { -// funcValue = this.func(currentSample, micSample); -// } - -// funcValue = Array.isArray(funcValue) ? [funcValue[0], funcValue[1]] : [funcValue, funcValue]; -// let hasValue = false; -// let ch = 2; -// while(ch--) { -// try { -// funcValue[ch] = +funcValue[ch]; -// } catch(err) { -// funcValue[ch] = NaN; -// } -// if(isDiagram) { -// if(!isNaN(funcValue[ch])) { -// this.outValue[ch] = this.getValues(funcValue[ch], ch); -// } else { -// this.lastByteValue[ch] = NaN; -// } -// hasValue = true; -// continue; -// } -// if(funcValue[ch] === this.lastFuncValue[ch]) { -// continue; -// } else if(!isNaN(funcValue[ch])) { -// this.outValue[ch] = this.getValues(funcValue[ch], ch); -// hasValue = true; -// } else if(!isNaN(this.lastFuncValue[ch])) { -// this.lastByteValue[ch] = NaN; -// hasValue = true; -// } -// } -// if(hasValue) { -// drawBuffer.push({ t: currentSample, value: [...this.lastByteValue] }); -// } -// byteSample += CT - this.lastTime; -// this.lastFuncValue = funcValue; -// this.lastTime = CT; -// } -// outputData[0][i] = this.outValue[0]; -// outputData[1][i] = this.outValue[1]; -// } -// if(Math.abs(byteSample) > Number.MAX_SAFE_INTEGER) { -// this.resetTime(); -// return true; -// } -// this.audioSample += chDataLen; -// let isSend = false; -// const data = {}; -// if(byteSample !== this.byteSample) { -// isSend = true; -// data.byteSample = this.byteSample = byteSample; -// } -// if(drawBuffer.length) { -// isSend = true; -// data.drawBuffer = drawBuffer; -// } -// if(isSend) { -// this.sendData(data); -// } -// return true; -// } -// receiveData(data) { -// if(data.byteSample !== undefined) { -// this.byteSample = +data.byteSample || 0; -// this.resetValues(); -// } - - -// if(data.playbackSpeed !== undefined) { -// const sampleRatio = this.sampleRatio / this.playbackSpeed; -// this.playbackSpeed = data.playbackSpeed; -// this.setSampleRatio(sampleRatio); -// } -// if(data.mode !== undefined) { -// this.isFuncbeat = data.mode === 'Funcbeat'; -// switch (data.mode) { -// case 'Bytebeat': -// this.getValues = (funcValue, ch) => (this.lastByteValue[ch] = funcValue & 255) / 127.5 - 1; -// break; -// case 'Signed Bytebeat': -// this.getValues = (funcValue, ch) => -// (this.lastByteValue[ch] = (funcValue + 128) & 255) / 127.5 - 1; -// break; -// case 'Floatbeat': -// case 'Funcbeat': -// this.getValues = (funcValue, ch) => { -// const limited = Math.max(Math.min(funcValue, 1), -1); -// this.lastByteValue[ch] = limited * 127.5 + 127.5 | 0 -// return limited; -// }; -// break; -// case 'Bitbeat': -// this.getValues = (funcValue, ch) => { -// this.lastByteValue[ch] = funcValue & 1 ? 255 : 0; -// return (funcValue & 1) - 0.5; -// }; -// break; -// case '2048': -// this.getValues = (funcValue, ch) => { -// this.lastByteValue[ch] = funcValue / 8 & 255; -// return (funcValue & 2047) / 1023.5 - 1 -// }; -// break; -// case 'logmode': -// this.getValues = (funcValue, ch) => (this.lastByteValue[ch] = (Math.log2(funcValue) * 32) & 255) / 127.5 - 1; -// break; -// case 'logHack': -// this.getValues = (funcValue, ch) => { -// const neg = (funcValue < 0) ? -32 : 32; -// return (this.lastByteValue[ch] = (Math.log2(Math.abs(funcValue)) * neg) & 255) / 127.5 - 1; -// }; -// break; -// case 'logHack2': -// this.getValues = (funcValue, ch) => { -// const neg = funcValue < 0 -// return funcValue == 0 ? 0 : ((this.lastByteValue[ch] = ((Math.log2(Math.abs(funcValue)) * (neg ? -16 : 16)) + (neg ? -127 : 128)) & 255) / 127.5 - 1); -// }; -// break; - -// default: this.getValues = (_funcValue) => NaN; -// } -// } -// if(data.setFunction !== undefined) { -// this.setFunction(data.setFunction); -// } -// if(data.resetTime === true) { -// this.resetTime(); -// } -// if(data.sampleRate !== undefined) { -// this.sampleRate = data.sampleRate; -// } -// if(data.sampleRatio !== undefined) { -// this.setSampleRatio(data.sampleRatio); -// } -// if(data.divisor !== undefined) { -// this.sampleDivisor = data.divisor; -// } -// if(data.DMode !== undefined) { -// this.soundMode = data.DMode; -// } -// if(data.drawMode !== undefined) { -// this.drawMode = data.drawMode; -// } -// } -// sendData(data) { -// this.port.postMessage(data); -// } -// resetTime() { -// this.byteSample = 0; -// this.resetValues(); -// this.sendData({ byteSample: 0 }); -// } -// resetValues() { -// this.audioSample = 0; -// this.lastTime = -1; -// this.outValue = [0, 0]; -// this.lastFuncValue = [null,null]; -// } -// setFunction(codeText) { -// const chyx = { -// /*bit*/ "bitC": function (x, y, z) { return x & y ? z : 0 }, -// /*bit reverse*/"br": function (x, size = 8) { -// if(size > 32) { throw new Error("br() Size cannot be greater than 32") } else { -// let result = 0; -// for (let idx = 0; idx < (size - 0); idx++) { -// result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1))) -// } -// return result -// } -// }, -// /*sin that loops every 128 "steps", instead of every pi steps*/"sinf": function (x) { return Math.sin(x / (128 / Math.PI)) }, -// /*cos that loops every 128 "steps", instead of every pi steps*/"cosf": function (x) { return Math.cos(x / (128 / Math.PI)) }, -// /*tan that loops every 128 "steps", instead of every pi steps*/"tanf": function (x) { return Math.tan(x / (128 / Math.PI)) }, -// /*converts t into a string composed of it's bits, regex's that*/"regG": function (t, X) { return X.test(t.toString(2)) } -// /*corrupt sound"crpt": function(x,y=8) {return chyx.br(chyx.br(x,y)+t,y)^chyx.br(t,y)}, -// decorrupt sound"decrpt": function(x,y=8) {return chyx.br(chyx.br(x^chyx.br(t,y),y)-t,y)},*/ -// } -// // Create shortened Math functions -// const params = Object.getOwnPropertyNames(Math); -// const values = params.map(k => Math[k]); -// const chyxNames = Object.getOwnPropertyNames(chyx); -// const chyxFuncs = chyxNames.map(k => chyx[k]); -// params.push('int', 'window', ...chyxNames); -// values.push(Math.floor, globalThis, ...chyxFuncs); -// // ByteBeatProcessor.deleteGlobals(); -// // Code testing -// let isCompiled = false; -// const oldFunc = this.func; - - -// if(this.isFuncbeat) { -// this.func = new Function(...params, codeText).bind(globalThis, ...values); -// } else { -// // Optimize code like eval(unescape(escape`XXXX`.replace(/u(..)/g,"$1%"))) -// codeText = codeText.trim().replace( -// /^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/, -// (match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%'))); -// this.func = new Function(...params, 't', '_micSample', `return 0,\n${ codeText || 0 };`) -// .bind(globalThis, ...values); -// } -// isCompiled = true; -// if(this.isFuncbeat) { -// this.func = this.func(); -// this.func(0, this.sampleRate, 0, [0, 0, 0]); -// } else { -// this.func(0, [0, 0, 0]); -// } - - -// this.sendData({ error: { message: '', isCompiled }, updateUrl: true }); -// } -// setSampleRatio(sampleRatio) { -// const timeOffset = Math.floor(this.sampleRatio * this.audioSample) - this.lastTime; -// this.sampleRatio = sampleRatio * this.playbackSpeed; -// this.lastTime = Math.floor(this.sampleRatio * this.audioSample) - timeOffset; -// } -// } - -// registerProcessor('byte-beat-processor', ByteBeatProcessor); \ No newline at end of file From 63f4f202f16730b57a6ec4e3ed735fb067ba114a Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 27 May 2025 17:35:35 +0200 Subject: [PATCH 20/74] working --- packages/superdough/superdough.mjs | 2 +- packages/superdough/synth.mjs | 10 +++------- packages/superdough/worklets.mjs | 28 +++++++++++++--------------- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 07ab08054..79c9093af 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -580,7 +580,7 @@ export const superdough = async (value, t, hapDuration, cps) => { // get source AudioNode let sourceNode; if (source) { - sourceNode = source(t, value, hapDuration,cps); + sourceNode = source(t, value, hapDuration, cps); } else if (getSound(s)) { const { onTrigger } = getSound(s); const onEnded = () => { diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index abadec311..1bd2ff8fc 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -147,13 +147,9 @@ export function registerSynthSounds() { registerSound( 'bytebeat', (begin, value, onended) => { - const defaultBeats = [ - '(t%255 >= t/255%255)*255', - '(t*(t*8%60 <= 300)|(-t)*(t*4%512 < 256))+t/400', - 't', - ] - const { n } = value - const { byteBeatExpression = defaultBeats[n % defaultBeats.length] } = value + const defaultBeats = ['(t%255 >= t/255%255)*255', '(t*(t*8%60 <= 300)|(-t)*(t*4%512 < 256))+t/400', 't']; + const { n } = value; + const { byteBeatExpression = defaultBeats[n % defaultBeats.length] } = value; const ac = getAudioContext(); let { duration } = value; diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 282c9a717..9c77857e2 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -767,11 +767,12 @@ class ByteBeatProcessor extends AudioWorkletProcessor { super(); this.codeText = '0'; this.port.onmessage = (event) => { - - this.codeText = event.data.trim().replace( - /^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/, - (match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%'))); - + this.codeText = event.data + .trim() + .replace( + /^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/, + (match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%')), + ); }; this.virtualRate = 112600; // target sample rate // this.nativeRate = sampleRate; // actual context rate, e.g., 48000 @@ -820,33 +821,30 @@ class ByteBeatProcessor extends AudioWorkletProcessor { return false; } if (this.t == null) { - this.t = params.begin[0] * this.virtualRate + this.t = params.begin[0] * this.virtualRate; } let codeText = this.codeText; this.func = Function('t', 'return ' + codeText); const output = outputs[0]; - const tIncrement = this.virtualRate / sampleRate - + const tIncrement = this.virtualRate / sampleRate; for (let i = 0; i < output[0].length; i++) { - let t = Math.floor(this.t) + let t = Math.floor(this.t); const detune = getParamValue(i, params.detune); const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100); - t = t / (this.virtualRate / 256) * freq + t = (t / (this.virtualRate / 256)) * freq; - const funcValue = this.func(t) - let signal = (funcValue & 255) / 127.5 - 1 + const funcValue = this.func(t); + let signal = (funcValue & 255) / 127.5 - 1; const out = signal; for (let c = 0; c < output.length; c++) { output[c][i] = out; } - this.t = this.t + tIncrement - + this.t = this.t + tIncrement; } - return true; // keep the audio processing going } } From 382dce65dff05128b5e847e95104c90ccd9d3da6 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 27 May 2025 17:39:55 +0200 Subject: [PATCH 21/74] working --- packages/superdough/worklets.mjs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 9c77857e2..b1cd1cb23 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -775,8 +775,6 @@ class ByteBeatProcessor extends AudioWorkletProcessor { ); }; this.virtualRate = 112600; // target sample rate - // this.nativeRate = sampleRate; // actual context rate, e.g., 48000 - // this.ratio = this.nativeRate / this.virtualRate; this.t = null; this.framebuffer = new Float32Array(Math.floor(sampleRate / 60)); this.func = null; @@ -838,7 +836,7 @@ class ByteBeatProcessor extends AudioWorkletProcessor { const funcValue = this.func(t); let signal = (funcValue & 255) / 127.5 - 1; - const out = signal; + const out = signal * 0.2; for (let c = 0; c < output.length; c++) { output[c][i] = out; } From dde2b9c6f8bbb5059abb86bec75ead6cfbd8ee45 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 27 May 2025 17:41:32 +0200 Subject: [PATCH 22/74] working --- .vscode/launch.json | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 5bd1b92fe..000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - - { - "type": "chrome", - "request": "launch", - "name": "Launch Chrome against localhost", - "url": "http://localhost:4321", - "webRoot": "${workspaceFolder}" - } - ] -} \ No newline at end of file From 1e3f546918a742806c2e0f8068d8fd2a16cdf46a Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 28 May 2025 16:36:53 +0200 Subject: [PATCH 23/74] bb waveforms --- packages/superdough/synth.mjs | 20 ++++++++++-- packages/superdough/worklets.mjs | 53 +++++++++++++++++++++++++++----- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 1bd2ff8fc..92163ab16 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -147,8 +147,24 @@ export function registerSynthSounds() { registerSound( 'bytebeat', (begin, value, onended) => { - const defaultBeats = ['(t%255 >= t/255%255)*255', '(t*(t*8%60 <= 300)|(-t)*(t*4%512 < 256))+t/400', 't']; - const { n } = value; + const defaultBeats = [ + '(t%255 >= t/255%255)*255', + '(t*(t*8%60 <= 300)|(-t)*(t*4%512 < 256))+t/400', + 't', + 't*(t >> 10^t)', + 't&128', + 't&t>>8', + '((t%255+t%128+t%64+t%32+t%16+t%127.8+t%64.8+t%32.8+t%16.8)/3)', + '((t%64+t%63.8+t%64.15+t%64.35+t%63.5)/1.25)', + '(t&(t>>7)-t)', + '(sin(t*PI/128)*127+127)', + '((t^t/2+t+64*(sin((t*PI/64)+(t*PI/32768))+64))%128*2)', + '((t^t/2+t+64*(cos >> 0))%127.85*2)', + '((t^t/2+t+64)%128*2)', + '(((t * .25)^(t * .25)/100+(t * .25))%128)*2', + '((t^t/2+t+64)%7 * 24)', + ]; + const { n = 0 } = value; const { byteBeatExpression = defaultBeats[n % defaultBeats.length] } = value; const ac = getAudioContext(); diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index b1cd1cb23..f163476ee 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -767,16 +767,56 @@ class ByteBeatProcessor extends AudioWorkletProcessor { super(); this.codeText = '0'; this.port.onmessage = (event) => { + const chyx = { + /*bit*/ bitC: function (x, y, z) { + return x & y ? z : 0; + }, + /*bit reverse*/ br: function (x, size = 8) { + if (size > 32) { + throw new Error('br() Size cannot be greater than 32'); + } else { + let result = 0; + for (let idx = 0; idx < size - 0; idx++) { + result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1))); + } + return result; + } + }, + /*sin that loops every 128 "steps", instead of every pi steps*/ sinf: function (x) { + return Math.sin(x / (128 / Math.PI)); + }, + /*cos that loops every 128 "steps", instead of every pi steps*/ cosf: function (x) { + return Math.cos(x / (128 / Math.PI)); + }, + /*tan that loops every 128 "steps", instead of every pi steps*/ tanf: function (x) { + return Math.tan(x / (128 / Math.PI)); + }, + /*converts t into a string composed of it's bits, regex's that*/ regG: function (t, X) { + return X.test(t.toString(2)); + }, + /*corrupt sound"crpt": function(x,y=8) {return chyx.br(chyx.br(x,y)+t,y)^chyx.br(t,y)}, + decorrupt sound"decrpt": function(x,y=8) {return chyx.br(chyx.br(x^chyx.br(t,y),y)-t,y)},*/ + }; + // Create shortened Math functions + const mathParams = Object.getOwnPropertyNames(Math); + const values = mathParams.map((k) => Math[k]); + const chyxNames = Object.getOwnPropertyNames(chyx); + const chyxFuncs = chyxNames.map((k) => chyx[k]); + mathParams.push('int', 'window', ...chyxNames); + values.push(Math.floor, globalThis, ...chyxFuncs); + + //Optimization pulled from dollchan.net, it seemed important + //Optimize code like eval(unescape(escape`XXXX`.replace(/u(..)/g,"$1%"))) this.codeText = event.data .trim() .replace( /^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/, (match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%')), ); + + this.func = new Function(...mathParams, 't', `return 0,\n${this.codeText || 0};`).bind(globalThis, ...values); }; - this.virtualRate = 112600; // target sample rate this.t = null; - this.framebuffer = new Float32Array(Math.floor(sampleRate / 60)); this.func = null; } @@ -819,20 +859,19 @@ class ByteBeatProcessor extends AudioWorkletProcessor { return false; } if (this.t == null) { - this.t = params.begin[0] * this.virtualRate; + this.t = params.begin[0] * sampleRate; } let codeText = this.codeText; - this.func = Function('t', 'return ' + codeText); + // this.func = Function('t', 'return ' + codeText); const output = outputs[0]; - const tIncrement = this.virtualRate / sampleRate; for (let i = 0; i < output[0].length; i++) { let t = Math.floor(this.t); const detune = getParamValue(i, params.detune); const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100); - t = (t / (this.virtualRate / 256)) * freq; + t = (t / (sampleRate / 256)) * freq; const funcValue = this.func(t); let signal = (funcValue & 255) / 127.5 - 1; @@ -840,7 +879,7 @@ class ByteBeatProcessor extends AudioWorkletProcessor { for (let c = 0; c < output.length; c++) { output[c][i] = out; } - this.t = this.t + tIncrement; + this.t = this.t + 1; } return true; // keep the audio processing going From 0287be547a1a4d798b1f9c2d9fa2af6fe6ef7771 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Thu, 29 May 2025 02:27:55 +0200 Subject: [PATCH 24/74] bbstart --- packages/core/controls.mjs | 15 +++- packages/superdough/superdough.mjs | 1 - packages/superdough/synth.mjs | 7 +- packages/superdough/worklets.mjs | 107 +++++++++++++++-------------- 4 files changed, 75 insertions(+), 55 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 5e0c450d6..3fa4b0507 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -451,13 +451,26 @@ export const { drive } = registerControl('drive'); * @name byteBeatExpression * @synonyms bbexpr * - * @param {number | Pattern} byteBeatExpression pattern the output channels + * @param {number | Pattern} byteBeatExpression bitwise expression for creating bytebeat * @example * s("bytebeat").bbexpr('t*(t>>15^t>>66)') * */ export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr'); +/** + * Create byte beats with custom expressions + * + * @name byteBeatStartTime + * @synonyms bbst + * + * @param {number | Pattern} byteBeatStartTime in seconds + * @example + * note("{c g a b c d}%16").s("bytebeat").bbexpr('t&t>>8').bbst("<0 300>") + * + */ +export const { byteBeatStartTime, bbst } = registerControl('byteBeatStartTime', 'bbst'); + /** * Allows you to set the output channels on the interface * diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 79c9093af..1069d4e84 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -476,7 +476,6 @@ export const superdough = async (value, t, hapDuration, cps) => { let { s = getDefaultValue('s'), bank, - byteBeatExpression, source, gain = getDefaultValue('gain'), postgain = getDefaultValue('postgain'), diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 92163ab16..670ef44bb 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -165,7 +165,9 @@ export function registerSynthSounds() { '((t^t/2+t+64)%7 * 24)', ]; const { n = 0 } = value; - const { byteBeatExpression = defaultBeats[n % defaultBeats.length] } = value; + const frequency = getFrequencyFromValue(value); + const { byteBeatExpression = defaultBeats[n % defaultBeats.length], byteBeatStartTime } = value; + const ac = getAudioContext(); let { duration } = value; @@ -176,7 +178,6 @@ export function registerSynthSounds() { ); const holdend = begin + duration; const end = holdend + release + 0.01; - const frequency = getFrequencyFromValue(value); let o = getWorklet( ac, @@ -191,7 +192,7 @@ export function registerSynthSounds() { }, ); - o.port.postMessage(byteBeatExpression); + o.port.postMessage({ codeText: byteBeatExpression, startTimeSeconds: byteBeatStartTime, frequency }); let envGain = gainNode(1); envGain = o.connect(envGain); diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index f163476ee..ec1f41e7b 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -762,59 +762,72 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor { registerProcessor('pulse-oscillator', PulseOscillatorProcessor); +/** BYTE BEATS */ +const chyx = { + /*bit*/ bitC: function (x, y, z) { + return x & y ? z : 0; + }, + /*bit reverse*/ br: function (x, size = 8) { + if (size > 32) { + throw new Error('br() Size cannot be greater than 32'); + } else { + let result = 0; + for (let idx = 0; idx < size - 0; idx++) { + result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1))); + } + return result; + } + }, + /*sin that loops every 128 "steps", instead of every pi steps*/ sinf: function (x) { + return Math.sin(x / (128 / Math.PI)); + }, + /*cos that loops every 128 "steps", instead of every pi steps*/ cosf: function (x) { + return Math.cos(x / (128 / Math.PI)); + }, + /*tan that loops every 128 "steps", instead of every pi steps*/ tanf: function (x) { + return Math.tan(x / (128 / Math.PI)); + }, + /*converts t into a string composed of it's bits, regex's that*/ regG: function (t, X) { + return X.test(t.toString(2)); + }, +}; + +// Create shortened Math functions +let mathParams, byteBeatHelperFuncs; +function getByteBeatFunc(codetext) { + if ((mathParams || byteBeatHelperFuncs) == null) { + mathParams = Object.getOwnPropertyNames(Math); + byteBeatHelperFuncs = mathParams.map((k) => Math[k]); + const chyxNames = Object.getOwnPropertyNames(chyx); + const chyxFuncs = chyxNames.map((k) => chyx[k]); + mathParams.push('int', 'window', ...chyxNames); + byteBeatHelperFuncs.push(Math.floor, globalThis, ...chyxFuncs); + } + return new Function(...mathParams, 't', `return 0,\n${codetext || 0};`).bind(globalThis, ...byteBeatHelperFuncs); +} + class ByteBeatProcessor extends AudioWorkletProcessor { constructor() { super(); - this.codeText = '0'; this.port.onmessage = (event) => { - const chyx = { - /*bit*/ bitC: function (x, y, z) { - return x & y ? z : 0; - }, - /*bit reverse*/ br: function (x, size = 8) { - if (size > 32) { - throw new Error('br() Size cannot be greater than 32'); - } else { - let result = 0; - for (let idx = 0; idx < size - 0; idx++) { - result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1))); - } - return result; - } - }, - /*sin that loops every 128 "steps", instead of every pi steps*/ sinf: function (x) { - return Math.sin(x / (128 / Math.PI)); - }, - /*cos that loops every 128 "steps", instead of every pi steps*/ cosf: function (x) { - return Math.cos(x / (128 / Math.PI)); - }, - /*tan that loops every 128 "steps", instead of every pi steps*/ tanf: function (x) { - return Math.tan(x / (128 / Math.PI)); - }, - /*converts t into a string composed of it's bits, regex's that*/ regG: function (t, X) { - return X.test(t.toString(2)); - }, - /*corrupt sound"crpt": function(x,y=8) {return chyx.br(chyx.br(x,y)+t,y)^chyx.br(t,y)}, - decorrupt sound"decrpt": function(x,y=8) {return chyx.br(chyx.br(x^chyx.br(t,y),y)-t,y)},*/ - }; - // Create shortened Math functions - const mathParams = Object.getOwnPropertyNames(Math); - const values = mathParams.map((k) => Math[k]); - const chyxNames = Object.getOwnPropertyNames(chyx); - const chyxFuncs = chyxNames.map((k) => chyx[k]); - mathParams.push('int', 'window', ...chyxNames); - values.push(Math.floor, globalThis, ...chyxFuncs); + let { codeText } = event.data; + const { startTimeSeconds, frequency } = event.data; + if (startTimeSeconds != null) { + const t = startTimeSeconds * sampleRate; + this.t = t; + this.t = (t / (sampleRate / 256)) * frequency; + } - //Optimization pulled from dollchan.net, it seemed important + //Optimization pulled from dollchan.net: https://github.com/Chasyxx/EnBeat_NEW, it seemed important //Optimize code like eval(unescape(escape`XXXX`.replace(/u(..)/g,"$1%"))) - this.codeText = event.data + codeText = codeText .trim() .replace( /^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/, (match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%')), ); - this.func = new Function(...mathParams, 't', `return 0,\n${this.codeText || 0};`).bind(globalThis, ...values); + this.func = getByteBeatFunc(codeText); }; this.t = null; this.func = null; @@ -861,23 +874,17 @@ class ByteBeatProcessor extends AudioWorkletProcessor { if (this.t == null) { this.t = params.begin[0] * sampleRate; } - - let codeText = this.codeText; - // this.func = Function('t', 'return ' + codeText); const output = outputs[0]; - for (let i = 0; i < output[0].length; i++) { - let t = Math.floor(this.t); const detune = getParamValue(i, params.detune); const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100); - - t = (t / (sampleRate / 256)) * freq; - + let t = (this.t / (sampleRate / 256)) * freq; const funcValue = this.func(t); let signal = (funcValue & 255) / 127.5 - 1; const out = signal * 0.2; + for (let c = 0; c < output.length; c++) { - output[c][i] = out; + output[c][i] = clamp(out, -0.2, 0.2); } this.t = this.t + 1; } From a4b55776d22ec89ceaffef4e2293146f79f14cd9 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Thu, 29 May 2025 02:33:06 +0200 Subject: [PATCH 25/74] test --- test/__snapshots__/examples.test.mjs.snap | 69 +++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index b9cdb444b..f886987ab 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -1525,6 +1525,75 @@ exports[`runs examples > example "byteBeatExpression" example index 0 1`] = ` ] `; +exports[`runs examples > example "byteBeatStartTime" example index 0 1`] = ` +[ + "[ 0/1 → 1/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 1/16 → 1/8 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 1/8 → 3/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 3/16 → 1/4 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 1/4 → 5/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 5/16 → 3/8 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 3/8 → 7/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 7/16 → 1/2 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 1/2 → 9/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 9/16 → 5/8 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 5/8 → 11/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 11/16 → 3/4 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 3/4 → 13/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 13/16 → 7/8 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 7/8 → 15/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 15/16 → 1/1 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 1/1 → 17/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 17/16 → 9/8 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 9/8 → 19/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 19/16 → 5/4 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 5/4 → 21/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 21/16 → 11/8 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 11/8 → 23/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 23/16 → 3/2 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 3/2 → 25/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 25/16 → 13/8 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 13/8 → 27/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 27/16 → 7/4 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 7/4 → 29/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 29/16 → 15/8 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 15/8 → 31/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 31/16 → 2/1 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 2/1 → 33/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 33/16 → 17/8 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 17/8 → 35/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 35/16 → 9/4 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 9/4 → 37/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 37/16 → 19/8 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 19/8 → 39/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 39/16 → 5/2 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 5/2 → 41/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 41/16 → 21/8 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 21/8 → 43/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 43/16 → 11/4 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 11/4 → 45/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 45/16 → 23/8 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 23/8 → 47/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 47/16 → 3/1 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", + "[ 3/1 → 49/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 49/16 → 25/8 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 25/8 → 51/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 51/16 → 13/4 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 13/4 → 53/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 53/16 → 27/8 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 27/8 → 55/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 55/16 → 7/2 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 7/2 → 57/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 57/16 → 29/8 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 29/8 → 59/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 59/16 → 15/4 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 15/4 → 61/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 61/16 → 31/8 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 31/8 → 63/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 63/16 → 4/1 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", +] +`; + exports[`runs examples > example "cat" example index 0 1`] = ` [ "[ 0/1 → 1/1 | note:e5 ]", From 0b0f9fe13be8613ea38b0a75edbfb87a751ffdcb Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Thu, 29 May 2025 12:14:18 +0200 Subject: [PATCH 26/74] preserve stepcount in chunks (#1358) * preserve stepcount in chunks * format --- packages/core/pattern.mjs | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 6e294129e..56a6b77d1 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -2441,9 +2441,14 @@ const _chunk = function (n, func, pat, back = false, fast = false) { return pat.when(binary_pat, func); }; -export const { chunk, slowchunk, slowChunk } = register(['chunk', 'slowchunk', 'slowChunk'], function (n, func, pat) { - return _chunk(n, func, pat, false, false); -}); +export const { chunk, slowchunk, slowChunk } = register( + ['chunk', 'slowchunk', 'slowChunk'], + function (n, func, pat) { + return _chunk(n, func, pat, false, false); + }, + true, + true, +); /** * Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles @@ -2455,9 +2460,14 @@ export const { chunk, slowchunk, slowChunk } = register(['chunk', 'slowchunk', ' * "0 1 2 3".chunkBack(4, x=>x.add(7)) * .scale("A:minor").note() */ -export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], function (n, func, pat) { - return _chunk(n, func, pat, true); -}); +export const { chunkBack, chunkback } = register( + ['chunkBack', 'chunkback'], + function (n, func, pat) { + return _chunk(n, func, pat, true); + }, + true, + true, +); /** * Like `chunk`, but the cycles of the source pattern aren't repeated @@ -2471,9 +2481,14 @@ export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], fun * .fastChunk(4, x => x.color('red')).slow(2) * .scale("C2:major").note() */ -export const { fastchunk, fastChunk } = register(['fastchunk', 'fastChunk'], function (n, func, pat) { - return _chunk(n, func, pat, false, true); -}); +export const { fastchunk, fastChunk } = register( + ['fastchunk', 'fastChunk'], + function (n, func, pat) { + return _chunk(n, func, pat, false, true); + }, + true, + true, +); // TODO - redefine elsewhere in terms of mask export const bypass = register( From 3d37c731036db98593617bbb031c1d46dc292125 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Thu, 29 May 2025 14:32:52 +0200 Subject: [PATCH 27/74] fixed --- packages/core/controls.mjs | 4 ++-- packages/superdough/synth.mjs | 2 +- packages/superdough/worklets.mjs | 17 +++++++++-------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 3fa4b0507..21c183da9 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -464,9 +464,9 @@ export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpressio * @name byteBeatStartTime * @synonyms bbst * - * @param {number | Pattern} byteBeatStartTime in seconds + * @param {number | Pattern} byteBeatStartTime in samples (t) * @example - * note("{c g a b c d}%16").s("bytebeat").bbexpr('t&t>>8').bbst("<0 300>") + * note("c3!8".add("{0 0 12 0 7 5 3}%8")).s("bytebeat:5").bbst("<3 1>".mul(10000))._scope() * */ export const { byteBeatStartTime, bbst } = registerControl('byteBeatStartTime', 'bbst'); diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 670ef44bb..834e54c6d 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -192,7 +192,7 @@ export function registerSynthSounds() { }, ); - o.port.postMessage({ codeText: byteBeatExpression, startTimeSeconds: byteBeatStartTime, frequency }); + o.port.postMessage({ codeText: byteBeatExpression, byteBeatStartTime, frequency }); let envGain = gainNode(1); envGain = o.connect(envGain); diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index ec1f41e7b..aa0fe5662 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -811,11 +811,10 @@ class ByteBeatProcessor extends AudioWorkletProcessor { super(); this.port.onmessage = (event) => { let { codeText } = event.data; - const { startTimeSeconds, frequency } = event.data; - if (startTimeSeconds != null) { - const t = startTimeSeconds * sampleRate; - this.t = t; - this.t = (t / (sampleRate / 256)) * frequency; + const { byteBeatStartTime } = event.data; + if (byteBeatStartTime != null) { + this.t = 0 + this.initialOffset = Math.floor(byteBeatStartTime) } //Optimization pulled from dollchan.net: https://github.com/Chasyxx/EnBeat_NEW, it seemed important @@ -829,6 +828,7 @@ class ByteBeatProcessor extends AudioWorkletProcessor { this.func = getByteBeatFunc(codeText); }; + this.initialOffset = null; this.t = null; this.func = null; } @@ -878,13 +878,14 @@ class ByteBeatProcessor extends AudioWorkletProcessor { for (let i = 0; i < output[0].length; i++) { const detune = getParamValue(i, params.detune); const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100); - let t = (this.t / (sampleRate / 256)) * freq; - const funcValue = this.func(t); + let local_t = ((this.t / (sampleRate / 256)) * freq) + this.initialOffset + const funcValue = this.func(local_t); let signal = (funcValue & 255) / 127.5 - 1; const out = signal * 0.2; for (let c = 0; c < output.length; c++) { - output[c][i] = clamp(out, -0.2, 0.2); + //prevent speaker blowout via clipping if threshold exceeds + output[c][i] = clamp(out, -0.4, 0.4); } this.t = this.t + 1; } From 009f5445bef7571675e03b7be41e5cd55f94a2be Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Thu, 29 May 2025 14:33:25 +0200 Subject: [PATCH 28/74] codeformat --- packages/superdough/worklets.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index aa0fe5662..24f7434a9 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -813,8 +813,8 @@ class ByteBeatProcessor extends AudioWorkletProcessor { let { codeText } = event.data; const { byteBeatStartTime } = event.data; if (byteBeatStartTime != null) { - this.t = 0 - this.initialOffset = Math.floor(byteBeatStartTime) + this.t = 0; + this.initialOffset = Math.floor(byteBeatStartTime); } //Optimization pulled from dollchan.net: https://github.com/Chasyxx/EnBeat_NEW, it seemed important @@ -878,7 +878,7 @@ class ByteBeatProcessor extends AudioWorkletProcessor { for (let i = 0; i < output[0].length; i++) { const detune = getParamValue(i, params.detune); const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100); - let local_t = ((this.t / (sampleRate / 256)) * freq) + this.initialOffset + let local_t = (this.t / (sampleRate / 256)) * freq + this.initialOffset; const funcValue = this.func(local_t); let signal = (funcValue & 255) / 127.5 - 1; const out = signal * 0.2; From 14a0dea826293f31d7fc39a908ad40877cbeab79 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Thu, 29 May 2025 14:35:37 +0200 Subject: [PATCH 29/74] fixtest --- test/__snapshots__/examples.test.mjs.snap | 146 +++++----------------- 1 file changed, 32 insertions(+), 114 deletions(-) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index f886987ab..34914664d 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -1527,70 +1527,38 @@ exports[`runs examples > example "byteBeatExpression" example index 0 1`] = ` exports[`runs examples > example "byteBeatStartTime" example index 0 1`] = ` [ - "[ 0/1 → 1/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 1/16 → 1/8 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 1/8 → 3/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 3/16 → 1/4 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 1/4 → 5/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 5/16 → 3/8 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 3/8 → 7/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 7/16 → 1/2 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 1/2 → 9/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 9/16 → 5/8 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 5/8 → 11/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 11/16 → 3/4 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 3/4 → 13/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 13/16 → 7/8 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 7/8 → 15/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 15/16 → 1/1 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 1/1 → 17/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 17/16 → 9/8 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 9/8 → 19/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 19/16 → 5/4 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 5/4 → 21/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 21/16 → 11/8 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 11/8 → 23/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 23/16 → 3/2 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 3/2 → 25/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 25/16 → 13/8 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 13/8 → 27/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 27/16 → 7/4 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 7/4 → 29/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 29/16 → 15/8 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 15/8 → 31/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 31/16 → 2/1 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 2/1 → 33/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 33/16 → 17/8 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 17/8 → 35/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 35/16 → 9/4 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 9/4 → 37/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 37/16 → 19/8 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 19/8 → 39/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 39/16 → 5/2 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 5/2 → 41/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 41/16 → 21/8 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 21/8 → 43/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 43/16 → 11/4 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 11/4 → 45/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 45/16 → 23/8 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 23/8 → 47/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 47/16 → 3/1 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:0 ]", - "[ 3/1 → 49/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 49/16 → 25/8 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 25/8 → 51/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 51/16 → 13/4 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 13/4 → 53/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 53/16 → 27/8 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 27/8 → 55/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 55/16 → 7/2 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 7/2 → 57/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 57/16 → 29/8 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 29/8 → 59/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 59/16 → 15/4 | note:d s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 15/4 → 61/16 | note:c s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 61/16 → 31/8 | note:g s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 31/8 → 63/16 | note:a s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", - "[ 63/16 → 4/1 | note:b s:bytebeat byteBeatExpression:t&t>>8 byteBeatStartTime:300 ]", + "[ 0/1 → 1/8 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]", + "[ 1/8 → 1/4 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]", + "[ 1/4 → 3/8 | note:60 s:bytebeat n:5 byteBeatStartTime:30000 ]", + "[ 3/8 → 1/2 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]", + "[ 1/2 → 5/8 | note:55 s:bytebeat n:5 byteBeatStartTime:30000 ]", + "[ 5/8 → 3/4 | note:53 s:bytebeat n:5 byteBeatStartTime:30000 ]", + "[ 3/4 → 7/8 | note:51 s:bytebeat n:5 byteBeatStartTime:30000 ]", + "[ 7/8 → 1/1 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]", + "[ 1/1 → 9/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]", + "[ 9/8 → 5/4 | note:60 s:bytebeat n:5 byteBeatStartTime:10000 ]", + "[ 5/4 → 11/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]", + "[ 11/8 → 3/2 | note:55 s:bytebeat n:5 byteBeatStartTime:10000 ]", + "[ 3/2 → 13/8 | note:53 s:bytebeat n:5 byteBeatStartTime:10000 ]", + "[ 13/8 → 7/4 | note:51 s:bytebeat n:5 byteBeatStartTime:10000 ]", + "[ 7/4 → 15/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]", + "[ 15/8 → 2/1 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]", + "[ 2/1 → 17/8 | note:60 s:bytebeat n:5 byteBeatStartTime:30000 ]", + "[ 17/8 → 9/4 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]", + "[ 9/4 → 19/8 | note:55 s:bytebeat n:5 byteBeatStartTime:30000 ]", + "[ 19/8 → 5/2 | note:53 s:bytebeat n:5 byteBeatStartTime:30000 ]", + "[ 5/2 → 21/8 | note:51 s:bytebeat n:5 byteBeatStartTime:30000 ]", + "[ 21/8 → 11/4 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]", + "[ 11/4 → 23/8 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]", + "[ 23/8 → 3/1 | note:60 s:bytebeat n:5 byteBeatStartTime:30000 ]", + "[ 3/1 → 25/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]", + "[ 25/8 → 13/4 | note:55 s:bytebeat n:5 byteBeatStartTime:10000 ]", + "[ 13/4 → 27/8 | note:53 s:bytebeat n:5 byteBeatStartTime:10000 ]", + "[ 27/8 → 7/2 | note:51 s:bytebeat n:5 byteBeatStartTime:10000 ]", + "[ 7/2 → 29/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]", + "[ 29/8 → 15/4 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]", + "[ 15/4 → 31/8 | note:60 s:bytebeat n:5 byteBeatStartTime:10000 ]", + "[ 31/8 → 4/1 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]", ] `; @@ -8100,56 +8068,6 @@ exports[`runs examples > example "scope" example index 0 1`] = ` ] `; -exports[`runs examples > example "scramble -Slices a pattern into the given number of parts, then plays those parts at random. Similar to \`shuffle\`, -but parts might be played more than once, or not at all, per cycle." example index 0 1`] = ` -[ - "[ 0/1 → 1/4 | note:c s:piano ]", - "[ 1/4 → 1/2 | note:d s:piano ]", - "[ 1/2 → 3/4 | note:d s:piano ]", - "[ 3/4 → 1/1 | note:c s:piano ]", - "[ 1/1 → 5/4 | note:e s:piano ]", - "[ 5/4 → 3/2 | note:e s:piano ]", - "[ 3/2 → 7/4 | note:e s:piano ]", - "[ 7/4 → 2/1 | note:c s:piano ]", - "[ 2/1 → 9/4 | note:f s:piano ]", - "[ 9/4 → 5/2 | note:d s:piano ]", - "[ 5/2 → 11/4 | note:d s:piano ]", - "[ 11/4 → 3/1 | note:e s:piano ]", - "[ 3/1 → 13/4 | note:c s:piano ]", - "[ 13/4 → 7/2 | note:f s:piano ]", - "[ 7/2 → 15/4 | note:d s:piano ]", - "[ 15/4 → 4/1 | note:f s:piano ]", -] -`; - -exports[`runs examples > example "scramble -Slices a pattern into the given number of parts, then plays those parts at random. Similar to \`shuffle\`, -but parts might be played more than once, or not at all, per cycle." example index 1 1`] = ` -[ - "[ 0/1 → 1/8 | note:c s:piano ]", - "[ 1/8 → 1/4 | note:d s:piano ]", - "[ 1/4 → 3/8 | note:d s:piano ]", - "[ 3/8 → 1/2 | note:c s:piano ]", - "[ 1/2 → 1/1 | note:g s:piano ]", - "[ 1/1 → 9/8 | note:e s:piano ]", - "[ 9/8 → 5/4 | note:e s:piano ]", - "[ 5/4 → 11/8 | note:e s:piano ]", - "[ 11/8 → 3/2 | note:c s:piano ]", - "[ 3/2 → 2/1 | note:g s:piano ]", - "[ 2/1 → 17/8 | note:f s:piano ]", - "[ 17/8 → 9/4 | note:d s:piano ]", - "[ 9/4 → 19/8 | note:d s:piano ]", - "[ 19/8 → 5/2 | note:e s:piano ]", - "[ 5/2 → 3/1 | note:g s:piano ]", - "[ 3/1 → 25/8 | note:c s:piano ]", - "[ 25/8 → 13/4 | note:f s:piano ]", - "[ 13/4 → 27/8 | note:d s:piano ]", - "[ 27/8 → 7/2 | note:f s:piano ]", - "[ 7/2 → 4/1 | note:g s:piano ]", -] -`; - exports[`runs examples > example "scramble" example index 0 1`] = ` [ "[ 0/1 → 1/4 | note:c s:piano ]", From b312ff63a9b847af9c74abe007ddfd29f6bfb016 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 29 May 2025 23:58:35 +0200 Subject: [PATCH 30/74] Publish - @strudel/codemirror@1.2.2 - @strudel/core@1.2.2 - @strudel/csound@1.2.3 - @strudel/draw@1.2.2 - @strudel/gamepad@1.2.2 - @strudel/hydra@1.2.2 - @strudel/midi@1.2.3 - @strudel/mini@1.2.2 - @strudel/motion@1.2.2 - @strudel/mqtt@1.2.2 - @strudel/osc@1.2.2 - @strudel/repl@1.2.3 - @strudel/serial@1.2.2 - @strudel/soundfonts@1.2.3 - superdough@1.2.3 - @strudel/tonal@1.2.2 - @strudel/transpiler@1.2.2 - @strudel/web@1.2.3 - @strudel/webaudio@1.2.3 - @strudel/xen@1.2.2 --- packages/codemirror/package.json | 2 +- packages/core/package.json | 2 +- packages/csound/package.json | 2 +- packages/draw/package.json | 2 +- packages/gamepad/package.json | 2 +- packages/hydra/package.json | 2 +- packages/midi/package.json | 2 +- packages/mini/package.json | 2 +- packages/motion/package.json | 2 +- packages/mqtt/package.json | 2 +- packages/osc/package.json | 2 +- packages/repl/package.json | 2 +- packages/serial/package.json | 2 +- packages/soundfonts/package.json | 2 +- packages/superdough/package.json | 2 +- packages/tonal/package.json | 2 +- packages/transpiler/package.json | 2 +- packages/web/package.json | 2 +- packages/webaudio/package.json | 2 +- packages/xen/package.json | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index bc82fe380..5036cd8a3 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/codemirror", - "version": "1.2.1", + "version": "1.2.2", "description": "Codemirror Extensions for Strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/core/package.json b/packages/core/package.json index 00438ee77..89b047d2a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/core", - "version": "1.2.1", + "version": "1.2.2", "description": "Port of Tidal Cycles to JavaScript", "main": "index.mjs", "type": "module", diff --git a/packages/csound/package.json b/packages/csound/package.json index c9831f9ac..f200cdaf4 100644 --- a/packages/csound/package.json +++ b/packages/csound/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/csound", - "version": "1.2.2", + "version": "1.2.3", "description": "csound bindings for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/draw/package.json b/packages/draw/package.json index 3b97e53d9..51750da2b 100644 --- a/packages/draw/package.json +++ b/packages/draw/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/draw", - "version": "1.2.1", + "version": "1.2.2", "description": "Helpers for drawing with Strudel", "main": "index.mjs", "type": "module", diff --git a/packages/gamepad/package.json b/packages/gamepad/package.json index 6332ee2ed..8b91a15e9 100644 --- a/packages/gamepad/package.json +++ b/packages/gamepad/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/gamepad", - "version": "1.2.1", + "version": "1.2.2", "description": "Gamepad Inputs for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/hydra/package.json b/packages/hydra/package.json index 8a2aff50d..4375ebefc 100644 --- a/packages/hydra/package.json +++ b/packages/hydra/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/hydra", - "version": "1.2.1", + "version": "1.2.2", "description": "Hydra integration for strudel", "main": "hydra.mjs", "type": "module", diff --git a/packages/midi/package.json b/packages/midi/package.json index 2264ef969..2956b99f3 100644 --- a/packages/midi/package.json +++ b/packages/midi/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/midi", - "version": "1.2.2", + "version": "1.2.3", "description": "Midi API for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/mini/package.json b/packages/mini/package.json index 68ec683bf..5ae0dc242 100644 --- a/packages/mini/package.json +++ b/packages/mini/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/mini", - "version": "1.2.1", + "version": "1.2.2", "description": "Mini notation for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/motion/package.json b/packages/motion/package.json index da89b21fb..850a75bd1 100644 --- a/packages/motion/package.json +++ b/packages/motion/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/motion", - "version": "1.2.1", + "version": "1.2.2", "description": "DeviceMotion API for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/mqtt/package.json b/packages/mqtt/package.json index 33ff4cdfb..9b9cdaa3b 100644 --- a/packages/mqtt/package.json +++ b/packages/mqtt/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/mqtt", - "version": "1.2.1", + "version": "1.2.2", "description": "MQTT API for strudel", "main": "mqtt.mjs", "type": "module", diff --git a/packages/osc/package.json b/packages/osc/package.json index a202ab606..7e87c1e1a 100644 --- a/packages/osc/package.json +++ b/packages/osc/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/osc", - "version": "1.2.1", + "version": "1.2.2", "description": "OSC messaging for strudel", "main": "osc.mjs", "type": "module", diff --git a/packages/repl/package.json b/packages/repl/package.json index b75b87c45..5487ce443 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/repl", - "version": "1.2.2", + "version": "1.2.3", "description": "Strudel REPL as a Web Component", "module": "index.mjs", "publishConfig": { diff --git a/packages/serial/package.json b/packages/serial/package.json index fe7a23c82..bf6e0dcab 100644 --- a/packages/serial/package.json +++ b/packages/serial/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/serial", - "version": "1.2.1", + "version": "1.2.2", "description": "Webserial API for strudel", "main": "serial.mjs", "type": "module", diff --git a/packages/soundfonts/package.json b/packages/soundfonts/package.json index 0d1bf200b..bf4fa2960 100644 --- a/packages/soundfonts/package.json +++ b/packages/soundfonts/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/soundfonts", - "version": "1.2.2", + "version": "1.2.3", "description": "Soundsfont support for strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/superdough/package.json b/packages/superdough/package.json index c111b217c..a835f252d 100644 --- a/packages/superdough/package.json +++ b/packages/superdough/package.json @@ -1,6 +1,6 @@ { "name": "superdough", - "version": "1.2.2", + "version": "1.2.3", "description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.", "main": "index.mjs", "type": "module", diff --git a/packages/tonal/package.json b/packages/tonal/package.json index b183c79c7..89f02b301 100644 --- a/packages/tonal/package.json +++ b/packages/tonal/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/tonal", - "version": "1.2.1", + "version": "1.2.2", "description": "Tonal functions for strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/transpiler/package.json b/packages/transpiler/package.json index cda9da6dc..93648bbc7 100644 --- a/packages/transpiler/package.json +++ b/packages/transpiler/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/transpiler", - "version": "1.2.1", + "version": "1.2.2", "description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.", "main": "index.mjs", "type": "module", diff --git a/packages/web/package.json b/packages/web/package.json index d602e6281..652496fee 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/web", - "version": "1.2.2", + "version": "1.2.3", "description": "Easy to setup, opiniated bundle of Strudel for the browser.", "module": "web.mjs", "publishConfig": { diff --git a/packages/webaudio/package.json b/packages/webaudio/package.json index c4b61e96b..5714fddf3 100644 --- a/packages/webaudio/package.json +++ b/packages/webaudio/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/webaudio", - "version": "1.2.2", + "version": "1.2.3", "description": "Web Audio helpers for Strudel", "main": "index.mjs", "type": "module", diff --git a/packages/xen/package.json b/packages/xen/package.json index fc874a557..76fecf525 100644 --- a/packages/xen/package.json +++ b/packages/xen/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/xen", - "version": "1.2.1", + "version": "1.2.2", "description": "Xenharmonic API for strudel", "main": "index.mjs", "type": "module", From e0a21a936bad89b1b8cf349fcde5b9a596122250 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Mon, 2 Jun 2025 12:57:37 +0100 Subject: [PATCH 31/74] berlin --- packages/core/signal.mjs | 43 ++++++++++++++++++++--- packages/superdough/worklets.mjs | 1 - test/__snapshots__/examples.test.mjs.snap | 2 ++ 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index b6ba5d5cc..8e2d12b74 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -486,13 +486,34 @@ export const wchooseCycles = (...pairs) => _wchooseWith(rand.segment(1), ...pair export const wrandcat = wchooseCycles; -// this function expects pat to be a pattern of floats... -export const perlinWith = (pat) => { - const pata = pat.fmap(Math.floor); - const patb = pat.fmap((t) => Math.floor(t) + 1); +function _perlin(t) { + let ta = Math.floor(t); + let tb = ta + 1; const smootherStep = (x) => 6.0 * x ** 5 - 15.0 * x ** 4 + 10.0 * x ** 3; const interp = (x) => (a) => (b) => a + smootherStep(x) * (b - a); - return pat.sub(pata).fmap(interp).appBoth(pata.fmap(timeToRand)).appBoth(patb.fmap(timeToRand)); + const v = interp(t - ta)(timeToRand(ta))(timeToRand(tb)); + return v; +} +export const perlinWith = (tpat) => { + return tpat.fmap(_perlin); +}; + +function _berlin(t) { + const prevRidgeStartIndex = Math.floor(t); + const nextRidgeStartIndex = prevRidgeStartIndex + 1; + + const prevRidgeBottomPoint = timeToRand(prevRidgeStartIndex); + const nextRidgeTopPoint = timeToRand(nextRidgeStartIndex) + prevRidgeBottomPoint; + + const currentPercent = (t - prevRidgeStartIndex) / (nextRidgeStartIndex - prevRidgeStartIndex); + const interp = (a, b, t) => { + return a + (b - a) * t; + }; + return interp(prevRidgeBottomPoint, nextRidgeTopPoint, currentPercent) / 2; +} + +export const berlinWith = (tpat) => { + return tpat.fmap(_berlin); }; /** @@ -506,6 +527,18 @@ export const perlinWith = (pat) => { */ export const perlin = perlinWith(time.fmap((v) => Number(v))); +/** + * Generates a continuous pattern of [berlin noise](conceived by Jame Coyne and Jade Rowland as a joke but turned out to be surprisingly cool and useful, + * like perlin noise but with sawtooth waves), in the range 0..1. + * + * @name berlin + * @example + * // ascending arpeggios + * $: n("0!16".add(berlin.fast(4).mul(14))).scale("d:minor")._pianoroll() + * + */ +export const berlin = berlinWith(time.fmap((v) => Number(v))); + export const degradeByWith = register( 'degradeByWith', (withPat, x, pat) => pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)), diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 24f7434a9..6f526ec8f 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -882,7 +882,6 @@ class ByteBeatProcessor extends AudioWorkletProcessor { const funcValue = this.func(local_t); let signal = (funcValue & 255) / 127.5 - 1; const out = signal * 0.2; - for (let c = 0; c < output.length; c++) { //prevent speaker blowout via clipping if threshold exceeds output[c][i] = clamp(out, -0.4, 0.4); diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 34914664d..074803057 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -1051,6 +1051,8 @@ exports[`runs examples > example "begin" example index 0 1`] = ` ] `; +exports[`runs examples > example "berlin" example index 0 1`] = `[]`; + exports[`runs examples > example "binary" example index 0 1`] = ` [ "[ 0/1 → 1/3 | s:hh ]", From cde943d73712d8db15908093a8f1fac46be854f5 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Mon, 2 Jun 2025 13:02:41 +0100 Subject: [PATCH 32/74] fix test --- packages/core/signal.mjs | 2 +- test/__snapshots__/examples.test.mjs.snap | 69 ++++++++++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 8e2d12b74..5fd83bce3 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -534,7 +534,7 @@ export const perlin = perlinWith(time.fmap((v) => Number(v))); * @name berlin * @example * // ascending arpeggios - * $: n("0!16".add(berlin.fast(4).mul(14))).scale("d:minor")._pianoroll() + * n("0!16".add(berlin.fast(4).mul(14))).scale("d:minor") * */ export const berlin = berlinWith(time.fmap((v) => Number(v))); diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 074803057..b57d319a7 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -1051,7 +1051,74 @@ exports[`runs examples > example "begin" example index 0 1`] = ` ] `; -exports[`runs examples > example "berlin" example index 0 1`] = `[]`; +exports[`runs examples > example "berlin" example index 0 1`] = ` +[ + "[ 0/1 → 1/16 | note:D3 ]", + "[ 1/16 → 1/8 | note:E3 ]", + "[ 1/8 → 3/16 | note:F3 ]", + "[ 3/16 → 1/4 | note:G3 ]", + "[ 1/4 → 5/16 | note:A3 ]", + "[ 5/16 → 3/8 | note:C4 ]", + "[ 3/8 → 7/16 | note:D4 ]", + "[ 7/16 → 1/2 | note:F4 ]", + "[ 1/2 → 9/16 | note:D4 ]", + "[ 9/16 → 5/8 | note:E4 ]", + "[ 5/8 → 11/16 | note:E4 ]", + "[ 11/16 → 3/4 | note:E4 ]", + "[ 3/4 → 13/16 | note:F3 ]", + "[ 13/16 → 7/8 | note:F3 ]", + "[ 7/8 → 15/16 | note:F3 ]", + "[ 15/16 → 1/1 | note:F3 ]", + "[ 1/1 → 17/16 | note:E3 ]", + "[ 17/16 → 9/8 | note:E3 ]", + "[ 9/8 → 19/16 | note:E3 ]", + "[ 19/16 → 5/4 | note:F3 ]", + "[ 5/4 → 21/16 | note:E3 ]", + "[ 21/16 → 11/8 | note:F3 ]", + "[ 11/8 → 23/16 | note:G3 ]", + "[ 23/16 → 3/2 | note:A3 ]", + "[ 3/2 → 25/16 | note:A3 ]", + "[ 25/16 → 13/8 | note:Bb3 ]", + "[ 13/8 → 27/16 | note:Bb3 ]", + "[ 27/16 → 7/4 | note:Bb3 ]", + "[ 7/4 → 29/16 | note:F3 ]", + "[ 29/16 → 15/8 | note:G3 ]", + "[ 15/8 → 31/16 | note:Bb3 ]", + "[ 31/16 → 2/1 | note:C4 ]", + "[ 2/1 → 33/16 | note:C4 ]", + "[ 33/16 → 17/8 | note:D4 ]", + "[ 17/8 → 35/16 | note:F4 ]", + "[ 35/16 → 9/4 | note:G4 ]", + "[ 9/4 → 37/16 | note:Bb3 ]", + "[ 37/16 → 19/8 | note:Bb3 ]", + "[ 19/8 → 39/16 | note:Bb3 ]", + "[ 39/16 → 5/2 | note:C4 ]", + "[ 5/2 → 41/16 | note:F3 ]", + "[ 41/16 → 21/8 | note:F3 ]", + "[ 21/8 → 43/16 | note:G3 ]", + "[ 43/16 → 11/4 | note:A3 ]", + "[ 11/4 → 45/16 | note:A3 ]", + "[ 45/16 → 23/8 | note:A3 ]", + "[ 23/8 → 47/16 | note:A3 ]", + "[ 47/16 → 3/1 | note:A3 ]", + "[ 3/1 → 49/16 | note:E3 ]", + "[ 49/16 → 25/8 | note:G3 ]", + "[ 25/8 → 51/16 | note:Bb3 ]", + "[ 51/16 → 13/4 | note:C4 ]", + "[ 13/4 → 53/16 | note:D4 ]", + "[ 53/16 → 27/8 | note:E4 ]", + "[ 27/8 → 55/16 | note:G4 ]", + "[ 55/16 → 7/2 | note:A4 ]", + "[ 7/2 → 57/16 | note:Bb3 ]", + "[ 57/16 → 29/8 | note:Bb3 ]", + "[ 29/8 → 59/16 | note:C4 ]", + "[ 59/16 → 15/4 | note:C4 ]", + "[ 15/4 → 61/16 | note:F3 ]", + "[ 61/16 → 31/8 | note:G3 ]", + "[ 31/8 → 63/16 | note:G3 ]", + "[ 63/16 → 4/1 | note:A3 ]", +] +`; exports[`runs examples > example "binary" example index 0 1`] = ` [ From df923174ede5ba5df5d54db5a22d90ff05778e1f Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 3 Jun 2025 01:22:37 +0100 Subject: [PATCH 33/74] working --- packages/core/pattern.mjs | 19 ++++++++++++ test/__snapshots__/examples.test.mjs.snap | 37 +++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 56a6b77d1..efbae1f3e 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -3192,6 +3192,25 @@ export const slice = register( false, // turns off auto-patternification ); +/** + * + * make something happen on event time + * uses browser timeout which is innacurate for audio tasks + * @name onTriggerTime + * @memberof Pattern + * @returns Pattern + * @example + * s("bd!8").onTriggerTime((hap) => {console.info(hap)}) + */ +Pattern.prototype.onTriggerTime = function (func) { + return this.onTrigger((t_deprecate, hap, currentTime, cps = 1, targetTime) => { + const diff = targetTime - currentTime; + window.setTimeout(() => { + func(hap); + }, diff * 1000); + }, false); +}; + /** * Works the same as slice, but changes the playback speed of each slice to match the duration of its step. * @name splice diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 34914664d..2ee135b9b 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -5950,6 +5950,43 @@ exports[`runs examples > example "often" example index 0 1`] = ` ] `; +exports[`runs examples > example "onTriggerTime" example index 0 1`] = ` +[ + "[ 0/1 → 1/8 | s:bd ]", + "[ 1/8 → 1/4 | s:bd ]", + "[ 1/4 → 3/8 | s:bd ]", + "[ 3/8 → 1/2 | s:bd ]", + "[ 1/2 → 5/8 | s:bd ]", + "[ 5/8 → 3/4 | s:bd ]", + "[ 3/4 → 7/8 | s:bd ]", + "[ 7/8 → 1/1 | s:bd ]", + "[ 1/1 → 9/8 | s:bd ]", + "[ 9/8 → 5/4 | s:bd ]", + "[ 5/4 → 11/8 | s:bd ]", + "[ 11/8 → 3/2 | s:bd ]", + "[ 3/2 → 13/8 | s:bd ]", + "[ 13/8 → 7/4 | s:bd ]", + "[ 7/4 → 15/8 | s:bd ]", + "[ 15/8 → 2/1 | s:bd ]", + "[ 2/1 → 17/8 | s:bd ]", + "[ 17/8 → 9/4 | s:bd ]", + "[ 9/4 → 19/8 | s:bd ]", + "[ 19/8 → 5/2 | s:bd ]", + "[ 5/2 → 21/8 | s:bd ]", + "[ 21/8 → 11/4 | s:bd ]", + "[ 11/4 → 23/8 | s:bd ]", + "[ 23/8 → 3/1 | s:bd ]", + "[ 3/1 → 25/8 | s:bd ]", + "[ 25/8 → 13/4 | s:bd ]", + "[ 13/4 → 27/8 | s:bd ]", + "[ 27/8 → 7/2 | s:bd ]", + "[ 7/2 → 29/8 | s:bd ]", + "[ 29/8 → 15/4 | s:bd ]", + "[ 15/4 → 31/8 | s:bd ]", + "[ 31/8 → 4/1 | s:bd ]", +] +`; + exports[`runs examples > example "orbit" example index 0 1`] = ` [ "[ 0/1 → 1/6 | s:hh delay:0.5 delaytime:0.25 orbit:1 ]", From 2c9f9d785f13e76d5c7304372d0e8c9f4df615d1 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 12 Jun 2025 08:39:27 +0200 Subject: [PATCH 34/74] hotfix: close bakery --- website/src/config.ts | 2 +- website/src/repl/components/Header.jsx | 8 ++-- .../src/repl/components/panel/PatternsTab.jsx | 37 +++++++++++-------- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/website/src/config.ts b/website/src/config.ts index 2f499d55f..e88b7f3e1 100644 --- a/website/src/config.ts +++ b/website/src/config.ts @@ -58,7 +58,7 @@ export const SIDEBAR: Sidebar = { { text: 'What is Strudel?', link: 'workshop/getting-started' }, { text: 'Showcase', link: 'intro/showcase' }, { text: 'Blog', link: 'blog' }, - { text: 'Community Bakery', link: 'bakery' }, + /* { text: 'Community Bakery', link: 'bakery' }, */ ], Workshop: [ // { text: 'Getting Started', link: 'workshop/getting-started' }, diff --git a/website/src/repl/components/Header.jsx b/website/src/repl/components/Header.jsx index 2963a5f97..a97e2bed0 100644 --- a/website/src/repl/components/Header.jsx +++ b/website/src/repl/components/Header.jsx @@ -93,7 +93,7 @@ export function Header({ context, embedded = false }) { > {!isEmbedded && update} - {!isEmbedded && ( + {/* !isEmbedded && ( - )} - {!isEmbedded && ( + ) */} + {/* !isEmbedded && ( - )} + ) */} {!isEmbedded && (
- {patternFilter === patternFilterName.user && ( - - updateCodeWindow( - context, - { ...userPatterns[id], collection: userPattern.collection }, - autoResetPatternOnChange, - ) - } - patterns={userPatterns} - started={context.started} - activePattern={activePattern} - viewingPatternID={viewingPatternID} - /> - )} + {/* {patternFilter === patternFilterName.user && ( */} + + updateCodeWindow( + context, + { ...userPatterns[id], collection: userPattern.collection }, + autoResetPatternOnChange, + ) + } + patterns={userPatterns} + started={context.started} + activePattern={activePattern} + viewingPatternID={viewingPatternID} + /> + {/* )} */}
); @@ -250,6 +250,11 @@ export function PatternsTab({ context }) { const { patternFilter } = useSettings(); return ( +
+ +
+ ); + /* return (
)}
- ); + ); */ } From 79f7938b3381b426e85b67ec163abaee5423ed92 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 12 Jun 2025 08:51:03 +0200 Subject: [PATCH 35/74] hotfix: add default code --- website/src/repl/useReplContext.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/src/repl/useReplContext.jsx b/website/src/repl/useReplContext.jsx index f88e5a6e2..708506cc5 100644 --- a/website/src/repl/useReplContext.jsx +++ b/website/src/repl/useReplContext.jsx @@ -59,6 +59,8 @@ async function getModule(name) { return modules.find((m) => m.packageName === name); } +const initialCode = `$: s("[bd ]*2").bank("tr909").dec(.4)`; + export function useReplContext() { const { isSyncEnabled, audioEngineTarget } = useSettings(); const shouldUseWebaudio = audioEngineTarget !== audioEngineTargets.osc; @@ -77,7 +79,7 @@ export function useReplContext() { transpiler, autodraw: false, root: containerRef.current, - initialCode: '// LOADING', + initialCode, pattern: silence, drawTime, drawContext, From 40ba5d44652d18dc84bcc4d9f9d542f18d573551 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 12 Jun 2025 09:11:19 +0200 Subject: [PATCH 36/74] hotfix: don't load patterns from db --- website/src/repl/util.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs index f49dd568f..9db280dad 100644 --- a/website/src/repl/util.mjs +++ b/website/src/repl/util.mjs @@ -7,7 +7,7 @@ import './Repl.css'; import { createClient } from '@supabase/supabase-js'; import { nanoid } from 'nanoid'; import { writeText } from '@tauri-apps/plugin-clipboard-manager'; -import { $featuredPatterns, loadDBPatterns } from '@src/user_pattern_utils.mjs'; +import { $featuredPatterns /* , loadDBPatterns */ } from '@src/user_pattern_utils.mjs'; // Create a single supabase client for interacting with your database export const supabase = createClient( @@ -16,9 +16,9 @@ export const supabase = createClient( ); let dbLoaded; -if (typeof window !== 'undefined') { +/* if (typeof window !== 'undefined') { dbLoaded = loadDBPatterns(); -} +} */ export async function initCode() { // load code from url hash (either short hash from database or decode long hash) From ef3b9a68e362218bef0a79f0954fad5fa6a38aae Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 12 Jun 2025 09:21:57 +0200 Subject: [PATCH 37/74] hotfix: leave out part about shuffle in welcome tab --- website/src/repl/components/panel/WelcomeTab.jsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/website/src/repl/components/panel/WelcomeTab.jsx b/website/src/repl/components/panel/WelcomeTab.jsx index 1049a581d..a43f02ad4 100644 --- a/website/src/repl/components/panel/WelcomeTab.jsx +++ b/website/src/repl/components/panel/WelcomeTab.jsx @@ -15,17 +15,18 @@ export function WelcomeTab({ context }) {
1. hit play - 2. change something -{' '} 3. hit update -
- If you don't like what you hear, try shuffle! + {/*
+ If you don't like what you hear, try shuffle! */}

- To learn more about what this all means, check out the{' '} + {/* To learn more about what this all means, check out the{' '} */} + To get started, check out the interactive tutorial . Also feel free to join the{' '} - tidalcycles discord channel + discord channel {' '} to ask any questions, give feedback or just say hello.

From 694a22af85494444c3d07c3c48da48d20afbe390 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 12 Jun 2025 09:51:04 +0200 Subject: [PATCH 38/74] hotfix: bring back loading code + fill in default pattern later --- website/src/repl/useReplContext.jsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/website/src/repl/useReplContext.jsx b/website/src/repl/useReplContext.jsx index 708506cc5..36e8099cb 100644 --- a/website/src/repl/useReplContext.jsx +++ b/website/src/repl/useReplContext.jsx @@ -59,7 +59,7 @@ async function getModule(name) { return modules.find((m) => m.packageName === name); } -const initialCode = `$: s("[bd ]*2").bank("tr909").dec(.4)`; +const initialCode = `// LOADING`; export function useReplContext() { const { isSyncEnabled, audioEngineTarget } = useSettings(); @@ -135,9 +135,10 @@ export function useReplContext() { code = latestCode; msg = `Your last session has been loaded!`; } else { - const { code: randomTune, name } = await getRandomTune(); - code = randomTune; - msg = `A random code snippet named "${name}" has been loaded!`; + /* const { code: randomTune, name } = await getRandomTune(); + code = randomTune; */ + code = '$: s("[bd ]*2").bank("tr909").dec(.4)'; + msg = `Default code has been loaded`; } editor.setCode(code); setDocumentTitle(code); From 372542dcd86f7f201d6b187c875be4b2032f972a Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Thu, 12 Jun 2025 10:23:21 +0200 Subject: [PATCH 39/74] working --- website/src/repl/components/Header.jsx | 4 +- website/src/repl/util.mjs | 76 ++++++++++++++++---------- 2 files changed, 49 insertions(+), 31 deletions(-) diff --git a/website/src/repl/components/Header.jsx b/website/src/repl/components/Header.jsx index a97e2bed0..ca4e1ecf9 100644 --- a/website/src/repl/components/Header.jsx +++ b/website/src/repl/components/Header.jsx @@ -102,7 +102,7 @@ export function Header({ context, embedded = false }) { shuffle ) */} - {/* !isEmbedded && ( + {!isEmbedded && ( - ) */} + )} {!isEmbedded && ( { - const hash = nanoid(12); - const shareUrl = window.location.origin + window.location.pathname + '?' + hash; - const { error } = await supabase.from('code_v1').insert([{ code: codeToShare, hash, ['public']: isPublic }]); - if (!error) { - lastShared = codeToShare; - // copy shareUrl to clipboard - if (isTauri()) { - await writeText(shareUrl); - } else { - await navigator.clipboard.writeText(shareUrl); - } - const message = `Link copied to clipboard: ${shareUrl}`; - alert(message); - // alert(message); - logger(message, 'highlight'); +//RIP due to SPAM +// export async function shareCode(codeToShare) { +// // const codeToShare = activeCode || code; +// if (lastShared === codeToShare) { +// logger(`Link already generated!`, 'error'); +// return; +// } + +// confirmDialog( +// 'Do you want your pattern to be public? If no, press cancel and you will get just a private link.', +// ).then(async (isPublic) => { +// const hash = nanoid(12); +// const shareUrl = window.location.origin + window.location.pathname + '?' + hash; +// const { error } = await supabase.from('code_v1').insert([{ code: codeToShare, hash, ['public']: isPublic }]); +// if (!error) { +// lastShared = codeToShare; +// // copy shareUrl to clipboard +// if (isTauri()) { +// await writeText(shareUrl); +// } else { +// await navigator.clipboard.writeText(shareUrl); +// } +// const message = `Link copied to clipboard: ${shareUrl}`; +// alert(message); +// // alert(message); +// logger(message, 'highlight'); +// } else { +// console.log('error', error); +// const message = `Error: ${error.message}`; +// // alert(message); +// logger(message); +// } +// }); +// } + +export async function shareCode() { + try { + const shareUrl = window.location.href; + if (isTauri()) { + await writeText(shareUrl); } else { - console.log('error', error); - const message = `Error: ${error.message}`; - // alert(message); - logger(message); + await navigator.clipboard.writeText(shareUrl); } - }); + const message = `Link copied to clipboard!`; + alert(message); + logger(message, 'highlight'); + } catch (e) { + console.error(e); + } } export const isIframe = () => window.location !== window.parent.location; From 94746b5fae73f34b287ca1c9ae20e65a7ff0202f Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Thu, 12 Jun 2025 13:37:58 +0100 Subject: [PATCH 40/74] Update README.md --- README.md | 53 +---------------------------------------------------- 1 file changed, 1 insertion(+), 52 deletions(-) diff --git a/README.md b/README.md index 12ee85035..0d576ac6d 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,3 @@ # strudel -[![Strudel test status](https://github.com/tidalcycles/strudel/actions/workflows/test.yml/badge.svg)](https://github.com/tidalcycles/strudel/actions) [![DOI](https://zenodo.org/badge/450927247.svg)](https://doi.org/10.5281/zenodo.6659278) - -An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using web technologies. This software is a bit more stable now, but please continue to tread carefully. - -- Try it here: -- Docs: -- Technical Blog Post: -- 1 Year of Strudel Blog Post: -- 2 Years of Strudel Blog Post: - -## Running Locally - -After cloning the project, you can run the REPL locally: - -1. Install [Node.js](https://nodejs.org/) -2. Install [pnpm](https://pnpm.io/installation) -3. Install dependencies by running the following command: - ```bash - pnpm i - ``` -4. Run the development server: - ```bash - pnpm dev - ``` - -## Using Strudel In Your Project - -This project is organized into many [packages](./packages), which are also available on [npm](https://www.npmjs.com/search?q=%40strudel). - -Read more about how to use these in your own project [here](https://strudel.cc/technical-manual/project-start). - -You will need to abide by the terms of the [GNU Affero Public Licence v3](LICENSE.md). As such, Strudel code can only be shared within free/open source projects under the same license -- see the license for details. - -Licensing info for the default sound banks can be found over on the [dough-samples](https://github.com/felixroos/dough-samples/blob/main/README.md) repository. - -## Contributing - -There are many ways to contribute to this project! See [contribution guide](./CONTRIBUTING.md). - - - - - -Made with [contrib.rocks](https://contrib.rocks). - -## Community - -There is a #strudel channel on the TidalCycles discord: - -You can also ask questions and find related discussions on the tidal club forum: - -The discord and forum is shared with the haskell (tidal) and python (vortex) siblings of this project. +Moving to https://codeberg.org/uzu/strudel From 84efa664caa4c78a0675326d6f76894d01e3310c Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Thu, 12 Jun 2025 13:39:00 +0100 Subject: [PATCH 41/74] Update README.md --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d576ac6d..792245236 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ # strudel -Moving to https://codeberg.org/uzu/strudel +Live coding patterns on the web +https://strudel.cc/ + +Development is moving to https://codeberg.org/uzu/strudel + +Please update your bookmarks. From b6e67f34743fee69a92b0d98ba9996d5409d1821 Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Thu, 12 Jun 2025 14:12:42 +0100 Subject: [PATCH 42/74] > codeberg --- CONTRIBUTING.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d8170cfc..618830b9c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,11 +2,14 @@ Thanks for wanting to contribute!!! There are many ways you can add value to this project +## Move to codeberg + +We are currently in the process of moving from github to codeberg -- not everything is working, please bear with us. + ## Communication Channels To get in touch with the contributors, either -- open a [github discussion](https://github.com/tidalcycles/strudel/discussions) or - [join the Tidal Discord Channel](https://discord.gg/remJ6gQA) and go to the #strudel channel - Find related discussions on the [tidal club forum](https://club.tidalcycles.org/) @@ -32,7 +35,7 @@ Use one of the Communication Channels listed above. ## Improve the Docs If you find some weak spots in the [docs](https://strudel.cc/workshop/getting-started/), -you can edit each file directly on github via the "Edit this page" link located in the right sidebar. +you can edit each file directly on codeburg. (we are currently fixing the "Edit this page" links in the right sidebar) ## Propose a Feature @@ -41,7 +44,7 @@ Maybe you even want to help with the implementation of that feature! ## Report a Bug -If you've found a bug, or some behaviour that does not seem right, you are welcome to file an [issue](https://github.com/tidalcycles/strudel/issues). +If you've found a bug, or some behaviour that does not seem right, you are welcome to file an [issue](https://codeberg.org/uzu/strudel/issues). Please check that it has not been reported before. ## Fix a Bug @@ -71,7 +74,7 @@ To get the project up and running for development, make sure you have installed: then, do the following: ```sh -git clone https://github.com/tidalcycles/strudel.git && cd strudel +git clone https://codeberg.org/uzu/strudel.git && cd strudel pnpm i # install at root to symlink packages pnpm start # start repl ``` @@ -113,7 +116,7 @@ You can run the same check with `pnpm check` ## Package Workflow -The project is split into multiple [packages](https://github.com/tidalcycles/strudel/tree/main/packages) with independent versioning. +The project is split into multiple [packages](https://codeberg.org/uzu/strudel/src/branch/main/packages) with independent versioning. When you run `pnpm i` on the root folder, [pnpm workspaces](https://pnpm.io/workspaces) will install all dependencies of all subpackages. This will allow any js file to import `@strudel/` to get the local version, allowing to develop multiple packages at the same time. From 580447d00af888513357b2a8f8ee74acace1fbb6 Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Thu, 12 Jun 2025 14:15:07 +0100 Subject: [PATCH 43/74] move technical manual from https://github.com/tidalcycles/strudel/wiki/Technical-Manual --- technical.manual.md | 193 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 technical.manual.md diff --git a/technical.manual.md b/technical.manual.md new file mode 100644 index 000000000..73180b145 --- /dev/null +++ b/technical.manual.md @@ -0,0 +1,193 @@ +This document introduces you to Strudel in a technical sense. If you just want to *use* Strudel, have a look at the [Tutorial](https://strudel.tidalcycles.org/tutorial/). + +## Strudel Packages + +There are different packages for different purposes. They.. + +- split up the code into smaller chunks +- can be selectively used to implement some sort of time based system + +Please refer to the individual README files in the [packages folder](https://github.com/tidalcycles/strudel/tree/main/packages) + +## REPL + +The [REPL](https://strudel.tidalcycles.org/) is the place where all packages come together to form a live coding system. It can also be seen as a reference implementation for users of the library. + +More info in the [REPL README](https://github.com/tidalcycles/strudel/tree/main/repl) + +# High Level Overview + + + +## 1. End User Code + +The End User Code is written in JavaScript with added syntax sugar. The [eval package](https://github.com/tidalcycles/strudel/tree/main/packages/eval#strudelcycleseval) evaluates the user code +after a transpilation step, which resolves the syntax sugar. If you don't want the syntax sugar, you can omit the eval package and call the native javascript `eval` instead. + +### 🍭 Syntax Sugar + +JavaScript Transpilation = converting valid JavaScript to valid JavaScript: + +```js +"c3 [e3 g3]".fast(2) +``` + +becomes + +```js +mini('c3 [e3 g3]') + .withMiniLocation([1, 0, 0], [1, 11, 11]) // source location + .fast(2); +``` + +- double quoted strings and backtick strings are turned into `mini` calls (single quoted strings are left as is) +- The source location is added by chaining `withMiniLocation`, which enables the real time highlighting later +- (psuedo) variable names that look like notes (like `c4`, `bb2` or `fs3`) are turned into strings +- support for top level await +- operator overloading could be implemented in the future + +This is how it works: + + + +- The user code is parsed with a [shift parser](https://github.com/shapesecurity/shift-parser-js), generating an AST +- The AST is transformed to resolve the syntax sugar +- The AST is used to generate code again (shift-codegen) + +Shift will most likely be replaced with acorn in the future, see https://github.com/tidalcycles/strudel/issues/174 + +### Mini Notation + +Another important part of the user code is the mini notation, which allows to express rhythms in a short manner. + +- the mini notation is [implemented as a PEG grammar](https://github.com/tidalcycles/strudel/blob/main/packages/mini/krill.pegjs), living in the [mini package](https://github.com/tidalcycles/strudel/tree/main/packages/mini) +- it is based on [krill](https://github.com/Mdashdotdashn/krill) by Mdashdotdashn +- the peg grammar is used to generate a parser with [peggyjs](https://peggyjs.org/) +- the generated parser takes a mini notation string and outputs an AST +- the AST can then be used to construct a pattern using the regular Strudel API + +Here's an example AST: + +```json +{ + "type_": "pattern", + "arguments_": { "alignment": "h" }, + "source_": [ + { + "type_": "element", "source_": "c3", + "location_": { "start": { "offset": 1, "line": 1, "column": 2 }, "end": { "offset": 4, "line": 1, "column": 5 } } + }, + { + "type_": "element", + "location_": { "start": { "offset": 4, "line": 1, "column": 5 }, "end": { "offset": 11, "line": 1, "column": 12 } } + "source_": { + "type_": "pattern", "arguments_": { "alignment": "h" }, + "source_": [ + { + "type_": "element", "source_": "e3", + "location_": { "start": { "offset": 5, "line": 1, "column": 6 }, "end": { "offset": 8, "line": 1, "column": 9 } } + }, + { + "type_": "element", "source_": "g3", + "location_": { "start": { "offset": 8, "line": 1, "column": 9 }, "end": { "offset": 10, "line": 1, "column": 11 } } + } + ] + }, + } + ] +} +``` + +which translates to `seq(c3, seq(e3, g3))` + +## 2. Querying & Scheduling + +When the user code has been evaluated, we hopefully get a Pattern instance, which we can use to query events from. +These events can then be used to trigger side effects in the real world. On that note, Events are mostly called Hap(s) in the codebase, because JS already has a built in `Event` class. + +### Querying + +> Querying = Asking a Pattern for Events within a certain time span + +```js +seq('c3', ['e3', 'g3']) // <--- Pattern + .queryArc(0, 2) // query events within 0 and 2 cycles + .map((hap) => hap.showWhole()); // make readable +``` + +yields + +```js +[ + '0/1 -> 1/2: c3', // cycle 0 + '1/2 -> 3/4: e3', + '3/4 -> 1/1: g3', + '1/1 -> 3/2: c3', // cycle 1 + '3/2 -> 7/4: e3', + '7/4 -> 2/1: g3', +]; +``` + +### 🗓️ Scheduling + +The scheduler will query events repeatedly, creating a possibly endless loop of time slices. +Here is a simplified example of how it works + +```js +let step = 0.5; // query interval in seconds +let tick = 0; // how many intervals have passed +let pattern = seq('c3', ['e3', 'g3']); // pattern from user +setInterval(() => { + const events = pattern.queryArc(tick * step, ++tick * step); + events.forEach((event) => { + console.log(event.showWhole()); + const o = getAudioContext().createOscillator(); + o.frequency.value = getFreq(event.value); + o.start(event.whole.begin); + o.stop(event.whole.begin + event.duration); + o.connect(getAudioContext().destination); + }); +}, step * 1000); // query each "step" seconds +``` + +## 3. Sound Output + +The third and last step is to use the scheduled events to make sound. +Patterns are wrapped with param functions to compose different properties of the sound. + +```js +note("[c2(3,8) [ bb1]]") // sets frequency + .s("") // sound source + .gain(.5) // turn down volume + .cutoff(sine.range(200,1000).slow(4)) // modulated cutoff + .slow(2) + .out().logValues()`, + ]} +/> +``` + +Here is an example Hap value with different properties: + +```js +{ note: 'a4', s: 'sawtooth', gain: 0.5, cutoff: 267 } +``` + + +
+ +- Patterns represent just values in time! +- Suitable for any time based output (music, visuals, movement, .. ?) + +### Supported Outputs + +At the time of writing this doc, the following outputs are supported: + +- Web Audio API `.out()` see [/webaudio](https://github.com/tidalcycles/strudel/tree/main/packages/webaudio) +- MIDI `.midi()` see [/midi](https://github.com/tidalcycles/strudel/tree/main/packages/midi) +- OSC `.osc()` see [/osc](https://github.com/tidalcycles/strudel/tree/main/packages/osc) +- Serial `.serial()` see [/serial](https://github.com/tidalcycles/strudel/tree/main/packages/serial) +- Tone.js `.tone()` (deprecated?) [/tone](https://github.com/tidalcycles/strudel/tree/main/packages/tone) +- WebDirt `.webdirt()` (deprecated?) [/webdirt](https://github.com/tidalcycles/strudel/tree/main/packages/webdirt) +- Speech `.speak()` (experimental) part of [/core](https://github.com/tidalcycles/strudel/tree/main/packages/core) + +These could change, so make sure to check the [packages folder](https://github.com/tidalcycles/strudel/tree/main/packages). From 5fdea7fd8002ae7454bccb88e81e1689e26f55bb Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Thu, 12 Jun 2025 14:36:45 +0100 Subject: [PATCH 44/74] github > strudel --- packages/README.md | 2 +- packages/codemirror/package.json | 6 +++--- packages/core/clockworker.js | 2 +- packages/core/controls.mjs | 12 ++++++------ packages/core/cyclist.mjs | 6 +++--- packages/core/drawLine.mjs | 2 +- packages/core/euclid.mjs | 2 +- packages/core/evaluate.mjs | 2 +- packages/core/fraction.mjs | 2 +- packages/core/hap.mjs | 2 +- packages/core/index.mjs | 2 +- packages/core/neocyclist.mjs | 2 +- packages/core/package.json | 4 ++-- packages/core/pattern.mjs | 2 +- packages/core/pick.mjs | 2 +- packages/core/repl.mjs | 2 +- packages/core/signal.mjs | 2 +- packages/core/speak.mjs | 2 +- packages/core/state.mjs | 2 +- packages/core/timespan.mjs | 2 +- packages/core/ui.mjs | 2 +- packages/core/util.mjs | 2 +- packages/core/value.mjs | 4 ++-- packages/csound/package.json | 6 +++--- packages/desktopbridge/index.mjs | 2 +- packages/desktopbridge/package.json | 6 +++--- packages/draw/draw.mjs | 2 +- packages/draw/package.json | 6 +++--- packages/draw/pianoroll.mjs | 2 +- packages/embed/package.json | 6 +++--- packages/gamepad/package.json | 6 +++--- packages/hs2js/package.json | 6 +++--- packages/hydra/package.json | 6 +++--- packages/midi/midi.mjs | 2 +- packages/midi/package.json | 6 +++--- packages/mini/krill.pegjs | 2 +- packages/mini/mini.mjs | 2 +- packages/mini/package.json | 6 +++--- packages/motion/package.json | 6 +++--- packages/mqtt/mqtt.mjs | 2 +- packages/mqtt/package.json | 6 +++--- packages/osc/osc.mjs | 2 +- packages/osc/package.json | 6 +++--- packages/osc/server.js | 2 +- packages/osc/tidal-sniffer.js | 2 +- packages/reference/package.json | 6 +++--- packages/repl/package.json | 6 +++--- packages/serial/package.json | 6 +++--- packages/serial/serial.mjs | 2 +- packages/soundfonts/package.json | 6 +++--- packages/superdough/index.mjs | 2 +- packages/superdough/package.json | 6 +++--- packages/superdough/superdough.mjs | 2 +- packages/tidal/package.json | 6 +++--- packages/tonal/package.json | 6 +++--- packages/tonal/tonal.mjs | 2 +- packages/tonal/tonleiter.mjs | 2 +- packages/tonal/voicings.mjs | 2 +- packages/transpiler/package.json | 6 +++--- packages/transpiler/transpiler.mjs | 2 +- packages/web/package.json | 6 +++--- packages/webaudio/index.mjs | 2 +- packages/webaudio/package.json | 6 +++--- packages/webaudio/webaudio.mjs | 4 ++-- packages/xen/package.json | 6 +++--- packages/xen/tune.mjs | 2 +- packages/xen/tunejs.js | 2 +- packages/xen/xen.mjs | 2 +- 68 files changed, 126 insertions(+), 126 deletions(-) diff --git a/packages/README.md b/packages/README.md index 7a7681b67..a5b93d321 100644 --- a/packages/README.md +++ b/packages/README.md @@ -2,4 +2,4 @@ Each folder represents one of the @strudel/* packages [published to npm](https://www.npmjs.com/org/strudel). -To understand how those pieces connect, refer to the [Technical Manual](https://github.com/tidalcycles/strudel/wiki/Technical-Manual) or the individual READMEs. +To understand how those pieces connect, refer to the [Technical Manual](https://codeberg.org/uzu/strudel/src/branch/main/technical-manual.md) or the individual READMEs. diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index 5036cd8a3..1ffe1724c 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -13,7 +13,7 @@ "type": "module", "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -28,9 +28,9 @@ ], "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@codemirror/autocomplete": "^6.18.4", "@codemirror/commands": "^6.8.0", diff --git a/packages/core/clockworker.js b/packages/core/clockworker.js index bcaf28725..77a45362e 100644 --- a/packages/core/clockworker.js +++ b/packages/core/clockworker.js @@ -113,7 +113,7 @@ self.onconnect = function (e) { port.start(); // Required when using addEventListener. Otherwise called implicitly by onmessage setter. }; -// used to consistently schedule events, for use in a service worker - see +// used to consistently schedule events, for use in a service worker - see function createClock( getTime, callback, // called slightly before each cycle diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 21c183da9..058f4536a 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1,6 +1,6 @@ /* controls.mjs - Registers audio controls for pattern manipulation and effects. -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ @@ -96,7 +96,7 @@ export const { source, src } = registerControl('source', 'src'); * @example * s("bd sd [~ bd] sd,hh*6").n("<0 1>") */ -// also see https://github.com/tidalcycles/strudel/pull/63 +// also see https://codeberg.org/uzu/strudel/pulls/63 export const { n } = registerControl('n'); /** * Plays the given note name or midi number. A note name consists of @@ -348,7 +348,7 @@ export const { bandf, bpf, bp } = registerControl(['bandf', 'bandq', 'bpenv'], ' * s("bd sd [~ bd] sd").bpf(500).bpq("<0 1 2 3>") * */ -// currently an alias of 'bandq' https://github.com/tidalcycles/strudel/issues/496 +// currently an alias of 'bandq' https://codeberg.org/uzu/strudel/issues/496 // ['bpq'], export const { bandq, bpq } = registerControl('bandq', 'bpq'); /** @@ -855,7 +855,7 @@ export const { fanchor } = registerControl('fanchor'); * s("bd sd [~ bd] sd,hh*8").hpf("<2000 2000:25>") * */ -// currently an alias of 'hcutoff' https://github.com/tidalcycles/strudel/issues/496 +// currently an alias of 'hcutoff' https://codeberg.org/uzu/strudel/issues/496 // ['hpf'], /** * Applies a vibrato to the frequency of the oscillator. @@ -922,7 +922,7 @@ export const { hresonance, hpq } = registerControl('hresonance', 'hpq'); * s("bd sd [~ bd] sd,hh*8").lpf(2000).lpq("<0 10 20 30>") * */ -// currently an alias of 'resonance' https://github.com/tidalcycles/strudel/issues/496 +// currently an alias of 'resonance' https://codeberg.org/uzu/strudel/issues/496 export const { resonance, lpq } = registerControl('resonance', 'lpq'); /** * DJ filter, below 0.5 is low pass filter, above is high pass filter. @@ -1288,7 +1288,7 @@ export const { semitone } = registerControl('semitone'); // TODO: synth param export const { voice } = registerControl('voice'); -// voicings // https://github.com/tidalcycles/strudel/issues/506 +// voicings // https://codeberg.org/uzu/strudel/issues/506 // chord to voice, like C Eb Fm7 G7. the symbols can be defined via addVoicings export const { chord } = registerControl('chord'); // which dictionary to use for the voicings diff --git a/packages/core/cyclist.mjs b/packages/core/cyclist.mjs index bd2db1223..f28dc604c 100644 --- a/packages/core/cyclist.mjs +++ b/packages/core/cyclist.mjs @@ -1,6 +1,6 @@ /* -cyclist.mjs - event scheduler for a single strudel instance. for multi-instance scheduler, see - see -Copyright (C) 2022 Strudel contributors - see +cyclist.mjs - event scheduler for a single strudel instance. for multi-instance scheduler, see - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ @@ -65,7 +65,7 @@ export class Cyclist { (hap.whole.begin - this.num_cycles_at_cps_change) / this.cps + this.seconds_at_cps_change + latency; const duration = hap.duration / this.cps; // the following line is dumb and only here for backwards compatibility - // see https://github.com/tidalcycles/strudel/pull/1004 + // see https://codeberg.org/uzu/strudel/pulls/1004 const deadline = targetTime - phase; onTrigger?.(hap, deadline, duration, this.cps, targetTime); if (hap.value.cps !== undefined && this.cps != hap.value.cps) { diff --git a/packages/core/drawLine.mjs b/packages/core/drawLine.mjs index 91b86b4ab..7509c0f6f 100644 --- a/packages/core/drawLine.mjs +++ b/packages/core/drawLine.mjs @@ -1,6 +1,6 @@ /* drawLine.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/core/euclid.mjs b/packages/core/euclid.mjs index ad0b01486..1a5be78b4 100644 --- a/packages/core/euclid.mjs +++ b/packages/core/euclid.mjs @@ -2,7 +2,7 @@ euclid.mjs - Bjorklund/Euclidean/Diaspora rhythms Copyright (C) 2023 Rohan Drape and strudel contributors -See for authors of this file. +See for authors of this file. The Bjorklund algorithm implementation is ported from the Haskell Music Theory Haskell module by Rohan Drape - https://rohandrape.net/?t=hmt diff --git a/packages/core/evaluate.mjs b/packages/core/evaluate.mjs index e3e73d596..0559a93a2 100644 --- a/packages/core/evaluate.mjs +++ b/packages/core/evaluate.mjs @@ -1,6 +1,6 @@ /* evaluate.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/core/fraction.mjs b/packages/core/fraction.mjs index dc7fe27a7..2e3bc68ea 100644 --- a/packages/core/fraction.mjs +++ b/packages/core/fraction.mjs @@ -1,6 +1,6 @@ /* fraction.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/core/hap.mjs b/packages/core/hap.mjs index 7a9e0a620..a6e3c55ad 100644 --- a/packages/core/hap.mjs +++ b/packages/core/hap.mjs @@ -1,6 +1,6 @@ /* hap.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import Fraction from './fraction.mjs'; diff --git a/packages/core/index.mjs b/packages/core/index.mjs index a10b68b09..e4daf445a 100644 --- a/packages/core/index.mjs +++ b/packages/core/index.mjs @@ -1,6 +1,6 @@ /* index.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/core/neocyclist.mjs b/packages/core/neocyclist.mjs index ad22cf006..5c175dc1f 100644 --- a/packages/core/neocyclist.mjs +++ b/packages/core/neocyclist.mjs @@ -1,6 +1,6 @@ /* neocyclist.mjs - event scheduler like cyclist, except recieves clock pulses from clockworker in order to sync across multiple instances. -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/core/package.json b/packages/core/package.json index 89b047d2a..d6853c961 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -15,7 +15,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -27,7 +27,7 @@ "author": "Alex McLean (https://slab.org)", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, "homepage": "https://strudel.cc", "dependencies": { diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index efbae1f3e..76b02c21e 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -1,6 +1,6 @@ /* pattern.mjs - Core pattern representation for strudel -Copyright (C) 2025 Strudel contributors - see +Copyright (C) 2025 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/core/pick.mjs b/packages/core/pick.mjs index 702201fa6..206fa619b 100644 --- a/packages/core/pick.mjs +++ b/packages/core/pick.mjs @@ -1,6 +1,6 @@ /* pick.mjs - methods that use one pattern to pick events from other patterns. -Copyright (C) 2024 Strudel contributors - see +Copyright (C) 2024 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index e703909ff..7a8cbbab2 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -225,7 +225,7 @@ export function repl({ export const getTrigger = ({ getTime, defaultOutput }) => async (hap, deadline, duration, cps, t) => { - // TODO: get rid of deadline after https://github.com/tidalcycles/strudel/pull/1004 + // TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004 try { if (!hap.context.onTrigger || !hap.context.dominantTrigger) { await defaultOutput(hap, deadline, duration, cps, t); diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 5fd83bce3..54da989a5 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -1,6 +1,6 @@ /* signal.mjs - continuous patterns -Copyright (C) 2024 Strudel contributors - see +Copyright (C) 2024 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/core/speak.mjs b/packages/core/speak.mjs index 6ae959544..7e548a73b 100644 --- a/packages/core/speak.mjs +++ b/packages/core/speak.mjs @@ -1,6 +1,6 @@ /* speak.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/core/state.mjs b/packages/core/state.mjs index db1f77eff..162dc7da9 100644 --- a/packages/core/state.mjs +++ b/packages/core/state.mjs @@ -1,6 +1,6 @@ /* state.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/core/timespan.mjs b/packages/core/timespan.mjs index 4cbfb999a..0dbc74fc8 100644 --- a/packages/core/timespan.mjs +++ b/packages/core/timespan.mjs @@ -1,6 +1,6 @@ /* timespan.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/core/ui.mjs b/packages/core/ui.mjs index 86ceb2863..5a2c54a30 100644 --- a/packages/core/ui.mjs +++ b/packages/core/ui.mjs @@ -1,6 +1,6 @@ /* ui.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/core/util.mjs b/packages/core/util.mjs index b7b1e8413..b81811bae 100644 --- a/packages/core/util.mjs +++ b/packages/core/util.mjs @@ -1,6 +1,6 @@ /* util.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/core/value.mjs b/packages/core/value.mjs index ef98bc370..9496405eb 100644 --- a/packages/core/value.mjs +++ b/packages/core/value.mjs @@ -1,6 +1,6 @@ /* value.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ @@ -9,7 +9,7 @@ import { logger } from './logger.mjs'; export function unionWithObj(a, b, func) { if (b?.value !== undefined && Object.keys(b).length === 1) { - // https://github.com/tidalcycles/strudel/issues/1026 + // https://codeberg.org/uzu/strudel/issues/1026 logger(`[warn]: Can't do arithmetic on control pattern.`); return a; } diff --git a/packages/csound/package.json b/packages/csound/package.json index f200cdaf4..837a397f9 100644 --- a/packages/csound/package.json +++ b/packages/csound/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -28,9 +28,9 @@ ], "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@csound/browser": "6.18.7", "@strudel/core": "workspace:*", diff --git a/packages/desktopbridge/index.mjs b/packages/desktopbridge/index.mjs index 591bbe34f..ffb88783f 100644 --- a/packages/desktopbridge/index.mjs +++ b/packages/desktopbridge/index.mjs @@ -1,6 +1,6 @@ /* index.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/desktopbridge/package.json b/packages/desktopbridge/package.json index 45e89f44e..6609016e0 100644 --- a/packages/desktopbridge/package.json +++ b/packages/desktopbridge/package.json @@ -7,7 +7,7 @@ "type": "module", "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -19,11 +19,11 @@ "author": "Jade Rowland ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, "dependencies": { "@strudel/core": "workspace:*", "@tauri-apps/api": "^2.2.0" }, - "homepage": "https://github.com/tidalcycles/strudel#readme" + "homepage": "https://codeberg.org/uzu/strudel#readme" } \ No newline at end of file diff --git a/packages/draw/draw.mjs b/packages/draw/draw.mjs index 0576c297b..c727c1d81 100644 --- a/packages/draw/draw.mjs +++ b/packages/draw/draw.mjs @@ -1,6 +1,6 @@ /* draw.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/draw/package.json b/packages/draw/package.json index 51750da2b..f2555a5c4 100644 --- a/packages/draw/package.json +++ b/packages/draw/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "titdalcycles", @@ -25,9 +25,9 @@ "author": "Felix Roos ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/core": "workspace:*" }, diff --git a/packages/draw/pianoroll.mjs b/packages/draw/pianoroll.mjs index d874c9686..1cf218fa0 100644 --- a/packages/draw/pianoroll.mjs +++ b/packages/draw/pianoroll.mjs @@ -1,6 +1,6 @@ /* pianoroll.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/embed/package.json b/packages/embed/package.json index afd887887..3b88acc62 100644 --- a/packages/embed/package.json +++ b/packages/embed/package.json @@ -6,7 +6,7 @@ "type": "module", "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -18,7 +18,7 @@ "author": "Felix Roos ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme" + "homepage": "https://codeberg.org/uzu/strudel#readme" } diff --git a/packages/gamepad/package.json b/packages/gamepad/package.json index 8b91a15e9..25b4a87b5 100644 --- a/packages/gamepad/package.json +++ b/packages/gamepad/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "titdalcycles", @@ -25,9 +25,9 @@ "author": "Yuta Nakayama ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/core": "workspace:*" }, diff --git a/packages/hs2js/package.json b/packages/hs2js/package.json index c650dfc83..3b93a3ede 100644 --- a/packages/hs2js/package.json +++ b/packages/hs2js/package.json @@ -16,7 +16,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "haskell", @@ -25,9 +25,9 @@ "author": "Felix Roos ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel", + "homepage": "https://codeberg.org/uzu/strudel/", "dependencies": { "web-tree-sitter": "^0.24.7" }, diff --git a/packages/hydra/package.json b/packages/hydra/package.json index 4375ebefc..c1294eb00 100644 --- a/packages/hydra/package.json +++ b/packages/hydra/package.json @@ -17,7 +17,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -29,9 +29,9 @@ "author": "Felix Roos ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/core": "workspace:*", "@strudel/draw": "workspace:*", diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index ce7cdb0e2..af2dd3a62 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -1,6 +1,6 @@ /* midi.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/midi/package.json b/packages/midi/package.json index 2956b99f3..513dbe197 100644 --- a/packages/midi/package.json +++ b/packages/midi/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "titdalcycles", @@ -25,9 +25,9 @@ "author": "Felix Roos ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/core": "workspace:*", "@strudel/webaudio": "workspace:*", diff --git a/packages/mini/krill.pegjs b/packages/mini/krill.pegjs index a593b4167..8af82caee 100644 --- a/packages/mini/krill.pegjs +++ b/packages/mini/krill.pegjs @@ -1,6 +1,6 @@ /* krill.pegjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/mini/mini.mjs b/packages/mini/mini.mjs index 6277daa91..d138d9fa0 100644 --- a/packages/mini/mini.mjs +++ b/packages/mini/mini.mjs @@ -1,6 +1,6 @@ /* mini.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/mini/package.json b/packages/mini/package.json index 5ae0dc242..9c96292d7 100644 --- a/packages/mini/package.json +++ b/packages/mini/package.json @@ -16,7 +16,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -28,9 +28,9 @@ "author": "Felix Roos ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/core": "workspace:*" }, diff --git a/packages/motion/package.json b/packages/motion/package.json index 850a75bd1..1fa87d878 100644 --- a/packages/motion/package.json +++ b/packages/motion/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "titdalcycles", @@ -25,9 +25,9 @@ "author": "Yuta Nakayama ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/core": "workspace:*" }, diff --git a/packages/mqtt/mqtt.mjs b/packages/mqtt/mqtt.mjs index c2322600c..aef01bd93 100644 --- a/packages/mqtt/mqtt.mjs +++ b/packages/mqtt/mqtt.mjs @@ -1,6 +1,6 @@ /* mqtt.mjs - for patterning the internet of things from strudel -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/mqtt/package.json b/packages/mqtt/package.json index 9b9cdaa3b..0eba694c3 100644 --- a/packages/mqtt/package.json +++ b/packages/mqtt/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "titdalcycles", @@ -25,9 +25,9 @@ "author": "Alex McLean ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/core": "workspace:*", "paho-mqtt": "^1.1.0" diff --git a/packages/osc/osc.mjs b/packages/osc/osc.mjs index 3c7b92d4e..ac70b9e73 100644 --- a/packages/osc/osc.mjs +++ b/packages/osc/osc.mjs @@ -1,6 +1,6 @@ /* osc.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/osc/package.json b/packages/osc/package.json index 7e87c1e1a..62b00090a 100644 --- a/packages/osc/package.json +++ b/packages/osc/package.json @@ -17,7 +17,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -32,9 +32,9 @@ ], "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/core": "workspace:*", "osc-js": "^2.4.1" diff --git a/packages/osc/server.js b/packages/osc/server.js index 7e0b90592..75fc5b1c0 100644 --- a/packages/osc/server.js +++ b/packages/osc/server.js @@ -1,6 +1,6 @@ /* server.js - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/osc/tidal-sniffer.js b/packages/osc/tidal-sniffer.js index 10bf0ad05..95e1fc329 100644 --- a/packages/osc/tidal-sniffer.js +++ b/packages/osc/tidal-sniffer.js @@ -1,6 +1,6 @@ /* tidal-sniffer.js - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/reference/package.json b/packages/reference/package.json index fd7edadff..289520ddd 100644 --- a/packages/reference/package.json +++ b/packages/reference/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -28,9 +28,9 @@ ], "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "devDependencies": { "vite": "^6.0.11" } diff --git a/packages/repl/package.json b/packages/repl/package.json index 5487ce443..1c10aebf7 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -14,7 +14,7 @@ "type": "module", "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -29,9 +29,9 @@ ], "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/codemirror": "workspace:*", "@strudel/core": "workspace:*", diff --git a/packages/serial/package.json b/packages/serial/package.json index bf6e0dcab..df671cb1a 100644 --- a/packages/serial/package.json +++ b/packages/serial/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "titdalcycles", @@ -25,9 +25,9 @@ "author": "Alex McLean ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/core": "workspace:*" }, diff --git a/packages/serial/serial.mjs b/packages/serial/serial.mjs index e0eeacedd..692109522 100644 --- a/packages/serial/serial.mjs +++ b/packages/serial/serial.mjs @@ -1,6 +1,6 @@ /* serial.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/soundfonts/package.json b/packages/soundfonts/package.json index bf4fa2960..d227359c7 100644 --- a/packages/soundfonts/package.json +++ b/packages/soundfonts/package.json @@ -13,7 +13,7 @@ "type": "module", "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -25,9 +25,9 @@ "author": "Felix Roos ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/core": "workspace:*", "@strudel/webaudio": "workspace:*", diff --git a/packages/superdough/index.mjs b/packages/superdough/index.mjs index 3247c5b49..fd49fe338 100644 --- a/packages/superdough/index.mjs +++ b/packages/superdough/index.mjs @@ -1,6 +1,6 @@ /* index.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/superdough/package.json b/packages/superdough/package.json index a835f252d..8fc3cca78 100644 --- a/packages/superdough/package.json +++ b/packages/superdough/package.json @@ -16,7 +16,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -28,9 +28,9 @@ "author": "Felix Roos ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "devDependencies": { "vite": "^6.0.11", "vite-plugin-bundle-audioworklet": "workspace:*" diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 1069d4e84..819cdeb37 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -1,6 +1,6 @@ /* superdough.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/tidal/package.json b/packages/tidal/package.json index 8e4871617..960b09001 100644 --- a/packages/tidal/package.json +++ b/packages/tidal/package.json @@ -6,7 +6,7 @@ "module": "tidal.mjs", "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel/tree/main/packages/tidal" + "url": "git+https://codeberg.org/uzu/strudel/src/branch/main/packages/tidal" }, "keywords": [ "haskell", @@ -15,9 +15,9 @@ "author": "Felix Roos ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel/tree/main/packages/hs2js", + "homepage": "https://codeberg.org/uzu/strudel/src/branch/main/packages/hs2js", "dependencies": { "@strudel/core": "workspace:*", "@strudel/mini": "workspace:*", diff --git a/packages/tonal/package.json b/packages/tonal/package.json index 89f02b301..98b2a5325 100644 --- a/packages/tonal/package.json +++ b/packages/tonal/package.json @@ -14,7 +14,7 @@ "type": "module", "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -26,9 +26,9 @@ "author": "Felix Roos ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/core": "workspace:*", "@tonaljs/tonal": "^4.10.0", diff --git a/packages/tonal/tonal.mjs b/packages/tonal/tonal.mjs index 78183d228..4fd622158 100644 --- a/packages/tonal/tonal.mjs +++ b/packages/tonal/tonal.mjs @@ -1,6 +1,6 @@ /* tonal.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/tonal/tonleiter.mjs b/packages/tonal/tonleiter.mjs index 15288fcc8..3814394f6 100644 --- a/packages/tonal/tonleiter.mjs +++ b/packages/tonal/tonleiter.mjs @@ -178,7 +178,7 @@ export function renderVoicing({ chord, dictionary, offset = 0, n, mode = 'below' return notes; } -// https://github.com/tidalcycles/strudel/blob/14184993d0ee7d69c47df57ac864a1a0f99a893f/packages/tonal/tonleiter.mjs +// https://codeberg.org/uzu/strudel/blob/14184993d0ee7d69c47df57ac864a1a0f99a893f/packages/tonal/tonleiter.mjs const steps = [1, 0, 2, 0, 3, 4, 0, 5, 0, 6, 0, 7]; const notes = ['C', '', 'D', '', 'E', 'F', '', 'G', '', 'A', '', 'B']; const noteLetters = ['C', 'D', 'E', 'F', 'G', 'A', 'B']; diff --git a/packages/tonal/voicings.mjs b/packages/tonal/voicings.mjs index 0a08575db..d81911e01 100644 --- a/packages/tonal/voicings.mjs +++ b/packages/tonal/voicings.mjs @@ -1,6 +1,6 @@ /* voicings.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/transpiler/package.json b/packages/transpiler/package.json index 93648bbc7..1a20a78f8 100644 --- a/packages/transpiler/package.json +++ b/packages/transpiler/package.json @@ -14,7 +14,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -26,9 +26,9 @@ "author": "Felix Roos ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/core": "workspace:*", "@strudel/mini": "workspace:*", diff --git a/packages/transpiler/transpiler.mjs b/packages/transpiler/transpiler.mjs index 2e566305f..2f5ed5309 100644 --- a/packages/transpiler/transpiler.mjs +++ b/packages/transpiler/transpiler.mjs @@ -194,7 +194,7 @@ function isLabelStatement(node) { } // converts label expressions to p calls: "x: y" to "y.p('x')" -// see https://github.com/tidalcycles/strudel/issues/990 +// see https://codeberg.org/uzu/strudel/issues/990 function labelToP(node) { return { type: 'ExpressionStatement', diff --git a/packages/web/package.json b/packages/web/package.json index 652496fee..6264849eb 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -14,7 +14,7 @@ "type": "module", "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -29,9 +29,9 @@ ], "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/core": "workspace:*", "@strudel/mini": "workspace:*", diff --git a/packages/webaudio/index.mjs b/packages/webaudio/index.mjs index 59672b617..362e61c44 100644 --- a/packages/webaudio/index.mjs +++ b/packages/webaudio/index.mjs @@ -1,6 +1,6 @@ /* index.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/webaudio/package.json b/packages/webaudio/package.json index 5714fddf3..f984d5321 100644 --- a/packages/webaudio/package.json +++ b/packages/webaudio/package.json @@ -17,7 +17,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -29,9 +29,9 @@ "author": "Felix Roos ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/core": "workspace:*", "@strudel/draw": "workspace:*", diff --git a/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs index 44a683480..f80f114fd 100644 --- a/packages/webaudio/webaudio.mjs +++ b/packages/webaudio/webaudio.mjs @@ -1,6 +1,6 @@ /* webaudio.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ @@ -16,7 +16,7 @@ const hap2value = (hap) => { }; export const webaudioOutputTrigger = (t, hap, ct, cps) => superdough(hap2value(hap), t - ct, hap.duration / cps, cps); -// uses more precise, absolute t if available, see https://github.com/tidalcycles/strudel/pull/1004 +// uses more precise, absolute t if available, see https://codeberg.org/uzu/strudel/pulls/1004 export const webaudioOutput = (hap, deadline, hapDuration, cps, t) => superdough(hap2value(hap), t ? `=${t}` : deadline, hapDuration); diff --git a/packages/xen/package.json b/packages/xen/package.json index 76fecf525..b79aea6e6 100644 --- a/packages/xen/package.json +++ b/packages/xen/package.json @@ -14,7 +14,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -26,9 +26,9 @@ "author": "Felix Roos ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/core": "workspace:*" }, diff --git a/packages/xen/tune.mjs b/packages/xen/tune.mjs index feed38f4f..01303bf52 100644 --- a/packages/xen/tune.mjs +++ b/packages/xen/tune.mjs @@ -1,6 +1,6 @@ /* tune.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/xen/tunejs.js b/packages/xen/tunejs.js index 7b1a804a0..6b5e7cb7c 100644 --- a/packages/xen/tunejs.js +++ b/packages/xen/tunejs.js @@ -1,6 +1,6 @@ /* tunejs.js - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/xen/xen.mjs b/packages/xen/xen.mjs index 4077632a5..cc96f4110 100644 --- a/packages/xen/xen.mjs +++ b/packages/xen/xen.mjs @@ -1,6 +1,6 @@ /* xen.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ From 307e5ea2d7536dcef26b2e7d453b797bd0f78e87 Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Thu, 12 Jun 2025 14:45:35 +0100 Subject: [PATCH 45/74] less github --- package.json | 4 ++-- technical.manual.md | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 01c3e7d5d..be3e1f1c8 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "tidalcycles", @@ -43,7 +43,7 @@ "author": "Alex McLean (https://slab.org)", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, "homepage": "https://strudel.cc", "dependencies": { diff --git a/technical.manual.md b/technical.manual.md index 73180b145..9b09c741d 100644 --- a/technical.manual.md +++ b/technical.manual.md @@ -7,13 +7,13 @@ There are different packages for different purposes. They.. - split up the code into smaller chunks - can be selectively used to implement some sort of time based system -Please refer to the individual README files in the [packages folder](https://github.com/tidalcycles/strudel/tree/main/packages) +Please refer to the individual README files in the [packages folder](https://codeberg.org/uzu/strudel/src/branch/main/packages) ## REPL The [REPL](https://strudel.tidalcycles.org/) is the place where all packages come together to form a live coding system. It can also be seen as a reference implementation for users of the library. -More info in the [REPL README](https://github.com/tidalcycles/strudel/tree/main/repl) +More info in the [REPL README](https://codeberg.org/uzu/strudel/src/branch/main/packages/repl/README.md) # High Level Overview @@ -21,7 +21,7 @@ More info in the [REPL README](https://github.com/tidalcycles/strudel/tree/main/ ## 1. End User Code -The End User Code is written in JavaScript with added syntax sugar. The [eval package](https://github.com/tidalcycles/strudel/tree/main/packages/eval#strudelcycleseval) evaluates the user code +The End User Code is written in JavaScript with added syntax sugar. The [eval package](https://codeberg.org/uzu/strudel/src/branch/main/packages/eval#strudelcycleseval) evaluates the user code after a transpilation step, which resolves the syntax sugar. If you don't want the syntax sugar, you can omit the eval package and call the native javascript `eval` instead. ### 🍭 Syntax Sugar @@ -60,7 +60,7 @@ Shift will most likely be replaced with acorn in the future, see https://github. Another important part of the user code is the mini notation, which allows to express rhythms in a short manner. -- the mini notation is [implemented as a PEG grammar](https://github.com/tidalcycles/strudel/blob/main/packages/mini/krill.pegjs), living in the [mini package](https://github.com/tidalcycles/strudel/tree/main/packages/mini) +- the mini notation is [implemented as a PEG grammar](https://codeberg.org/uzu/strudel/src/branch/main/packages/mini/krill.pegjs), living in the [mini package](https://codeberg.org/uzu/strudel/src/branch/main/packages/mini) - it is based on [krill](https://github.com/Mdashdotdashn/krill) by Mdashdotdashn - the peg grammar is used to generate a parser with [peggyjs](https://peggyjs.org/) - the generated parser takes a mini notation string and outputs an AST @@ -182,12 +182,12 @@ Here is an example Hap value with different properties: At the time of writing this doc, the following outputs are supported: -- Web Audio API `.out()` see [/webaudio](https://github.com/tidalcycles/strudel/tree/main/packages/webaudio) -- MIDI `.midi()` see [/midi](https://github.com/tidalcycles/strudel/tree/main/packages/midi) -- OSC `.osc()` see [/osc](https://github.com/tidalcycles/strudel/tree/main/packages/osc) -- Serial `.serial()` see [/serial](https://github.com/tidalcycles/strudel/tree/main/packages/serial) -- Tone.js `.tone()` (deprecated?) [/tone](https://github.com/tidalcycles/strudel/tree/main/packages/tone) -- WebDirt `.webdirt()` (deprecated?) [/webdirt](https://github.com/tidalcycles/strudel/tree/main/packages/webdirt) -- Speech `.speak()` (experimental) part of [/core](https://github.com/tidalcycles/strudel/tree/main/packages/core) +- Web Audio API `.out()` see [/webaudio](https://codeberg.org/uzu/strudel/src/branch/main/packages/webaudio) +- MIDI `.midi()` see [/midi](https://codeberg.org/uzu/strudel/src/branch/main/packages/midi) +- OSC `.osc()` see [/osc](https://codeberg.org/uzu/strudel/src/branch/main/packages/osc) +- Serial `.serial()` see [/serial](https://codeberg.org/uzu/strudel/src/branch/main/packages/serial) +- Tone.js `.tone()` (deprecated?) [/tone](https://codeberg.org/uzu/strudel/src/branch/main/packages/tone) +- WebDirt `.webdirt()` (deprecated?) [/webdirt](https://codeberg.org/uzu/strudel/src/branch/main/packages/webdirt) +- Speech `.speak()` (experimental) part of [/core](https://codeberg.org/uzu/strudel/src/branch/main/packages/core) -These could change, so make sure to check the [packages folder](https://github.com/tidalcycles/strudel/tree/main/packages). +These could change, so make sure to check the [packages folder](https://codeberg.org/uzu/strudel/src/branch/main/packages). From c0ae12f32f49b3386bd3e07d8ffc754545df84bd Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 12 Jun 2025 17:26:19 +0100 Subject: [PATCH 46/74] add codeberg repo update command --- CONTRIBUTING.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 618830b9c..aa84cbce7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,6 +6,13 @@ Thanks for wanting to contribute!!! There are many ways you can add value to thi We are currently in the process of moving from github to codeberg -- not everything is working, please bear with us. +To update your local clone, you can run this command: + +``` +git remote set-url origin git@codeberg.org:uzu/strudel.git +``` + + ## Communication Channels To get in touch with the contributors, either From 990f1c6ece10f61dca48d23b81d95c755bf99d54 Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 12 Jun 2025 17:29:29 +0100 Subject: [PATCH 47/74] degithub --- README.md | 2 +- technical.manual.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 12ee85035..e225d9bde 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Licensing info for the default sound banks can be found over on the [dough-sampl There are many ways to contribute to this project! See [contribution guide](./CONTRIBUTING.md). - + diff --git a/technical.manual.md b/technical.manual.md index 9b09c741d..58f7cd934 100644 --- a/technical.manual.md +++ b/technical.manual.md @@ -54,7 +54,7 @@ This is how it works: - The AST is transformed to resolve the syntax sugar - The AST is used to generate code again (shift-codegen) -Shift will most likely be replaced with acorn in the future, see https://github.com/tidalcycles/strudel/issues/174 +Shift will most likely be replaced with acorn in the future, see https://codeberg.org/uzu/strudel/issues/174 ### Mini Notation From a3d9d68c45fb0ccbf883acfff68b15bcbb235b7b Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 12 Jun 2025 18:00:39 +0100 Subject: [PATCH 48/74] less github --- examples/tidal-repl/package.json | 6 +++--- jsdoc/jsdoc-synonyms.js | 2 +- my-patterns/README.md | 22 +++++++++------------- packages/core/test/value.test.mjs | 2 +- packages/hs2js/README.md | 2 +- website/agpl-header.txt | 4 ++-- website/src/config.ts | 2 +- website/src/user_pattern_utils.mjs | 2 +- 8 files changed, 19 insertions(+), 23 deletions(-) diff --git a/examples/tidal-repl/package.json b/examples/tidal-repl/package.json index 4da2f086b..7c1f55c38 100644 --- a/examples/tidal-repl/package.json +++ b/examples/tidal-repl/package.json @@ -11,7 +11,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" + "url": "git+https://codeberg.org/uzu/strudel.git }, "keywords": [ "titdalcycles", @@ -23,9 +23,9 @@ "author": "Felix Roos ", "license": "AGPL-3.0-or-later", "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" + "url": "https://codeberg.org/uzu/strudel/issues" }, - "homepage": "https://github.com/tidalcycles/strudel#readme", + "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/web": "workspace:*", "hs2js": "workspace:*" diff --git a/jsdoc/jsdoc-synonyms.js b/jsdoc/jsdoc-synonyms.js index 09190846f..0b52420bc 100644 --- a/jsdoc/jsdoc-synonyms.js +++ b/jsdoc/jsdoc-synonyms.js @@ -1,6 +1,6 @@ /* jsdoc-synonyms.js - Add support for @synonym tag -Copyright (C) 2023 Strudel contributors - see +Copyright (C) 2023 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/my-patterns/README.md b/my-patterns/README.md index c8d694ea8..5283ad81b 100644 --- a/my-patterns/README.md +++ b/my-patterns/README.md @@ -5,22 +5,24 @@ made into a pattern swatch. Example: +Please note: These instructions have not been fully tested/adapted since strudel moved to codeberg from github. PRs welcome! + ## deploy -### 1. fork the [strudel repo on github](https://github.com/tidalcycles/strudel.git) +### 1. fork the [strudel repo on codeberg](https://codeberg.org/uzu/strudel.git) -### 2. clone your fork to your machine `git clone https://github.com//strudel.git strudel && cd strudel` +### 2. clone your fork to your machine `git clone https://codeberg.org//strudel.git strudel && cd strudel` ### 3. create a separate branch like `git branch patternuary && git checkout patternuary` ### 4. save one or more .txt files in the my-patterns folder -### 5. edit `website/public/CNAME` to contain `.github.io/strudel` +### 5. edit `website/public/CNAME` to contain `.codeberg.page/strudel` -### 6. edit `website/astro.config.mjs` to use site: `https://.github.io` and base `/strudel`, like this +### 6. edit `website/astro.config.mjs` to use site: `https://.codeberg.page` and base `/strudel`, like this ```js -const site = 'https://.github.io'; +const site = 'https://.codeberg.page'; const base = '/strudel'; ``` @@ -30,15 +32,9 @@ const base = '/strudel'; git add . && git commit -m "site config" && git push --set-upstream origin ``` -### 8. deploy to github pages +### 8. deploy to codeberg pages -- go to settings -> pages and select "Github Actions" as source -- go to settings -> environments -> github-pages and press the edit button next to `main` and type in `patternuary` (under "Deployment branches") -- go to Actions -> `Build and Deploy` and click `Run workflow` with branch `patternuary` - -### 9. view your patterns at `.github.io/strudel/swatch/` - -Alternatively, github pages allows you to use a custom domain, like https://mycooldomain.org/swatch/. [See their documentation for details](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site). +### 9. view your patterns at `.codeberg.page/strudel/swatch/` ### 10. optional: automatic deployment diff --git a/packages/core/test/value.test.mjs b/packages/core/test/value.test.mjs index 87cba57d0..35d9f5e10 100644 --- a/packages/core/test/value.test.mjs +++ b/packages/core/test/value.test.mjs @@ -1,6 +1,6 @@ /* value.test.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2025 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/hs2js/README.md b/packages/hs2js/README.md index 24d9c7749..f84f9cded 100644 --- a/packages/hs2js/README.md +++ b/packages/hs2js/README.md @@ -2,7 +2,7 @@ Experimental haskell in javascript interpreter. Many haskell features are not implemented. This projects mainly exists to be able to write and interpret [Tidal Cycles](https://tidalcycles.org/) code in the browser, -as part of [Strudel](https://github.com/tidalcycles/strudel). This project could only exist thanks to [tree-sitter-haskell](https://github.com/tree-sitter/tree-sitter-haskell). +as part of [Strudel](https://codeberg.org/uzu/strudel). This project could only exist thanks to [tree-sitter-haskell](https://github.com/tree-sitter/tree-sitter-haskell). ## Installation diff --git a/website/agpl-header.txt b/website/agpl-header.txt index 6fd0c0fc9..8b7b6d631 100644 --- a/website/agpl-header.txt +++ b/website/agpl-header.txt @@ -1,10 +1,10 @@ /* Strudel - javascript-based environment for live coding algorithmic (musical) patterns -https://strudel.cc / https://github.com/tidalcycles/strudel/ +https://strudel.cc / https://codeberg.org/uzu/strudel/ Copyright (C) Strudel contributors -https://github.com/tidalcycles/strudel/graphs/contributors +https://codeberg.org/uzu/strudel/activity/contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by diff --git a/website/src/config.ts b/website/src/config.ts index e88b7f3e1..490a067b2 100644 --- a/website/src/config.ts +++ b/website/src/config.ts @@ -28,7 +28,7 @@ export const KNOWN_LANGUAGES = { } as const; export const KNOWN_LANGUAGE_CODES = Object.values(KNOWN_LANGUAGES); -export const GITHUB_EDIT_URL = `https://github.com/tidalcycles/strudel/tree/main/website`; +export const GITHUB_EDIT_URL = `https://codeberg.org/uzu/strudel/src/branch/main/website`; export const COMMUNITY_INVITE_URL = `https://discord.com/invite/HGEdXmRkzT`; diff --git a/website/src/user_pattern_utils.mjs b/website/src/user_pattern_utils.mjs index 18442cb4c..791c6a8f9 100644 --- a/website/src/user_pattern_utils.mjs +++ b/website/src/user_pattern_utils.mjs @@ -96,7 +96,7 @@ export async function loadDBPatterns() { } } -// reason: https://github.com/tidalcycles/strudel/issues/857 +// reason: https://codeberg.org/uzu/strudel/issues/857 const $activePattern = sessionAtom('activePattern', ''); export function setActivePattern(key) { From fec38bd5d2d0f0a0162bbc08325cecf6fcc84d31 Mon Sep 17 00:00:00 2001 From: yaxu Date: Fri, 13 Jun 2025 00:07:24 +0200 Subject: [PATCH 49/74] Update .github/workflows/test.yml --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 180f87521..6f2246790 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ on: [push, pull_request] jobs: build: - runs-on: ubuntu-latest + runs-on: toplap-runner strategy: matrix: node-version: [20] From 1141b1803b92469ef87a7be3022c7cf7a321599d Mon Sep 17 00:00:00 2001 From: yaxu Date: Fri, 13 Jun 2025 00:08:39 +0200 Subject: [PATCH 50/74] Update .github/workflows/test.yml --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6f2246790..180f87521 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ on: [push, pull_request] jobs: build: - runs-on: toplap-runner + runs-on: ubuntu-latest strategy: matrix: node-version: [20] From 8c8e91417079fd90629d7067d21e19697d53fe3c Mon Sep 17 00:00:00 2001 From: yaxu Date: Fri, 13 Jun 2025 08:53:53 +0200 Subject: [PATCH 51/74] Update .github/workflows/test.yml --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 180f87521..498cd800b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ on: [push, pull_request] jobs: build: - runs-on: ubuntu-latest + runs-on: docker strategy: matrix: node-version: [20] From 062201d1dce00779f400236abd38724843571f1d Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 13 Jun 2025 08:07:35 +0100 Subject: [PATCH 52/74] fix json --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index be3e1f1c8..d18291eb2 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", @@ -74,4 +74,4 @@ "vitest": "^3.0.4", "vite-plugin-bundle-audioworklet": "workspace:*" } -} +} \ No newline at end of file From dcac254790eafe8a35155da2c77595ea2ddc0b09 Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 13 Jun 2025 08:17:55 +0100 Subject: [PATCH 53/74] missing double quotes --- examples/tidal-repl/package.json | 2 +- packages/codemirror/package.json | 2 +- packages/core/package.json | 2 +- packages/csound/package.json | 2 +- packages/desktopbridge/package.json | 4 ++-- packages/draw/package.json | 2 +- packages/embed/package.json | 2 +- packages/gamepad/package.json | 2 +- packages/hs2js/package.json | 2 +- packages/hydra/package.json | 2 +- packages/midi/package.json | 2 +- packages/mini/package.json | 2 +- packages/motion/package.json | 2 +- packages/mqtt/package.json | 2 +- packages/osc/package.json | 2 +- packages/reference/package.json | 2 +- packages/repl/package.json | 2 +- packages/serial/package.json | 2 +- packages/soundfonts/package.json | 2 +- packages/superdough/package.json | 2 +- packages/tonal/package.json | 2 +- packages/transpiler/package.json | 2 +- packages/web/package.json | 2 +- packages/webaudio/package.json | 2 +- packages/xen/package.json | 2 +- 25 files changed, 26 insertions(+), 26 deletions(-) diff --git a/examples/tidal-repl/package.json b/examples/tidal-repl/package.json index 7c1f55c38..21c8ee177 100644 --- a/examples/tidal-repl/package.json +++ b/examples/tidal-repl/package.json @@ -11,7 +11,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "titdalcycles", diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index 1ffe1724c..4f8508c90 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -13,7 +13,7 @@ "type": "module", "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", diff --git a/packages/core/package.json b/packages/core/package.json index d6853c961..f4170f2b5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -15,7 +15,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", diff --git a/packages/csound/package.json b/packages/csound/package.json index 837a397f9..04a5ff246 100644 --- a/packages/csound/package.json +++ b/packages/csound/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", diff --git a/packages/desktopbridge/package.json b/packages/desktopbridge/package.json index 6609016e0..a01ec1f8a 100644 --- a/packages/desktopbridge/package.json +++ b/packages/desktopbridge/package.json @@ -7,7 +7,7 @@ "type": "module", "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", @@ -26,4 +26,4 @@ "@tauri-apps/api": "^2.2.0" }, "homepage": "https://codeberg.org/uzu/strudel#readme" -} \ No newline at end of file +} diff --git a/packages/draw/package.json b/packages/draw/package.json index f2555a5c4..ee1b8dd00 100644 --- a/packages/draw/package.json +++ b/packages/draw/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "titdalcycles", diff --git a/packages/embed/package.json b/packages/embed/package.json index 3b88acc62..a0cc33de1 100644 --- a/packages/embed/package.json +++ b/packages/embed/package.json @@ -6,7 +6,7 @@ "type": "module", "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", diff --git a/packages/gamepad/package.json b/packages/gamepad/package.json index 25b4a87b5..3efb2e084 100644 --- a/packages/gamepad/package.json +++ b/packages/gamepad/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "titdalcycles", diff --git a/packages/hs2js/package.json b/packages/hs2js/package.json index 3b93a3ede..c0bf8fa8c 100644 --- a/packages/hs2js/package.json +++ b/packages/hs2js/package.json @@ -16,7 +16,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "haskell", diff --git a/packages/hydra/package.json b/packages/hydra/package.json index c1294eb00..b022de87d 100644 --- a/packages/hydra/package.json +++ b/packages/hydra/package.json @@ -17,7 +17,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", diff --git a/packages/midi/package.json b/packages/midi/package.json index 513dbe197..4efd329d8 100644 --- a/packages/midi/package.json +++ b/packages/midi/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "titdalcycles", diff --git a/packages/mini/package.json b/packages/mini/package.json index 9c96292d7..5d94301d4 100644 --- a/packages/mini/package.json +++ b/packages/mini/package.json @@ -16,7 +16,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", diff --git a/packages/motion/package.json b/packages/motion/package.json index 1fa87d878..a7db05680 100644 --- a/packages/motion/package.json +++ b/packages/motion/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "titdalcycles", diff --git a/packages/mqtt/package.json b/packages/mqtt/package.json index 0eba694c3..f522e3354 100644 --- a/packages/mqtt/package.json +++ b/packages/mqtt/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "titdalcycles", diff --git a/packages/osc/package.json b/packages/osc/package.json index 62b00090a..7d19fbbfc 100644 --- a/packages/osc/package.json +++ b/packages/osc/package.json @@ -17,7 +17,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", diff --git a/packages/reference/package.json b/packages/reference/package.json index 289520ddd..8dc966cc2 100644 --- a/packages/reference/package.json +++ b/packages/reference/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", diff --git a/packages/repl/package.json b/packages/repl/package.json index 1c10aebf7..bfa404c75 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -14,7 +14,7 @@ "type": "module", "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", diff --git a/packages/serial/package.json b/packages/serial/package.json index df671cb1a..c04a69cd0 100644 --- a/packages/serial/package.json +++ b/packages/serial/package.json @@ -13,7 +13,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "titdalcycles", diff --git a/packages/soundfonts/package.json b/packages/soundfonts/package.json index d227359c7..2c87a6e05 100644 --- a/packages/soundfonts/package.json +++ b/packages/soundfonts/package.json @@ -13,7 +13,7 @@ "type": "module", "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", diff --git a/packages/superdough/package.json b/packages/superdough/package.json index 8fc3cca78..439b83718 100644 --- a/packages/superdough/package.json +++ b/packages/superdough/package.json @@ -16,7 +16,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", diff --git a/packages/tonal/package.json b/packages/tonal/package.json index 98b2a5325..614e86f74 100644 --- a/packages/tonal/package.json +++ b/packages/tonal/package.json @@ -14,7 +14,7 @@ "type": "module", "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", diff --git a/packages/transpiler/package.json b/packages/transpiler/package.json index 1a20a78f8..2a5e39776 100644 --- a/packages/transpiler/package.json +++ b/packages/transpiler/package.json @@ -14,7 +14,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", diff --git a/packages/web/package.json b/packages/web/package.json index 6264849eb..0feddc82d 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -14,7 +14,7 @@ "type": "module", "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", diff --git a/packages/webaudio/package.json b/packages/webaudio/package.json index f984d5321..5cc0a5538 100644 --- a/packages/webaudio/package.json +++ b/packages/webaudio/package.json @@ -17,7 +17,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", diff --git a/packages/xen/package.json b/packages/xen/package.json index b79aea6e6..88c2bb082 100644 --- a/packages/xen/package.json +++ b/packages/xen/package.json @@ -14,7 +14,7 @@ }, "repository": { "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git + "url": "git+https://codeberg.org/uzu/strudel.git" }, "keywords": [ "tidalcycles", From 3dbae7907cba29b340aea371bdaac2ba4fdec8b1 Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 13 Jun 2025 08:22:46 +0100 Subject: [PATCH 54/74] ignore .pnpm-store --- .prettierignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.prettierignore b/.prettierignore index 950e59f19..c9584aca0 100644 --- a/.prettierignore +++ b/.prettierignore @@ -13,3 +13,4 @@ pnpm-workspace.yaml website/.astro !tidal-drum-machines.json !tidal-drum-machines-alias.json +.pnpm-store From d7b83e200c01820a0733dcb99bef577a34089209 Mon Sep 17 00:00:00 2001 From: Bernhard Wagner Date: Thu, 12 Jun 2025 19:32:41 +0200 Subject: [PATCH 55/74] fix issue #1368 euclidLegatoRot --- .gitignore | 1 + packages/core/euclid.mjs | 44 ++++++----- packages/core/test/euclid.test.js | 89 +++++++++++++++++++++++ packages/core/test/util.test.mjs | 65 ++++++++++++++--- packages/core/util.mjs | 2 + pnpm-workspace.yaml | 16 ++-- test/__snapshots__/examples.test.mjs.snap | 25 ++++--- test/__snapshots__/tunes.test.mjs.snap | 24 +++--- 8 files changed, 205 insertions(+), 61 deletions(-) create mode 100644 packages/core/test/euclid.test.js diff --git a/.gitignore b/.gitignore index 59d9940e1..2be3ee698 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,4 @@ fabric.properties samples/* !samples/README.md +.idea/ diff --git a/packages/core/euclid.mjs b/packages/core/euclid.mjs index 1a5be78b4..c3bb7a6d5 100644 --- a/packages/core/euclid.mjs +++ b/packages/core/euclid.mjs @@ -10,9 +10,8 @@ https://rohandrape.net/?t=hmt This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -import { Pattern, timeCat, register, silence } from './pattern.mjs'; +import { timeCat, register, silence } from './pattern.mjs'; import { rotate, flatten, splitAt, zipWith } from './util.mjs'; -import Fraction from './fraction.mjs'; const left = function (n, x) { const [ons, offs] = n; @@ -42,29 +41,26 @@ const _bjork = function (n, x) { export const bjork = function (ons, steps) { const inverted = ons < 0; - ons = Math.abs(ons); - const offs = steps - ons; - const x = Array(ons).fill([1]); - const y = Array(offs).fill([0]); - const result = _bjork([ons, offs], [x, y]); - const p = flatten(result[1][0]).concat(flatten(result[1][1])); - if (inverted) { - return p.map((x) => (x === 0 ? 1 : 0)); - } - return p; + const absOns = Math.abs(ons); + const offs = steps - absOns; + const ones = Array(absOns).fill([1]); + const zeros = Array(offs).fill([0]); + const result = _bjork([absOns, offs], [ones, zeros]); + const pattern = flatten(result[1][0]).concat(flatten(result[1][1])); + return inverted ? pattern.map((x) => 1 - x) : pattern; }; /** - * Changes the structure of the pattern to form an euclidean rhythm. - * Euclidian rhythms are rhythms obtained using the greatest common + * Changes the structure of the pattern to form an Euclidean rhythm. + * Euclidean rhythms are rhythms obtained using the greatest common * divisor of two numbers. They were described in 2004 by Godfried - * Toussaint, a canadian computer scientist. Euclidian rhythms are + * Toussaint, a Canadian computer scientist. Euclidean rhythms are * really useful for computer/algorithmic music because they can * describe a large number of rhythms with a couple of numbers. * * @memberof Pattern * @name euclid - * @param {number} pulses the number of onsets / beats + * @param {number} pulses the number of onsets/beats * @param {number} steps the number of steps to fill * @returns Pattern * @example @@ -76,7 +72,7 @@ export const bjork = function (ons, steps) { * Like `euclid`, but has an additional parameter for 'rotating' the resulting sequence. * @memberof Pattern * @name euclidRot - * @param {number} pulses the number of onsets / beats + * @param {number} pulses the number of onsets/beats * @param {number} steps the number of steps to fill * @param {number} rotation offset in steps * @returns Pattern @@ -86,13 +82,13 @@ export const bjork = function (ons, steps) { */ /** - * @example // A thirteenth century Persian rhythm called Khafif-e-ramal. + * @example // A thirteenth-century Persian rhythm called Khafif-e-ramal. * note("c3").euclid(2,5) * @example // The archetypal pattern of the Cumbia from Colombia, as well as a Calypso rhythm from Trinidad. * note("c3").euclid(3,4) * @example // Another thirteenth century Persian rhythm by the name of Khafif-e-ramal, as well as a Rumanian folk-dance rhythm. * note("c3").euclidRot(3,5,2) - * @example // A Ruchenitza rhythm used in a Bulgarian folk-dance. + * @example // A Ruchenitza rhythm used in a Bulgarian folk dance. * note("c3").euclid(3,7) * @example // The Cuban tresillo pattern. * note("c3").euclid(3,8) @@ -151,8 +147,10 @@ export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], fun * so there will be no gaps. * @name euclidLegato * @memberof Pattern - * @param {number} pulses the number of onsets / beats + * @param {number} pulses the number of onsets/beats * @param {number} steps the number of steps to fill + * @param rotation offset in steps + * @param pat * @example * note("c3").euclidLegato(3,8) */ @@ -161,13 +159,13 @@ const _euclidLegato = function (pulses, steps, rotation, pat) { if (pulses < 1) { return silence; } - const bin_pat = _euclidRot(pulses, steps, rotation); + const bin_pat = _euclidRot(pulses, steps, 0); const gapless = bin_pat .join('') .split('1') .slice(1) .map((s) => [s.length + 1, true]); - return pat.struct(timeCat(...gapless)); + return pat.struct(timeCat(...gapless)).late(rotation / steps); }; export const euclidLegato = register(['euclidLegato'], function (pulses, steps, pat) { @@ -180,7 +178,7 @@ export const euclidLegato = register(['euclidLegato'], function (pulses, steps, * the resulting sequence * @name euclidLegatoRot * @memberof Pattern - * @param {number} pulses the number of onsets / beats + * @param {number} pulses the number of onsets/beats * @param {number} steps the number of steps to fill * @param {number} rotation offset in steps * @example diff --git a/packages/core/test/euclid.test.js b/packages/core/test/euclid.test.js new file mode 100644 index 000000000..a33ec9514 --- /dev/null +++ b/packages/core/test/euclid.test.js @@ -0,0 +1,89 @@ +import { bjork } from '../euclid.mjs'; +import { describe, expect, it } from 'vitest'; +import { fastcat } from '../pattern.mjs'; + +describe('bjork', () => { + it('should apply bjorklund to ons and steps', () => { + expect(bjork(3, 8)).toStrictEqual([1, 0, 0, 1, 0, 0, 1, 0]); + expect(bjork(-3, 8)).toStrictEqual([0, 1, 1, 0, 1, 1, 0, 1]); + expect(bjork(8, 8)).toStrictEqual([1, 1, 1, 1, 1, 1, 1, 1]); + expect(bjork(-8, 8)).toStrictEqual([0, 0, 0, 0, 0, 0, 0, 0]); + expect(bjork(5, 8)).toStrictEqual([1, 0, 1, 1, 0, 1, 1, 0]); + }); +}); + +describe('euclid', () => { + it('Can create euclid', () => { + expect( + fastcat('a') + .euclid(3, 8) + .firstCycle() + .sort((a, b) => a.part.begin.sub(b.part.begin)) + .map((a) => a.showWhole(true)), + ).toStrictEqual(['0/1 → 1/8: a', '3/8 → 1/2: a', '3/4 → 7/8: a']); + expect( + fastcat('a') + .euclid(5, 8) + .firstCycle() + .sort((a, b) => a.part.begin.sub(b.part.begin)) + .map((a) => a.showWhole(true)), + ).toStrictEqual(['0/1 → 1/8: a', '1/4 → 3/8: a', '3/8 → 1/2: a', '5/8 → 3/4: a', '3/4 → 7/8: a']); + }); +}); + +describe('euclidRot', () => { + it('Can create euclidRot', () => { + expect( + fastcat('a') + .euclidRot(3, 8, 2) + .firstCycle() + .sort((a, b) => a.part.begin.sub(b.part.begin)) + .map((a) => a.showWhole(true)), + ).toStrictEqual(['0/1 → 1/8: a', '1/4 → 3/8: a', '5/8 → 3/4: a']); + expect( + fastcat('a') + .euclidRot(5, 8, 2) + .firstCycle() + .sort((a, b) => a.part.begin.sub(b.part.begin)) + .map((a) => a.showWhole(true)), + ).toStrictEqual(['0/1 → 1/8: a', '1/4 → 3/8: a', '1/2 → 5/8: a', '5/8 → 3/4: a', '7/8 → 1/1: a']); + }); +}); + +describe('euclidLegato', () => { + it('Can create euclidLegato', () => { + expect( + fastcat('a') + .euclidLegato(3, 8) + .firstCycle() + .sort((a, b) => a.part.begin.sub(b.part.begin)) + .map((a) => a.showWhole(true)), + ).toStrictEqual(['0/1 → 3/8: a', '3/8 → 3/4: a', '3/4 → 1/1: a']); + expect( + fastcat('a') + .euclidLegato(5, 8) + .firstCycle() + .sort((a, b) => a.part.begin.sub(b.part.begin)) + .map((a) => a.showWhole(true)), + ).toStrictEqual(['0/1 → 1/4: a', '1/4 → 3/8: a', '3/8 → 5/8: a', '5/8 → 3/4: a', '3/4 → 1/1: a']); + }); +}); + +describe('euclidLegatoRot', () => { + it('Can create euclidLegatoRot', () => { + expect( + fastcat('a') + .euclidLegatoRot(3, 8, 2) + .firstCycle() + .sort((a, b) => a.part.begin.sub(b.part.begin)) + .map((a) => a.showWhole(true)), + ).toStrictEqual(['0/1 → 1/4: a', '1/4 → 5/8: a', '5/8 → 1/1: a']); + expect( + fastcat('a') + .euclidLegatoRot(5, 8, 2) + .firstCycle() + .sort((a, b) => a.part.begin.sub(b.part.begin)) + .map((a) => a.showWhole(true)), + ).toStrictEqual(['0/1 → 1/4: a', '1/4 → 1/2: a', '1/2 → 5/8: a', '5/8 → 7/8: a', '7/8 → 1/1: a']); + }); +}); diff --git a/packages/core/test/util.test.mjs b/packages/core/test/util.test.mjs index 6b1053a3f..a511e0fc2 100644 --- a/packages/core/test/util.test.mjs +++ b/packages/core/test/util.test.mjs @@ -6,21 +6,25 @@ This program is free software: you can redistribute it and/or modify it under th import { pure } from '../pattern.mjs'; import { - isNote, - tokenizeNote, - noteToMidi, - midiToFreq, - freqToMidi, _mod, compose, + flatten, + fractionalArgs, + freqToMidi, getFrequency, getPlayableNoteValue, - parseNumeral, - parseFractional, + isNote, + midiToFreq, + noteToMidi, numeralArgs, - fractionalArgs, + parseFractional, + parseNumeral, + rotate, + splitAt, + tokenizeNote, + zipWith, } from '../util.mjs'; -import { describe, it, expect } from 'vitest'; +import { describe, expect, it } from 'vitest'; describe('isNote', () => { it('should recognize notes without accidentals', () => { @@ -233,3 +237,46 @@ describe('fractionalArgs', () => { expect(add('q', 2)).toBe(2.25); }); }); + +describe('rotate', () => { + it('should rotate array to the left', () => { + expect(rotate([0, 1, 2, 3], 2)).toStrictEqual([2, 3, 0, 1]); + expect(rotate([0, 1, 2, 3], 0)).toStrictEqual([0, 1, 2, 3]); + expect(rotate([0, 1, 2, 3], -3)).toStrictEqual([1, 2, 3, 0]); + expect(rotate([0, 1, 2, 3], 3)).toStrictEqual([3, 0, 1, 2]); + expect(rotate([0], 3)).toStrictEqual([0]); + expect(rotate([], 3)).toStrictEqual([]); + }); +}); + +describe('flatten', () => { + it('should flatten array by one level', () => { + expect(flatten([0, 1, 2, 3])).toStrictEqual([0, 1, 2, 3]); + expect(flatten([0, 1, [2, 3]])).toStrictEqual([0, 1, 2, 3]); + expect(flatten([0, [1, [2, 3]]])).toStrictEqual([0, 1, [2, 3]]); + expect(flatten([0])).toStrictEqual([0]); + expect(flatten([])).toStrictEqual([]); + }); +}); + +describe('splitAt', () => { + it('should split array into two', () => { + expect(splitAt(2, [0, 1, 2, 3])).toStrictEqual([ + [0, 1], + [2, 3], + ]); + expect(splitAt(0, [0, 1, 2, 3])).toStrictEqual([[], [0, 1, 2, 3]]); + expect(splitAt(-3, [0, 1, 2, 3])).toStrictEqual([[0], [1, 2, 3]]); + expect(splitAt(3, [0, 1, 2, 3])).toStrictEqual([[0, 1, 2], [3]]); + }); +}); + +describe('zipWith', () => { + it('should use the function to combine the two arrays element-wise', () => { + expect(zipWith((a, b) => a + b, [0, 1, 2, 3], [0, 1, 2, 3])).toStrictEqual([0, 2, 4, 6]); + expect(zipWith((a, b) => a + b, [0, 1, 2, 3], [0, 1, 2])).toStrictEqual([0, 2, 4, NaN]); + expect(zipWith((a, b) => a + b, [0, 1, 2], [0, 1, 2, 3])).toStrictEqual([0, 2, 4]); + expect(zipWith((a) => a, [0, 1, 2], [1, 2, 3, 0])).toStrictEqual([0, 1, 2]); + expect(zipWith((a, b) => b, [0, 1, 2], [1, 2, 3, 0])).toStrictEqual([1, 2, 3]); + }); +}); diff --git a/packages/core/util.mjs b/packages/core/util.mjs index b81811bae..756fac8e8 100644 --- a/packages/core/util.mjs +++ b/packages/core/util.mjs @@ -162,6 +162,7 @@ export const compose = (...funcs) => pipe(...funcs.reverse()); // Removes 'None' values from given list export const removeUndefineds = (xs) => xs.filter((x) => x != undefined); +// flattens by one level export const flatten = (arr) => [].concat(...arr); export const id = (a) => a; @@ -237,6 +238,7 @@ export const splitAt = function (index, value) { return [value.slice(0, index), value.slice(index)]; }; +// Uses the function f to combine the arrays xs, ys element-wise export const zipWith = (f, xs, ys) => xs.map((n, i) => f(n, ys[i])); export const pairs = function (xs) { diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ef8cc99d5..a4fca9ff7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,12 @@ packages: - # all packages in direct subdirs of packages/ - - "packages/*" - - "examples/*" - - "tools/dbpatch" - - "website/" + - packages/* + - examples/* + - tools/dbpatch + - website/ + +onlyBuiltDependencies: + - esbuild + - nx + - sharp + - tree-sitter + - tree-sitter-haskell diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 0eacfdfb0..85948f0c0 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -3145,18 +3145,19 @@ exports[`runs examples > example "euclidLegato" example index 0 1`] = ` exports[`runs examples > example "euclidLegatoRot" example index 0 1`] = ` [ - "[ 0/1 → 1/4 | note:c3 ]", - "[ 1/4 → 3/4 | note:c3 ]", - "[ 3/4 → 1/1 | note:c3 ]", - "[ 1/1 → 5/4 | note:c3 ]", - "[ 5/4 → 7/4 | note:c3 ]", - "[ 7/4 → 2/1 | note:c3 ]", - "[ 2/1 → 9/4 | note:c3 ]", - "[ 9/4 → 11/4 | note:c3 ]", - "[ 11/4 → 3/1 | note:c3 ]", - "[ 3/1 → 13/4 | note:c3 ]", - "[ 13/4 → 15/4 | note:c3 ]", - "[ 15/4 → 4/1 | note:c3 ]", + "[ -1/5 ⇜ (0/1 → 1/5) | note:c3 ]", + "[ 1/5 → 2/5 | note:c3 ]", + "[ 2/5 → 4/5 | note:c3 ]", + "[ 4/5 → 6/5 | note:c3 ]", + "[ 6/5 → 7/5 | note:c3 ]", + "[ 7/5 → 9/5 | note:c3 ]", + "[ 9/5 → 11/5 | note:c3 ]", + "[ 11/5 → 12/5 | note:c3 ]", + "[ 12/5 → 14/5 | note:c3 ]", + "[ 14/5 → 16/5 | note:c3 ]", + "[ 16/5 → 17/5 | note:c3 ]", + "[ 17/5 → 19/5 | note:c3 ]", + "[ (19/5 → 4/1) ⇝ 21/5 | note:c3 ]", ] `; diff --git a/test/__snapshots__/tunes.test.mjs.snap b/test/__snapshots__/tunes.test.mjs.snap index 06267b8f9..4a0ddd539 100644 --- a/test/__snapshots__/tunes.test.mjs.snap +++ b/test/__snapshots__/tunes.test.mjs.snap @@ -7318,12 +7318,12 @@ exports[`renders tunes > tune: randomBells 1`] = ` [ "[ -9/8 ⇜ (0/1 → 3/8) | gain:0.6 note:A3 velocity:0.5989903202280402 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ -3/4 ⇜ (0/1 → 3/4) | gain:0.6 note:C5 velocity:0.8369929669424891 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", - "[ 0/1 → 3/2 | note:D2 s:bass clip:1 gain:0.8 ]", + "[ 0/1 → 3/2 | note:F2 s:bass clip:1 gain:0.8 ]", "[ 0/1 → 9/4 | gain:0.6 note:D3 velocity:0.5 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 3/8 → 21/8 | gain:0.6 note:F5 velocity:0.9213038925081491 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 3/4 → 3/1 | gain:0.6 note:C5 velocity:0.8426077850162983 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", - "[ 3/2 → 9/4 | note:D2 s:bass clip:1 gain:0.8 ]", - "[ 9/4 → 3/1 | note:D2 s:bass clip:1 gain:0.8 ]", + "[ 3/2 → 9/4 | note:F2 s:bass clip:1 gain:0.8 ]", + "[ 9/4 → 3/1 | note:F2 s:bass clip:1 gain:0.8 ]", "[ 9/4 → 9/2 | gain:0.6 note:D4 velocity:0.7006962578743696 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 21/8 → 39/8 | gain:0.6 note:C4 velocity:0.6507943943142891 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 3/1 → 9/2 | note:D2 s:bass clip:1 gain:0.8 ]", @@ -7335,12 +7335,12 @@ exports[`renders tunes > tune: randomBells 1`] = ` "[ (21/4 → 6/1) ⇝ 27/4 | gain:0.6 note:D4 velocity:0.6988155404105783 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 39/8 ⇜ (6/1 → 51/8) | gain:0.6 note:D5 velocity:0.8758113365620375 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 21/4 ⇜ (6/1 → 27/4) | gain:0.6 note:D4 velocity:0.6988155404105783 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", - "[ 6/1 → 15/2 | note:A2 s:bass clip:1 gain:0.8 ]", + "[ 6/1 → 15/2 | note:D2 s:bass clip:1 gain:0.8 ]", "[ 6/1 → 33/4 | gain:0.6 note:G4 velocity:0.7597710825502872 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 51/8 → 69/8 | gain:0.6 note:G4 velocity:0.7743164440616965 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 27/4 → 9/1 | gain:0.6 note:C5 velocity:0.8362447572872043 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", - "[ 15/2 → 33/4 | note:A2 s:bass clip:1 gain:0.8 ]", - "[ 33/4 → 9/1 | note:A2 s:bass clip:1 gain:0.8 ]", + "[ 15/2 → 33/4 | note:D2 s:bass clip:1 gain:0.8 ]", + "[ 33/4 → 9/1 | note:D2 s:bass clip:1 gain:0.8 ]", "[ 33/4 → 21/2 | gain:0.6 note:A3 velocity:0.5914018759503961 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 69/8 → 87/8 | gain:0.6 note:G4 velocity:0.754063542932272 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 9/1 → 21/2 | note:A2 s:bass clip:1 gain:0.8 ]", @@ -7352,12 +7352,12 @@ exports[`renders tunes > tune: randomBells 1`] = ` "[ (45/4 → 12/1) ⇝ 51/4 | gain:0.6 note:A4 velocity:0.7972785895690322 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 87/8 ⇜ (12/1 → 99/8) | gain:0.6 note:F4 velocity:0.7347871446982026 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 45/4 ⇜ (12/1 → 51/4) | gain:0.6 note:A4 velocity:0.7972785895690322 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", - "[ 12/1 → 27/2 | note:G2 s:bass clip:1 gain:0.8 ]", + "[ 12/1 → 27/2 | note:A2 s:bass clip:1 gain:0.8 ]", "[ 12/1 → 57/4 | gain:0.6 note:G5 velocity:0.9797635599970818 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 99/8 → 117/8 | gain:0.6 note:C4 velocity:0.6662392104044557 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 51/4 → 15/1 | gain:0.6 note:F5 velocity:0.9516951469704509 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", - "[ 27/2 → 57/4 | note:G2 s:bass clip:1 gain:0.8 ]", - "[ 57/4 → 15/1 | note:G2 s:bass clip:1 gain:0.8 ]", + "[ 27/2 → 57/4 | note:A2 s:bass clip:1 gain:0.8 ]", + "[ 57/4 → 15/1 | note:A2 s:bass clip:1 gain:0.8 ]", "[ 57/4 → 33/2 | gain:0.6 note:F5 velocity:0.9182533202692866 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 117/8 → 135/8 | gain:0.6 note:G3 velocity:0.5711571052670479 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 15/1 → 33/2 | note:G2 s:bass clip:1 gain:0.8 ]", @@ -7369,12 +7369,12 @@ exports[`renders tunes > tune: randomBells 1`] = ` "[ (69/4 → 18/1) ⇝ 75/4 | gain:0.6 note:F3 velocity:0.5081270858645439 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 135/8 ⇜ (18/1 → 147/8) | gain:0.6 note:F5 velocity:0.9456470254808664 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 69/4 ⇜ (18/1 → 75/4) | gain:0.6 note:F3 velocity:0.5081270858645439 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", - "[ 18/1 → 39/2 | note:F2 s:bass clip:1 gain:0.8 ]", + "[ 18/1 → 39/2 | note:G2 s:bass clip:1 gain:0.8 ]", "[ 18/1 → 81/4 | gain:0.6 note:A3 velocity:0.6086445553228259 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 147/8 → 165/8 | gain:0.6 note:F3 velocity:0.5062594395130873 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 75/4 → 21/1 | gain:0.6 note:D4 velocity:0.6716219391673803 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", - "[ 39/2 → 81/4 | note:F2 s:bass clip:1 gain:0.8 ]", - "[ 81/4 → 21/1 | note:F2 s:bass clip:1 gain:0.8 ]", + "[ 39/2 → 81/4 | note:G2 s:bass clip:1 gain:0.8 ]", + "[ 81/4 → 21/1 | note:G2 s:bass clip:1 gain:0.8 ]", "[ 81/4 → 45/2 | gain:0.6 note:D4 velocity:0.7043459005653858 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 165/8 → 183/8 | gain:0.6 note:D5 velocity:0.8878388572484255 s:bell delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", "[ 21/1 → 45/2 | note:F2 s:bass clip:1 gain:0.8 ]", From 7f50bcebd29a36d90b13678ebc6473a512b0f291 Mon Sep 17 00:00:00 2001 From: Bernhard Wagner Date: Fri, 13 Jun 2025 10:28:03 +0200 Subject: [PATCH 56/74] avoid floating point inaccuracy by using Fraction --- packages/core/euclid.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/euclid.mjs b/packages/core/euclid.mjs index c3bb7a6d5..2ee9962da 100644 --- a/packages/core/euclid.mjs +++ b/packages/core/euclid.mjs @@ -12,6 +12,7 @@ This program is free software: you can redistribute it and/or modify it under th import { timeCat, register, silence } from './pattern.mjs'; import { rotate, flatten, splitAt, zipWith } from './util.mjs'; +import Fraction, { lcm } from './fraction.mjs'; const left = function (n, x) { const [ons, offs] = n; @@ -165,7 +166,7 @@ const _euclidLegato = function (pulses, steps, rotation, pat) { .split('1') .slice(1) .map((s) => [s.length + 1, true]); - return pat.struct(timeCat(...gapless)).late(rotation / steps); + return pat.struct(timeCat(...gapless)).late(Fraction(rotation).div(steps)); }; export const euclidLegato = register(['euclidLegato'], function (pulses, steps, pat) { From 38e7fe606c3a355277ca5dbf7ef88568f0762ca8 Mon Sep 17 00:00:00 2001 From: yaxu Date: Fri, 13 Jun 2025 16:47:40 +0200 Subject: [PATCH 57/74] Update .github/workflows/deploy.yml --- .github/workflows/deploy.yml | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9a853c783..669073887 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,13 +2,6 @@ name: Build and Deploy on: [workflow_dispatch] -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - deployments: write - # Allow one concurrent deployment concurrency: group: "pages" @@ -16,10 +9,9 @@ concurrency: jobs: build: - runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} + runs-on: docker + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 @@ -35,15 +27,11 @@ jobs: - name: Build run: pnpm build - - name: Setup Pages - uses: actions/configure-pages@v2 - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - # Upload entire repository - path: "./website/dist" - - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 + - name: Deploy + run: | + eval $(ssh-agent -s) + echo "$SSH_PRIVATE_KEY" | ssh-add - + apt update && apt install -y rsync + mkdir ~/.ssh + ssh-keyscan matrix.toplap.org > ~/.ssh/known_hosts + rsync -atv --progress ./website/dist strudel@matrix.toplap.org:/home/strudel/dist \ No newline at end of file From 8fba92f447cab4a104b694f86dd74303dcc5cfc8 Mon Sep 17 00:00:00 2001 From: yaxu Date: Fri, 13 Jun 2025 17:10:37 +0200 Subject: [PATCH 58/74] Update .github/workflows/deploy.yml --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 669073887..8eefebc2c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -34,4 +34,4 @@ jobs: apt update && apt install -y rsync mkdir ~/.ssh ssh-keyscan matrix.toplap.org > ~/.ssh/known_hosts - rsync -atv --progress ./website/dist strudel@matrix.toplap.org:/home/strudel/dist \ No newline at end of file + rsync -atv --progress ./website/dist/ strudel@matrix.toplap.org:/home/strudel/deploy \ No newline at end of file From 3428e18e7d36142ed10acd54251cc2ccce87b6f1 Mon Sep 17 00:00:00 2001 From: yaxu Date: Fri, 13 Jun 2025 17:26:37 +0200 Subject: [PATCH 59/74] Update .github/workflows/deploy.yml --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8eefebc2c..f82f0e30f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -34,4 +34,4 @@ jobs: apt update && apt install -y rsync mkdir ~/.ssh ssh-keyscan matrix.toplap.org > ~/.ssh/known_hosts - rsync -atv --progress ./website/dist/ strudel@matrix.toplap.org:/home/strudel/deploy \ No newline at end of file + rsync -atv --delete --progress ./website/dist/ strudel@matrix.toplap.org:/home/strudel/deploy \ No newline at end of file From 0c193238c55217725045de3d1fdfe75a7a2636d5 Mon Sep 17 00:00:00 2001 From: yaxu Date: Fri, 13 Jun 2025 17:31:03 +0200 Subject: [PATCH 60/74] Update .github/workflows/deploy.yml --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f82f0e30f..d8049d767 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -34,4 +34,4 @@ jobs: apt update && apt install -y rsync mkdir ~/.ssh ssh-keyscan matrix.toplap.org > ~/.ssh/known_hosts - rsync -atv --delete --progress ./website/dist/ strudel@matrix.toplap.org:/home/strudel/deploy \ No newline at end of file + rsync -atv --delete --delete-after --progress ./website/dist/ strudel@matrix.toplap.org:/home/strudel/deploy \ No newline at end of file From 47de9e45ff9e976908f2aa59a54bda211933b6cb Mon Sep 17 00:00:00 2001 From: yaxu Date: Fri, 13 Jun 2025 17:31:33 +0200 Subject: [PATCH 61/74] Update .github/workflows/deploy.yml --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d8049d767..561fee658 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 20 - cache: "pnpm" + # cache: "pnpm" - name: Install Dependencies run: pnpm install From aa20526963fe216fe9ed82f5f3bda114a31f024e Mon Sep 17 00:00:00 2001 From: yaxu Date: Fri, 13 Jun 2025 17:33:47 +0200 Subject: [PATCH 62/74] Update .github/workflows/test.yml --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 498cd800b..765f5958d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - cache: 'pnpm' + # cache: 'pnpm' - run: pnpm install - run: pnpm run format-check - run: pnpm run lint From 2bc3d69fb0e240214cd24304aef4d85d0f7d12ad Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 14 Jun 2025 14:01:11 +0200 Subject: [PATCH 63/74] replace github links in release notes --- .../content/blog/release-0.0.2-schwindlig.mdx | 52 ++-- .../blog/release-0.0.2.1-stuermisch.mdx | 80 ++--- .../content/blog/release-0.0.3-maelstrom.mdx | 104 +++---- .../src/content/blog/release-0.0.4-gischt.mdx | 62 ++-- .../content/blog/release-0.3.0-donauwelle.mdx | 74 ++--- .../content/blog/release-0.4.0-brandung.mdx | 8 +- .../src/content/blog/release-0.5.0-wirbel.mdx | 54 ++-- .../blog/release-0.6.0-zimtschnecke.mdx | 104 +++---- .../content/blog/release-0.7.0-zuckerguss.mdx | 118 +++---- .../blog/release-0.8.0-himbeermuffin.mdx | 84 ++--- .../blog/release-0.9.0-bananenbrot.mdx | 82 ++--- .../blog/release-1.0.0-geburtstagskuchen.mdx | 288 +++++++++--------- website/src/content/blog/year-2.mdx | 4 +- 13 files changed, 557 insertions(+), 557 deletions(-) diff --git a/website/src/content/blog/release-0.0.2-schwindlig.mdx b/website/src/content/blog/release-0.0.2-schwindlig.mdx index cf4bc19d6..8dbff4928 100644 --- a/website/src/content/blog/release-0.0.2-schwindlig.mdx +++ b/website/src/content/blog/release-0.0.2-schwindlig.mdx @@ -8,33 +8,33 @@ author: froos ## What's Changed -- Most work done as [commits to main](https://github.com/tidalcycles/strudel/commits/2a0d8c3f77ff7b34e82602e2d02400707f367316) -- repl + reify functions by @felixroos in https://github.com/tidalcycles/strudel/pull/2 -- Fix path by @yaxu in https://github.com/tidalcycles/strudel/pull/3 -- update readme for local dev by @kindohm in https://github.com/tidalcycles/strudel/pull/4 -- Patternify all the things by @yaxu in https://github.com/tidalcycles/strudel/pull/5 -- krill parser + improved repl by @felixroos in https://github.com/tidalcycles/strudel/pull/6 -- fixed editor crash by @felixroos in https://github.com/tidalcycles/strudel/pull/7 -- timeCat by @yaxu in https://github.com/tidalcycles/strudel/pull/8 -- Bugfix every, and create more top level functions by @yaxu in https://github.com/tidalcycles/strudel/pull/9 -- Failing test for `when` WIP by @yaxu in https://github.com/tidalcycles/strudel/pull/10 -- Added mask() and struct() by @yaxu in https://github.com/tidalcycles/strudel/pull/11 -- Add continuous signals (sine, cosine, saw, etc) by @yaxu in https://github.com/tidalcycles/strudel/pull/13 -- add apply and layer, and missing div/mul methods by @yaxu in https://github.com/tidalcycles/strudel/pull/15 -- higher latencyHint by @felixroos in https://github.com/tidalcycles/strudel/pull/16 -- test: 📦 Add missing dependency and a CI check, to prevent oversights ;p by @puria in https://github.com/tidalcycles/strudel/pull/17 -- fix: 💄 Enhance visualisation of the Tutorial on mobile by @puria in https://github.com/tidalcycles/strudel/pull/19 -- Stateful queries and events (WIP) by @yaxu in https://github.com/tidalcycles/strudel/pull/14 -- Fix resolveState by @yaxu in https://github.com/tidalcycles/strudel/pull/22 -- added \_asNumber + interpret numbers as midi by @felixroos in https://github.com/tidalcycles/strudel/pull/21 -- Update package.json by @ChiakiUehira in https://github.com/tidalcycles/strudel/pull/23 -- packaging by @felixroos in https://github.com/tidalcycles/strudel/pull/24 +- Most work done as [commits to main](https://codeberg.org/uzu/strudel/commit/2a0d8c3f77ff7b34e82602e2d02400707f367316) +- repl + reify functions by @felixroos in https://codeberg.org/uzu/strudel/pulls/2 +- Fix path by @yaxu in https://codeberg.org/uzu/strudel/pulls/3 +- update readme for local dev by @kindohm in https://codeberg.org/uzu/strudel/pulls/4 +- Patternify all the things by @yaxu in https://codeberg.org/uzu/strudel/pulls/5 +- krill parser + improved repl by @felixroos in https://codeberg.org/uzu/strudel/pulls/6 +- fixed editor crash by @felixroos in https://codeberg.org/uzu/strudel/pulls/7 +- timeCat by @yaxu in https://codeberg.org/uzu/strudel/pulls/8 +- Bugfix every, and create more top level functions by @yaxu in https://codeberg.org/uzu/strudel/pulls/9 +- Failing test for `when` WIP by @yaxu in https://codeberg.org/uzu/strudel/pulls/10 +- Added mask() and struct() by @yaxu in https://codeberg.org/uzu/strudel/pulls/11 +- Add continuous signals (sine, cosine, saw, etc) by @yaxu in https://codeberg.org/uzu/strudel/pulls/13 +- add apply and layer, and missing div/mul methods by @yaxu in https://codeberg.org/uzu/strudel/pulls/15 +- higher latencyHint by @felixroos in https://codeberg.org/uzu/strudel/pulls/16 +- test: 📦 Add missing dependency and a CI check, to prevent oversights ;p by @puria in https://codeberg.org/uzu/strudel/pulls/17 +- fix: 💄 Enhance visualisation of the Tutorial on mobile by @puria in https://codeberg.org/uzu/strudel/pulls/19 +- Stateful queries and events (WIP) by @yaxu in https://codeberg.org/uzu/strudel/pulls/14 +- Fix resolveState by @yaxu in https://codeberg.org/uzu/strudel/pulls/22 +- added \_asNumber + interpret numbers as midi by @felixroos in https://codeberg.org/uzu/strudel/pulls/21 +- Update package.json by @ChiakiUehira in https://codeberg.org/uzu/strudel/pulls/23 +- packaging by @felixroos in https://codeberg.org/uzu/strudel/pulls/24 ## New Contributors -- @felixroos made their first contribution in https://github.com/tidalcycles/strudel/pull/2 -- @kindohm made their first contribution in https://github.com/tidalcycles/strudel/pull/4 -- @puria made their first contribution in https://github.com/tidalcycles/strudel/pull/17 -- @ChiakiUehira made their first contribution in https://github.com/tidalcycles/strudel/pull/23 +- @felixroos made their first contribution in https://codeberg.org/uzu/strudel/pulls/2 +- @kindohm made their first contribution in https://codeberg.org/uzu/strudel/pulls/4 +- @puria made their first contribution in https://codeberg.org/uzu/strudel/pulls/17 +- @ChiakiUehira made their first contribution in https://codeberg.org/uzu/strudel/pulls/23 -**Full Changelog**: https://github.com/tidalcycles/strudel/commits/2a0d8c3f77ff7b34e82602e2d02400707f367316 +**Full Changelog**: https://codeberg.org/uzu/strudel/commit/2a0d8c3f77ff7b34e82602e2d02400707f367316 diff --git a/website/src/content/blog/release-0.0.2.1-stuermisch.mdx b/website/src/content/blog/release-0.0.2.1-stuermisch.mdx index edefd2b09..441d99f76 100644 --- a/website/src/content/blog/release-0.0.2.1-stuermisch.mdx +++ b/website/src/content/blog/release-0.0.2.1-stuermisch.mdx @@ -7,47 +7,47 @@ author: froos ## What's Changed -- Add chunk, chunkBack and iterBack by @yaxu in https://github.com/tidalcycles/strudel/pull/25 -- Update tutorial.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/37 -- Update tutorial.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/38 -- Compose by @felixroos in https://github.com/tidalcycles/strudel/pull/40 -- Fix polymeter by @yaxu in https://github.com/tidalcycles/strudel/pull/44 -- First run at squeezeBind, ref #32 by @yaxu in https://github.com/tidalcycles/strudel/pull/48 -- Implement `chop()` by @yaxu in https://github.com/tidalcycles/strudel/pull/50 -- OSC and SuperDirt support by @yaxu in https://github.com/tidalcycles/strudel/pull/27 -- More functions by @yaxu in https://github.com/tidalcycles/strudel/pull/56 -- More functions by @yaxu in https://github.com/tidalcycles/strudel/pull/61 -- Separate out strudel.mjs, make index.mjs aggregate module by @yaxu in https://github.com/tidalcycles/strudel/pull/62 -- Speech output by @felixroos in https://github.com/tidalcycles/strudel/pull/67 -- use new fixed version of osc-js package by @felixroos in https://github.com/tidalcycles/strudel/pull/68 -- First effort at rand() by @yaxu in https://github.com/tidalcycles/strudel/pull/69 -- More randomness, fix `rand`, and add `brand`, `irand` and `choose` by @yaxu in https://github.com/tidalcycles/strudel/pull/70 -- webaudio package by @felixroos in https://github.com/tidalcycles/strudel/pull/26 -- Port `perlin` noise, `rangex`, and `palindrome` by @yaxu in https://github.com/tidalcycles/strudel/pull/73 -- More random functions by @yaxu in https://github.com/tidalcycles/strudel/pull/74 -- Try to fix appLeft / appRight by @yaxu in https://github.com/tidalcycles/strudel/pull/75 -- Basic webserial support by @yaxu in https://github.com/tidalcycles/strudel/pull/80 -- Webaudio in REPL by @felixroos in https://github.com/tidalcycles/strudel/pull/77 -- add `striate()` by @yaxu in https://github.com/tidalcycles/strudel/pull/76 -- Tidy up a couple of old files by @mindofmatthew in https://github.com/tidalcycles/strudel/pull/84 -- Add pattern composers, implements #82 by @yaxu in https://github.com/tidalcycles/strudel/pull/83 -- Fiddles with cat/stack by @yaxu in https://github.com/tidalcycles/strudel/pull/90 -- Paper by @felixroos in https://github.com/tidalcycles/strudel/pull/98 -- Change to Affero GPL by @yaxu in https://github.com/tidalcycles/strudel/pull/101 -- Work on Codemirror 6 highlighting by @mindofmatthew in https://github.com/tidalcycles/strudel/pull/102 -- Codemirror 6 by @felixroos in https://github.com/tidalcycles/strudel/pull/97 -- Tune tests by @felixroos in https://github.com/tidalcycles/strudel/pull/104 -- /embed package: web component for repl by @felixroos in https://github.com/tidalcycles/strudel/pull/106 -- Reset, Restart and other composers by @felixroos in https://github.com/tidalcycles/strudel/pull/88 -- Embed style by @felixroos in https://github.com/tidalcycles/strudel/pull/109 -- In source doc by @yaxu in https://github.com/tidalcycles/strudel/pull/105 -- `.brak()`, `.inside()` and `.outside()` by @yaxu in https://github.com/tidalcycles/strudel/pull/112 -- loopAt by @yaxu in https://github.com/tidalcycles/strudel/pull/114 -- Osc timing improvements by @yaxu in https://github.com/tidalcycles/strudel/pull/113 +- Add chunk, chunkBack and iterBack by @yaxu in https://codeberg.org/uzu/strudel/pulls/25 +- Update tutorial.mdx by @bwagner in https://codeberg.org/uzu/strudel/pulls/37 +- Update tutorial.mdx by @bwagner in https://codeberg.org/uzu/strudel/pulls/38 +- Compose by @felixroos in https://codeberg.org/uzu/strudel/pulls/40 +- Fix polymeter by @yaxu in https://codeberg.org/uzu/strudel/pulls/44 +- First run at squeezeBind, ref #32 by @yaxu in https://codeberg.org/uzu/strudel/pulls/48 +- Implement `chop()` by @yaxu in https://codeberg.org/uzu/strudel/pulls/50 +- OSC and SuperDirt support by @yaxu in https://codeberg.org/uzu/strudel/pulls/27 +- More functions by @yaxu in https://codeberg.org/uzu/strudel/pulls/56 +- More functions by @yaxu in https://codeberg.org/uzu/strudel/pulls/61 +- Separate out strudel.mjs, make index.mjs aggregate module by @yaxu in https://codeberg.org/uzu/strudel/pulls/62 +- Speech output by @felixroos in https://codeberg.org/uzu/strudel/pulls/67 +- use new fixed version of osc-js package by @felixroos in https://codeberg.org/uzu/strudel/pulls/68 +- First effort at rand() by @yaxu in https://codeberg.org/uzu/strudel/pulls/69 +- More randomness, fix `rand`, and add `brand`, `irand` and `choose` by @yaxu in https://codeberg.org/uzu/strudel/pulls/70 +- webaudio package by @felixroos in https://codeberg.org/uzu/strudel/pulls/26 +- Port `perlin` noise, `rangex`, and `palindrome` by @yaxu in https://codeberg.org/uzu/strudel/pulls/73 +- More random functions by @yaxu in https://codeberg.org/uzu/strudel/pulls/74 +- Try to fix appLeft / appRight by @yaxu in https://codeberg.org/uzu/strudel/pulls/75 +- Basic webserial support by @yaxu in https://codeberg.org/uzu/strudel/pulls/80 +- Webaudio in REPL by @felixroos in https://codeberg.org/uzu/strudel/pulls/77 +- add `striate()` by @yaxu in https://codeberg.org/uzu/strudel/pulls/76 +- Tidy up a couple of old files by @mindofmatthew in https://codeberg.org/uzu/strudel/pulls/84 +- Add pattern composers, implements #82 by @yaxu in https://codeberg.org/uzu/strudel/pulls/83 +- Fiddles with cat/stack by @yaxu in https://codeberg.org/uzu/strudel/pulls/90 +- Paper by @felixroos in https://codeberg.org/uzu/strudel/pulls/98 +- Change to Affero GPL by @yaxu in https://codeberg.org/uzu/strudel/pulls/101 +- Work on Codemirror 6 highlighting by @mindofmatthew in https://codeberg.org/uzu/strudel/pulls/102 +- Codemirror 6 by @felixroos in https://codeberg.org/uzu/strudel/pulls/97 +- Tune tests by @felixroos in https://codeberg.org/uzu/strudel/pulls/104 +- /embed package: web component for repl by @felixroos in https://codeberg.org/uzu/strudel/pulls/106 +- Reset, Restart and other composers by @felixroos in https://codeberg.org/uzu/strudel/pulls/88 +- Embed style by @felixroos in https://codeberg.org/uzu/strudel/pulls/109 +- In source doc by @yaxu in https://codeberg.org/uzu/strudel/pulls/105 +- `.brak()`, `.inside()` and `.outside()` by @yaxu in https://codeberg.org/uzu/strudel/pulls/112 +- loopAt by @yaxu in https://codeberg.org/uzu/strudel/pulls/114 +- Osc timing improvements by @yaxu in https://codeberg.org/uzu/strudel/pulls/113 ## New Contributors -- @bwagner made their first contribution in https://github.com/tidalcycles/strudel/pull/37 -- @mindofmatthew made their first contribution in https://github.com/tidalcycles/strudel/pull/84 +- @bwagner made their first contribution in https://codeberg.org/uzu/strudel/pulls/37 +- @mindofmatthew made their first contribution in https://codeberg.org/uzu/strudel/pulls/84 -**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.0.2...@strudel.cycles/core@0.1.0 +**Full Changelog**: https://codeberg.org/uzu/strudel/compare/v0.0.2...@strudel.cycles/core@0.1.0 diff --git a/website/src/content/blog/release-0.0.3-maelstrom.mdx b/website/src/content/blog/release-0.0.3-maelstrom.mdx index 3710a9ce5..5141b34a7 100644 --- a/website/src/content/blog/release-0.0.3-maelstrom.mdx +++ b/website/src/content/blog/release-0.0.3-maelstrom.mdx @@ -8,59 +8,59 @@ author: froos ## What's Changed -- Add chunk, chunkBack and iterBack by @yaxu in https://github.com/tidalcycles/strudel/pull/25 -- Update tutorial.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/37 -- Update tutorial.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/38 -- Compose by @felixroos in https://github.com/tidalcycles/strudel/pull/40 -- Fix polymeter by @yaxu in https://github.com/tidalcycles/strudel/pull/44 -- First run at squeezeBind, ref #32 by @yaxu in https://github.com/tidalcycles/strudel/pull/48 -- Implement `chop()` by @yaxu in https://github.com/tidalcycles/strudel/pull/50 -- OSC and SuperDirt support by @yaxu in https://github.com/tidalcycles/strudel/pull/27 -- More functions by @yaxu in https://github.com/tidalcycles/strudel/pull/56 -- More functions by @yaxu in https://github.com/tidalcycles/strudel/pull/61 -- Separate out strudel.mjs, make index.mjs aggregate module by @yaxu in https://github.com/tidalcycles/strudel/pull/62 -- Speech output by @felixroos in https://github.com/tidalcycles/strudel/pull/67 -- use new fixed version of osc-js package by @felixroos in https://github.com/tidalcycles/strudel/pull/68 -- First effort at rand() by @yaxu in https://github.com/tidalcycles/strudel/pull/69 -- More randomness, fix `rand`, and add `brand`, `irand` and `choose` by @yaxu in https://github.com/tidalcycles/strudel/pull/70 -- webaudio package by @felixroos in https://github.com/tidalcycles/strudel/pull/26 -- Port `perlin` noise, `rangex`, and `palindrome` by @yaxu in https://github.com/tidalcycles/strudel/pull/73 -- More random functions by @yaxu in https://github.com/tidalcycles/strudel/pull/74 -- Try to fix appLeft / appRight by @yaxu in https://github.com/tidalcycles/strudel/pull/75 -- Basic webserial support by @yaxu in https://github.com/tidalcycles/strudel/pull/80 -- Webaudio in REPL by @felixroos in https://github.com/tidalcycles/strudel/pull/77 -- add `striate()` by @yaxu in https://github.com/tidalcycles/strudel/pull/76 -- Tidy up a couple of old files by @mindofmatthew in https://github.com/tidalcycles/strudel/pull/84 -- Add pattern composers, implements #82 by @yaxu in https://github.com/tidalcycles/strudel/pull/83 -- Fiddles with cat/stack by @yaxu in https://github.com/tidalcycles/strudel/pull/90 -- Paper by @felixroos in https://github.com/tidalcycles/strudel/pull/98 -- Change to Affero GPL by @yaxu in https://github.com/tidalcycles/strudel/pull/101 -- Work on Codemirror 6 highlighting by @mindofmatthew in https://github.com/tidalcycles/strudel/pull/102 -- Codemirror 6 by @felixroos in https://github.com/tidalcycles/strudel/pull/97 -- Tune tests by @felixroos in https://github.com/tidalcycles/strudel/pull/104 -- /embed package: web component for repl by @felixroos in https://github.com/tidalcycles/strudel/pull/106 -- Reset, Restart and other composers by @felixroos in https://github.com/tidalcycles/strudel/pull/88 -- Embed style by @felixroos in https://github.com/tidalcycles/strudel/pull/109 -- In source doc by @yaxu in https://github.com/tidalcycles/strudel/pull/105 -- `.brak()`, `.inside()` and `.outside()` by @yaxu in https://github.com/tidalcycles/strudel/pull/112 -- loopAt by @yaxu in https://github.com/tidalcycles/strudel/pull/114 -- Osc timing improvements by @yaxu in https://github.com/tidalcycles/strudel/pull/113 -- react package + vite build by @felixroos in https://github.com/tidalcycles/strudel/pull/116 -- In source doc by @felixroos in https://github.com/tidalcycles/strudel/pull/117 -- fix: #108 by @felixroos in https://github.com/tidalcycles/strudel/pull/123 -- fix: #122 ctrl enter would add newline by @felixroos in https://github.com/tidalcycles/strudel/pull/124 -- Webdirt by @felixroos in https://github.com/tidalcycles/strudel/pull/121 -- Fix link to contributing to tutorial docs by @stephendwolff in https://github.com/tidalcycles/strudel/pull/129 -- Pianoroll enhancements by @felixroos in https://github.com/tidalcycles/strudel/pull/131 -- add createParam + createParams by @felixroos in https://github.com/tidalcycles/strudel/pull/110 -- remove cycle + delta from onTrigger by @felixroos in https://github.com/tidalcycles/strudel/pull/135 -- Scheduler improvements by @felixroos in https://github.com/tidalcycles/strudel/pull/134 -- add onTrigger helper by @felixroos in https://github.com/tidalcycles/strudel/pull/136 +- Add chunk, chunkBack and iterBack by @yaxu in https://codeberg.org/uzu/strudel/pulls/25 +- Update tutorial.mdx by @bwagner in https://codeberg.org/uzu/strudel/pulls/37 +- Update tutorial.mdx by @bwagner in https://codeberg.org/uzu/strudel/pulls/38 +- Compose by @felixroos in https://codeberg.org/uzu/strudel/pulls/40 +- Fix polymeter by @yaxu in https://codeberg.org/uzu/strudel/pulls/44 +- First run at squeezeBind, ref #32 by @yaxu in https://codeberg.org/uzu/strudel/pulls/48 +- Implement `chop()` by @yaxu in https://codeberg.org/uzu/strudel/pulls/50 +- OSC and SuperDirt support by @yaxu in https://codeberg.org/uzu/strudel/pulls/27 +- More functions by @yaxu in https://codeberg.org/uzu/strudel/pulls/56 +- More functions by @yaxu in https://codeberg.org/uzu/strudel/pulls/61 +- Separate out strudel.mjs, make index.mjs aggregate module by @yaxu in https://codeberg.org/uzu/strudel/pulls/62 +- Speech output by @felixroos in https://codeberg.org/uzu/strudel/pulls/67 +- use new fixed version of osc-js package by @felixroos in https://codeberg.org/uzu/strudel/pulls/68 +- First effort at rand() by @yaxu in https://codeberg.org/uzu/strudel/pulls/69 +- More randomness, fix `rand`, and add `brand`, `irand` and `choose` by @yaxu in https://codeberg.org/uzu/strudel/pulls/70 +- webaudio package by @felixroos in https://codeberg.org/uzu/strudel/pulls/26 +- Port `perlin` noise, `rangex`, and `palindrome` by @yaxu in https://codeberg.org/uzu/strudel/pulls/73 +- More random functions by @yaxu in https://codeberg.org/uzu/strudel/pulls/74 +- Try to fix appLeft / appRight by @yaxu in https://codeberg.org/uzu/strudel/pulls/75 +- Basic webserial support by @yaxu in https://codeberg.org/uzu/strudel/pulls/80 +- Webaudio in REPL by @felixroos in https://codeberg.org/uzu/strudel/pulls/77 +- add `striate()` by @yaxu in https://codeberg.org/uzu/strudel/pulls/76 +- Tidy up a couple of old files by @mindofmatthew in https://codeberg.org/uzu/strudel/pulls/84 +- Add pattern composers, implements #82 by @yaxu in https://codeberg.org/uzu/strudel/pulls/83 +- Fiddles with cat/stack by @yaxu in https://codeberg.org/uzu/strudel/pulls/90 +- Paper by @felixroos in https://codeberg.org/uzu/strudel/pulls/98 +- Change to Affero GPL by @yaxu in https://codeberg.org/uzu/strudel/pulls/101 +- Work on Codemirror 6 highlighting by @mindofmatthew in https://codeberg.org/uzu/strudel/pulls/102 +- Codemirror 6 by @felixroos in https://codeberg.org/uzu/strudel/pulls/97 +- Tune tests by @felixroos in https://codeberg.org/uzu/strudel/pulls/104 +- /embed package: web component for repl by @felixroos in https://codeberg.org/uzu/strudel/pulls/106 +- Reset, Restart and other composers by @felixroos in https://codeberg.org/uzu/strudel/pulls/88 +- Embed style by @felixroos in https://codeberg.org/uzu/strudel/pulls/109 +- In source doc by @yaxu in https://codeberg.org/uzu/strudel/pulls/105 +- `.brak()`, `.inside()` and `.outside()` by @yaxu in https://codeberg.org/uzu/strudel/pulls/112 +- loopAt by @yaxu in https://codeberg.org/uzu/strudel/pulls/114 +- Osc timing improvements by @yaxu in https://codeberg.org/uzu/strudel/pulls/113 +- react package + vite build by @felixroos in https://codeberg.org/uzu/strudel/pulls/116 +- In source doc by @felixroos in https://codeberg.org/uzu/strudel/pulls/117 +- fix: #108 by @felixroos in https://codeberg.org/uzu/strudel/pulls/123 +- fix: #122 ctrl enter would add newline by @felixroos in https://codeberg.org/uzu/strudel/pulls/124 +- Webdirt by @felixroos in https://codeberg.org/uzu/strudel/pulls/121 +- Fix link to contributing to tutorial docs by @stephendwolff in https://codeberg.org/uzu/strudel/pulls/129 +- Pianoroll enhancements by @felixroos in https://codeberg.org/uzu/strudel/pulls/131 +- add createParam + createParams by @felixroos in https://codeberg.org/uzu/strudel/pulls/110 +- remove cycle + delta from onTrigger by @felixroos in https://codeberg.org/uzu/strudel/pulls/135 +- Scheduler improvements by @felixroos in https://codeberg.org/uzu/strudel/pulls/134 +- add onTrigger helper by @felixroos in https://codeberg.org/uzu/strudel/pulls/136 ## New Contributors -- @bwagner made their first contribution in https://github.com/tidalcycles/strudel/pull/37 -- @mindofmatthew made their first contribution in https://github.com/tidalcycles/strudel/pull/84 -- @stephendwolff made their first contribution in https://github.com/tidalcycles/strudel/pull/129 +- @bwagner made their first contribution in https://codeberg.org/uzu/strudel/pulls/37 +- @mindofmatthew made their first contribution in https://codeberg.org/uzu/strudel/pulls/84 +- @stephendwolff made their first contribution in https://codeberg.org/uzu/strudel/pulls/129 -**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.0.2...v0.0.3 +**Full Changelog**: https://codeberg.org/uzu/strudel/compare/v0.0.2...v0.0.3 diff --git a/website/src/content/blog/release-0.0.4-gischt.mdx b/website/src/content/blog/release-0.0.4-gischt.mdx index 9149137ca..f06dd38e8 100644 --- a/website/src/content/blog/release-0.0.4-gischt.mdx +++ b/website/src/content/blog/release-0.0.4-gischt.mdx @@ -8,38 +8,38 @@ author: froos ## What's Changed -- Webaudio rewrite by @felixroos in https://github.com/tidalcycles/strudel/pull/138 -- Fix createParam() by @yaxu in https://github.com/tidalcycles/strudel/pull/140 -- Soundfont Support by @felixroos in https://github.com/tidalcycles/strudel/pull/139 -- Serial twiddles by @yaxu in https://github.com/tidalcycles/strudel/pull/141 -- Pianoroll Object Support by @felixroos in https://github.com/tidalcycles/strudel/pull/142 -- flash effect on ctrl enter by @felixroos in https://github.com/tidalcycles/strudel/pull/144 -- can now generate short link for sharing by @felixroos in https://github.com/tidalcycles/strudel/pull/146 -- Sampler optimizations and more by @felixroos in https://github.com/tidalcycles/strudel/pull/148 -- Final update to demo.pdf by @yaxu in https://github.com/tidalcycles/strudel/pull/151 -- add webdirt drum samples to prebake for general availability by @larkob in https://github.com/tidalcycles/strudel/pull/150 -- update to tutorial documentation by @larkob in https://github.com/tidalcycles/strudel/pull/162 -- add chooseInWith/chooseCycles by @yaxu in https://github.com/tidalcycles/strudel/pull/166 -- fix: jsdoc comments by @felixroos in https://github.com/tidalcycles/strudel/pull/169 -- Pianoroll fixes by @felixroos in https://github.com/tidalcycles/strudel/pull/163 -- Talk fixes by @felixroos in https://github.com/tidalcycles/strudel/pull/164 -- Amend shapeshifter to allow use of dynamic import by @debrisapron in https://github.com/tidalcycles/strudel/pull/171 -- add more shapeshifter flags by @felixroos in https://github.com/tidalcycles/strudel/pull/99 -- Replace react-codemirror6 with @uiw/react-codemirror by @felixroos in https://github.com/tidalcycles/strudel/pull/173 -- fix some annoying bugs by @felixroos in https://github.com/tidalcycles/strudel/pull/177 -- incorporate elements of randomness to the mini notation by @bpow in https://github.com/tidalcycles/strudel/pull/165 -- replace mocha with vitest by @felixroos in https://github.com/tidalcycles/strudel/pull/175 -- scheduler improvements by @felixroos in https://github.com/tidalcycles/strudel/pull/181 -- Fix codemirror bug by @felixroos in https://github.com/tidalcycles/strudel/pull/186 -- wait for prebake to finish before evaluating by @felixroos in https://github.com/tidalcycles/strudel/pull/189 -- fix regression: old way of setting frequencies was broken by @felixroos in https://github.com/tidalcycles/strudel/pull/190 -- Soundfont file support by @felixroos in https://github.com/tidalcycles/strudel/pull/183 -- change "stride"/"offset" of successive degradeBy/chooseIn by @bpow in https://github.com/tidalcycles/strudel/pull/185 +- Webaudio rewrite by @felixroos in https://codeberg.org/uzu/strudel/pulls/138 +- Fix createParam() by @yaxu in https://codeberg.org/uzu/strudel/pulls/140 +- Soundfont Support by @felixroos in https://codeberg.org/uzu/strudel/pulls/139 +- Serial twiddles by @yaxu in https://codeberg.org/uzu/strudel/pulls/141 +- Pianoroll Object Support by @felixroos in https://codeberg.org/uzu/strudel/pulls/142 +- flash effect on ctrl enter by @felixroos in https://codeberg.org/uzu/strudel/pulls/144 +- can now generate short link for sharing by @felixroos in https://codeberg.org/uzu/strudel/pulls/146 +- Sampler optimizations and more by @felixroos in https://codeberg.org/uzu/strudel/pulls/148 +- Final update to demo.pdf by @yaxu in https://codeberg.org/uzu/strudel/pulls/151 +- add webdirt drum samples to prebake for general availability by @larkob in https://codeberg.org/uzu/strudel/pulls/150 +- update to tutorial documentation by @larkob in https://codeberg.org/uzu/strudel/pulls/162 +- add chooseInWith/chooseCycles by @yaxu in https://codeberg.org/uzu/strudel/pulls/166 +- fix: jsdoc comments by @felixroos in https://codeberg.org/uzu/strudel/pulls/169 +- Pianoroll fixes by @felixroos in https://codeberg.org/uzu/strudel/pulls/163 +- Talk fixes by @felixroos in https://codeberg.org/uzu/strudel/pulls/164 +- Amend shapeshifter to allow use of dynamic import by @debrisapron in https://codeberg.org/uzu/strudel/pulls/171 +- add more shapeshifter flags by @felixroos in https://codeberg.org/uzu/strudel/pulls/99 +- Replace react-codemirror6 with @uiw/react-codemirror by @felixroos in https://codeberg.org/uzu/strudel/pulls/173 +- fix some annoying bugs by @felixroos in https://codeberg.org/uzu/strudel/pulls/177 +- incorporate elements of randomness to the mini notation by @bpow in https://codeberg.org/uzu/strudel/pulls/165 +- replace mocha with vitest by @felixroos in https://codeberg.org/uzu/strudel/pulls/175 +- scheduler improvements by @felixroos in https://codeberg.org/uzu/strudel/pulls/181 +- Fix codemirror bug by @felixroos in https://codeberg.org/uzu/strudel/pulls/186 +- wait for prebake to finish before evaluating by @felixroos in https://codeberg.org/uzu/strudel/pulls/189 +- fix regression: old way of setting frequencies was broken by @felixroos in https://codeberg.org/uzu/strudel/pulls/190 +- Soundfont file support by @felixroos in https://codeberg.org/uzu/strudel/pulls/183 +- change "stride"/"offset" of successive degradeBy/chooseIn by @bpow in https://codeberg.org/uzu/strudel/pulls/185 ## New Contributors -- @larkob made their first contribution in https://github.com/tidalcycles/strudel/pull/150 -- @debrisapron made their first contribution in https://github.com/tidalcycles/strudel/pull/171 -- @bpow made their first contribution in https://github.com/tidalcycles/strudel/pull/165 +- @larkob made their first contribution in https://codeberg.org/uzu/strudel/pulls/150 +- @debrisapron made their first contribution in https://codeberg.org/uzu/strudel/pulls/171 +- @bpow made their first contribution in https://codeberg.org/uzu/strudel/pulls/165 -**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.0.3...v0.0.4 +**Full Changelog**: https://codeberg.org/uzu/strudel/compare/v0.0.3...v0.0.4 diff --git a/website/src/content/blog/release-0.3.0-donauwelle.mdx b/website/src/content/blog/release-0.3.0-donauwelle.mdx index 054b36b31..a551dc53b 100644 --- a/website/src/content/blog/release-0.3.0-donauwelle.mdx +++ b/website/src/content/blog/release-0.3.0-donauwelle.mdx @@ -22,44 +22,44 @@ author: froos ## What's Changed -- Fix numbers in sampler by @felixroos in https://github.com/tidalcycles/strudel/pull/196 -- document random functions by @felixroos in https://github.com/tidalcycles/strudel/pull/199 -- add rollup-plugin-visualizer to build by @felixroos in https://github.com/tidalcycles/strudel/pull/200 -- add vowel to .out by @felixroos in https://github.com/tidalcycles/strudel/pull/201 -- Coarse crush shape by @felixroos in https://github.com/tidalcycles/strudel/pull/205 -- Webaudio guide by @felixroos in https://github.com/tidalcycles/strudel/pull/207 -- Even more docs by @felixroos in https://github.com/tidalcycles/strudel/pull/212 -- Just another docs PR by @felixroos in https://github.com/tidalcycles/strudel/pull/215 -- sampler features + fixes by @felixroos in https://github.com/tidalcycles/strudel/pull/217 -- samples now have envelopes by @felixroos in https://github.com/tidalcycles/strudel/pull/218 -- encapsulate webaudio output by @felixroos in https://github.com/tidalcycles/strudel/pull/219 -- Fix squeeze join by @yaxu in https://github.com/tidalcycles/strudel/pull/220 -- Feedback Delay by @felixroos in https://github.com/tidalcycles/strudel/pull/213 -- support negative speeds by @felixroos in https://github.com/tidalcycles/strudel/pull/222 -- focus tweak for squeezeJoin - another go at fixing #216 by @yaxu in https://github.com/tidalcycles/strudel/pull/221 -- Reverb by @felixroos in https://github.com/tidalcycles/strudel/pull/224 -- fix fastgap for events that go across cycle boundaries by @yaxu in https://github.com/tidalcycles/strudel/pull/225 -- Core util tests by @mystery-house in https://github.com/tidalcycles/strudel/pull/226 -- Refactor tunes away from tone by @felixroos in https://github.com/tidalcycles/strudel/pull/230 -- Just another docs branch by @felixroos in https://github.com/tidalcycles/strudel/pull/228 -- Patternify range by @yaxu in https://github.com/tidalcycles/strudel/pull/231 -- Out by default by @felixroos in https://github.com/tidalcycles/strudel/pull/232 -- Fix zero length queries WIP by @yaxu in https://github.com/tidalcycles/strudel/pull/234 -- add vcsl sample library by @felixroos in https://github.com/tidalcycles/strudel/pull/235 -- fx on stereo speakers by @felixroos in https://github.com/tidalcycles/strudel/pull/236 -- Tidal drum machines by @felixroos in https://github.com/tidalcycles/strudel/pull/237 -- Object arithmetic by @felixroos in https://github.com/tidalcycles/strudel/pull/238 -- Load samples from url by @felixroos in https://github.com/tidalcycles/strudel/pull/239 -- feat: support github: links by @felixroos in https://github.com/tidalcycles/strudel/pull/240 -- in source example tests by @felixroos in https://github.com/tidalcycles/strudel/pull/242 -- Readme + TLC by @felixroos in https://github.com/tidalcycles/strudel/pull/244 -- patchday by @felixroos in https://github.com/tidalcycles/strudel/pull/246 -- Some tunes by @felixroos in https://github.com/tidalcycles/strudel/pull/247 -- snapshot tests on shared snippets by @felixroos in https://github.com/tidalcycles/strudel/pull/243 -- General purpose scheduler by @felixroos in https://github.com/tidalcycles/strudel/pull/248 +- Fix numbers in sampler by @felixroos in https://codeberg.org/uzu/strudel/pulls/196 +- document random functions by @felixroos in https://codeberg.org/uzu/strudel/pulls/199 +- add rollup-plugin-visualizer to build by @felixroos in https://codeberg.org/uzu/strudel/pulls/200 +- add vowel to .out by @felixroos in https://codeberg.org/uzu/strudel/pulls/201 +- Coarse crush shape by @felixroos in https://codeberg.org/uzu/strudel/pulls/205 +- Webaudio guide by @felixroos in https://codeberg.org/uzu/strudel/pulls/207 +- Even more docs by @felixroos in https://codeberg.org/uzu/strudel/pulls/212 +- Just another docs PR by @felixroos in https://codeberg.org/uzu/strudel/pulls/215 +- sampler features + fixes by @felixroos in https://codeberg.org/uzu/strudel/pulls/217 +- samples now have envelopes by @felixroos in https://codeberg.org/uzu/strudel/pulls/218 +- encapsulate webaudio output by @felixroos in https://codeberg.org/uzu/strudel/pulls/219 +- Fix squeeze join by @yaxu in https://codeberg.org/uzu/strudel/pulls/220 +- Feedback Delay by @felixroos in https://codeberg.org/uzu/strudel/pulls/213 +- support negative speeds by @felixroos in https://codeberg.org/uzu/strudel/pulls/222 +- focus tweak for squeezeJoin - another go at fixing #216 by @yaxu in https://codeberg.org/uzu/strudel/pulls/221 +- Reverb by @felixroos in https://codeberg.org/uzu/strudel/pulls/224 +- fix fastgap for events that go across cycle boundaries by @yaxu in https://codeberg.org/uzu/strudel/pulls/225 +- Core util tests by @mystery-house in https://codeberg.org/uzu/strudel/pulls/226 +- Refactor tunes away from tone by @felixroos in https://codeberg.org/uzu/strudel/pulls/230 +- Just another docs branch by @felixroos in https://codeberg.org/uzu/strudel/pulls/228 +- Patternify range by @yaxu in https://codeberg.org/uzu/strudel/pulls/231 +- Out by default by @felixroos in https://codeberg.org/uzu/strudel/pulls/232 +- Fix zero length queries WIP by @yaxu in https://codeberg.org/uzu/strudel/pulls/234 +- add vcsl sample library by @felixroos in https://codeberg.org/uzu/strudel/pulls/235 +- fx on stereo speakers by @felixroos in https://codeberg.org/uzu/strudel/pulls/236 +- Tidal drum machines by @felixroos in https://codeberg.org/uzu/strudel/pulls/237 +- Object arithmetic by @felixroos in https://codeberg.org/uzu/strudel/pulls/238 +- Load samples from url by @felixroos in https://codeberg.org/uzu/strudel/pulls/239 +- feat: support github: links by @felixroos in https://codeberg.org/uzu/strudel/pulls/240 +- in source example tests by @felixroos in https://codeberg.org/uzu/strudel/pulls/242 +- Readme + TLC by @felixroos in https://codeberg.org/uzu/strudel/pulls/244 +- patchday by @felixroos in https://codeberg.org/uzu/strudel/pulls/246 +- Some tunes by @felixroos in https://codeberg.org/uzu/strudel/pulls/247 +- snapshot tests on shared snippets by @felixroos in https://codeberg.org/uzu/strudel/pulls/243 +- General purpose scheduler by @felixroos in https://codeberg.org/uzu/strudel/pulls/248 ## New Contributors -- @mystery-house made their first contribution in https://github.com/tidalcycles/strudel/pull/226 +- @mystery-house made their first contribution in https://codeberg.org/uzu/strudel/pulls/226 -**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.0.4...v0.3.0 +**Full Changelog**: https://codeberg.org/uzu/strudel/compare/v0.0.4...v0.3.0 diff --git a/website/src/content/blog/release-0.4.0-brandung.mdx b/website/src/content/blog/release-0.4.0-brandung.mdx index 06023662b..01e2f9b89 100644 --- a/website/src/content/blog/release-0.4.0-brandung.mdx +++ b/website/src/content/blog/release-0.4.0-brandung.mdx @@ -8,8 +8,8 @@ author: froos ## What's Changed -- new transpiler based on acorn by @felixroos in https://github.com/tidalcycles/strudel/pull/249 -- Webaudio build by @felixroos in https://github.com/tidalcycles/strudel/pull/250 -- Repl refactoring by @felixroos in https://github.com/tidalcycles/strudel/pull/255 +- new transpiler based on acorn by @felixroos in https://codeberg.org/uzu/strudel/pulls/249 +- Webaudio build by @felixroos in https://codeberg.org/uzu/strudel/pulls/250 +- Repl refactoring by @felixroos in https://codeberg.org/uzu/strudel/pulls/255 -**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.3.0...v0.4.0 +**Full Changelog**: https://codeberg.org/uzu/strudel/compare/v0.3.0...v0.4.0 diff --git a/website/src/content/blog/release-0.5.0-wirbel.mdx b/website/src/content/blog/release-0.5.0-wirbel.mdx index 032639fd9..3c1e1bfe2 100644 --- a/website/src/content/blog/release-0.5.0-wirbel.mdx +++ b/website/src/content/blog/release-0.5.0-wirbel.mdx @@ -26,34 +26,34 @@ author: froos ## What's Changed -- Binaries by @felixroos in https://github.com/tidalcycles/strudel/pull/254 -- fix tutorial bugs by @felixroos in https://github.com/tidalcycles/strudel/pull/263 -- fix performance bottleneck by @felixroos in https://github.com/tidalcycles/strudel/pull/266 -- Tidying up core by @yaxu in https://github.com/tidalcycles/strudel/pull/256 -- tonal update with fixed memory leak by @felixroos in https://github.com/tidalcycles/strudel/pull/272 -- add eslint by @felixroos in https://github.com/tidalcycles/strudel/pull/271 -- release version bumps by @felixroos in https://github.com/tidalcycles/strudel/pull/273 -- Support sending CRC16 bytes with serial messages by @yaxu in https://github.com/tidalcycles/strudel/pull/276 -- add licenses / credits to all tunes + remove some by @felixroos in https://github.com/tidalcycles/strudel/pull/277 -- add basic csound output by @felixroos in https://github.com/tidalcycles/strudel/pull/275 -- do not recompile orc by @felixroos in https://github.com/tidalcycles/strudel/pull/278 -- implement collect + arp function by @felixroos in https://github.com/tidalcycles/strudel/pull/281 -- Switch 'operators' from .whatHow to .what.how by @yaxu in https://github.com/tidalcycles/strudel/pull/285 -- Fancy hap show, include part in snapshots by @yaxu in https://github.com/tidalcycles/strudel/pull/291 -- Reorganise pattern.mjs with a 'toplevel first' regime by @yaxu in https://github.com/tidalcycles/strudel/pull/286 -- add prettier task by @felixroos in https://github.com/tidalcycles/strudel/pull/296 -- Move stuff to new register function by @felixroos in https://github.com/tidalcycles/strudel/pull/295 -- can now add bare numbers to numeral object props by @felixroos in https://github.com/tidalcycles/strudel/pull/287 -- update vitest by @felixroos in https://github.com/tidalcycles/strudel/pull/297 -- remove whitespace from highlighted region by @felixroos in https://github.com/tidalcycles/strudel/pull/298 -- .defragmentHaps() for merging touching haps that share a whole and value by @yaxu in https://github.com/tidalcycles/strudel/pull/299 -- fix whitespace trimming by @felixroos in https://github.com/tidalcycles/strudel/pull/300 -- add freq support to sampler by @felixroos in https://github.com/tidalcycles/strudel/pull/301 -- add lint + prettier check before test by @felixroos in https://github.com/tidalcycles/strudel/pull/305 -- Updated csoundm to use the register facility . by @gogins in https://github.com/tidalcycles/strudel/pull/303 +- Binaries by @felixroos in https://codeberg.org/uzu/strudel/pulls/254 +- fix tutorial bugs by @felixroos in https://codeberg.org/uzu/strudel/pulls/263 +- fix performance bottleneck by @felixroos in https://codeberg.org/uzu/strudel/pulls/266 +- Tidying up core by @yaxu in https://codeberg.org/uzu/strudel/pulls/256 +- tonal update with fixed memory leak by @felixroos in https://codeberg.org/uzu/strudel/pulls/272 +- add eslint by @felixroos in https://codeberg.org/uzu/strudel/pulls/271 +- release version bumps by @felixroos in https://codeberg.org/uzu/strudel/pulls/273 +- Support sending CRC16 bytes with serial messages by @yaxu in https://codeberg.org/uzu/strudel/pulls/276 +- add licenses / credits to all tunes + remove some by @felixroos in https://codeberg.org/uzu/strudel/pulls/277 +- add basic csound output by @felixroos in https://codeberg.org/uzu/strudel/pulls/275 +- do not recompile orc by @felixroos in https://codeberg.org/uzu/strudel/pulls/278 +- implement collect + arp function by @felixroos in https://codeberg.org/uzu/strudel/pulls/281 +- Switch 'operators' from .whatHow to .what.how by @yaxu in https://codeberg.org/uzu/strudel/pulls/285 +- Fancy hap show, include part in snapshots by @yaxu in https://codeberg.org/uzu/strudel/pulls/291 +- Reorganise pattern.mjs with a 'toplevel first' regime by @yaxu in https://codeberg.org/uzu/strudel/pulls/286 +- add prettier task by @felixroos in https://codeberg.org/uzu/strudel/pulls/296 +- Move stuff to new register function by @felixroos in https://codeberg.org/uzu/strudel/pulls/295 +- can now add bare numbers to numeral object props by @felixroos in https://codeberg.org/uzu/strudel/pulls/287 +- update vitest by @felixroos in https://codeberg.org/uzu/strudel/pulls/297 +- remove whitespace from highlighted region by @felixroos in https://codeberg.org/uzu/strudel/pulls/298 +- .defragmentHaps() for merging touching haps that share a whole and value by @yaxu in https://codeberg.org/uzu/strudel/pulls/299 +- fix whitespace trimming by @felixroos in https://codeberg.org/uzu/strudel/pulls/300 +- add freq support to sampler by @felixroos in https://codeberg.org/uzu/strudel/pulls/301 +- add lint + prettier check before test by @felixroos in https://codeberg.org/uzu/strudel/pulls/305 +- Updated csoundm to use the register facility . by @gogins in https://codeberg.org/uzu/strudel/pulls/303 ## New Contributors -- @gogins made their first contribution in https://github.com/tidalcycles/strudel/pull/303 +- @gogins made their first contribution in https://codeberg.org/uzu/strudel/pulls/303 -**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.4.0...v0.5.0 +**Full Changelog**: https://codeberg.org/uzu/strudel/compare/v0.4.0...v0.5.0 diff --git a/website/src/content/blog/release-0.6.0-zimtschnecke.mdx b/website/src/content/blog/release-0.6.0-zimtschnecke.mdx index 2736974bf..19536581d 100644 --- a/website/src/content/blog/release-0.6.0-zimtschnecke.mdx +++ b/website/src/content/blog/release-0.6.0-zimtschnecke.mdx @@ -26,59 +26,59 @@ author: froos ## What's Changed -- support freq in pianoroll by @felixroos in https://github.com/tidalcycles/strudel/pull/308 -- ICLC2023 paper WIP by @yaxu in https://github.com/tidalcycles/strudel/pull/306 -- fix: copy share link to clipboard was broken for some browsers by @felixroos in https://github.com/tidalcycles/strudel/pull/311 -- Jsdoc component by @felixroos in https://github.com/tidalcycles/strudel/pull/312 -- object support for .scale by @felixroos in https://github.com/tidalcycles/strudel/pull/307 -- Astro build by @felixroos in https://github.com/tidalcycles/strudel/pull/315 -- Reference tab sort by @felixroos in https://github.com/tidalcycles/strudel/pull/318 -- tutorial updates by @jarmitage in https://github.com/tidalcycles/strudel/pull/320 -- support notes without octave by @felixroos in https://github.com/tidalcycles/strudel/pull/323 -- mini repl improvements by @felixroos in https://github.com/tidalcycles/strudel/pull/324 -- fix: workaround Object.assign globalThis by @felixroos in https://github.com/tidalcycles/strudel/pull/326 -- add examples route by @felixroos in https://github.com/tidalcycles/strudel/pull/327 -- add my-patterns by @felixroos in https://github.com/tidalcycles/strudel/pull/328 -- my-patterns build + deploy by @felixroos in https://github.com/tidalcycles/strudel/pull/329 -- my-patterns: fix paths + update readme by @felixroos in https://github.com/tidalcycles/strudel/pull/330 -- improve displaying 's' in pianoroll by @felixroos in https://github.com/tidalcycles/strudel/pull/331 -- fix: can now multiply floats in mini notation by @felixroos in https://github.com/tidalcycles/strudel/pull/332 -- Embed mode improvements by @felixroos in https://github.com/tidalcycles/strudel/pull/333 -- testing + docs docs by @felixroos in https://github.com/tidalcycles/strudel/pull/334 -- animate mvp by @felixroos in https://github.com/tidalcycles/strudel/pull/335 -- Tidy parser, implement polymeters by @yaxu in https://github.com/tidalcycles/strudel/pull/336 -- animation options by @felixroos in https://github.com/tidalcycles/strudel/pull/337 -- move /my-patterns to /swatch by @yaxu in https://github.com/tidalcycles/strudel/pull/338 -- more animate functions + mini repl fix by @felixroos in https://github.com/tidalcycles/strudel/pull/340 -- Patternify euclid, fast, slow and polymeter step parameters in mininotation by @yaxu in https://github.com/tidalcycles/strudel/pull/341 -- fixes #346 by @felixroos in https://github.com/tidalcycles/strudel/pull/347 -- Fix prebake base path by @felixroos in https://github.com/tidalcycles/strudel/pull/345 -- Fix Bjorklund by @yaxu in https://github.com/tidalcycles/strudel/pull/343 -- docs: tidal comparison + add global fx + add missing sampler fx by @felixroos in https://github.com/tidalcycles/strudel/pull/356 -- Fix .out(), renaming webaudio's out() to webaudio() by @yaxu in https://github.com/tidalcycles/strudel/pull/361 -- Support for multiple mininotation operators by @yaxu in https://github.com/tidalcycles/strudel/pull/350 -- doc structuring by @felixroos in https://github.com/tidalcycles/strudel/pull/360 -- add https to url by @urswilke in https://github.com/tidalcycles/strudel/pull/364 -- document more functions + change arp join by @felixroos in https://github.com/tidalcycles/strudel/pull/369 -- improve new draw logic by @felixroos in https://github.com/tidalcycles/strudel/pull/372 -- Draw fixes by @felixroos in https://github.com/tidalcycles/strudel/pull/377 -- update my-patterns instructions by @felixroos in https://github.com/tidalcycles/strudel/pull/384 -- docs: use note instead of n to mitigate confusion by @felixroos in https://github.com/tidalcycles/strudel/pull/385 -- add run + test + docs by @felixroos in https://github.com/tidalcycles/strudel/pull/386 -- Rename a to angle by @felixroos in https://github.com/tidalcycles/strudel/pull/387 -- document csound by @felixroos in https://github.com/tidalcycles/strudel/pull/391 -- Notes are not essential :) by @yaxu in https://github.com/tidalcycles/strudel/pull/393 -- add ribbon + test + docs by @felixroos in https://github.com/tidalcycles/strudel/pull/388 -- Add tidal-drum-patterns to examples by @urswilke in https://github.com/tidalcycles/strudel/pull/379 -- add pattern methods hurry, press and pressBy by @yaxu in https://github.com/tidalcycles/strudel/pull/397 -- proper builds + use pnpm workspaces by @felixroos in https://github.com/tidalcycles/strudel/pull/396 -- fix: minirepl styles by @felixroos in https://github.com/tidalcycles/strudel/pull/398 -- can now await initAudio + initAudioOnFirstClick by @felixroos in https://github.com/tidalcycles/strudel/pull/399 -- release webaudio by @felixroos in https://github.com/tidalcycles/strudel/pull/400 +- support freq in pianoroll by @felixroos in https://codeberg.org/uzu/strudel/pulls/308 +- ICLC2023 paper WIP by @yaxu in https://codeberg.org/uzu/strudel/pulls/306 +- fix: copy share link to clipboard was broken for some browsers by @felixroos in https://codeberg.org/uzu/strudel/pulls/311 +- Jsdoc component by @felixroos in https://codeberg.org/uzu/strudel/pulls/312 +- object support for .scale by @felixroos in https://codeberg.org/uzu/strudel/pulls/307 +- Astro build by @felixroos in https://codeberg.org/uzu/strudel/pulls/315 +- Reference tab sort by @felixroos in https://codeberg.org/uzu/strudel/pulls/318 +- tutorial updates by @jarmitage in https://codeberg.org/uzu/strudel/pulls/320 +- support notes without octave by @felixroos in https://codeberg.org/uzu/strudel/pulls/323 +- mini repl improvements by @felixroos in https://codeberg.org/uzu/strudel/pulls/324 +- fix: workaround Object.assign globalThis by @felixroos in https://codeberg.org/uzu/strudel/pulls/326 +- add examples route by @felixroos in https://codeberg.org/uzu/strudel/pulls/327 +- add my-patterns by @felixroos in https://codeberg.org/uzu/strudel/pulls/328 +- my-patterns build + deploy by @felixroos in https://codeberg.org/uzu/strudel/pulls/329 +- my-patterns: fix paths + update readme by @felixroos in https://codeberg.org/uzu/strudel/pulls/330 +- improve displaying 's' in pianoroll by @felixroos in https://codeberg.org/uzu/strudel/pulls/331 +- fix: can now multiply floats in mini notation by @felixroos in https://codeberg.org/uzu/strudel/pulls/332 +- Embed mode improvements by @felixroos in https://codeberg.org/uzu/strudel/pulls/333 +- testing + docs docs by @felixroos in https://codeberg.org/uzu/strudel/pulls/334 +- animate mvp by @felixroos in https://codeberg.org/uzu/strudel/pulls/335 +- Tidy parser, implement polymeters by @yaxu in https://codeberg.org/uzu/strudel/pulls/336 +- animation options by @felixroos in https://codeberg.org/uzu/strudel/pulls/337 +- move /my-patterns to /swatch by @yaxu in https://codeberg.org/uzu/strudel/pulls/338 +- more animate functions + mini repl fix by @felixroos in https://codeberg.org/uzu/strudel/pulls/340 +- Patternify euclid, fast, slow and polymeter step parameters in mininotation by @yaxu in https://codeberg.org/uzu/strudel/pulls/341 +- fixes #346 by @felixroos in https://codeberg.org/uzu/strudel/pulls/347 +- Fix prebake base path by @felixroos in https://codeberg.org/uzu/strudel/pulls/345 +- Fix Bjorklund by @yaxu in https://codeberg.org/uzu/strudel/pulls/343 +- docs: tidal comparison + add global fx + add missing sampler fx by @felixroos in https://codeberg.org/uzu/strudel/pulls/356 +- Fix .out(), renaming webaudio's out() to webaudio() by @yaxu in https://codeberg.org/uzu/strudel/pulls/361 +- Support for multiple mininotation operators by @yaxu in https://codeberg.org/uzu/strudel/pulls/350 +- doc structuring by @felixroos in https://codeberg.org/uzu/strudel/pulls/360 +- add https to url by @urswilke in https://codeberg.org/uzu/strudel/pulls/364 +- document more functions + change arp join by @felixroos in https://codeberg.org/uzu/strudel/pulls/369 +- improve new draw logic by @felixroos in https://codeberg.org/uzu/strudel/pulls/372 +- Draw fixes by @felixroos in https://codeberg.org/uzu/strudel/pulls/377 +- update my-patterns instructions by @felixroos in https://codeberg.org/uzu/strudel/pulls/384 +- docs: use note instead of n to mitigate confusion by @felixroos in https://codeberg.org/uzu/strudel/pulls/385 +- add run + test + docs by @felixroos in https://codeberg.org/uzu/strudel/pulls/386 +- Rename a to angle by @felixroos in https://codeberg.org/uzu/strudel/pulls/387 +- document csound by @felixroos in https://codeberg.org/uzu/strudel/pulls/391 +- Notes are not essential :) by @yaxu in https://codeberg.org/uzu/strudel/pulls/393 +- add ribbon + test + docs by @felixroos in https://codeberg.org/uzu/strudel/pulls/388 +- Add tidal-drum-patterns to examples by @urswilke in https://codeberg.org/uzu/strudel/pulls/379 +- add pattern methods hurry, press and pressBy by @yaxu in https://codeberg.org/uzu/strudel/pulls/397 +- proper builds + use pnpm workspaces by @felixroos in https://codeberg.org/uzu/strudel/pulls/396 +- fix: minirepl styles by @felixroos in https://codeberg.org/uzu/strudel/pulls/398 +- can now await initAudio + initAudioOnFirstClick by @felixroos in https://codeberg.org/uzu/strudel/pulls/399 +- release webaudio by @felixroos in https://codeberg.org/uzu/strudel/pulls/400 ## New Contributors -- @jarmitage made their first contribution in https://github.com/tidalcycles/strudel/pull/320 -- @urswilke made their first contribution in https://github.com/tidalcycles/strudel/pull/364 +- @jarmitage made their first contribution in https://codeberg.org/uzu/strudel/pulls/320 +- @urswilke made their first contribution in https://codeberg.org/uzu/strudel/pulls/364 -**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.5.0...v0.6.0 +**Full Changelog**: https://codeberg.org/uzu/strudel/compare/v0.5.0...v0.6.0 diff --git a/website/src/content/blog/release-0.7.0-zuckerguss.mdx b/website/src/content/blog/release-0.7.0-zuckerguss.mdx index 0a3a4649b..f67deee73 100644 --- a/website/src/content/blog/release-0.7.0-zuckerguss.mdx +++ b/website/src/content/blog/release-0.7.0-zuckerguss.mdx @@ -23,66 +23,66 @@ author: froos ## What's Changed -- pin @csound/browser to 6.18.3 + bump by @felixroos in https://github.com/tidalcycles/strudel/pull/403 -- update csound + fix sound output by @felixroos in https://github.com/tidalcycles/strudel/pull/404 -- fix: share url on subpath by @felixroos in https://github.com/tidalcycles/strudel/pull/405 -- add shabda doc by @felixroos in https://github.com/tidalcycles/strudel/pull/407 -- Update effects.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/410 -- improve effects doc by @felixroos in https://github.com/tidalcycles/strudel/pull/409 -- google gtfo by @felixroos in https://github.com/tidalcycles/strudel/pull/413 -- improve samples doc by @felixroos in https://github.com/tidalcycles/strudel/pull/411 -- PWA with offline support by @felixroos in https://github.com/tidalcycles/strudel/pull/417 -- add caching strategy for missing file types + cache all samples loaded from github by @felixroos in https://github.com/tidalcycles/strudel/pull/419 -- add more offline caching by @felixroos in https://github.com/tidalcycles/strudel/pull/421 -- add cdn.freesound to cache list by @felixroos in https://github.com/tidalcycles/strudel/pull/425 -- minirepl: add keyboard shortcuts by @felixroos in https://github.com/tidalcycles/strudel/pull/429 -- Themes by @felixroos in https://github.com/tidalcycles/strudel/pull/431 -- autocomplete preparations by @felixroos in https://github.com/tidalcycles/strudel/pull/427 -- Fix anchors by @felixroos in https://github.com/tidalcycles/strudel/pull/433 -- Update code.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/436 -- Update mini-notation.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/437 -- Update synths.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/438 -- FIXES: Warning about jsxBracketSameLine deprecation by @bwagner in https://github.com/tidalcycles/strudel/pull/461 -- Composable functions by @yaxu in https://github.com/tidalcycles/strudel/pull/390 -- weave and weaveWith by @yaxu in https://github.com/tidalcycles/strudel/pull/465 -- slice and splice by @yaxu in https://github.com/tidalcycles/strudel/pull/466 -- fix: osc should not return a promise by @felixroos in https://github.com/tidalcycles/strudel/pull/472 -- FIXES: freqs instead of pitches by @bwagner in https://github.com/tidalcycles/strudel/pull/464 -- Update input-output.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/471 -- settings tab with vim / emacs modes + additional themes and fonts by @felixroos in https://github.com/tidalcycles/strudel/pull/467 -- fix: hash links by @felixroos in https://github.com/tidalcycles/strudel/pull/473 -- midi cc support by @felixroos in https://github.com/tidalcycles/strudel/pull/478 -- Fix array args by @felixroos in https://github.com/tidalcycles/strudel/pull/480 -- docs: packages + offline by @felixroos in https://github.com/tidalcycles/strudel/pull/482 -- Update mini-notation.mdx by @yaxu in https://github.com/tidalcycles/strudel/pull/365 -- Revert "Another attempt at composable functions - WIP (#390)" by @felixroos in https://github.com/tidalcycles/strudel/pull/484 -- fix app height by @felixroos in https://github.com/tidalcycles/strudel/pull/485 -- add algolia creds + optimize sidebar for crawling by @felixroos in https://github.com/tidalcycles/strudel/pull/488 -- refactor react package by @felixroos in https://github.com/tidalcycles/strudel/pull/490 -- react style fixes by @felixroos in https://github.com/tidalcycles/strudel/pull/491 -- implement cps in scheduler by @felixroos in https://github.com/tidalcycles/strudel/pull/493 -- Add control aliases by @yaxu in https://github.com/tidalcycles/strudel/pull/497 -- fix: nano-repl highlighting by @felixroos in https://github.com/tidalcycles/strudel/pull/501 -- Reinstate slice and splice by @yaxu in https://github.com/tidalcycles/strudel/pull/500 -- can now use : as a replacement for space in scales by @felixroos in https://github.com/tidalcycles/strudel/pull/502 -- Support list syntax in mininotation by @yaxu in https://github.com/tidalcycles/strudel/pull/512 -- update react to 18 by @felixroos in https://github.com/tidalcycles/strudel/pull/514 -- add arrange function by @felixroos in https://github.com/tidalcycles/strudel/pull/508 -- Update README.md by @bwagner in https://github.com/tidalcycles/strudel/pull/474 -- add 2 illegible fonts by @felixroos in https://github.com/tidalcycles/strudel/pull/518 -- registerSound API + improved sounds tab + regroup soundfonts by @felixroos in https://github.com/tidalcycles/strudel/pull/516 -- fix: envelopes in chrome by @felixroos in https://github.com/tidalcycles/strudel/pull/521 -- Update samples.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/524 -- Update intro.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/525 -- fix(footer): fix link to tidalcycles by @revolunet in https://github.com/tidalcycles/strudel/pull/529 -- FIXES: alias pm for polymeter by @bwagner in https://github.com/tidalcycles/strudel/pull/527 -- Maintain random seed state in parser, not globally by @ijc8 in https://github.com/tidalcycles/strudel/pull/531 -- feat: add freq support to gm soundfonts by @felixroos in https://github.com/tidalcycles/strudel/pull/534 -- Update lerna by @felixroos in https://github.com/tidalcycles/strudel/pull/535 +- pin @csound/browser to 6.18.3 + bump by @felixroos in https://codeberg.org/uzu/strudel/pulls/403 +- update csound + fix sound output by @felixroos in https://codeberg.org/uzu/strudel/pulls/404 +- fix: share url on subpath by @felixroos in https://codeberg.org/uzu/strudel/pulls/405 +- add shabda doc by @felixroos in https://codeberg.org/uzu/strudel/pulls/407 +- Update effects.mdx by @bwagner in https://codeberg.org/uzu/strudel/pulls/410 +- improve effects doc by @felixroos in https://codeberg.org/uzu/strudel/pulls/409 +- google gtfo by @felixroos in https://codeberg.org/uzu/strudel/pulls/413 +- improve samples doc by @felixroos in https://codeberg.org/uzu/strudel/pulls/411 +- PWA with offline support by @felixroos in https://codeberg.org/uzu/strudel/pulls/417 +- add caching strategy for missing file types + cache all samples loaded from github by @felixroos in https://codeberg.org/uzu/strudel/pulls/419 +- add more offline caching by @felixroos in https://codeberg.org/uzu/strudel/pulls/421 +- add cdn.freesound to cache list by @felixroos in https://codeberg.org/uzu/strudel/pulls/425 +- minirepl: add keyboard shortcuts by @felixroos in https://codeberg.org/uzu/strudel/pulls/429 +- Themes by @felixroos in https://codeberg.org/uzu/strudel/pulls/431 +- autocomplete preparations by @felixroos in https://codeberg.org/uzu/strudel/pulls/427 +- Fix anchors by @felixroos in https://codeberg.org/uzu/strudel/pulls/433 +- Update code.mdx by @bwagner in https://codeberg.org/uzu/strudel/pulls/436 +- Update mini-notation.mdx by @bwagner in https://codeberg.org/uzu/strudel/pulls/437 +- Update synths.mdx by @bwagner in https://codeberg.org/uzu/strudel/pulls/438 +- FIXES: Warning about jsxBracketSameLine deprecation by @bwagner in https://codeberg.org/uzu/strudel/pulls/461 +- Composable functions by @yaxu in https://codeberg.org/uzu/strudel/pulls/390 +- weave and weaveWith by @yaxu in https://codeberg.org/uzu/strudel/pulls/465 +- slice and splice by @yaxu in https://codeberg.org/uzu/strudel/pulls/466 +- fix: osc should not return a promise by @felixroos in https://codeberg.org/uzu/strudel/pulls/472 +- FIXES: freqs instead of pitches by @bwagner in https://codeberg.org/uzu/strudel/pulls/464 +- Update input-output.mdx by @bwagner in https://codeberg.org/uzu/strudel/pulls/471 +- settings tab with vim / emacs modes + additional themes and fonts by @felixroos in https://codeberg.org/uzu/strudel/pulls/467 +- fix: hash links by @felixroos in https://codeberg.org/uzu/strudel/pulls/473 +- midi cc support by @felixroos in https://codeberg.org/uzu/strudel/pulls/478 +- Fix array args by @felixroos in https://codeberg.org/uzu/strudel/pulls/480 +- docs: packages + offline by @felixroos in https://codeberg.org/uzu/strudel/pulls/482 +- Update mini-notation.mdx by @yaxu in https://codeberg.org/uzu/strudel/pulls/365 +- Revert "Another attempt at composable functions - WIP (#390)" by @felixroos in https://codeberg.org/uzu/strudel/pulls/484 +- fix app height by @felixroos in https://codeberg.org/uzu/strudel/pulls/485 +- add algolia creds + optimize sidebar for crawling by @felixroos in https://codeberg.org/uzu/strudel/pulls/488 +- refactor react package by @felixroos in https://codeberg.org/uzu/strudel/pulls/490 +- react style fixes by @felixroos in https://codeberg.org/uzu/strudel/pulls/491 +- implement cps in scheduler by @felixroos in https://codeberg.org/uzu/strudel/pulls/493 +- Add control aliases by @yaxu in https://codeberg.org/uzu/strudel/pulls/497 +- fix: nano-repl highlighting by @felixroos in https://codeberg.org/uzu/strudel/pulls/501 +- Reinstate slice and splice by @yaxu in https://codeberg.org/uzu/strudel/pulls/500 +- can now use : as a replacement for space in scales by @felixroos in https://codeberg.org/uzu/strudel/pulls/502 +- Support list syntax in mininotation by @yaxu in https://codeberg.org/uzu/strudel/pulls/512 +- update react to 18 by @felixroos in https://codeberg.org/uzu/strudel/pulls/514 +- add arrange function by @felixroos in https://codeberg.org/uzu/strudel/pulls/508 +- Update README.md by @bwagner in https://codeberg.org/uzu/strudel/pulls/474 +- add 2 illegible fonts by @felixroos in https://codeberg.org/uzu/strudel/pulls/518 +- registerSound API + improved sounds tab + regroup soundfonts by @felixroos in https://codeberg.org/uzu/strudel/pulls/516 +- fix: envelopes in chrome by @felixroos in https://codeberg.org/uzu/strudel/pulls/521 +- Update samples.mdx by @bwagner in https://codeberg.org/uzu/strudel/pulls/524 +- Update intro.mdx by @bwagner in https://codeberg.org/uzu/strudel/pulls/525 +- fix(footer): fix link to tidalcycles by @revolunet in https://codeberg.org/uzu/strudel/pulls/529 +- FIXES: alias pm for polymeter by @bwagner in https://codeberg.org/uzu/strudel/pulls/527 +- Maintain random seed state in parser, not globally by @ijc8 in https://codeberg.org/uzu/strudel/pulls/531 +- feat: add freq support to gm soundfonts by @felixroos in https://codeberg.org/uzu/strudel/pulls/534 +- Update lerna by @felixroos in https://codeberg.org/uzu/strudel/pulls/535 ## New Contributors -- @revolunet made their first contribution in https://github.com/tidalcycles/strudel/pull/529 -- @ijc8 made their first contribution in https://github.com/tidalcycles/strudel/pull/531 +- @revolunet made their first contribution in https://codeberg.org/uzu/strudel/pulls/529 +- @ijc8 made their first contribution in https://codeberg.org/uzu/strudel/pulls/531 -**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.6.0...v0.7.0 +**Full Changelog**: https://codeberg.org/uzu/strudel/compare/v0.6.0...v0.7.0 diff --git a/website/src/content/blog/release-0.8.0-himbeermuffin.mdx b/website/src/content/blog/release-0.8.0-himbeermuffin.mdx index 7620140b5..babab5954 100644 --- a/website/src/content/blog/release-0.8.0-himbeermuffin.mdx +++ b/website/src/content/blog/release-0.8.0-himbeermuffin.mdx @@ -82,49 +82,49 @@ A big thanks to all the contributors! ## What's Changed -- fix period key for dvorak + remove duplicated code by @felixroos in https://github.com/tidalcycles/strudel/pull/537 -- improve initial loading + wait before eval by @felixroos in https://github.com/tidalcycles/strudel/pull/538 -- do not reset cps before eval #517 by @felixroos in https://github.com/tidalcycles/strudel/pull/539 -- feat: add loader bar to animate loading state by @felixroos in https://github.com/tidalcycles/strudel/pull/542 -- add firacode font by @felixroos in https://github.com/tidalcycles/strudel/pull/544 -- fix: allow whitespace at the end of a mini pattern by @felixroos in https://github.com/tidalcycles/strudel/pull/547 -- fix: reset time on stop by @felixroos in https://github.com/tidalcycles/strudel/pull/548 -- fix: load soundfonts in prebake by @felixroos in https://github.com/tidalcycles/strudel/pull/550 -- fix: colorable highlighting by @felixroos in https://github.com/tidalcycles/strudel/pull/553 -- fix: make soundfonts import dynamic by @felixroos in https://github.com/tidalcycles/strudel/pull/556 -- add basic triads and guidetone voicings by @felixroos in https://github.com/tidalcycles/strudel/pull/557 -- Patchday by @felixroos in https://github.com/tidalcycles/strudel/pull/559 -- Vanilla JS Refactoring by @felixroos in https://github.com/tidalcycles/strudel/pull/563 -- repl: add option to display line numbers by @roipoussiere in https://github.com/tidalcycles/strudel/pull/582 -- learn/tonal: fix typo in "scaleTran[s]pose" by @srenatus in https://github.com/tidalcycles/strudel/pull/585 -- Music metadata by @roipoussiere in https://github.com/tidalcycles/strudel/pull/580 -- New Workshop by @felixroos in https://github.com/tidalcycles/strudel/pull/587 -- Fix option dot by @felixroos in https://github.com/tidalcycles/strudel/pull/596 -- fix: allow f for flat notes like tidal by @felixroos in https://github.com/tidalcycles/strudel/pull/593 -- fix: division by zero by @felixroos in https://github.com/tidalcycles/strudel/pull/591 -- Solmization added by @dariacotocu in https://github.com/tidalcycles/strudel/pull/570 -- improve cursor by @felixroos in https://github.com/tidalcycles/strudel/pull/597 -- enable auto-completion by @roipoussiere in https://github.com/tidalcycles/strudel/pull/588 -- add ratio function by @felixroos in https://github.com/tidalcycles/strudel/pull/602 -- editor: enable line wrapping by @roipoussiere in https://github.com/tidalcycles/strudel/pull/581 -- tonal fixes by @felixroos in https://github.com/tidalcycles/strudel/pull/607 -- fix: flatten scale lists by @felixroos in https://github.com/tidalcycles/strudel/pull/605 -- clip now works like legato in tidal by @felixroos in https://github.com/tidalcycles/strudel/pull/598 -- fix: doc links by @felixroos in https://github.com/tidalcycles/strudel/pull/612 -- tauri desktop app by @vasilymilovidov in https://github.com/tidalcycles/strudel/pull/613 -- add spiral viz by @felixroos in https://github.com/tidalcycles/strudel/pull/614 -- patterning ui settings by @felixroos in https://github.com/tidalcycles/strudel/pull/606 -- Fix typo on packages.mdx by @paikwiki in https://github.com/tidalcycles/strudel/pull/520 -- cps dependent functions by @felixroos in https://github.com/tidalcycles/strudel/pull/620 -- desktop: play samples from disk by @felixroos in https://github.com/tidalcycles/strudel/pull/621 -- fix: midi clock drift by @felixroos in https://github.com/tidalcycles/strudel/pull/627 +- fix period key for dvorak + remove duplicated code by @felixroos in https://codeberg.org/uzu/strudel/pulls/537 +- improve initial loading + wait before eval by @felixroos in https://codeberg.org/uzu/strudel/pulls/538 +- do not reset cps before eval #517 by @felixroos in https://codeberg.org/uzu/strudel/pulls/539 +- feat: add loader bar to animate loading state by @felixroos in https://codeberg.org/uzu/strudel/pulls/542 +- add firacode font by @felixroos in https://codeberg.org/uzu/strudel/pulls/544 +- fix: allow whitespace at the end of a mini pattern by @felixroos in https://codeberg.org/uzu/strudel/pulls/547 +- fix: reset time on stop by @felixroos in https://codeberg.org/uzu/strudel/pulls/548 +- fix: load soundfonts in prebake by @felixroos in https://codeberg.org/uzu/strudel/pulls/550 +- fix: colorable highlighting by @felixroos in https://codeberg.org/uzu/strudel/pulls/553 +- fix: make soundfonts import dynamic by @felixroos in https://codeberg.org/uzu/strudel/pulls/556 +- add basic triads and guidetone voicings by @felixroos in https://codeberg.org/uzu/strudel/pulls/557 +- Patchday by @felixroos in https://codeberg.org/uzu/strudel/pulls/559 +- Vanilla JS Refactoring by @felixroos in https://codeberg.org/uzu/strudel/pulls/563 +- repl: add option to display line numbers by @roipoussiere in https://codeberg.org/uzu/strudel/pulls/582 +- learn/tonal: fix typo in "scaleTran[s]pose" by @srenatus in https://codeberg.org/uzu/strudel/pulls/585 +- Music metadata by @roipoussiere in https://codeberg.org/uzu/strudel/pulls/580 +- New Workshop by @felixroos in https://codeberg.org/uzu/strudel/pulls/587 +- Fix option dot by @felixroos in https://codeberg.org/uzu/strudel/pulls/596 +- fix: allow f for flat notes like tidal by @felixroos in https://codeberg.org/uzu/strudel/pulls/593 +- fix: division by zero by @felixroos in https://codeberg.org/uzu/strudel/pulls/591 +- Solmization added by @dariacotocu in https://codeberg.org/uzu/strudel/pulls/570 +- improve cursor by @felixroos in https://codeberg.org/uzu/strudel/pulls/597 +- enable auto-completion by @roipoussiere in https://codeberg.org/uzu/strudel/pulls/588 +- add ratio function by @felixroos in https://codeberg.org/uzu/strudel/pulls/602 +- editor: enable line wrapping by @roipoussiere in https://codeberg.org/uzu/strudel/pulls/581 +- tonal fixes by @felixroos in https://codeberg.org/uzu/strudel/pulls/607 +- fix: flatten scale lists by @felixroos in https://codeberg.org/uzu/strudel/pulls/605 +- clip now works like legato in tidal by @felixroos in https://codeberg.org/uzu/strudel/pulls/598 +- fix: doc links by @felixroos in https://codeberg.org/uzu/strudel/pulls/612 +- tauri desktop app by @vasilymilovidov in https://codeberg.org/uzu/strudel/pulls/613 +- add spiral viz by @felixroos in https://codeberg.org/uzu/strudel/pulls/614 +- patterning ui settings by @felixroos in https://codeberg.org/uzu/strudel/pulls/606 +- Fix typo on packages.mdx by @paikwiki in https://codeberg.org/uzu/strudel/pulls/520 +- cps dependent functions by @felixroos in https://codeberg.org/uzu/strudel/pulls/620 +- desktop: play samples from disk by @felixroos in https://codeberg.org/uzu/strudel/pulls/621 +- fix: midi clock drift by @felixroos in https://codeberg.org/uzu/strudel/pulls/627 ## New Contributors -- @roipoussiere made their first contribution in https://github.com/tidalcycles/strudel/pull/582 -- @srenatus made their first contribution in https://github.com/tidalcycles/strudel/pull/585 -- @dariacotocu made their first contribution in https://github.com/tidalcycles/strudel/pull/570 -- @vasilymilovidov made their first contribution in https://github.com/tidalcycles/strudel/pull/613 -- @paikwiki made their first contribution in https://github.com/tidalcycles/strudel/pull/520 +- @roipoussiere made their first contribution in https://codeberg.org/uzu/strudel/pulls/582 +- @srenatus made their first contribution in https://codeberg.org/uzu/strudel/pulls/585 +- @dariacotocu made their first contribution in https://codeberg.org/uzu/strudel/pulls/570 +- @vasilymilovidov made their first contribution in https://codeberg.org/uzu/strudel/pulls/613 +- @paikwiki made their first contribution in https://codeberg.org/uzu/strudel/pulls/520 -**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.7.0...v0.8.0 +**Full Changelog**: https://codeberg.org/uzu/strudel/compare/v0.7.0...v0.8.0 diff --git a/website/src/content/blog/release-0.9.0-bananenbrot.mdx b/website/src/content/blog/release-0.9.0-bananenbrot.mdx index ae77b4247..944675e38 100644 --- a/website/src/content/blog/release-0.9.0-bananenbrot.mdx +++ b/website/src/content/blog/release-0.9.0-bananenbrot.mdx @@ -29,14 +29,14 @@ Main new features include: Related PRs: -- superdough: encapsulates web audio output by @felixroos in https://github.com/tidalcycles/strudel/pull/664 -- basic fm by @felixroos in https://github.com/tidalcycles/strudel/pull/669 -- Wave Selection and Global Envelope on the FM Synth Modulator by @Bubobubobubobubo in https://github.com/tidalcycles/strudel/pull/683 -- control osc partial count with n by @felixroos in https://github.com/tidalcycles/strudel/pull/674 -- ZZFX Synth support by @Bubobubobubobubo in https://github.com/tidalcycles/strudel/pull/684 -- Adding filter envelopes and filter order selection by @Bubobubobubobubo in https://github.com/tidalcycles/strudel/pull/692 -- Adding loop points and thus wavetable synthesis by @Bubobubobubobubo in https://github.com/tidalcycles/strudel/pull/698 -- Adding vibrato to base oscillators by @Bubobubobubobubo in https://github.com/tidalcycles/strudel/pull/693 +- superdough: encapsulates web audio output by @felixroos in https://codeberg.org/uzu/strudel/pulls/664 +- basic fm by @felixroos in https://codeberg.org/uzu/strudel/pulls/669 +- Wave Selection and Global Envelope on the FM Synth Modulator by @Bubobubobubobubo in https://codeberg.org/uzu/strudel/pulls/683 +- control osc partial count with n by @felixroos in https://codeberg.org/uzu/strudel/pulls/674 +- ZZFX Synth support by @Bubobubobubobubo in https://codeberg.org/uzu/strudel/pulls/684 +- Adding filter envelopes and filter order selection by @Bubobubobubobubo in https://codeberg.org/uzu/strudel/pulls/692 +- Adding loop points and thus wavetable synthesis by @Bubobubobubobubo in https://codeberg.org/uzu/strudel/pulls/698 +- Adding vibrato to base oscillators by @Bubobubobubobubo in https://codeberg.org/uzu/strudel/pulls/693 ## Desktop App Improvements @@ -45,70 +45,70 @@ which do not depend on browser APIs! You can see superdough, superdirt via OSC + hardware synths via MIDI all together playing in harmony in this [awesome video](https://www.youtube.com/watch?v=lxQgBeLQBgk). These are the related PRs: -- Create Midi Integration for Tauri Desktop app by @daslyfe in https://github.com/tidalcycles/strudel/pull/685 -- add sleep timer + improve message iterating by @daslyfe in https://github.com/tidalcycles/strudel/pull/688 -- fix MIDI CC messages by @vasilymilovidov in https://github.com/tidalcycles/strudel/pull/690 -- Direct OSC Support in Tauri by @daslyfe in https://github.com/tidalcycles/strudel/pull/694 -- Add logging from tauri by @daslyfe in https://github.com/tidalcycles/strudel/pull/697 -- fix osc bundle timestamp glitches caused by drifting clock by @daslyfe in https://github.com/tidalcycles/strudel/pull/666 -- Midi time fixes by @daslyfe in https://github.com/tidalcycles/strudel/pull/668 -- [Bug Fix] Account for numeral notation when converting to midi by @daslyfe in https://github.com/tidalcycles/strudel/pull/656 -- [Bug Fix] Midi: Don't treat note 0 as false by @daslyfe in https://github.com/tidalcycles/strudel/pull/657 +- Create Midi Integration for Tauri Desktop app by @daslyfe in https://codeberg.org/uzu/strudel/pulls/685 +- add sleep timer + improve message iterating by @daslyfe in https://codeberg.org/uzu/strudel/pulls/688 +- fix MIDI CC messages by @vasilymilovidov in https://codeberg.org/uzu/strudel/pulls/690 +- Direct OSC Support in Tauri by @daslyfe in https://codeberg.org/uzu/strudel/pulls/694 +- Add logging from tauri by @daslyfe in https://codeberg.org/uzu/strudel/pulls/697 +- fix osc bundle timestamp glitches caused by drifting clock by @daslyfe in https://codeberg.org/uzu/strudel/pulls/666 +- Midi time fixes by @daslyfe in https://codeberg.org/uzu/strudel/pulls/668 +- [Bug Fix] Account for numeral notation when converting to midi by @daslyfe in https://codeberg.org/uzu/strudel/pulls/656 +- [Bug Fix] Midi: Don't treat note 0 as false by @daslyfe in https://codeberg.org/uzu/strudel/pulls/657 ## Visuals -- 2 new FFT based vizualisations have now landed: [scope and fscope](https://github.com/tidalcycles/strudel/pull/677) (featured in the video at the top). -- pianoroll has new options, see [PR](https://github.com/tidalcycles/strudel/pull/679) +- 2 new FFT based vizualisations have now landed: [scope and fscope](https://codeberg.org/uzu/strudel/pulls/677) (featured in the video at the top). +- pianoroll has new options, see [PR](https://codeberg.org/uzu/strudel/pulls/679) Related PRs: -- Scope by @felixroos in https://github.com/tidalcycles/strudel/pull/677 ([demo](https://strudel.tidalcycles.org/?hXVQF-KxMI8p)) -- Pianoroll improvements by @felixroos in https://github.com/tidalcycles/strudel/pull/679 ([demo](https://strudel.tidalcycles.org/?aPMKqXGVMgSM)) +- Scope by @felixroos in https://codeberg.org/uzu/strudel/pulls/677 ([demo](https://strudel.tidalcycles.org/?hXVQF-KxMI8p)) +- Pianoroll improvements by @felixroos in https://codeberg.org/uzu/strudel/pulls/679 ([demo](https://strudel.tidalcycles.org/?aPMKqXGVMgSM)) ## Voicings There is now a new way to play chord voicings + a huge selection of chord voicings available. Find out more in these PRs: -- stateless voicings + tonleiter lib by @felixroos in https://github.com/tidalcycles/strudel/pull/647 ([demo](https://strudel.tidalcycles.org/?FoILM0Hs9y9f)) -- ireal voicings by @felixroos in https://github.com/tidalcycles/strudel/pull/653 ([demo](https://strudel.tidalcycles.org/?bv_TjY9hOC28)) +- stateless voicings + tonleiter lib by @felixroos in https://codeberg.org/uzu/strudel/pulls/647 ([demo](https://strudel.tidalcycles.org/?FoILM0Hs9y9f)) +- ireal voicings by @felixroos in https://codeberg.org/uzu/strudel/pulls/653 ([demo](https://strudel.tidalcycles.org/?bv_TjY9hOC28)) ## Adaptive Highlighting Thanks to @mindofmatthew , the highlighting will adapt to edits instantly! Related PRs: -- More work on highlight IDs by @mindofmatthew in https://github.com/tidalcycles/strudel/pull/636 -- Adaptive Highlighting by @felixroos in https://github.com/tidalcycles/strudel/pull/634 +- More work on highlight IDs by @mindofmatthew in https://codeberg.org/uzu/strudel/pulls/636 +- Adaptive Highlighting by @felixroos in https://codeberg.org/uzu/strudel/pulls/634 ## UI Changes -- teletext theme + fonts by @felixroos in https://github.com/tidalcycles/strudel/pull/681 (featured in video at the top) -- togglable panel position by @felixroos in https://github.com/tidalcycles/strudel/pull/667 +- teletext theme + fonts by @felixroos in https://codeberg.org/uzu/strudel/pulls/681 (featured in video at the top) +- togglable panel position by @felixroos in https://codeberg.org/uzu/strudel/pulls/667 ## Other New Features -- slice: list mode by @felixroos in https://github.com/tidalcycles/strudel/pull/645 ([demo](https://strudel.tidalcycles.org/?bAYIqz5NLjRr)) -- add emoji support by @felixroos in https://github.com/tidalcycles/strudel/pull/680 ([demo](https://strudel.tidalcycles.org/?a6FgLz475gN9)) +- slice: list mode by @felixroos in https://codeberg.org/uzu/strudel/pulls/645 ([demo](https://strudel.tidalcycles.org/?bAYIqz5NLjRr)) +- add emoji support by @felixroos in https://codeberg.org/uzu/strudel/pulls/680 ([demo](https://strudel.tidalcycles.org/?a6FgLz475gN9)) ## Articles -- Understand pitch by @felixroos in https://github.com/tidalcycles/strudel/pull/652 +- Understand pitch by @felixroos in https://codeberg.org/uzu/strudel/pulls/652 ## Other Fixes & Enhancements -- fix: out of range error by @felixroos in https://github.com/tidalcycles/strudel/pull/630 -- fix: update canvas size on window resize by @felixroos in https://github.com/tidalcycles/strudel/pull/631 -- FIXES: TODO in rotateChroma by @bwagner in https://github.com/tidalcycles/strudel/pull/650 -- snapshot tests: sort haps by part by @felixroos in https://github.com/tidalcycles/strudel/pull/637 -- Delete old packages by @felixroos in https://github.com/tidalcycles/strudel/pull/639 -- update vitest by @felixroos in https://github.com/tidalcycles/strudel/pull/651 -- fix: welcome message for latestCode by @felixroos in https://github.com/tidalcycles/strudel/pull/659 -- fix: always run previous trigger by @felixroos in https://github.com/tidalcycles/strudel/pull/660 +- fix: out of range error by @felixroos in https://codeberg.org/uzu/strudel/pulls/630 +- fix: update canvas size on window resize by @felixroos in https://codeberg.org/uzu/strudel/pulls/631 +- FIXES: TODO in rotateChroma by @bwagner in https://codeberg.org/uzu/strudel/pulls/650 +- snapshot tests: sort haps by part by @felixroos in https://codeberg.org/uzu/strudel/pulls/637 +- Delete old packages by @felixroos in https://codeberg.org/uzu/strudel/pulls/639 +- update vitest by @felixroos in https://codeberg.org/uzu/strudel/pulls/651 +- fix: welcome message for latestCode by @felixroos in https://codeberg.org/uzu/strudel/pulls/659 +- fix: always run previous trigger by @felixroos in https://codeberg.org/uzu/strudel/pulls/660 ## New Contributors -- @daslyfe made their first contribution in https://github.com/tidalcycles/strudel/pull/656 -- @Bubobubobubobubo made their first contribution in https://github.com/tidalcycles/strudel/pull/683 +- @daslyfe made their first contribution in https://codeberg.org/uzu/strudel/pulls/656 +- @Bubobubobubobubo made their first contribution in https://codeberg.org/uzu/strudel/pulls/683 -**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.8.0...v0.9.0 +**Full Changelog**: https://codeberg.org/uzu/strudel/compare/v0.8.0...v0.9.0 A big thanks to all the contributors! diff --git a/website/src/content/blog/release-1.0.0-geburtstagskuchen.mdx b/website/src/content/blog/release-1.0.0-geburtstagskuchen.mdx index 5784fe52d..0ee149a20 100644 --- a/website/src/content/blog/release-1.0.0-geburtstagskuchen.mdx +++ b/website/src/content/blog/release-1.0.0-geburtstagskuchen.mdx @@ -27,13 +27,13 @@ Let me write up some of the highlights: This version changes the default cps value from 1 to 0.5 to give patterns a little bit more time by default. If you find your existing patterns to be suddenly half the speed, just add a `setcps(1)` to the top and it should sound as it did before! -- make 0.5hz cps the default by @yaxu in https://github.com/tidalcycles/strudel/pull/931 +- make 0.5hz cps the default by @yaxu in https://codeberg.org/uzu/strudel/pulls/931 ## New Domain Strudel is now available under [strudel.cc](https://strudel.cc/). The old domain still works but you might not get the most recent version. -- replace strudel.tidalcycles.org with strudel.cc by @felixroos in https://github.com/tidalcycles/strudel/pull/768 +- replace strudel.tidalcycles.org with strudel.cc by @felixroos in https://codeberg.org/uzu/strudel/pulls/768 ## Strudel on Mastodon @@ -43,52 +43,52 @@ Strudel now has a mastodon presence: https://social.toplap.org/@strudel superdough, the audio engine of strudel has gotten some new features: -- Create phaser effect by @daslyfe in https://github.com/tidalcycles/strudel/pull/798 -- Multichannel audio by @daslyfe in https://github.com/tidalcycles/strudel/pull/820 -- Audio device selection by @daslyfe in https://github.com/tidalcycles/strudel/pull/854 -- Better convolution reverb by generating impulse responses by @Bubobubobubobubo and @felixroos in https://github.com/tidalcycles/strudel/pull/718 -- Add 'white', 'pink' and 'brown' oscillators + refactor synth by @Bubobubobubobubo and @felixroos in https://github.com/tidalcycles/strudel/pull/713 -- New noise type: "crackle" by @Bubobubobubobubo in https://github.com/tidalcycles/strudel/pull/806 -- Add support for using samples as impulse response buffers for the reverb by @vasilymilovidov in https://github.com/tidalcycles/strudel/pull/717 -- Compressor by @felixroos in https://github.com/tidalcycles/strudel/pull/729 -- Adding vibrato to Superdough sampler by @Bubobubobubobubo and @felixroos in https://github.com/tidalcycles/strudel/pull/706 -- Further Envelope improvements by @daslyfe in https://github.com/tidalcycles/strudel/pull/868 -- Add more vowel qualities for the vowels function by @fnordomat in https://github.com/tidalcycles/strudel/pull/907 -- pitch envelope by @felixroos in https://github.com/tidalcycles/strudel/pull/913 +- Create phaser effect by @daslyfe in https://codeberg.org/uzu/strudel/pulls/798 +- Multichannel audio by @daslyfe in https://codeberg.org/uzu/strudel/pulls/820 +- Audio device selection by @daslyfe in https://codeberg.org/uzu/strudel/pulls/854 +- Better convolution reverb by generating impulse responses by @Bubobubobubobubo and @felixroos in https://codeberg.org/uzu/strudel/pulls/718 +- Add 'white', 'pink' and 'brown' oscillators + refactor synth by @Bubobubobubobubo and @felixroos in https://codeberg.org/uzu/strudel/pulls/713 +- New noise type: "crackle" by @Bubobubobubobubo in https://codeberg.org/uzu/strudel/pulls/806 +- Add support for using samples as impulse response buffers for the reverb by @vasilymilovidov in https://codeberg.org/uzu/strudel/pulls/717 +- Compressor by @felixroos in https://codeberg.org/uzu/strudel/pulls/729 +- Adding vibrato to Superdough sampler by @Bubobubobubobubo and @felixroos in https://codeberg.org/uzu/strudel/pulls/706 +- Further Envelope improvements by @daslyfe in https://codeberg.org/uzu/strudel/pulls/868 +- Add more vowel qualities for the vowels function by @fnordomat in https://codeberg.org/uzu/strudel/pulls/907 +- pitch envelope by @felixroos in https://codeberg.org/uzu/strudel/pulls/913 ## Slider Controls The new `slider` function inlines a draggable slider element into the code, bridging the gap between code and GUI. -- widgets by @felixroos in https://github.com/tidalcycles/strudel/pull/714 -- Slider afterthoughts by @felixroos in https://github.com/tidalcycles/strudel/pull/723 -- add xfade by @felixroos in https://github.com/tidalcycles/strudel/pull/780 +- widgets by @felixroos in https://codeberg.org/uzu/strudel/pulls/714 +- Slider afterthoughts by @felixroos in https://codeberg.org/uzu/strudel/pulls/723 +- add xfade by @felixroos in https://codeberg.org/uzu/strudel/pulls/780 ## Improved MIDI integration Pattern params [can now be controlled with cc messages](https://www.youtube.com/watch?v=e2-Sv_jjDQk) + you can now send a MIDI clock to sync your DAW with strudel. -- Midi in by @felixroos in https://github.com/tidalcycles/strudel/pull/699 -- add midi clock support by @felixroos in https://github.com/tidalcycles/strudel/pull/710 +- Midi in by @felixroos in https://codeberg.org/uzu/strudel/pulls/699 +- add midi clock support by @felixroos in https://codeberg.org/uzu/strudel/pulls/710 ## hydra [hydra](https://hydra.ojack.xyz), the live coding video synth [can now be used directly inside the strudel REPL](https://strudel.cc/learn/hydra/). -- Hydra integration by @felixroos in https://github.com/tidalcycles/strudel/pull/759 -- add options param to initHydra by @kasparsj in https://github.com/tidalcycles/strudel/pull/808 -- Hydra fixes and improvements by @atfornes in https://github.com/tidalcycles/strudel/pull/818 +- Hydra integration by @felixroos in https://codeberg.org/uzu/strudel/pulls/759 +- add options param to initHydra by @kasparsj in https://codeberg.org/uzu/strudel/pulls/808 +- Hydra fixes and improvements by @atfornes in https://codeberg.org/uzu/strudel/pulls/818 ## Vanilla REPL The codemirror editor and the repl abstraction have been refactored from react to vanilla JS! This should give some performance improvements and less dependency / maintenance burden: -- Vanilla repl 2 by @felixroos in https://github.com/tidalcycles/strudel/pull/863 -- Vanilla repl 3 by @felixroos in https://github.com/tidalcycles/strudel/pull/865 -- more work on vanilla repl: repl web component + package + MicroRepl by @felixroos in https://github.com/tidalcycles/strudel/pull/866 -- main repl vanillification by @felixroos in https://github.com/tidalcycles/strudel/pull/873 -- final vanillification by @felixroos in https://github.com/tidalcycles/strudel/pull/876 +- Vanilla repl 2 by @felixroos in https://codeberg.org/uzu/strudel/pulls/863 +- Vanilla repl 3 by @felixroos in https://codeberg.org/uzu/strudel/pulls/865 +- more work on vanilla repl: repl web component + package + MicroRepl by @felixroos in https://codeberg.org/uzu/strudel/pulls/866 +- main repl vanillification by @felixroos in https://codeberg.org/uzu/strudel/pulls/873 +- final vanillification by @felixroos in https://codeberg.org/uzu/strudel/pulls/876 ## Doc Changes @@ -97,25 +97,25 @@ Plenty of things have been added to the docs, including a [showcase of what peop
show PRs -- Showcase by @felixroos in https://github.com/tidalcycles/strudel/pull/885 -- Recipes by @felixroos in https://github.com/tidalcycles/strudel/pull/742 -- Document striate function by @ilesinge in https://github.com/tidalcycles/strudel/pull/766 -- Document adsr function by @ilesinge in https://github.com/tidalcycles/strudel/pull/767 -- Add function params in reference tab by @ilesinge in https://github.com/tidalcycles/strudel/pull/785 -- Update first-sounds.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/794 -- Update recap.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/797 -- Update pattern-effects.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/796 -- Update first-effects.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/795 -- Document pianoroll by @ilesinge in https://github.com/tidalcycles/strudel/pull/784 -- Add doc for euclidLegatoRot, wordfall and slider by @ilesinge in https://github.com/tidalcycles/strudel/pull/801 -- Improve documentation for synonym functions by @ilesinge in https://github.com/tidalcycles/strudel/pull/800 -- Add and style algolia search by @ilesinge in https://github.com/tidalcycles/strudel/pull/827 -- Fix a typo by @drewgbarnes in https://github.com/tidalcycles/strudel/pull/830 -- add mastodon link by @felixroos in https://github.com/tidalcycles/strudel/pull/884 -- adds a blog by @felixroos in https://github.com/tidalcycles/strudel/pull/911 -- community bakery by @felixroos in https://github.com/tidalcycles/strudel/pull/923 -- Blog improvements by @felixroos in https://github.com/tidalcycles/strudel/pull/919 -- 2 years blog post by @felixroos in https://github.com/tidalcycles/strudel/pull/929 +- Showcase by @felixroos in https://codeberg.org/uzu/strudel/pulls/885 +- Recipes by @felixroos in https://codeberg.org/uzu/strudel/pulls/742 +- Document striate function by @ilesinge in https://codeberg.org/uzu/strudel/pulls/766 +- Document adsr function by @ilesinge in https://codeberg.org/uzu/strudel/pulls/767 +- Add function params in reference tab by @ilesinge in https://codeberg.org/uzu/strudel/pulls/785 +- Update first-sounds.mdx by @bwagner in https://codeberg.org/uzu/strudel/pulls/794 +- Update recap.mdx by @bwagner in https://codeberg.org/uzu/strudel/pulls/797 +- Update pattern-effects.mdx by @bwagner in https://codeberg.org/uzu/strudel/pulls/796 +- Update first-effects.mdx by @bwagner in https://codeberg.org/uzu/strudel/pulls/795 +- Document pianoroll by @ilesinge in https://codeberg.org/uzu/strudel/pulls/784 +- Add doc for euclidLegatoRot, wordfall and slider by @ilesinge in https://codeberg.org/uzu/strudel/pulls/801 +- Improve documentation for synonym functions by @ilesinge in https://codeberg.org/uzu/strudel/pulls/800 +- Add and style algolia search by @ilesinge in https://codeberg.org/uzu/strudel/pulls/827 +- Fix a typo by @drewgbarnes in https://codeberg.org/uzu/strudel/pulls/830 +- add mastodon link by @felixroos in https://codeberg.org/uzu/strudel/pulls/884 +- adds a blog by @felixroos in https://codeberg.org/uzu/strudel/pulls/911 +- community bakery by @felixroos in https://codeberg.org/uzu/strudel/pulls/923 +- Blog improvements by @felixroos in https://codeberg.org/uzu/strudel/pulls/919 +- 2 years blog post by @felixroos in https://codeberg.org/uzu/strudel/pulls/929
@@ -124,34 +124,34 @@ Plenty of things have been added to the docs, including a [showcase of what peop
There is a lot more -- mini notation: international alphabets support by @ilesinge in https://github.com/tidalcycles/strudel/pull/751 -- Add shabda shortcut by @ilesinge in https://github.com/tidalcycles/strudel/pull/740 -- add play function by @felixroos in https://github.com/tidalcycles/strudel/pull/758 (superseded by next) -- tidal style d1 ... d9 functions + more by @felixroos in https://github.com/tidalcycles/strudel/pull/805 -- add vscode bindings by @Dsm0 in https://github.com/tidalcycles/strudel/pull/773 -- Implement optional hover tooltip with function documentation by @ilesinge in https://github.com/tidalcycles/strudel/pull/783 -- samples loading shortcuts: by @felixroos in https://github.com/tidalcycles/strudel/pull/788 -- add option to disable active line highlighting in Code Settings by @kasparsj in https://github.com/tidalcycles/strudel/pull/804 -- Color hsl by @felixroos in https://github.com/tidalcycles/strudel/pull/815 -- Patterns tab + Refactor Panel by @felixroos in https://github.com/tidalcycles/strudel/pull/769 -- patterns tab: import patterns + style by @felixroos in https://github.com/tidalcycles/strudel/pull/852 -- Export patterns + ui tweaks by @felixroos in https://github.com/tidalcycles/strudel/pull/855 -- Pattern organization by @felixroos in https://github.com/tidalcycles/strudel/pull/858 -- Sound Import from local file system by @daslyfe in https://github.com/tidalcycles/strudel/pull/839 -- bugfix: suspend and close existing audio context when changing interface by @daslyfe in https://github.com/tidalcycles/strudel/pull/882 -- add root mode for voicings by @felixroos in https://github.com/tidalcycles/strudel/pull/887 -- scales can now be anchored by @felixroos in https://github.com/tidalcycles/strudel/pull/888 -- add dough function for raw dsp by @felixroos in https://github.com/tidalcycles/strudel/pull/707 (experimental) -- support mininotation '..' range operator, fixes #715 by @yaxu in https://github.com/tidalcycles/strudel/pull/716 -- Add pick and squeeze functions by @daslyfe in https://github.com/tidalcycles/strudel/pull/771 -- support , in < > by @felixroos in https://github.com/tidalcycles/strudel/pull/886 -- public sharing by @felixroos in https://github.com/tidalcycles/strudel/pull/910 -- pick, pickmod, inhabit, inhabitmod by @yaxu in https://github.com/tidalcycles/strudel/pull/921 -- Mini-notation additions towards tidal compatibility by @yaxu in https://github.com/tidalcycles/strudel/pull/926 -- add pickF and pickmodF by @geikha in https://github.com/tidalcycles/strudel/pull/924 -- Make splice cps-aware by @yaxu in https://github.com/tidalcycles/strudel/pull/932 -- Refactor cps functions by @felixroos in https://github.com/tidalcycles/strudel/pull/933 -- Add useful pattern selection behavior for performing. by @daslyfe in https://github.com/tidalcycles/strudel/pull/897 +- mini notation: international alphabets support by @ilesinge in https://codeberg.org/uzu/strudel/pulls/751 +- Add shabda shortcut by @ilesinge in https://codeberg.org/uzu/strudel/pulls/740 +- add play function by @felixroos in https://codeberg.org/uzu/strudel/pulls/758 (superseded by next) +- tidal style d1 ... d9 functions + more by @felixroos in https://codeberg.org/uzu/strudel/pulls/805 +- add vscode bindings by @Dsm0 in https://codeberg.org/uzu/strudel/pulls/773 +- Implement optional hover tooltip with function documentation by @ilesinge in https://codeberg.org/uzu/strudel/pulls/783 +- samples loading shortcuts: by @felixroos in https://codeberg.org/uzu/strudel/pulls/788 +- add option to disable active line highlighting in Code Settings by @kasparsj in https://codeberg.org/uzu/strudel/pulls/804 +- Color hsl by @felixroos in https://codeberg.org/uzu/strudel/pulls/815 +- Patterns tab + Refactor Panel by @felixroos in https://codeberg.org/uzu/strudel/pulls/769 +- patterns tab: import patterns + style by @felixroos in https://codeberg.org/uzu/strudel/pulls/852 +- Export patterns + ui tweaks by @felixroos in https://codeberg.org/uzu/strudel/pulls/855 +- Pattern organization by @felixroos in https://codeberg.org/uzu/strudel/pulls/858 +- Sound Import from local file system by @daslyfe in https://codeberg.org/uzu/strudel/pulls/839 +- bugfix: suspend and close existing audio context when changing interface by @daslyfe in https://codeberg.org/uzu/strudel/pulls/882 +- add root mode for voicings by @felixroos in https://codeberg.org/uzu/strudel/pulls/887 +- scales can now be anchored by @felixroos in https://codeberg.org/uzu/strudel/pulls/888 +- add dough function for raw dsp by @felixroos in https://codeberg.org/uzu/strudel/pulls/707 (experimental) +- support mininotation '..' range operator, fixes #715 by @yaxu in https://codeberg.org/uzu/strudel/pulls/716 +- Add pick and squeeze functions by @daslyfe in https://codeberg.org/uzu/strudel/pulls/771 +- support , in < > by @felixroos in https://codeberg.org/uzu/strudel/pulls/886 +- public sharing by @felixroos in https://codeberg.org/uzu/strudel/pulls/910 +- pick, pickmod, inhabit, inhabitmod by @yaxu in https://codeberg.org/uzu/strudel/pulls/921 +- Mini-notation additions towards tidal compatibility by @yaxu in https://codeberg.org/uzu/strudel/pulls/926 +- add pickF and pickmodF by @geikha in https://codeberg.org/uzu/strudel/pulls/924 +- Make splice cps-aware by @yaxu in https://codeberg.org/uzu/strudel/pulls/932 +- Refactor cps functions by @felixroos in https://codeberg.org/uzu/strudel/pulls/933 +- Add useful pattern selection behavior for performing. by @daslyfe in https://codeberg.org/uzu/strudel/pulls/897
@@ -160,79 +160,79 @@ Plenty of things have been added to the docs, including a [showcase of what peop
show -- fix: finally repair envelopes by @felixroos in https://github.com/tidalcycles/strudel/pull/861 -- fix: reverb regenerate loophole by @felixroos in https://github.com/tidalcycles/strudel/pull/726 -- fix: reverb roomsize not required by @felixroos in https://github.com/tidalcycles/strudel/pull/731 -- fix: reverb sampleRate by @felixroos in https://github.com/tidalcycles/strudel/pull/732 -- consume n with scale by @felixroos in https://github.com/tidalcycles/strudel/pull/727 -- fix: hashes in urls by @felixroos in https://github.com/tidalcycles/strudel/pull/728 -- [Bug Fix] chooseWith: prevent pattern from stopping audio when selection is >= 1 or < 0 by @daslyfe in https://github.com/tidalcycles/strudel/pull/741 -- Fix addivite synthesis phases by @felixroos in https://github.com/tidalcycles/strudel/pull/762 -- fix: scale offset by @felixroos in https://github.com/tidalcycles/strudel/pull/764 -- fix zen mode logo overlap by @felixroos in https://github.com/tidalcycles/strudel/pull/760 -- fix: share copy to clipboard + alert by @felixroos in https://github.com/tidalcycles/strudel/pull/774 -- fix: style issues by @felixroos in https://github.com/tidalcycles/strudel/pull/781 -- Fix scope pos + document by @felixroos in https://github.com/tidalcycles/strudel/pull/786 -- don't use anchor links for reference by @felixroos in https://github.com/tidalcycles/strudel/pull/791 -- remove unwanted cm6 outline for strudelTheme by @kasparsj in https://github.com/tidalcycles/strudel/pull/802 -- FIXES: palindrome abc -> abccba by @bwagner in https://github.com/tidalcycles/strudel/pull/831 -- Bug Fix #119: Clock drift by @daslyfe in https://github.com/tidalcycles/strudel/pull/874 -- bugfix: sound select indexes out of bounds by @daslyfe in https://github.com/tidalcycles/strudel/pull/871 -- Error tolerance by @felixroos in https://github.com/tidalcycles/strudel/pull/880 -- fix: make sure n is never undefined before nanFallback by @felixroos in https://github.com/tidalcycles/strudel/pull/881 -- fix: invisible selection on vim + emacs mode by @felixroos in https://github.com/tidalcycles/strudel/pull/889 -- fix: autocomplete / tooltip code example bug by @felixroos in https://github.com/tidalcycles/strudel/pull/898 -- Fix examples page, piano() and a few workshop imgs by @shiyouganai in https://github.com/tidalcycles/strudel/pull/848 -- fix: trailing slash confusion by @felixroos in https://github.com/tidalcycles/strudel/pull/743 -- fix: try different trailing slash behavior by @felixroos in https://github.com/tidalcycles/strudel/pull/744 -- Fix krill build command in README by @ilesinge in https://github.com/tidalcycles/strudel/pull/748 -- Fix for #1. Enables named instruments for csoundm. by @gogins in https://github.com/tidalcycles/strudel/pull/662 -- fix: missing hash for links starting with / by @felixroos in https://github.com/tidalcycles/strudel/pull/845 -- fix: swatch png src by @felixroos in https://github.com/tidalcycles/strudel/pull/846 -- Fix edge case with rehype-urls and trailing slashes in image file paths by @shiyouganai in https://github.com/tidalcycles/strudel/pull/849 -- fix: multiple repls by @felixroos in https://github.com/tidalcycles/strudel/pull/813 -- Fix chunk, add fastChunk and repeatCycles by @yaxu in https://github.com/tidalcycles/strudel/pull/712 -- Update tauri.yml workflow file by @vasilymilovidov in https://github.com/tidalcycles/strudel/pull/705 -- vite-vanilla-repl readme fix by @felixroos in https://github.com/tidalcycles/strudel/pull/737 -- completely revert config mess by @felixroos in https://github.com/tidalcycles/strudel/pull/745 -- hopefully fix trailing slashes bug by @felixroos in https://github.com/tidalcycles/strudel/pull/753 -- Update vite pwa by @felixroos in https://github.com/tidalcycles/strudel/pull/772 -- Update to Astro 3 by @felixroos in https://github.com/tidalcycles/strudel/pull/775 -- support multiple named serial connections, change default baudrate by @yaxu in https://github.com/tidalcycles/strudel/pull/551 -- CHANGES: github action checkout v2 -> v4 by @bwagner in https://github.com/tidalcycles/strudel/pull/837 -- CHANGES: pin pnpm to version 8.3.1 by @bwagner in https://github.com/tidalcycles/strudel/pull/834 -- CHANGES: github action pnpm version from 7 to 8.3.1 by @bwagner in https://github.com/tidalcycles/strudel/pull/835 -- ADDS: JetBrains IDE files and directories to .gitignore by @bwagner in https://github.com/tidalcycles/strudel/pull/840 -- Prevent 404 on Algolia crawls by @ilesinge in https://github.com/tidalcycles/strudel/pull/838 -- Add in fixes from my fork to slashocalypse branch by @shiyouganai in https://github.com/tidalcycles/strudel/pull/843 -- improve slashing + base href behavior by @felixroos in https://github.com/tidalcycles/strudel/pull/842 -- CHANGES: pnpm 8.1.3 to 8.11.0 by @bwagner in https://github.com/tidalcycles/strudel/pull/850 -- add missing trailing slashes by @felixroos in https://github.com/tidalcycles/strudel/pull/860 -- move all examples to separate examples folder by @felixroos in https://github.com/tidalcycles/strudel/pull/878 -- Dependency update by @felixroos in https://github.com/tidalcycles/strudel/pull/879 -- Update Vite version so hot reload works properly with newest pnpm version by @daslyfe in https://github.com/tidalcycles/strudel/pull/892 -- prevent vite from complaining about additional exports in jsx files by @daslyfe in https://github.com/tidalcycles/strudel/pull/891 -- fix some build warnings by @felixroos in https://github.com/tidalcycles/strudel/pull/902 -- Remove hideHeader for better mobile UI and consistency by @rjulian in https://github.com/tidalcycles/strudel/pull/894 -- Fix: swatch/[name].png.js static path by @oscarbyrne in https://github.com/tidalcycles/strudel/pull/916 -- rename @strudel.cycles/_ packages to @strudel/_ by @felixroos in https://github.com/tidalcycles/strudel/pull/917 -- `pick` now accepts lookup tables, with alternate cycle squeezing behaviour as new `inhabit` function by @yaxu in https://github.com/tidalcycles/strudel/pull/918 -- Revert "`pick` now accepts lookup tables, with alternate cycle squeezing behaviour as new `inhabit` function" by @yaxu in https://github.com/tidalcycles/strudel/pull/920 -- Fix pattern tab not showing patterns without created date by @daslyfe in https://github.com/tidalcycles/strudel/pull/934 +- fix: finally repair envelopes by @felixroos in https://codeberg.org/uzu/strudel/pulls/861 +- fix: reverb regenerate loophole by @felixroos in https://codeberg.org/uzu/strudel/pulls/726 +- fix: reverb roomsize not required by @felixroos in https://codeberg.org/uzu/strudel/pulls/731 +- fix: reverb sampleRate by @felixroos in https://codeberg.org/uzu/strudel/pulls/732 +- consume n with scale by @felixroos in https://codeberg.org/uzu/strudel/pulls/727 +- fix: hashes in urls by @felixroos in https://codeberg.org/uzu/strudel/pulls/728 +- [Bug Fix] chooseWith: prevent pattern from stopping audio when selection is >= 1 or < 0 by @daslyfe in https://codeberg.org/uzu/strudel/pulls/741 +- Fix addivite synthesis phases by @felixroos in https://codeberg.org/uzu/strudel/pulls/762 +- fix: scale offset by @felixroos in https://codeberg.org/uzu/strudel/pulls/764 +- fix zen mode logo overlap by @felixroos in https://codeberg.org/uzu/strudel/pulls/760 +- fix: share copy to clipboard + alert by @felixroos in https://codeberg.org/uzu/strudel/pulls/774 +- fix: style issues by @felixroos in https://codeberg.org/uzu/strudel/pulls/781 +- Fix scope pos + document by @felixroos in https://codeberg.org/uzu/strudel/pulls/786 +- don't use anchor links for reference by @felixroos in https://codeberg.org/uzu/strudel/pulls/791 +- remove unwanted cm6 outline for strudelTheme by @kasparsj in https://codeberg.org/uzu/strudel/pulls/802 +- FIXES: palindrome abc -> abccba by @bwagner in https://codeberg.org/uzu/strudel/pulls/831 +- Bug Fix #119: Clock drift by @daslyfe in https://codeberg.org/uzu/strudel/pulls/874 +- bugfix: sound select indexes out of bounds by @daslyfe in https://codeberg.org/uzu/strudel/pulls/871 +- Error tolerance by @felixroos in https://codeberg.org/uzu/strudel/pulls/880 +- fix: make sure n is never undefined before nanFallback by @felixroos in https://codeberg.org/uzu/strudel/pulls/881 +- fix: invisible selection on vim + emacs mode by @felixroos in https://codeberg.org/uzu/strudel/pulls/889 +- fix: autocomplete / tooltip code example bug by @felixroos in https://codeberg.org/uzu/strudel/pulls/898 +- Fix examples page, piano() and a few workshop imgs by @shiyouganai in https://codeberg.org/uzu/strudel/pulls/848 +- fix: trailing slash confusion by @felixroos in https://codeberg.org/uzu/strudel/pulls/743 +- fix: try different trailing slash behavior by @felixroos in https://codeberg.org/uzu/strudel/pulls/744 +- Fix krill build command in README by @ilesinge in https://codeberg.org/uzu/strudel/pulls/748 +- Fix for #1. Enables named instruments for csoundm. by @gogins in https://codeberg.org/uzu/strudel/pulls/662 +- fix: missing hash for links starting with / by @felixroos in https://codeberg.org/uzu/strudel/pulls/845 +- fix: swatch png src by @felixroos in https://codeberg.org/uzu/strudel/pulls/846 +- Fix edge case with rehype-urls and trailing slashes in image file paths by @shiyouganai in https://codeberg.org/uzu/strudel/pulls/849 +- fix: multiple repls by @felixroos in https://codeberg.org/uzu/strudel/pulls/813 +- Fix chunk, add fastChunk and repeatCycles by @yaxu in https://codeberg.org/uzu/strudel/pulls/712 +- Update tauri.yml workflow file by @vasilymilovidov in https://codeberg.org/uzu/strudel/pulls/705 +- vite-vanilla-repl readme fix by @felixroos in https://codeberg.org/uzu/strudel/pulls/737 +- completely revert config mess by @felixroos in https://codeberg.org/uzu/strudel/pulls/745 +- hopefully fix trailing slashes bug by @felixroos in https://codeberg.org/uzu/strudel/pulls/753 +- Update vite pwa by @felixroos in https://codeberg.org/uzu/strudel/pulls/772 +- Update to Astro 3 by @felixroos in https://codeberg.org/uzu/strudel/pulls/775 +- support multiple named serial connections, change default baudrate by @yaxu in https://codeberg.org/uzu/strudel/pulls/551 +- CHANGES: github action checkout v2 -> v4 by @bwagner in https://codeberg.org/uzu/strudel/pulls/837 +- CHANGES: pin pnpm to version 8.3.1 by @bwagner in https://codeberg.org/uzu/strudel/pulls/834 +- CHANGES: github action pnpm version from 7 to 8.3.1 by @bwagner in https://codeberg.org/uzu/strudel/pulls/835 +- ADDS: JetBrains IDE files and directories to .gitignore by @bwagner in https://codeberg.org/uzu/strudel/pulls/840 +- Prevent 404 on Algolia crawls by @ilesinge in https://codeberg.org/uzu/strudel/pulls/838 +- Add in fixes from my fork to slashocalypse branch by @shiyouganai in https://codeberg.org/uzu/strudel/pulls/843 +- improve slashing + base href behavior by @felixroos in https://codeberg.org/uzu/strudel/pulls/842 +- CHANGES: pnpm 8.1.3 to 8.11.0 by @bwagner in https://codeberg.org/uzu/strudel/pulls/850 +- add missing trailing slashes by @felixroos in https://codeberg.org/uzu/strudel/pulls/860 +- move all examples to separate examples folder by @felixroos in https://codeberg.org/uzu/strudel/pulls/878 +- Dependency update by @felixroos in https://codeberg.org/uzu/strudel/pulls/879 +- Update Vite version so hot reload works properly with newest pnpm version by @daslyfe in https://codeberg.org/uzu/strudel/pulls/892 +- prevent vite from complaining about additional exports in jsx files by @daslyfe in https://codeberg.org/uzu/strudel/pulls/891 +- fix some build warnings by @felixroos in https://codeberg.org/uzu/strudel/pulls/902 +- Remove hideHeader for better mobile UI and consistency by @rjulian in https://codeberg.org/uzu/strudel/pulls/894 +- Fix: swatch/[name].png.js static path by @oscarbyrne in https://codeberg.org/uzu/strudel/pulls/916 +- rename @strudel.cycles/_ packages to @strudel/_ by @felixroos in https://codeberg.org/uzu/strudel/pulls/917 +- `pick` now accepts lookup tables, with alternate cycle squeezing behaviour as new `inhabit` function by @yaxu in https://codeberg.org/uzu/strudel/pulls/918 +- Revert "`pick` now accepts lookup tables, with alternate cycle squeezing behaviour as new `inhabit` function" by @yaxu in https://codeberg.org/uzu/strudel/pulls/920 +- Fix pattern tab not showing patterns without created date by @daslyfe in https://codeberg.org/uzu/strudel/pulls/934
## New Contributors -- @ilesinge made their first contribution in https://github.com/tidalcycles/strudel/pull/748 -- @Dsm0 made their first contribution in https://github.com/tidalcycles/strudel/pull/773 -- @kasparsj made their first contribution in https://github.com/tidalcycles/strudel/pull/802 -- @atfornes made their first contribution in https://github.com/tidalcycles/strudel/pull/818 -- @drewgbarnes made their first contribution in https://github.com/tidalcycles/strudel/pull/830 -- @shiyouganai made their first contribution in https://github.com/tidalcycles/strudel/pull/843 -- @rjulian made their first contribution in https://github.com/tidalcycles/strudel/pull/894 -- @fnordomat made their first contribution in https://github.com/tidalcycles/strudel/pull/907 -- @oscarbyrne made their first contribution in https://github.com/tidalcycles/strudel/pull/916 -- @geikha made their first contribution in https://github.com/tidalcycles/strudel/pull/924 +- @ilesinge made their first contribution in https://codeberg.org/uzu/strudel/pulls/748 +- @Dsm0 made their first contribution in https://codeberg.org/uzu/strudel/pulls/773 +- @kasparsj made their first contribution in https://codeberg.org/uzu/strudel/pulls/802 +- @atfornes made their first contribution in https://codeberg.org/uzu/strudel/pulls/818 +- @drewgbarnes made their first contribution in https://codeberg.org/uzu/strudel/pulls/830 +- @shiyouganai made their first contribution in https://codeberg.org/uzu/strudel/pulls/843 +- @rjulian made their first contribution in https://codeberg.org/uzu/strudel/pulls/894 +- @fnordomat made their first contribution in https://codeberg.org/uzu/strudel/pulls/907 +- @oscarbyrne made their first contribution in https://codeberg.org/uzu/strudel/pulls/916 +- @geikha made their first contribution in https://codeberg.org/uzu/strudel/pulls/924 -**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.9.0...v1.0.0 +**Full Changelog**: https://codeberg.org/uzu/strudel/compare/v0.9.0...v1.0.0 diff --git a/website/src/content/blog/year-2.mdx b/website/src/content/blog/year-2.mdx index 38e39bbfd..0ea4616a3 100644 --- a/website/src/content/blog/year-2.mdx +++ b/website/src/content/blog/year-2.mdx @@ -216,9 +216,9 @@ Here is a recording of the first session we organized over the discord server: The midi integration has gotten a few new features: -- [clock out](https://github.com/tidalcycles/strudel/pull/710) to sync your midi devices / DAW to the strudel clock (better doc coming soon) +- [clock out](https://codeberg.org/uzu/strudel/pulls/710) to sync your midi devices / DAW to the strudel clock (better doc coming soon) - [cc output](https://strudel.cc/learn/input-output/#ccn--ccv) to send cc values to your gear -- [cc input](https://github.com/tidalcycles/strudel/pull/699) to control strudel via MIDI (better doc coming soon) +- [cc input](https://codeberg.org/uzu/strudel/pulls/699) to control strudel via MIDI (better doc coming soon) Here is a little demo of me fiddling with a midi controller, changing a piano pattern: From 912b3270aaaf1a28f2444f130c179bf110edc048 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 14 Jun 2025 14:35:19 +0200 Subject: [PATCH 64/74] use yt for gh video assets + replace release asset links --- .../content/blog/release-0.8.0-himbeermuffin.mdx | 14 +++++++------- .../src/content/blog/release-0.9.0-bananenbrot.mdx | 6 +++--- .../blog/release-1.0.0-geburtstagskuchen.mdx | 2 -- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/website/src/content/blog/release-0.8.0-himbeermuffin.mdx b/website/src/content/blog/release-0.8.0-himbeermuffin.mdx index babab5954..1eb1888a7 100644 --- a/website/src/content/blog/release-0.8.0-himbeermuffin.mdx +++ b/website/src/content/blog/release-0.8.0-himbeermuffin.mdx @@ -6,7 +6,7 @@ tags: ['meta'] author: froos --- -import BlogVideo from '../../components/BlogVideo.astro'; +import { Youtube } from '@src/components/Youtube'; These are the release notes for Strudel 0.8.0 aka "Himbeermuffin"! @@ -18,11 +18,11 @@ Let me write up some of the highlights: Besides the REPL (https://strudel.tidalcycles.org/), Strudel is now also distributed as a Desktop App via https://tauri.app/! Thanks to [vasilymilovidov](https://github.com/vasilymilovidov)! -- [Linux: Debian based](https://github.com/tidalcycles/strudel/releases/download/v0.8.0/strudel_0.1.0_amd64.deb) -- [Linux: AppImage](https://github.com/tidalcycles/strudel/releases/download/v0.8.0/strudel_0.1.0_amd64.AppImage) -- [MacOS](https://github.com/tidalcycles/strudel/releases/download/v0.8.0/Strudel_0.1.0_x64.dmg) -- [Windows .exe](https://github.com/tidalcycles/strudel/releases/download/v0.8.0/Strudel_0.1.0_x64-setup.exe) -- [Windows .msi](https://github.com/tidalcycles/strudel/releases/download/v0.8.0/Strudel_0.1.0_x64_en-US.msi) +- [Linux: Debian based](https://codeberg.org/uzu/strudel/releases/download/v0.8.0/strudel_0.1.0_amd64.deb) +- [Linux: AppImage](https://codeberg.org/uzu/strudel/releases/download/v0.8.0/strudel_0.1.0_amd64.AppImage) +- [MacOS](https://codeberg.org/uzu/strudel/releases/download/v0.8.0/Strudel_0.1.0_x64.dmg) +- [Windows .exe](https://codeberg.org/uzu/strudel/releases/download/v0.8.0/Strudel_0.1.0_x64-setup.exe) +- [Windows .msi](https://codeberg.org/uzu/strudel/releases/download/v0.8.0/Strudel_0.1.0_x64_en-US.msi) edit: the desktop app performance on linux is currently not that great.. the web REPL runs much smoother (using firefox or chromium) @@ -43,7 +43,7 @@ I would be very happy to collect some feedback on how it works across different Also still undocumented, but you can now visualize patterns as a spiral via `.spiral()`: - + This is especially nice because strudel is not only the name of a dessert but also the german word for vortex! The spiral is very fitting to visualize cycles because you can align cycles vertically, while surfing along an infinite twisted timeline. diff --git a/website/src/content/blog/release-0.9.0-bananenbrot.mdx b/website/src/content/blog/release-0.9.0-bananenbrot.mdx index 944675e38..eb6894f69 100644 --- a/website/src/content/blog/release-0.9.0-bananenbrot.mdx +++ b/website/src/content/blog/release-0.9.0-bananenbrot.mdx @@ -6,8 +6,6 @@ tags: ['meta'] author: froos --- -import BlogVideo from '../../components/BlogVideo.astro'; - These are the release notes for Strudel 0.9.0 aka "Bananenbrot"! The last release was over 11 weeks ago, so a lot of things have happened! @@ -25,7 +23,9 @@ Main new features include: - [vibrato](https://strudel.tidalcycles.org/learn/synths#vibrato) - an integration of [ZZFX](https://strudel.tidalcycles.org/learn/synths#zzfx) - +import { Youtube } from '@src/components/Youtube'; + + Related PRs: diff --git a/website/src/content/blog/release-1.0.0-geburtstagskuchen.mdx b/website/src/content/blog/release-1.0.0-geburtstagskuchen.mdx index 0ee149a20..bfbf24a20 100644 --- a/website/src/content/blog/release-1.0.0-geburtstagskuchen.mdx +++ b/website/src/content/blog/release-1.0.0-geburtstagskuchen.mdx @@ -6,8 +6,6 @@ tags: ['meta'] author: froos --- -import BlogVideo from '../../components/BlogVideo.astro'; - These are the release notes for Strudel 1.0.0 aka "Geburtstagskuchen" This release marks the 2 year anniversary of the project, the first commit was on the 22nd January 2022 by Alex McLean. From 0163c8ba25712f56c0aa725a770d8c84a3b6552f Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 14 Jun 2025 14:47:16 +0200 Subject: [PATCH 65/74] replace remaining links to old github repo --- website/src/content/blog/year-2.mdx | 2 +- website/src/pages/learn/getting-started.mdx | 2 +- website/src/pages/learn/input-output.mdx | 4 +-- website/src/pages/learn/metadata.mdx | 2 -- website/src/pages/learn/samples.mdx | 2 +- website/src/pages/learn/strudel-vs-tidal.mdx | 8 ++--- website/src/pages/technical-manual/docs.mdx | 4 +-- .../src/pages/technical-manual/packages.mdx | 30 +++++++++---------- .../pages/technical-manual/project-start.mdx | 6 ++-- website/src/pages/technical-manual/repl.mdx | 4 +-- 10 files changed, 31 insertions(+), 33 deletions(-) diff --git a/website/src/content/blog/year-2.mdx b/website/src/content/blog/year-2.mdx index 0ea4616a3..1a33c673b 100644 --- a/website/src/content/blog/year-2.mdx +++ b/website/src/content/blog/year-2.mdx @@ -228,7 +228,7 @@ Here is a little demo of me fiddling with a midi controller, changing a piano pa - You can now run [hydra inside strudel](https://strudel.cc/learn/hydra/) + you can even run strudel in [hydra](https://hydra.ojack.xyz), thanks to [Olivia Jack](https://ojack.xyz/) and [Ámbar Tenorio Fornés](https://atenor.io/)! Read more [here](https://alpaca.pubpub.org/pub/b7hwrjfk/release/2?readingCollection=1def0192) - There is now a [VSCode Plugin](https://marketplace.visualstudio.com/items?itemName=roipoussiere.tidal-strudel) thanks to [roipoussiere](https://github.com/roipoussiere)! It allows you run patterns from a `.strudel` file inside VSCode. -- Strudel can now be downloaded as a desktop app, thanks to [vasilymilovidov](https://github.com/vasilymilovidov) who has wrapped the REPL with [tauri](https://tauri.app/). You can download it [on the releases page](https://github.com/tidalcycles/strudel/releases) (scroll down to Assets). The performance is not optimal on MacOS and Linux, which is why it is still considered experimental +- Strudel can now be downloaded as a desktop app, thanks to [vasilymilovidov](https://github.com/vasilymilovidov) who has wrapped the REPL with [tauri](https://tauri.app/). You can download it [on the releases page](https://codeberg.org/uzu/strudel/releases) (scroll down to Assets). The performance is not optimal on MacOS and Linux, which is why it is still considered experimental ## Stats diff --git a/website/src/pages/learn/getting-started.mdx b/website/src/pages/learn/getting-started.mdx index d18a05607..2d056d3b4 100644 --- a/website/src/pages/learn/getting-started.mdx +++ b/website/src/pages/learn/getting-started.mdx @@ -81,7 +81,7 @@ s("bd,[~ ],hh*8") // drums Please note that this project is still in its experimental state. In the future, parts of it might change significantly. This tutorial is also far from complete. -You can contribute to it clicking 'Edit this page' in the top right, or by visiting the [Strudel GitHub page](https://github.com/tidalcycles/strudel/). +You can contribute to it clicking 'Edit this page' in the top right, or by visiting the [Strudel GitHub page](https://codeberg.org/uzu/strudel/). # What's next? diff --git a/website/src/pages/learn/input-output.mdx b/website/src/pages/learn/input-output.mdx index 92379aa6f..0dd48b7de 100644 --- a/website/src/pages/learn/input-output.mdx +++ b/website/src/pages/learn/input-output.mdx @@ -8,7 +8,7 @@ import { JsDoc } from '../../docs/JsDoc'; # MIDI, OSC and MQTT -Normally, Strudel is used to pattern sound, using its own '[web audio](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API)'-based synthesiser called [SuperDough](https://github.com/tidalcycles/strudel/tree/main/packages/superdough). +Normally, Strudel is used to pattern sound, using its own '[web audio](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API)'-based synthesiser called [SuperDough](https://codeberg.org/uzu/strudel/src/branch/main/packages/superdough). It is also possible to pattern other things with Strudel, such as software and hardware synthesisers with MIDI, other software using Open Sound Control/OSC (including the [SuperDirt](https://github.com/musikinformatik/SuperDirt/) synthesiser commonly used with Strudel's sibling [TidalCycles](https://tidalcycles.org/)), or the MQTT 'internet of things' protocol. @@ -184,7 +184,7 @@ To get SuperDirt to work with Strudel, you need to 1. install SuperCollider + sc3 plugins, see [Tidal Docs](https://tidalcycles.org/docs/) (Install Tidal) for more info. 2. install SuperDirt, or the [StrudelDirt](https://github.com/daslyfe/StrudelDirt) fork which is optimised for use with Strudel 3. install [node.js](https://nodejs.org/en/) -4. download [Strudel Repo](https://github.com/tidalcycles/strudel/) (or git clone, if you have git installed) +4. download [Strudel Repo](https://codeberg.org/uzu/strudel/) (or git clone, if you have git installed) 5. run `pnpm i` in the strudel directory 6. run `pnpm run osc` to start the osc server, which forwards OSC messages from Strudel REPL to SuperCollider diff --git a/website/src/pages/learn/metadata.mdx b/website/src/pages/learn/metadata.mdx index e8bb86dfc..61db16f0e 100644 --- a/website/src/pages/learn/metadata.mdx +++ b/website/src/pages/learn/metadata.mdx @@ -18,8 +18,6 @@ You can optionally add some music metadata in your Strudel code, by using tags i Like other comments, those are ignored by Strudel, but it can be used by other tools to retrieve some information about the music. -It is for instance used by the [swatch tool](https://github.com/tidalcycles/strudel/tree/main/my-patterns) to display pattern titles in the [examples page](https://strudel.cc/examples/). - ## Alternative syntax You can also use comment blocks: diff --git a/website/src/pages/learn/samples.mdx b/website/src/pages/learn/samples.mdx index 890de3aae..cd7944caa 100644 --- a/website/src/pages/learn/samples.mdx +++ b/website/src/pages/learn/samples.mdx @@ -62,7 +62,7 @@ To see which sample names are available, open the `sounds` tab in the [REPL](htt Note that only the sample maps (mapping names to URLs) are loaded initially, while the audio samples themselves are not loaded until they are actually played. This behaviour of loading things only when they are needed is also called `lazy loading`. While it saves resources, it can also lead to sounds not being audible the first time they are triggered, because the sound is still loading. -[This might be fixed in the future](https://github.com/tidalcycles/strudel/issues/187) +[This might be fixed in the future](https://codeberg.org/uzu/strudel/issues/187) # Sound Banks diff --git a/website/src/pages/learn/strudel-vs-tidal.mdx b/website/src/pages/learn/strudel-vs-tidal.mdx index 40ea188fd..9c1cc6b73 100644 --- a/website/src/pages/learn/strudel-vs-tidal.mdx +++ b/website/src/pages/learn/strudel-vs-tidal.mdx @@ -79,7 +79,7 @@ Instead of `+` / `add`, you can use any of the available operators of the first ## Function Compatibility -[This issue](https://github.com/tidalcycles/strudel/issues/31) tracks which Tidal functions are implemented in Strudel. +[This issue](https://codeberg.org/uzu/strudel/issues/31) tracks which Tidal functions are implemented in Strudel. The list might not be 100% up to date and probably also misses some functions completely.. Feel encouraged to search the source code for a function you're looking for. If you find a function that's not on the list, please tell! @@ -89,7 +89,7 @@ If you find a function that's not on the list, please tell! As seen in the example, the `#` operator (shorthand for `|>`) is also just a function call in strudel. So `note "c5" # s "gtr"` becomes `note("c5").s('gtr')`. -[This file](https://github.com/tidalcycles/strudel/blob/main/packages/core/controls.mjs) lists all available control params. +[This file](https://codeberg.org/uzu/strudel/src/branch/main/packages/core/controls.mjs) lists all available control params. Note that not all of those work in the Webaudio Output of Strudel. If you find a tidal control that's not on the list, please tell! @@ -107,11 +107,11 @@ You can find a [list of available effects here](/learn/effects/). ### Sampler Strudel's sampler supports [a subset](/learn/samples) of Superdirt's sampler. -Also, samples are always loaded from a URL rather than from the disk, although [that might be possible in the future](https://github.com/tidalcycles/strudel/issues/118). +Also, samples are always loaded from a URL rather than from the disk, although [that might be possible in the future](https://codeberg.org/uzu/strudel/issues/118). ## Evaluation -The Strudel REPL does not support [block based evaluation](https://github.com/tidalcycles/strudel/issues/34) yet. +The Strudel REPL does not support [block based evaluation](https://codeberg.org/uzu/strudel/issues/34) yet. You can use labeled statements and `_` to mute: *8").scale('G4 minor') This will load the strudel website in an iframe, using the code provided within the HTML comments ``. The HTML comments are needed to make sure the browser won't interpret it as HTML. -For alternative ways to load this package, see the [@strudel/embed README](https://github.com/tidalcycles/strudel/tree/main/packages/embed#strudelembed). +For alternative ways to load this package, see the [@strudel/embed README](https://codeberg.org/uzu/strudel/src/branch/main/packages/embed#strudel-embed). ### @strudel/repl @@ -99,7 +99,7 @@ The upside of using the repl without an iframe is that you can pin the strudel v This will guarantee your pattern wont break due to changes to the strudel project in the future. -For more info on this package, see the [@strudel/repl README](https://github.com/tidalcycles/strudel/tree/main/packages/repl#strudelrepl). +For more info on this package, see the [@strudel/repl README](https://codeberg.org/uzu/strudel/src/branch/main/packages/repl#strudel-repl). ## With your own UI @@ -118,7 +118,7 @@ If you'd rather use your own UI, you can use the `@strudel/web` package: ``` -For more info on this package, see the [@strudel/web README](https://github.com/tidalcycles/strudel/tree/main/packages/web#strudelweb). +For more info on this package, see the [@strudel/web README]https://codeberg.org/uzu/strudel/src/branch/main/packages/web#strudel-web). ## Via npm diff --git a/website/src/pages/technical-manual/repl.mdx b/website/src/pages/technical-manual/repl.mdx index f336ce361..f53efac41 100644 --- a/website/src/pages/technical-manual/repl.mdx +++ b/website/src/pages/technical-manual/repl.mdx @@ -17,7 +17,7 @@ Besides a UI for playback control and meta information, the main part of the REP 2. While the REPL is running, the `Scheduler` queries the active `Pattern` by a regular interval, generating `Events` (also known as `Haps` in Strudel) for the next time span. 3. For each scheduling tick, all generated `Events` are triggered by calling their `onTrigger` method, which is set by the output. - + ## User Code @@ -75,7 +75,7 @@ The sum of both is passed to `withLoc` to tell each element its location, which Another important part of the user code is the mini notation, which allows to express rhythms in a short manner. -- the mini notation is [implemented as a PEG grammar](https://github.com/tidalcycles/strudel/blob/main/packages/mini/krill.pegjs), living in the [mini package](https://github.com/tidalcycles/strudel/tree/main/packages/mini) +- the mini notation is [implemented as a PEG grammar](https://codeberg.org/uzu/strudel/src/branch/talk/packages/mini/krill.pegjs), living in the [mini package](https://codeberg.org/uzu/strudel/src/branch/main/packages/mini) - it is based on [krill](https://github.com/Mdashdotdashn/krill) by Mdashdotdashn - the peg grammar is used to generate a parser with [peggyjs](https://peggyjs.org/) - the generated parser takes a mini notation string and outputs an AST From a7f044e1aae575355d8a92dea3ca05ef480a8c64 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 14 Jun 2025 16:17:22 +0200 Subject: [PATCH 66/74] more degithubbing --- packages/core/test/controls.test.mjs | 2 +- packages/core/test/drawLine.test.mjs | 2 +- packages/core/test/fraction.test.mjs | 2 +- packages/core/test/pattern.test.mjs | 2 +- packages/core/test/util.test.mjs | 2 +- packages/mini/test/mini.test.mjs | 2 +- packages/repl/README.md | 2 +- packages/tonal/test/tonal.test.mjs | 2 +- packages/tonal/test/tonleiter.test.mjs | 2 +- packages/transpiler/test/transpiler.test.mjs | 2 +- packages/xen/test/xen.test.mjs | 2 +- technical.manual.md | 6 +++--- website/src/cx.mjs | 2 +- website/src/repl/Repl.jsx | 2 +- website/src/repl/components/panel/WelcomeTab.jsx | 2 +- website/src/repl/tunes.mjs | 2 +- website/src/repl/useReplContext.jsx | 2 +- 17 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/core/test/controls.test.mjs b/packages/core/test/controls.test.mjs index f0af02eb6..667c3a4e5 100644 --- a/packages/core/test/controls.test.mjs +++ b/packages/core/test/controls.test.mjs @@ -1,6 +1,6 @@ /* controls.test.mjs - -Copyright (C) 2023 Strudel contributors - see +Copyright (C) 2023 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/core/test/drawLine.test.mjs b/packages/core/test/drawLine.test.mjs index 84359a7cf..2240080cd 100644 --- a/packages/core/test/drawLine.test.mjs +++ b/packages/core/test/drawLine.test.mjs @@ -1,6 +1,6 @@ /* drawLine.test.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/core/test/fraction.test.mjs b/packages/core/test/fraction.test.mjs index 6b710d043..b167c9483 100644 --- a/packages/core/test/fraction.test.mjs +++ b/packages/core/test/fraction.test.mjs @@ -1,6 +1,6 @@ /* fraction.test.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs index 08611032c..696f13fef 100644 --- a/packages/core/test/pattern.test.mjs +++ b/packages/core/test/pattern.test.mjs @@ -1,6 +1,6 @@ /* pattern.test.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/core/test/util.test.mjs b/packages/core/test/util.test.mjs index a511e0fc2..d3033a82c 100644 --- a/packages/core/test/util.test.mjs +++ b/packages/core/test/util.test.mjs @@ -1,6 +1,6 @@ /* util.test.mjs - Tests for the core 'util' module -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/mini/test/mini.test.mjs b/packages/mini/test/mini.test.mjs index 15d501111..c38997308 100644 --- a/packages/mini/test/mini.test.mjs +++ b/packages/mini/test/mini.test.mjs @@ -1,6 +1,6 @@ /* mini.test.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/repl/README.md b/packages/repl/README.md index 46819712c..3db4cc3f7 100644 --- a/packages/repl/README.md +++ b/packages/repl/README.md @@ -91,7 +91,7 @@ or ``` -The `.editor` property on the `strudel-editor` web component gives you the instance of [StrudelMirror](https://github.com/tidalcycles/strudel/blob/a46bd9b36ea7d31c9f1d3fca484297c7da86893f/packages/codemirror/codemirror.mjs#L124) that runs the REPL. +The `.editor` property on the `strudel-editor` web component gives you the instance of [StrudelMirror](https://codeberg.org/uzu/strudel/src/branch/a46bd9b36ea7d31c9f1d3fca484297c7da86893f/packages/codemirror/codemirror.mjs#L124) that runs the REPL. For example, you could use `setCode` to change the code from the outside, `start` / `stop` to toggle playback or `evaluate` to evaluate the code. diff --git a/packages/tonal/test/tonal.test.mjs b/packages/tonal/test/tonal.test.mjs index 8dd0e1861..c1155c238 100644 --- a/packages/tonal/test/tonal.test.mjs +++ b/packages/tonal/test/tonal.test.mjs @@ -1,6 +1,6 @@ /* tonal.test.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/tonal/test/tonleiter.test.mjs b/packages/tonal/test/tonleiter.test.mjs index e1a693579..b53a8f405 100644 --- a/packages/tonal/test/tonleiter.test.mjs +++ b/packages/tonal/test/tonleiter.test.mjs @@ -1,6 +1,6 @@ /* tonleiter.test.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/transpiler/test/transpiler.test.mjs b/packages/transpiler/test/transpiler.test.mjs index 988479b4f..02970cf43 100644 --- a/packages/transpiler/test/transpiler.test.mjs +++ b/packages/transpiler/test/transpiler.test.mjs @@ -1,6 +1,6 @@ /* transpiler.test.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/xen/test/xen.test.mjs b/packages/xen/test/xen.test.mjs index 977b0f694..a0982208a 100644 --- a/packages/xen/test/xen.test.mjs +++ b/packages/xen/test/xen.test.mjs @@ -1,6 +1,6 @@ /* xen.test.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/technical.manual.md b/technical.manual.md index 58f7cd934..6068b48fe 100644 --- a/technical.manual.md +++ b/technical.manual.md @@ -17,7 +17,7 @@ More info in the [REPL README](https://codeberg.org/uzu/strudel/src/branch/main/ # High Level Overview - + ## 1. End User Code @@ -48,7 +48,7 @@ mini('c3 [e3 g3]') This is how it works: - + - The user code is parsed with a [shift parser](https://github.com/shapesecurity/shift-parser-js), generating an AST - The AST is transformed to resolve the syntax sugar @@ -171,7 +171,7 @@ Here is an example Hap value with different properties: ```js { note: 'a4', s: 'sawtooth', gain: 0.5, cutoff: 267 } ``` - +
diff --git a/website/src/cx.mjs b/website/src/cx.mjs index 4e4aea08d..d8ff9eda1 100644 --- a/website/src/cx.mjs +++ b/website/src/cx.mjs @@ -1,6 +1,6 @@ /* cx.js - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index e46eb7171..813f63897 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -1,6 +1,6 @@ /* Repl.jsx - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/website/src/repl/components/panel/WelcomeTab.jsx b/website/src/repl/components/panel/WelcomeTab.jsx index a43f02ad4..b3b167c74 100644 --- a/website/src/repl/components/panel/WelcomeTab.jsx +++ b/website/src/repl/components/panel/WelcomeTab.jsx @@ -38,7 +38,7 @@ export function WelcomeTab({ context }) { , which is a popular live coding language for music, written in Haskell. Strudel is free/open source software: you can redistribute and/or modify it under the terms of the{' '} - + GNU Affero General Public License . You can find the source code at{' '} diff --git a/website/src/repl/tunes.mjs b/website/src/repl/tunes.mjs index cffe0c0eb..774e04e49 100644 --- a/website/src/repl/tunes.mjs +++ b/website/src/repl/tunes.mjs @@ -1,6 +1,6 @@ /* tunes.mjs - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/website/src/repl/useReplContext.jsx b/website/src/repl/useReplContext.jsx index 36e8099cb..12621c50a 100644 --- a/website/src/repl/useReplContext.jsx +++ b/website/src/repl/useReplContext.jsx @@ -1,6 +1,6 @@ /* Repl.jsx - -Copyright (C) 2022 Strudel contributors - see +Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ From d856e567c17c04cad083850955d838e3e328e404 Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 14 Jun 2025 15:22:27 +0100 Subject: [PATCH 67/74] link back to tech manual in wiki --- packages/README.md | 2 +- technical.manual.md | 193 -------------------------------------------- 2 files changed, 1 insertion(+), 194 deletions(-) delete mode 100644 technical.manual.md diff --git a/packages/README.md b/packages/README.md index a5b93d321..98938d6c3 100644 --- a/packages/README.md +++ b/packages/README.md @@ -2,4 +2,4 @@ Each folder represents one of the @strudel/* packages [published to npm](https://www.npmjs.com/org/strudel). -To understand how those pieces connect, refer to the [Technical Manual](https://codeberg.org/uzu/strudel/src/branch/main/technical-manual.md) or the individual READMEs. +To understand how those pieces connect, refer to the [Technical Manual](https://codeberg.org/uzu/strudel/wiki/Technical-Manual) or the individual READMEs. diff --git a/technical.manual.md b/technical.manual.md deleted file mode 100644 index 6068b48fe..000000000 --- a/technical.manual.md +++ /dev/null @@ -1,193 +0,0 @@ -This document introduces you to Strudel in a technical sense. If you just want to *use* Strudel, have a look at the [Tutorial](https://strudel.tidalcycles.org/tutorial/). - -## Strudel Packages - -There are different packages for different purposes. They.. - -- split up the code into smaller chunks -- can be selectively used to implement some sort of time based system - -Please refer to the individual README files in the [packages folder](https://codeberg.org/uzu/strudel/src/branch/main/packages) - -## REPL - -The [REPL](https://strudel.tidalcycles.org/) is the place where all packages come together to form a live coding system. It can also be seen as a reference implementation for users of the library. - -More info in the [REPL README](https://codeberg.org/uzu/strudel/src/branch/main/packages/repl/README.md) - -# High Level Overview - - - -## 1. End User Code - -The End User Code is written in JavaScript with added syntax sugar. The [eval package](https://codeberg.org/uzu/strudel/src/branch/main/packages/eval#strudelcycleseval) evaluates the user code -after a transpilation step, which resolves the syntax sugar. If you don't want the syntax sugar, you can omit the eval package and call the native javascript `eval` instead. - -### 🍭 Syntax Sugar - -JavaScript Transpilation = converting valid JavaScript to valid JavaScript: - -```js -"c3 [e3 g3]".fast(2) -``` - -becomes - -```js -mini('c3 [e3 g3]') - .withMiniLocation([1, 0, 0], [1, 11, 11]) // source location - .fast(2); -``` - -- double quoted strings and backtick strings are turned into `mini` calls (single quoted strings are left as is) -- The source location is added by chaining `withMiniLocation`, which enables the real time highlighting later -- (psuedo) variable names that look like notes (like `c4`, `bb2` or `fs3`) are turned into strings -- support for top level await -- operator overloading could be implemented in the future - -This is how it works: - - - -- The user code is parsed with a [shift parser](https://github.com/shapesecurity/shift-parser-js), generating an AST -- The AST is transformed to resolve the syntax sugar -- The AST is used to generate code again (shift-codegen) - -Shift will most likely be replaced with acorn in the future, see https://codeberg.org/uzu/strudel/issues/174 - -### Mini Notation - -Another important part of the user code is the mini notation, which allows to express rhythms in a short manner. - -- the mini notation is [implemented as a PEG grammar](https://codeberg.org/uzu/strudel/src/branch/main/packages/mini/krill.pegjs), living in the [mini package](https://codeberg.org/uzu/strudel/src/branch/main/packages/mini) -- it is based on [krill](https://github.com/Mdashdotdashn/krill) by Mdashdotdashn -- the peg grammar is used to generate a parser with [peggyjs](https://peggyjs.org/) -- the generated parser takes a mini notation string and outputs an AST -- the AST can then be used to construct a pattern using the regular Strudel API - -Here's an example AST: - -```json -{ - "type_": "pattern", - "arguments_": { "alignment": "h" }, - "source_": [ - { - "type_": "element", "source_": "c3", - "location_": { "start": { "offset": 1, "line": 1, "column": 2 }, "end": { "offset": 4, "line": 1, "column": 5 } } - }, - { - "type_": "element", - "location_": { "start": { "offset": 4, "line": 1, "column": 5 }, "end": { "offset": 11, "line": 1, "column": 12 } } - "source_": { - "type_": "pattern", "arguments_": { "alignment": "h" }, - "source_": [ - { - "type_": "element", "source_": "e3", - "location_": { "start": { "offset": 5, "line": 1, "column": 6 }, "end": { "offset": 8, "line": 1, "column": 9 } } - }, - { - "type_": "element", "source_": "g3", - "location_": { "start": { "offset": 8, "line": 1, "column": 9 }, "end": { "offset": 10, "line": 1, "column": 11 } } - } - ] - }, - } - ] -} -``` - -which translates to `seq(c3, seq(e3, g3))` - -## 2. Querying & Scheduling - -When the user code has been evaluated, we hopefully get a Pattern instance, which we can use to query events from. -These events can then be used to trigger side effects in the real world. On that note, Events are mostly called Hap(s) in the codebase, because JS already has a built in `Event` class. - -### Querying - -> Querying = Asking a Pattern for Events within a certain time span - -```js -seq('c3', ['e3', 'g3']) // <--- Pattern - .queryArc(0, 2) // query events within 0 and 2 cycles - .map((hap) => hap.showWhole()); // make readable -``` - -yields - -```js -[ - '0/1 -> 1/2: c3', // cycle 0 - '1/2 -> 3/4: e3', - '3/4 -> 1/1: g3', - '1/1 -> 3/2: c3', // cycle 1 - '3/2 -> 7/4: e3', - '7/4 -> 2/1: g3', -]; -``` - -### 🗓️ Scheduling - -The scheduler will query events repeatedly, creating a possibly endless loop of time slices. -Here is a simplified example of how it works - -```js -let step = 0.5; // query interval in seconds -let tick = 0; // how many intervals have passed -let pattern = seq('c3', ['e3', 'g3']); // pattern from user -setInterval(() => { - const events = pattern.queryArc(tick * step, ++tick * step); - events.forEach((event) => { - console.log(event.showWhole()); - const o = getAudioContext().createOscillator(); - o.frequency.value = getFreq(event.value); - o.start(event.whole.begin); - o.stop(event.whole.begin + event.duration); - o.connect(getAudioContext().destination); - }); -}, step * 1000); // query each "step" seconds -``` - -## 3. Sound Output - -The third and last step is to use the scheduled events to make sound. -Patterns are wrapped with param functions to compose different properties of the sound. - -```js -note("[c2(3,8) [ bb1]]") // sets frequency - .s("") // sound source - .gain(.5) // turn down volume - .cutoff(sine.range(200,1000).slow(4)) // modulated cutoff - .slow(2) - .out().logValues()`, - ]} -/> -``` - -Here is an example Hap value with different properties: - -```js -{ note: 'a4', s: 'sawtooth', gain: 0.5, cutoff: 267 } -``` - - -
- -- Patterns represent just values in time! -- Suitable for any time based output (music, visuals, movement, .. ?) - -### Supported Outputs - -At the time of writing this doc, the following outputs are supported: - -- Web Audio API `.out()` see [/webaudio](https://codeberg.org/uzu/strudel/src/branch/main/packages/webaudio) -- MIDI `.midi()` see [/midi](https://codeberg.org/uzu/strudel/src/branch/main/packages/midi) -- OSC `.osc()` see [/osc](https://codeberg.org/uzu/strudel/src/branch/main/packages/osc) -- Serial `.serial()` see [/serial](https://codeberg.org/uzu/strudel/src/branch/main/packages/serial) -- Tone.js `.tone()` (deprecated?) [/tone](https://codeberg.org/uzu/strudel/src/branch/main/packages/tone) -- WebDirt `.webdirt()` (deprecated?) [/webdirt](https://codeberg.org/uzu/strudel/src/branch/main/packages/webdirt) -- Speech `.speak()` (experimental) part of [/core](https://codeberg.org/uzu/strudel/src/branch/main/packages/core) - -These could change, so make sure to check the [packages folder](https://codeberg.org/uzu/strudel/src/branch/main/packages). From 9738c90b82dbae40e2bf5ad2e477d464d644ccdc Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 14 Jun 2025 16:23:03 +0200 Subject: [PATCH 68/74] even more degithubbing --- packages/core/test/pattern.test.mjs | 2 +- packages/repl/README.md | 2 +- paper/demo-preprocessed.md | 2 +- paper/demo.md | 2 +- paper/iclc2023.html | 6 +- paper/iclc2023.md | 2 +- .../src/components/Footer/AvatarList.astro | 169 ------------------ website/src/components/Header/Search.tsx | 2 +- .../RightSidebar/RightSidebar.astro | 1 - .../src/repl/components/panel/WelcomeTab.jsx | 2 +- 10 files changed, 10 insertions(+), 180 deletions(-) delete mode 100644 website/src/components/Footer/AvatarList.astro diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs index 696f13fef..45a1e2a98 100644 --- a/packages/core/test/pattern.test.mjs +++ b/packages/core/test/pattern.test.mjs @@ -884,7 +884,7 @@ describe('Pattern', () => { ); }); it('Doesnt drop haps in the 9th cycle', () => { - // fixed with https://github.com/tidalcycles/strudel/commit/72eeaf446e3d5e186d63cc0d2276f0723cde017a + // fixed with https://codeberg.org/uzu/strudel/commit/72eeaf446e3d5e186d63cc0d2276f0723cde017a expect(sequence(1, 2, 3).ply(2).early(8).firstCycle().length).toBe(6); }); }); diff --git a/packages/repl/README.md b/packages/repl/README.md index 3db4cc3f7..5b43b5e79 100644 --- a/packages/repl/README.md +++ b/packages/repl/README.md @@ -17,7 +17,7 @@ You can also pin the version like this: ``` This has the advantage that your code will always work, regardless of potential breaking changes in the strudel codebase. -See [releases](https://github.com/tidalcycles/strudel/releases) for the latest versions. +See [releases](https://codeberg.org/uzu/strudel/releases) for the latest versions. ## Use Web Component diff --git a/paper/demo-preprocessed.md b/paper/demo-preprocessed.md index e22823d90..2f0871e7c 100644 --- a/paper/demo-preprocessed.md +++ b/paper/demo-preprocessed.md @@ -201,7 +201,7 @@ interfaces. The Strudel REPL is available at , including an interactive tutorial. The repository is at -, all the code is open source +, all the code is open source under the GPL-3.0 License. # Acknowledgments diff --git a/paper/demo.md b/paper/demo.md index 23fe27754..841fb6064 100644 --- a/paper/demo.md +++ b/paper/demo.md @@ -128,7 +128,7 @@ For the future, it is planned to integrate alternative sound engines such as Gli # Links The Strudel REPL is available at , including an interactive tutorial. -The repository is at , all the code is open source under the GPL-3.0 License. +The repository is at , all the code is open source under the GPL-3.0 License. # Acknowledgments diff --git a/paper/iclc2023.html b/paper/iclc2023.html index a83e075a2..3771f560b 100644 --- a/paper/iclc2023.html +++ b/paper/iclc2023.html @@ -374,7 +374,7 @@ by the output.
REPL control flow
@@ -720,8 +720,8 @@ class="header-section-number">11 Links href="https://strudel.cc" class="uri">https://strudel.cc, including an interactive tutorial. The repository is at https://github.com/tidalcycles/strudel, all the code is +href="https://codeberg.org/uzu/strudel" +class="uri">https://codeberg.org/uzu/strudel, all the code is open source under the AGPL-3.0 License.

12 Acknowledgments

diff --git a/paper/iclc2023.md b/paper/iclc2023.md index 3afb27828..fdc9f0ca4 100644 --- a/paper/iclc2023.md +++ b/paper/iclc2023.md @@ -451,7 +451,7 @@ While Haskell's type system makes it a great language for the ongoing developmen # Links The Strudel REPL is available at , including an interactive tutorial. -The repository is at , all the code is open source under the AGPL-3.0 License. +The repository is at , all the code is open source under the AGPL-3.0 License. # Acknowledgments diff --git a/website/src/components/Footer/AvatarList.astro b/website/src/components/Footer/AvatarList.astro deleted file mode 100644 index 86bbcc875..000000000 --- a/website/src/components/Footer/AvatarList.astro +++ /dev/null @@ -1,169 +0,0 @@ ---- -// fetch all commits for just this page's path -type Props = { - path: string; -}; -const { path } = Astro.props as Props; -const resolvedPath = `website/src/pages${path}.mdx`; -const url = `https://api.github.com/repos/tidalcycles/strudel/commits?path=${resolvedPath}`; -const commitsURL = `https://github.com/tidalcycles/strudel/commits/main/${resolvedPath}`; - -type Commit = { - author: { - id: string; - login: string; - }; -}; - -async function getCommits(url: string) { - try { - const token = import.meta.env.SNOWPACK_PUBLIC_GITHUB_TOKEN ?? 'hello'; - if (!token) { - throw new Error('Cannot find "SNOWPACK_PUBLIC_GITHUB_TOKEN" used for escaping rate-limiting.'); - } - - const auth = `Basic ${Buffer.from(token, 'binary').toString('base64')}`; - - const res = await fetch(url, { - method: 'GET', - headers: { - Authorization: auth, - 'User-Agent': 'astro-docs/1.0', - }, - }); - - const data = await res.json(); - - if (!res.ok) { - throw new Error( - `Request to fetch commits failed. Reason: ${res.statusText} - Message: ${data.message}`, - ); - } - - return data as Commit[]; - } catch (e) { - console.warn(`[error] /src/components/AvatarList.astro - ${(e as any)?.message ?? e}`); - return [] as Commit[]; - } -} - -function removeDups(arr: Commit[]) { - const map = new Map(); - for (let item of arr) { - const author = item.author; - // Deduplicate based on author.id - //map.set(author.id, { login: author.login, id: author.id }); - author && map.set(author.id, { login: author.login, id: author.id }); - } - - return [...map.values()]; -} - -const data = await getCommits(url); -const unique = removeDups(data); -const recentContributors = unique.slice(0, 3); // only show avatars for the 3 most recent contributors -const additionalContributors = unique.length - recentContributors.length; // list the rest of them as # of extra contributors ---- - - -
-
    - { - recentContributors.map((item) => ( -
  • - - {`Contributor - -
  • - )) - } -
- { - additionalContributors > 0 && ( - - {`and ${additionalContributors} additional contributor${ - additionalContributors > 1 ? 's' : '' - }.`} - - ) - } - {unique.length === 0 && Contributors} -
- - diff --git a/website/src/components/Header/Search.tsx b/website/src/components/Header/Search.tsx index c982d80b6..f200dc79f 100644 --- a/website/src/components/Header/Search.tsx +++ b/website/src/components/Header/Search.tsx @@ -82,7 +82,7 @@ export default function Search() { appId={ALGOLIA.appId} apiKey={ALGOLIA.apiKey} getMissingResultsUrl={({ query }) => { - return `https://github.com/tidalcycles/strudel/issues/new?title=Missing doc for ${query}`; + return `https://codeberg.org/uzu/strudel/issues/new?title=Missing%20doc%20for${encodeURIComponent(query)}`; }} transformItems={(items) => { return items.map((item) => { diff --git a/website/src/components/RightSidebar/RightSidebar.astro b/website/src/components/RightSidebar/RightSidebar.astro index cd501b1a2..28c713022 100644 --- a/website/src/components/RightSidebar/RightSidebar.astro +++ b/website/src/components/RightSidebar/RightSidebar.astro @@ -18,5 +18,4 @@ currentPage = currentPage.endsWith('/') ? currentPage.slice(0, -1) : currentPage diff --git a/website/src/repl/components/panel/WelcomeTab.jsx b/website/src/repl/components/panel/WelcomeTab.jsx index b3b167c74..cd5fe030a 100644 --- a/website/src/repl/components/panel/WelcomeTab.jsx +++ b/website/src/repl/components/panel/WelcomeTab.jsx @@ -42,7 +42,7 @@ export function WelcomeTab({ context }) { GNU Affero General Public License . You can find the source code at{' '} - + github . You can also find licensing info{' '} From ee7d7830992ee521dac2d511158c0a0f3c133ed0 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 14 Jun 2025 16:27:54 +0200 Subject: [PATCH 69/74] fix homepage link --- website/src/pages/learn/getting-started.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/pages/learn/getting-started.mdx b/website/src/pages/learn/getting-started.mdx index 2d056d3b4..b3348ccda 100644 --- a/website/src/pages/learn/getting-started.mdx +++ b/website/src/pages/learn/getting-started.mdx @@ -14,7 +14,7 @@ These pages will introduce you to [Strudel](https://strudel.cc/), a web-based [l # What is Strudel? -[Strudel](https://strudel.cc/) is a version of [Tidal Cycles](https://tidalcycles.org) written in [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript), initiated by [Alex McLean](https://slab.org) and [Felix Roos](https://github.com/felixroos) in 2022. +[Strudel](https://strudel.cc/) is a version of [Tidal Cycles](https://tidalcycles.org) written in [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript), initiated by [Alex McLean](https://slab.org) and [Felix Roos](https://froos.cc/) in 2022. Tidal Cycles, also known as Tidal, is a language for [algorithmic pattern](https://algorithmicpattern.org), and though it is most commonly used for [making music](https://tidalcycles.org/docs/showcase), it can be used for any kind of pattern making activity, including [weaving](https://www.youtube.com/watch?v=TfEmEsusXjU). Tidal was first implemented as a library written in the [Haskell](https://www.haskell.org/) functional programming language, and by itself it does not make any sound. From 2cb22e94b56bde24a461e6c216381c8c3242de11 Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 14 Jun 2025 23:26:59 +0100 Subject: [PATCH 70/74] move iclc paper under docs/ --- {paper => docs/iclc2023-paper}/Makefile | 0 {paper => docs/iclc2023-paper}/README.md | 0 {paper => docs/iclc2023-paper}/bin/code-filter.py | 0 {paper => docs/iclc2023-paper}/citations.json | 0 {paper => docs/iclc2023-paper}/demo-preprocessed.md | 0 {paper => docs/iclc2023-paper}/demo.md | 0 {paper => docs/iclc2023-paper}/demo.pdf | Bin {paper => docs/iclc2023-paper}/iclc-reviews.md | 0 {paper => docs/iclc2023-paper}/iclc2023.html | 0 {paper => docs/iclc2023-paper}/iclc2023.md | 0 {paper => docs/iclc2023-paper}/iclc2023.pdf | Bin {paper => docs/iclc2023-paper}/iclc2023x.pdf | Bin {paper => docs/iclc2023-paper}/images/cc.png | Bin .../iclc2023-paper}/images/strudel-screenshot.png | Bin .../iclc2023-paper}/images/strudel-screenshot2.png | Bin .../iclc2023-paper}/images/strudelflow.png | Bin {paper => docs/iclc2023-paper}/inconsolata.sty | 0 {paper => docs/iclc2023-paper}/make.sh | 0 {paper => docs/iclc2023-paper}/pandoc/iclc.html | 0 {paper => docs/iclc2023-paper}/pandoc/iclc.latex | 0 {paper => docs/iclc2023-paper}/pandoc/iclc.sty | 0 .../iclc2023-paper}/paper-preprocessed.md | 0 {paper => docs/iclc2023-paper}/paper.md | 0 {paper => docs/iclc2023-paper}/paper.pdf | Bin .../iclc2023-paper}/tex/latex-template.tex | 0 .../iclc2023-paper}/tex/sig-alternate.cls | 0 {paper => docs/iclc2023-paper}/tex/waccopyright.sty | 0 27 files changed, 0 insertions(+), 0 deletions(-) rename {paper => docs/iclc2023-paper}/Makefile (100%) rename {paper => docs/iclc2023-paper}/README.md (100%) rename {paper => docs/iclc2023-paper}/bin/code-filter.py (100%) rename {paper => docs/iclc2023-paper}/citations.json (100%) rename {paper => docs/iclc2023-paper}/demo-preprocessed.md (100%) rename {paper => docs/iclc2023-paper}/demo.md (100%) rename {paper => docs/iclc2023-paper}/demo.pdf (100%) rename {paper => docs/iclc2023-paper}/iclc-reviews.md (100%) rename {paper => docs/iclc2023-paper}/iclc2023.html (100%) rename {paper => docs/iclc2023-paper}/iclc2023.md (100%) rename {paper => docs/iclc2023-paper}/iclc2023.pdf (100%) rename {paper => docs/iclc2023-paper}/iclc2023x.pdf (100%) rename {paper => docs/iclc2023-paper}/images/cc.png (100%) rename {paper => docs/iclc2023-paper}/images/strudel-screenshot.png (100%) rename {paper => docs/iclc2023-paper}/images/strudel-screenshot2.png (100%) rename {paper => docs/iclc2023-paper}/images/strudelflow.png (100%) rename {paper => docs/iclc2023-paper}/inconsolata.sty (100%) rename {paper => docs/iclc2023-paper}/make.sh (100%) rename {paper => docs/iclc2023-paper}/pandoc/iclc.html (100%) rename {paper => docs/iclc2023-paper}/pandoc/iclc.latex (100%) rename {paper => docs/iclc2023-paper}/pandoc/iclc.sty (100%) rename {paper => docs/iclc2023-paper}/paper-preprocessed.md (100%) rename {paper => docs/iclc2023-paper}/paper.md (100%) rename {paper => docs/iclc2023-paper}/paper.pdf (100%) rename {paper => docs/iclc2023-paper}/tex/latex-template.tex (100%) rename {paper => docs/iclc2023-paper}/tex/sig-alternate.cls (100%) rename {paper => docs/iclc2023-paper}/tex/waccopyright.sty (100%) diff --git a/paper/Makefile b/docs/iclc2023-paper/Makefile similarity index 100% rename from paper/Makefile rename to docs/iclc2023-paper/Makefile diff --git a/paper/README.md b/docs/iclc2023-paper/README.md similarity index 100% rename from paper/README.md rename to docs/iclc2023-paper/README.md diff --git a/paper/bin/code-filter.py b/docs/iclc2023-paper/bin/code-filter.py similarity index 100% rename from paper/bin/code-filter.py rename to docs/iclc2023-paper/bin/code-filter.py diff --git a/paper/citations.json b/docs/iclc2023-paper/citations.json similarity index 100% rename from paper/citations.json rename to docs/iclc2023-paper/citations.json diff --git a/paper/demo-preprocessed.md b/docs/iclc2023-paper/demo-preprocessed.md similarity index 100% rename from paper/demo-preprocessed.md rename to docs/iclc2023-paper/demo-preprocessed.md diff --git a/paper/demo.md b/docs/iclc2023-paper/demo.md similarity index 100% rename from paper/demo.md rename to docs/iclc2023-paper/demo.md diff --git a/paper/demo.pdf b/docs/iclc2023-paper/demo.pdf similarity index 100% rename from paper/demo.pdf rename to docs/iclc2023-paper/demo.pdf diff --git a/paper/iclc-reviews.md b/docs/iclc2023-paper/iclc-reviews.md similarity index 100% rename from paper/iclc-reviews.md rename to docs/iclc2023-paper/iclc-reviews.md diff --git a/paper/iclc2023.html b/docs/iclc2023-paper/iclc2023.html similarity index 100% rename from paper/iclc2023.html rename to docs/iclc2023-paper/iclc2023.html diff --git a/paper/iclc2023.md b/docs/iclc2023-paper/iclc2023.md similarity index 100% rename from paper/iclc2023.md rename to docs/iclc2023-paper/iclc2023.md diff --git a/paper/iclc2023.pdf b/docs/iclc2023-paper/iclc2023.pdf similarity index 100% rename from paper/iclc2023.pdf rename to docs/iclc2023-paper/iclc2023.pdf diff --git a/paper/iclc2023x.pdf b/docs/iclc2023-paper/iclc2023x.pdf similarity index 100% rename from paper/iclc2023x.pdf rename to docs/iclc2023-paper/iclc2023x.pdf diff --git a/paper/images/cc.png b/docs/iclc2023-paper/images/cc.png similarity index 100% rename from paper/images/cc.png rename to docs/iclc2023-paper/images/cc.png diff --git a/paper/images/strudel-screenshot.png b/docs/iclc2023-paper/images/strudel-screenshot.png similarity index 100% rename from paper/images/strudel-screenshot.png rename to docs/iclc2023-paper/images/strudel-screenshot.png diff --git a/paper/images/strudel-screenshot2.png b/docs/iclc2023-paper/images/strudel-screenshot2.png similarity index 100% rename from paper/images/strudel-screenshot2.png rename to docs/iclc2023-paper/images/strudel-screenshot2.png diff --git a/paper/images/strudelflow.png b/docs/iclc2023-paper/images/strudelflow.png similarity index 100% rename from paper/images/strudelflow.png rename to docs/iclc2023-paper/images/strudelflow.png diff --git a/paper/inconsolata.sty b/docs/iclc2023-paper/inconsolata.sty similarity index 100% rename from paper/inconsolata.sty rename to docs/iclc2023-paper/inconsolata.sty diff --git a/paper/make.sh b/docs/iclc2023-paper/make.sh similarity index 100% rename from paper/make.sh rename to docs/iclc2023-paper/make.sh diff --git a/paper/pandoc/iclc.html b/docs/iclc2023-paper/pandoc/iclc.html similarity index 100% rename from paper/pandoc/iclc.html rename to docs/iclc2023-paper/pandoc/iclc.html diff --git a/paper/pandoc/iclc.latex b/docs/iclc2023-paper/pandoc/iclc.latex similarity index 100% rename from paper/pandoc/iclc.latex rename to docs/iclc2023-paper/pandoc/iclc.latex diff --git a/paper/pandoc/iclc.sty b/docs/iclc2023-paper/pandoc/iclc.sty similarity index 100% rename from paper/pandoc/iclc.sty rename to docs/iclc2023-paper/pandoc/iclc.sty diff --git a/paper/paper-preprocessed.md b/docs/iclc2023-paper/paper-preprocessed.md similarity index 100% rename from paper/paper-preprocessed.md rename to docs/iclc2023-paper/paper-preprocessed.md diff --git a/paper/paper.md b/docs/iclc2023-paper/paper.md similarity index 100% rename from paper/paper.md rename to docs/iclc2023-paper/paper.md diff --git a/paper/paper.pdf b/docs/iclc2023-paper/paper.pdf similarity index 100% rename from paper/paper.pdf rename to docs/iclc2023-paper/paper.pdf diff --git a/paper/tex/latex-template.tex b/docs/iclc2023-paper/tex/latex-template.tex similarity index 100% rename from paper/tex/latex-template.tex rename to docs/iclc2023-paper/tex/latex-template.tex diff --git a/paper/tex/sig-alternate.cls b/docs/iclc2023-paper/tex/sig-alternate.cls similarity index 100% rename from paper/tex/sig-alternate.cls rename to docs/iclc2023-paper/tex/sig-alternate.cls diff --git a/paper/tex/waccopyright.sty b/docs/iclc2023-paper/tex/waccopyright.sty similarity index 100% rename from paper/tex/waccopyright.sty rename to docs/iclc2023-paper/tex/waccopyright.sty From cc4cad05b2f7fce4f50155a4799a67ea35bd5b2e Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 14 Jun 2025 23:33:52 +0100 Subject: [PATCH 71/74] add the technical manual again --- docs/technical-manual/index.md | 197 ++++++++++++++++++++++++++ docs/technical-manual/shiftflow.png | Bin 0 -> 90584 bytes docs/technical-manual/strudelflow.png | Bin 0 -> 84696 bytes docs/technical-manual/waa-nodes.png | Bin 0 -> 50450 bytes 4 files changed, 197 insertions(+) create mode 100644 docs/technical-manual/index.md create mode 100644 docs/technical-manual/shiftflow.png create mode 100644 docs/technical-manual/strudelflow.png create mode 100644 docs/technical-manual/waa-nodes.png diff --git a/docs/technical-manual/index.md b/docs/technical-manual/index.md new file mode 100644 index 000000000..16c9a5533 --- /dev/null +++ b/docs/technical-manual/index.md @@ -0,0 +1,197 @@ +This document introduces you to Strudel in a technical sense. + +It is rather out of date, but there might still be useful info below. + +If you just want to *use* Strudel, have a look at the [Tutorial](https://strudel.tidalcycles.org/tutorial/). + +## Strudel Packages + +There are different packages for different purposes. They.. + +- split up the code into smaller chunks +- can be selectively used to implement some sort of time based system + +Please refer to the individual README files in the [packages folder](https://codeberg.org/uzu/strudel/src/branch/main/packages) + +## REPL + +The [REPL](https://strudel.tidalcycles.org/) is the place where all packages come together to form a live coding system. It can also be seen as a reference implementation for users of the library. + +More info in the [REPL README](https://codeberg.org/uzu/strudel/src/branch/main/packages/repl/README.md) + +# High Level Overview + + + +## 1. End User Code + +The End User Code is written in JavaScript with added syntax sugar. The [eval package](https://github.com/tidalcycles/strudel/tree/main/packages/eval#strudelcycleseval) evaluates the user code +after a transpilation step, which resolves the syntax sugar. If you don't want the syntax sugar, you can omit the eval package and call the native javascript `eval` instead. + +### 🍭 Syntax Sugar + +JavaScript Transpilation = converting valid JavaScript to valid JavaScript: + +```js +"c3 [e3 g3]".fast(2) +``` + +becomes + +```js +mini('c3 [e3 g3]') + .withMiniLocation([1, 0, 0], [1, 11, 11]) // source location + .fast(2); +``` + +- double quoted strings and backtick strings are turned into `mini` calls (single quoted strings are left as is) +- The source location is added by chaining `withMiniLocation`, which enables the real time highlighting later +- (psuedo) variable names that look like notes (like `c4`, `bb2` or `fs3`) are turned into strings +- support for top level await +- operator overloading could be implemented in the future + +This is how it works: + + + +- The user code is parsed with a [shift parser](https://github.com/shapesecurity/shift-parser-js), generating an AST +- The AST is transformed to resolve the syntax sugar +- The AST is used to generate code again (shift-codegen) + +Shift will most likely be replaced with acorn in the future, see https://github.com/tidalcycles/strudel/issues/174 + +### Mini Notation + +Another important part of the user code is the mini notation, which allows to express rhythms in a short manner. + +- the mini notation is [implemented as a PEG grammar](https://github.com/tidalcycles/strudel/blob/main/packages/mini/krill.pegjs), living in the [mini package](https://github.com/tidalcycles/strudel/tree/main/packages/mini) +- it is based on [krill](https://github.com/Mdashdotdashn/krill) by Mdashdotdashn +- the peg grammar is used to generate a parser with [peggyjs](https://peggyjs.org/) +- the generated parser takes a mini notation string and outputs an AST +- the AST can then be used to construct a pattern using the regular Strudel API + +Here's an example AST: + +```json +{ + "type_": "pattern", + "arguments_": { "alignment": "h" }, + "source_": [ + { + "type_": "element", "source_": "c3", + "location_": { "start": { "offset": 1, "line": 1, "column": 2 }, "end": { "offset": 4, "line": 1, "column": 5 } } + }, + { + "type_": "element", + "location_": { "start": { "offset": 4, "line": 1, "column": 5 }, "end": { "offset": 11, "line": 1, "column": 12 } } + "source_": { + "type_": "pattern", "arguments_": { "alignment": "h" }, + "source_": [ + { + "type_": "element", "source_": "e3", + "location_": { "start": { "offset": 5, "line": 1, "column": 6 }, "end": { "offset": 8, "line": 1, "column": 9 } } + }, + { + "type_": "element", "source_": "g3", + "location_": { "start": { "offset": 8, "line": 1, "column": 9 }, "end": { "offset": 10, "line": 1, "column": 11 } } + } + ] + }, + } + ] +} +``` + +which translates to `seq(c3, seq(e3, g3))` + +## 2. Querying & Scheduling + +When the user code has been evaluated, we hopefully get a Pattern instance, which we can use to query events from. +These events can then be used to trigger side effects in the real world. On that note, Events are mostly called Hap(s) in the codebase, because JS already has a built in `Event` class. + +### Querying + +> Querying = Asking a Pattern for Events within a certain time span + +```js +seq('c3', ['e3', 'g3']) // <--- Pattern + .queryArc(0, 2) // query events within 0 and 2 cycles + .map((hap) => hap.showWhole()); // make readable +``` + +yields + +```js +[ + '0/1 -> 1/2: c3', // cycle 0 + '1/2 -> 3/4: e3', + '3/4 -> 1/1: g3', + '1/1 -> 3/2: c3', // cycle 1 + '3/2 -> 7/4: e3', + '7/4 -> 2/1: g3', +]; +``` + +### 🗓️ Scheduling + +The scheduler will query events repeatedly, creating a possibly endless loop of time slices. +Here is a simplified example of how it works + +```js +let step = 0.5; // query interval in seconds +let tick = 0; // how many intervals have passed +let pattern = seq('c3', ['e3', 'g3']); // pattern from user +setInterval(() => { + const events = pattern.queryArc(tick * step, ++tick * step); + events.forEach((event) => { + console.log(event.showWhole()); + const o = getAudioContext().createOscillator(); + o.frequency.value = getFreq(event.value); + o.start(event.whole.begin); + o.stop(event.whole.begin + event.duration); + o.connect(getAudioContext().destination); + }); +}, step * 1000); // query each "step" seconds +``` + +## 3. Sound Output + +The third and last step is to use the scheduled events to make sound. +Patterns are wrapped with param functions to compose different properties of the sound. + +```js +note("[c2(3,8) [ bb1]]") // sets frequency + .s("") // sound source + .gain(.5) // turn down volume + .cutoff(sine.range(200,1000).slow(4)) // modulated cutoff + .slow(2) + .out().logValues()`, + ]} +/> +``` + +Here is an example Hap value with different properties: + +```js +{ note: 'a4', s: 'sawtooth', gain: 0.5, cutoff: 267 } +``` + + +
+ +- Patterns represent just values in time! +- Suitable for any time based output (music, visuals, movement, .. ?) + +### Supported Outputs + +At the time of writing this doc, the following outputs are supported: + +- Web Audio API `.out()` see [/webaudio](https://github.com/tidalcycles/strudel/tree/main/packages/webaudio) +- MIDI `.midi()` see [/midi](https://github.com/tidalcycles/strudel/tree/main/packages/midi) +- OSC `.osc()` see [/osc](https://github.com/tidalcycles/strudel/tree/main/packages/osc) +- Serial `.serial()` see [/serial](https://github.com/tidalcycles/strudel/tree/main/packages/serial) +- Tone.js `.tone()` (deprecated?) [/tone](https://github.com/tidalcycles/strudel/tree/main/packages/tone) +- WebDirt `.webdirt()` (deprecated?) [/webdirt](https://github.com/tidalcycles/strudel/tree/main/packages/webdirt) +- Speech `.speak()` (experimental) part of [/core](https://github.com/tidalcycles/strudel/tree/main/packages/core) + +These could change, so make sure to check the [packages folder](https://github.com/tidalcycles/strudel/tree/main/packages). diff --git a/docs/technical-manual/shiftflow.png b/docs/technical-manual/shiftflow.png new file mode 100644 index 0000000000000000000000000000000000000000..8d91658dcb1d5db29f5c8009dcfadc0d9200b19f GIT binary patch literal 90584 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 *sd!B@`9#?etzVvn33C3{X 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 literal 0 HcmV?d00001 diff --git a/docs/technical-manual/strudelflow.png b/docs/technical-manual/strudelflow.png new file mode 100644 index 0000000000000000000000000000000000000000..72cdf494bdb28e181df263387e412fe95698c795 GIT binary patch literal 84696 zcmagF1ys~uyEZ&@$IvA)A}L5oH-jMEAV{}#cS(Z^2q;K{fYObCbSaV|-6GxH@b2;d zJSX0B-tU{WX04goznyno`@XM49-2pR?if^vz84u0WS+WG;3 zpf=iS>ACAED+!xBIdGa<$*VGE5x3v(1 z={{EGQg)WHw6T@>b^Ny~SXE#}x*j z5kEhI(f@tK-A)Xqr>sUV4-VoHqU8}cC)cC@`naXH?f>I(M9cqp+}Xy- z-O0_y$@xDv@ZYBRkLLf;5ggzD-o@M0`G0#@S^59_aR-P0whcFTSx;~f|2owFwx)ki z;HKr{Z23sT(#^@k)!b6n)6&uX{@;^8oQv>N2UBZHB}+$ZcN;MnHy5|y|J9^xYw@>* zM^NBDTM&cUdmyBt?Pv?8=lRbjz5i?j;Pi17(T%D|J z?LnKHhMYA0Q&|~)ZXtet4j#^bh5_Oe27PU_99-O5+|8v;TwH%g{`)kr3JX(r)BnHc5xPw;3VJ9j3(MQOxjVV~{Qc=4h1axn`RCg|U+r!G zYC1jrUwI3gnj_9$4Cd_WWZ_|MY4LY*Fx)?_ZcbM2UZ$>=Qr1A!VlXKyD_bBXA9^)} z4%2gUa&vL={r4VTHkM$t|M%V={cB~95VrU4rHMZJABz|L`@nxOR?zRC-+&VU-sI7L zxD)W@KOB;!Bk)45z}&09{Yt*4GAZQ68x zksNn5@q%2V06UXyNlIJb;gb5&0jZ*wx8|3p159LLap5u%&(+SSM-n+DgwkCuI!*7> z#s=3*>3zTc{pXQRbp+GHN6epA-i>8l2|mL5c&M3JRo7?V&e(%#O438YBuzJm8?~1g zN-JzBmwH{cca8OqQJ1t_v&|#{_t`_~Tu#N4q!QZ^O&(X0^xM0g;?&(8mtu?#wF{G( zM-Gb%!9%$Yt$Q=4Ye?t2OAn3vjHg3ybw^?!aD2fzv2%7c`uWztxeQRo>;Lg;LP-H!!adLTM@XkG(_ve=vk9h2?2k{e_DL~x7a+ZJQ27!=0 zK)jKjYB26YAoP%@a49YC^zFF-XRWnV{_`!NxzcyPIEwGF*D&IeyQ$3VOPLlSYpwY3Hj?2jheKht~;{-$;`V z=)V%%XMZ;ktj5H`d^`Ql{JRDYqeqtYonVccSC%-PM$p!!wmScuYSvK=QHA?SfJ8k6 zs{ZZ*pYMWerzpgIye(uV+l-RFkHG?u-aq)*mR(l))bnXlxy9W{(8w*TtOUJWPBsDb zc^Kwd=i@nvs#-rrMQTPEc;!AMnwp0|IUcrS_OiNOB z)Mzq6RuKjlZ;LA@;p%46zfV=YS)zwd<<{c84oPPoM!!?%qrmw2qd%h@#p%ZcD2#l|i~WJ=I1|H2wA+^N$w%>g=<;@b0gH_;|$XSnBr+U0^W@ zZ`9OCgo264NmQYWo?MuWeJCSsYIw>hx`mq5WC~MQ?`QjX!G16ynMMIcPoloDAIak52VE2(pPK@B1^1_}bVT`KVX=Icc- zB+Ci^LV{Q`W)!JhG|3_&9=_zF>#1+23t!*HGy`>T^zl8}OCyoJpk}os4%Rk7 zqlc~(I%@YVAgyzDRm4j)6LXlfpb(Tw>(QpD;#8`k!hN{I%2PtSLW3&(*&<$J>OA6rmjm*VN(9P+$`!Pzq6o2>ar>+Pjv$Hz8$!s?&N`Z6uYu zE8LWY*@vXhoV|m?!(~ooTy{0?pU>@{?d=YoI&PZ?%GU(ufhjQZOx?{es& z1BmXS%We(=;;3s~;|Zm6_dm~*L!ncD2SI*gNZ40kzZV|GLigwI2%p+6@Jq_2^n}o} zdj_|{G()Ps{ZNaK(s*(&E$sSetO1wPG>sdoPL?l1Leha$$ReGPN1~FCsnT64RzM;J z#fJoIei?PKgXv3ZQHI2Db!?JA=yO#VN>t3)TuF;wnJ8ogCneO(icH8z1K)twe|EUw zh`b+@!EdHQ$CT@?dFM~MX|21{VvkyMJq~n&qzf(x%k}4SE67e#g~{%8N(-ueOv?VI zj$L1kEBl5BJqVt}3uYf9F$*|LT0d*pVX)WWWH5T^3+)4if7tfG;gkpVo!|EqOvp6|u^W?!NJM_9{XWc* zXU+JG&Is$nn1Gpo5f*!Cx5TYfTqm(Oo`c8fb8tQvsp(vq1O%oP?C*DX3Zc)cX*6O% z{=G`X((>Eg5!geBuq)nXAu+%417k{`O#y?^Dh60C+U~{}^E<4iOVZ$ii|4&N`?-_P z&{an`N^lu-UqW>Xoga(7Jovn%g&JO{y)hY&qaB8e5se+SCz(jF^cG)|?^(cWXOV2J zca{3_QB_YOv_I*J*L#uWBxN`o-jHD=6GHO`{fp7Uo!hn=(H?Np#oV*}JVygnf0L@g z%*B7f&2-tsgF4P7Y_s-fhiXM|O4q(%^+2H$uNetu)d zA`iE)7iTc#pMFJ&Pqb3GRoYc(@|fbGr}SsGP_=WptSHwsAnwQ>dS(t)UaMGcXo9Sp z!+o-jkhIwM;E*1OQQwELak=4R@y=THhXu2!PKlYTv9iCc`j)l|jv@aYSh>gXhp`o0 z%;+d3n|6~CC!}ZQYaW)ev1+?ztEn&HaQ@FcFDpOOKVimjXwf(={^?`Pge-kZO0-fe zzp+(8g**Se!z4cH@U4bA_R1`FUMR-Tru(qm>!aBZnsEw+hYa?(w5(pbIX|!7vuK<* zGeSrFg_2=fe!AJShL&rV;)R+*C;cBZ(|*=If-K5Oz(+SLZx7AB)MH^ zkobuX9i4Jc$6Kc_<0p4FoXx;7_7O}l6B(41c(Tau`|Sqzo`fUx-$shf^IiLLQQO!) zrk3r2zXPkmIOm0g+}*P3-Fcw!X%`T4+_|U3Br=0hZPmMWdMGKC0aA{n<^hVw&BU8b ziNsY0D0HR9np$G=LH>(|+{Uhr&fK4OR@Eo3qoTIe!cb^+(O|Arqnp~X>n*0FRl1ctrYW`~BUKV{yC5vA-00C>%bexp|n=cl3Td z&Ry+(q=ry;$`ZZ$o1?$+)KoZC{>wczHqq2cBB;8G8krA+Li#FBxXr;%;QsgHJ%A;y z05~v9b!Tg>t>UM`b)^FC`xFTLAzO2&tK_;nTW?*txOQTUUv+5;OQfL_^DeIeb2@fX zw}6yj>KX9}a-IU5Gfp}}yE<7kC_V~%ezaKiA#v_{eQ*i40W}_a#>-GSfTy17Wpwls z2f_6+sw(sm+P{E^W?4gG9R9N^@Mn1$yf*SIFM$L&o=@E2zj{uKre$06GqU0*(&8@e zPKn9!$;m}bY-}rgzWqzjE?H$A0eJWe)HYjLb>aLZCv9!*6Kg-2i`CSgMC7)#cyMe{ zhtFzW4(m&@ydZ)4+-nOsYG58k&81oKyU59?*hfkE#A3-M56Ai7%;v0>r2nk3ul_2> z&~{#>oq~quIX@SdbN`NK5L5mvS~eCgI%|MGI1=a-Bf3Dr%RC zd`hN7H!xh5vnhLSN8f@alpl#3Cl%nZFrGOuuUeSFa;bkUVSUP*=qK;nXp)<5R4ELS zyRfsdS#K&T+I>kjNoe`Q8e;URtSs=s4E1*p)pGF*4u*a9YLCCWh;1e_xA#8iYR+(L zAo%e*$~7U`)2tBTDs0H|b4faOxQtoSk1j20hwR)Sy2&b<)~M7bh1k|EJov-PMa!!SI?rE{erCxzQi^w`b3$kCi(29D=fY+(TI21M5i9 z!i#}nkQlo>sgr>x#gQ0u!=p#-Qwf%c&`l={8(d|d6QHNks+Bc9Q?T_dn3p~Tpy)@5 z)}`-0qJd4TQzbVQtN*S!Ed4NPTi^WGYWDBx8KXogTWuv3RpD+&0CdP2`&DN; zHtTrDa3LxB=8v$f`in5GJ10W*caKj_PPp58wmr?v@$)eS$RB<0dI#(Y#gX-MB~SaL zryn;6b=jlvG z-(-qp>xpz_0wDy$Xg;xguCDr}p_*D*ug7t6jDWasbMfS`rIx{Q$eg^jCLu;B#9SQ%^~)0U5n7; z3nbPr#Jns_1mN%_<@%&qtHvkL-%UCZiCi>{eZogEq}@*Whv89C$1DN{pTAn207zeF zt#emM=$vwE*bPoDH#`SuS5>YoSOC84;~^T2>Ibk7I18DPI;lE%?;3={!T@5>6O)IB zOTsizawt?ty;x*f*bl8=D1tdHS9V`uCbny8^vMkrVWbKYS|)Ni;Vcn?o63bbVm96_ z?a;Aw6(x90FbfF!bBan6soDfQV0AF_q{HO1uXE9_pKt+iJ*d=t zEDwj{Xf`zUxxXe%__(l!n1<>=AYzEDiGJ1|hXJNQ3QiciB`tN15c;gMND2ny(cFH> zTS`mPR=}L5vf=q1ac;;9KM3j|pSbn!JN`}*g5_#etBI{In-IbJILer+NlU1}Egokq zRQ#;A!tsP2NZnpdd63GTO|5S>bs7&k@{&Q}c`&SB$At?Ex)NqhB~BlM1m%-#M->vMh+K%lg*w+=-M^qS(?5mHhY!U7>{Edn4)YlY-JY$w zXiH))PoYUig_%77t|OJ{RqJ=+Zca?*I~yWs{%cqXF4KrZ13j#dVhIw~=^SiW`F-gK z4zH`p~8oO;U&~xoO+eonAvedE#KR7+p%&2g|K-9 z6ylLWdci`+P2p=$N-+q;b9HQQ3(*GLc!$5V}B!g$|D9rP0ZX^L=g*rD)UpKSlpBhVwIVG5JU50 zlUchTl%?@jQv%o|LaQ4-MYOQ}xf|FVPgng~jyB}QtMMqJIuc^2WsI3P<@_4(46KL) zjj=utsrsRgT(}t72Zw(g`b_dn$Tyeu@)4qIcS-)_&SDZUCM6A2c*0Y=N;VzJNAxg- zQfn#|Y=&1&%7S1SQT-_yBA@y%o$wMdbCnX9IJ&zBm3G3SqRMznC;2Z-03KET@GA46 zi%W_y6iQjmRqH*Ls9fj|0SeX+&HcV_OJE=q6~&{Rq8vqz9r>0}{mE0?+ZAo#u5 z9*T(`IyH|Eg-FU%&{{8gQmAGFn#lDydoEeU!hfaTbAuKNEwh z_ZUiesj0}mEq7s2hTvOT$t1DL$jI2(*~zewAVb_eJWPi&#Xgmm4jc|vt#yRs2)eD} zir=1_L)vm>V}~avFQ2B94F{cC-cicKA`uZ0 z6+w4_sK_B&78m1?b3Sj&6!W7XEJ!e!BYa-p_JWP!A zmomV$e4{GVKxLqb=ka;pe>zt9;wKqCJ|13xuJ)Lr1{Hznu-nDq%GJAv&wge^I6y7a z_atKpmRRxcGr;=PJ1j4~oL_@`uakw{nLN{}Oscw$PDE!U!F@u^w&jEDvNFyYgT5~| z>?ihttwBr>dnczjEc}IlGsB2bTc&&s^W(3Itlx)$#eY)!k_1zdEn^Cp|7>E}*9p*5 zI~D87R#6du{^c~>$JV!~XaB2|tTP1`RGps{x#P2B#MmFZ0>j4;6%b^Sga8_tnwK7b z{>7PL^-LObd#c*n3i5q(toUF(FK#e>;9K+{Wp*DG%lB3nEmYNTko@y|uJGAZ@KfJ@ zRnqOm1APw6HqTmm^>&^#t$tHRfa|UII|U(apJ&k{Lf@U7p8oENCgzSC8ym|3(lp)K z9-se&N6pWe?m&)oV!c81jTUVD`SEu6@_xkl~ zpzN<3bgz9vH56*<>xZse8XAN)_!cDTgXibxQwRt5&W5%JiojP%dcq@*L*Hi5K{g=Z zCcO}=H)zhz&d$7S<-~XC#Md0XUhPtJ&RIi#k-wAD6JH0P2tX2*?sGwGtb`ABW;!@T zl<}Xq{pY(u^vW&aP16%CefVS6>Tm&=mKic1M6bcsQW~KlEdf_%w8YZu5!Qx=hFBA` zmF5@_j<(pKvf5hGSTYU;in@{EVJ>3)p4Ohdv%I=GGGM}3Ljz<1oQ)Ib&Vx3Wd)s~v zGj#)8jN2O$7yEX&cVS!DSQ>Uh1-j)J4%T2YCk2gP%*3K`8F~Ip-%jVk!FV| zv;d{j$Z`uA8zet=Q+aTETqsdWML{80P_)TzLd~u2Y9-?X91zqpgRAs6*$U;9JW_Od z6i&GG`p(X4pNF^oB6+>b#?%YQ)O$dAC<)LbBO>1FTz1z#mAfb8%E%zEq|>l|;H_qC zTuvR6DG@YZF|Drp>=`A${UqU$9$4)3^mIkQ)oIF^`m<-zz)KlSFsGqH+OvuQ(5PJI5+g-+S?Xkg7U z3aM%VEMaXqGLcq+dndlu^Fy~cSH!0crk&`(s|q@~pZ8KDu!5YUueIlQt&GFKeJ<9jRhH zl==FzR%|(Xx`Su@rwNS`fmuc)85xw8yPG}c(rMn`XIBjQf%19eTu&k+`p0KyUk#)Q z^on}k-JEsqo%PmiKQX2bXgx3{XF%20*EgG&v$TAmZ(uMoHij10OUx_UHF(9r&fd8? zkX8wz#@yPn%=d6JaOkN?hq_+^-G8>VwS@rxyO)1fM#Oc|)YC`c4c2%npOt^th`2b< zI-aROR119M;Arx{D8zSoHtv{VxyNoPb)>vBHLSk7i`#AXr>$GsC{JXEC`!u9Ba?^_ zd-4&VHnQ`)<1nbRVEpw~(BJG{%AGb(Lh^UaJ?WWJJ0i*#XKlw*Da$* zL1F+__~Zc|#xoB2B%a4lR9*EEITEaDUW^Ndjr1WOm&GmkBpsaq-M5yX53# z)804t-swk#w$lS(GCV&1&i&Hu>-v6@_e`BbConW3Hl&%EnURr^5ECv6%>&l36v}TJ z^3ORZqKb+n6i=^Dg7Yat!c1Qoe$t+EEf+!`ju#nD$Qj3mk+j%$N$8DQ%d1-Ff)T~ND?(oWEg%Y zFD<}DMaHtGdJ`A7ygrgwkYnM`h1pdXtgEAAcD6f*g|rk+T*(@o96fUJ;^24pVP7)) z!ds1(NDQQno4$+lIy+f9U&I88uzFv%efM098;{c)P;`vbQ5G_IW~EwAufU#PUOpti z#WlR-Eck}Oqur#Q(2O$pmc%d)Q3O1UHwxyvde}1M&|y?A!1Qqw5tA_8Dm6H$UiOUQ z2;}7=8FNu6eL}}YN5E%ZJF9w}2>>1%8Yma8=Fi+^+ANa8t$w3)_Vp=T??*}~>4&p~ zrJQ~I^Ss{szzl2AK%=ko`*=ln%HiCHfwqZLJp%M)AnL0++t%Nmys$nx1$Ffib_h{{ z%fJX`a4EngXMe%s`UixAOe*|pH>8m-JVdTf~m zKKdK|-6ufcp^cNc6+{FZ+fkIKiE&#*jgzY7^Lk^NI&{g%|j30 zvzwfnO0AffacUK_Jy`~$x&97{bvKuk5AWL7k?6^&iu`x~28Sw@Lu(aOb2Z5rMGG8?yH1o`}`B!(@f&YCn9j zM<|$sgX2?iaatzlRw)V0FDfefm{ou%M~`$chP45Z zN&K>8-R(d-d_+OI`E*iAw+M@2cz8IT(EiCxU^pRWxb(iaD53OX+QK!HudlBea;*th z6u4Va&08<`yynfxB1Dc8JEcHi6Xw~Yi?u9)uPTa)vHAOazHvJ<_00Yme!ji{+L-&k zK3W?xUuX#cjx%jzS{itB?C&3X-(rNKAno9`hx{poW%rk7sC!2aen+-UmK;3`_6kh; z&`hLm#d72yu=eNAA0BjY*>F2sCM`YR4?*E>4g6|$8#!72SufLhj(|=u_(47?>*Ql- zf(JOM#+|Ye>^koeUQFv)bdS_i5W%U@9jik-tEzam=bLAAJeDKysEPEwoLX-@w$W8sjb}!8|wSVgEzP`u)a495urB;@_$-DRK@%8j)VH?#v70nNO9F=eV3%ub=~;+ z6XS!4H?5qY;?alR?1q8F!T0LD{TGb*3{yr+OUub>Yl07HX~Z4Etogh2alm_-1Fss< zZV(V4cDg;4$Tw_fZ{PJO=t7ZCKwxM~_z4vi)!UYOmbl)~@o}B&QHdL3u$u&~u#e5n zhV^VUYTWc6zcSUtNQm_sqYZjaH_b{y&g*xCk#ZorbApBUh}7RjfmlVOphHzIr#+UX ztg5_|C7k>G&&*XkpYQy$vv4JcaMxc$t;ZBCE;G|9 zy|*hrTbJp?&wG6gy_CCM-OB>_mku5^SChVHj#%?>$dxA9y{TS0y03bpZ(+ku`Iw7~ z9`b2wUakdz%?#TIWhEtirMt0p^l~y?AlNIGD|#+m81-(70Y{)S=v^bJqpLeqe`Pyg z_PhZG`RaJ=mXvhpA;QrEuGf4w)nASa{7RhOvJBoEnATeIW}c}vcJpv5R#|7MT^ugK z=FXhS)4u1+UK@l_v+v@MNzsz6%~7CG<%fqFth)T>``FbqUi!$qdC~5bZTV^J+G7pj z=a<(goohSkLX&jS@RsgjAVDBQwq|Vk`+=kMv};P#-J@hJ{$+G^8b@RhoH*hT#P2uI zbD>S}?XT&$AJ@S%cBQ03^Uc&Jq3sf$8}&HMesTZy zFcv`saGmb3P>sGd@<&Er*%kdj6`e+Fmrd5Z+w~zwX`6D{&(z*KE?=zo%;N2IPafrO zi+d$M>NZ@dADCS`+Q54LfuiS*ra4NHpZVeYCI$0T>gM_NV|f1ujN;w)w?DBT9%wDP z+E9pf(k-3uH(azb0Az!~4NRcAIl942Qhw^KS~63J>LUwF_NOwuc}F)rF9EoimRJsL zsZ!W^*ltnY?3H)B{N!wFZzOQ8UO6Z}Hg!EX*}M1j0UaMvKwQ=-@@oULEQ9e+wGoA` zlGr2mBhD2D+GF4r_qZ)#vyrB~%JN%O z2d}`n<4k}kHt567BklJ>$nfiR?+#cKLl`!kdxbNym8a7%XWh6CxVzAnSK88m=g=9qS{o><5wNc9tOjiFv;H%IrWi!;acS zZPw|p3zBU{$(FAN{c1#pEnbMayseo@v)JTQ$7VtB=b$TZ25W~*qgB2L2;b`{#}IF8 zKiV@HY7jWs3L$W|{hb)*F%mr77w{I%{k=@5;O+N@RAb*h!~FHfAPYrVl7YL;G#r|Ry}Qtwk>YUoIv}ThJHxzCT*Kt^QAT6 z?U%s3-GDbiuK0cuoAQEtPET#oZqM?AUKhS|it_n7s~vL}IAm_)vsYNK$MNEnGAwVD z;j8B@%Nv5MG%Vrw?*gHDqf>&xk-*hrv##WT{HKRq-I)|luLaq3MgB>KfJvZc*_o2Ax*@*5q_2t9%&-$F9+mpoTH5o=sp{K=T?_vEZWl>3z!U30WCK_+}{+FE_L@xXBYL(Gaz z$roZ^wGZa(NVeN#ir2*B=%|r98$=0aR)r10>H<^!9Sm^gb{q!F< z+v!6og!0m9zUkPw7_5e_hY}?K3H$guw6s!Qw;g+6_${;_EV+H5q3UUUe(iNlKb-0A zdZ6hQD7I7?y0*ZLOP_ZOT$JC?gR zfvZtAii7ln%+ixUR@ijzc`=m-$`Bvb)#P-eMPg30>peue9TQ^4c9{Ew1KmbP-|zi9 z9Y!r?qYvk(U=?+BQZ`b5fGeTqePsb+eNvMG$KFXp@N0D!_$uwdDS8x36PRN#QNQ-N z|JbweK_CB%vTr!_p=0p}Z?)NpR)i4=V^m3M)T%CGKeENunTcf#R_swK4<`CE+#rjl zn&n|z^sz+Fa1t;+npAjVphh?Q#W6e+ddA&4-9f8 zd{>)o2qgl6>I30B7DEb}-$e z+^elVuR(4g`!vb!pY@fxI`1{$i`M#w?jHRU?-VO9PB9=hDAGXFpuG}8k(oY_{5Ch2i9Q<@v2%GAI&j8bgRQ8@$VCw)GIZbz>2r>Er7R>R zhK?Yt^|J_6ZwUC;gV*_X2S{qeYcF@A~P0^M|hZ2fcNp*dzct${p_vHLz zh(p%c$*Hj%A2}$4MX~f`#?5!VpPz~m;QWxaBrt=BstAuiH*Ntcx&9z@9nSp{biU3W zBu4f&iPVv@>E-ZfoABI<)MbuaZ(WTujJDou_z~6;olz@3qO+XIQx)Mk{)U z|DH^oQkk%c@oD=)rY@!eBoyE>bl3g(&D7-3(;ruP#pa;E5--qvHO zzP9TW`fY5mkSk`*KXx`h-qnoKMz^3GDA9ao(5%t)ZscZ`*5u|nvJjU4{!lb&@(G82 zlx==~gNxgCfYawuCZ?Htv(gr%h*~a9P428Ecjg2ObLRt)(t)?{gwYJ+1=wx^(wVgTt<#-QaFh8yUnCjz057n}H zL#H&a?&!5)FlhVYyiO z#I3#OP@-H7|DSU)M9ciq3wxZJnY;|{FIGph>?g=hNI@esUZ?bqs1?3@(Bb)o4+GN^ zf<{%4BoFs zj{h>#U+^=z5`W;qU=o5yI47M$h#Vr>Uk(y+DXsB`_+Ysf<}z(} zH(zd8+rQ(cSCZjA3Gg*CpbO$RPMUGMpUsDZUOLM=Y&$QV{xEFYhm_)K@|yObzvHLP zgVm}h%kB5gW%N@Nl2j0ORkeM-FEr=uaI~|9S9nL|vpHD4wXl`w7E#Xbl{~1#!0DB& z-#D>;9F{-q;%VplOGf)lzeV*|%wawV(W9qZ0O2fMUmSt*Z0yC@h^a%L$O-{^XbquK zc(C~uxOs{FaO6Ry2^urvqBz;`Sv1Z{nQ6utv?(968&F=gXu8>KS}sQpVBT)Fs;gvJ zT$rw@rd|6TXXtiE1)XZI166OD{ppK5?-v&lDZT%|D;oXzD=f|_iZ3H|*ISL+q3@Z* z+o|6lGZWqcOiEkSD6pE|A+|Q(H6N#OhO)O3ZAwEYPOho0x69UzfZBAZB|(Lr-Y4U! z0fFLTA=P>02cc~VNi|Bu(3KA$-C0&P`7;yLOnyzwY@gl#fsT$ImgFiCESFen$|WDp zC@jf1z560R!<#-k-0IGQ&*vnI6bK_hd3;Zda2UB|${KarrOME+{5yKu$4&9-*JvU2 z#uc};%rg4tK6Sj0&zP%T0`#tqFF1L{e_KCut8lj6fk;eTKb+_|m!BEP#9tf((6!_)?y0x)kn;#St*;uyt&4-j?V?+mAn_ryn=W?J_5d4VlDak8QJdj-5y*=-LH% z_&KJ37w|`-p!;A>kdGvA1I7FU~#$j4$Y^`NbWO zMx$5<>rM>qwTzstpEAx1`0iC^gIjMi6@Vnm+`xXFZlhl5AGYs!XZ?cI|Zx zf`BVct7aqL!P9=Ox1W|f*Y>yC3HH^-#JJ_W(P+%rBuYWbGRJ^}_j0&_?Pyezztf=RcB5@Yt3W2vk4TUD$bFBA6 zl@tgrn2O`2V1(sLcLQ(461jb`uKY?Rl1RF-LHPE&&^6uN9{ah!Z-@I%`@EO4?uA3+ z8Yw4hsXWzi_l(=F^7RwzbNOFj8ekQy;6|24NQf+;A3pFLKt_(en*B|B>V+4IFepu)Jm^LpoEy22FYZvl_+4Xv0kuhit$NiN8@ZZsdK z3%UUR0|CqgNDcq|<)!cW&C@4VA23mo5%tQ{7u#o7c&+H54h-_uSbMyUttMQ8ZW*gq zH=)(Qb%DI4^HN86>e=rTUv&cmyR92|YXM!*^+CT!?%A6_2kfUF9LTv177DVD5S+gHDZM^y(1&Ufe@;mqh{G<{~hyh7;~#QyLauok8*vj7-u>_ zJ|M#*Hcc6r0j#R7bRuA_bHdrjOwjfucS=m4`Hpw48hshuyr2B=laQUsxa`#I2Y7F` z-zb{p4Dci$pau>M3^cZg$qrC1|BZ~&cY9Zn35vXDHAJ{i5E%)8WE@t- zK2T=hFP$^54Ij>{jvUgJP%!4|0q4=!Zlc9S(M7B(3e{{DFi=$`04e{s=sxWxgsNgE zNv6XB27Z1`OD9&w_07%qu17^{^M< zRP#Zeub|rx;!Z&k-;wdFnJaaS(?APxq|~UxtR$lH=F<v{Jhv!WGH ze|Fwqlp4JXlV+e1dV8%jHZ>7j9*A0VYEwQDdvI_N*6ayNlt3o7v0w@1GF361HZv#o z;83p4_Ih{cn#{}G{(OVZ4Of6NCa|{V=4Pyk>3SzRd^+*;8lr@LdB{43TB5zIUIsiN zk5LA|soZc$g*pJTSMWr%eJ*?#Qn`|%z7@>R8v)g-sj11XtaMpT?>w)@2YdhoOJuai zjwI}{fIuE74uKg!MP3pJ!uEm5vF`yWL|YHaqSo|voI?)gycT4E9)}T5Hn=hO^!AR7 zjwTX5cbuw{t$!dwh?$*t|5OIrx)6t8WTfLz;G!RF7MJ&EU7#T&0kTu-V_^DN#`Wi0 z>&=m%azX=2v8(>_RVoUk5KiobuMLW@^vWAxpQcftG@01{umA%EuRcFe;96CZ0^^dh z>$Gj1Friu@N~islLB7eJrKP3Zb|6n*Vb+Z;deB82K^h~C`D=VU7ZjqIzALP$07Qx zQjPd&>F8_*Jo|>tLBeNmil27-movitP!N{5Y;Z9azM#p;3EE~zyjPJ74bS+ zs1$0b6gY4MBPfSzcz+st95xk|Bt`CH@cb+Q}0&J$aA7T+I74|80-{pz7b#&N&@~UIHlZc zvJ9P)Uf^tj3t9W2xT`k)uM+gj1tF6`zPt?L!U zV!mhi=%Ik>6TYygDwyT$?@t5Dap(OLR^2hAh`N_hXuJj-dham-%A*R$Ev9<`805!50t+w$Nkxt2D2pM=x)~KSW9vF^2l8WdjU^@05IVg9*z)J z)z^~)NB49?oQ#U$`OTA|VxE^3>0z2*cx&zh&9XN2Akv`H#oxWT(N*E4VN_@hggpM9 zne^iL+7>jEQR)o5JGM5VPGa)a_IQGntxjQOsb6Wt1xPbi`mTTwrLLK+Q{T&RAweEVoxjzPgpgr^Zyzk!3p#X{+_@2w za~ow^!$TItgCI~e;MG|7=TECBL`%}g*OWdMA10Cp8jsx8FJV0@(mJ$x`TAJadwszocrXL{#f~B7y6cg2b!48mkI@^7|7|k7sFeH7j zumk_?V)3}(sbdD)Gq$oGQ(%3B4B1MK`sviQKPCSnND#PAp%i|{dWc03Q=R&!x5}+! z#p`}&b6#uiaTet`sK|{WMxcxZF#*bfHDvh3S*ku)RU!Jm+#ANZ&?KUOfB^0|P<))8 zE;9HmI8nE3vBuc5ung#MPs;%jix4cT@bM6BHjxA6n_H)qzvQDhQ1k-TJdveL-@CN8 z9*>&uNLfD$n%7vdndzfRgG3YL3mLz-Cmhoq1wmw$q>2dTu|Gv&1-y*FQJfy&hbo;G zT2f4k^78I)fRJH5nEupz2b4c|)n3~m>U*KpK?F7o#@F+Z#cv7s3qmrDoC|d`KIwW` zYghI4T{;+B@; zfiiI;@rRRna%S?OEE4@#U7pcRRIS9LJRm?w>GW(A+mwz{5+xfaRnLA20>p{~wn+Z^ z&^K{xExzE`@}AgC%eAvBZgFw(y`t)M%D@qYZlEr6cH)3g$$Q87*BKC<0`eMy{m|Gb z>@Av^$|igt(4%>8KPGwKUaVDG{vZQx4$$~>i;EQn>_^Q>9{M&Ll>lA|VETbt?@}aP z&q$t}`MjHZw!FFdeSpb;R=5BfVc1!f<6L8^iPOpE7}k}Ylam}URKQod4wT@0{v{YN zq{7ObWMo3~&t$(h%ngoeY9>G-jm@3l51MQ|A!gRkBD|h}#LOT4NkIzKV8q{esli_y zlLDnF>Pge+UaG?3woHWsx18_t03$7gNZJI{n4fmmPLa9;boA69|;CmRq%J?@+6JhL7RcciTvEa zomamzi>AISH3jGD*zpIW@3XVjsT3UkJvHNQQ{(Ctc|0iye4q$#ARYYYz#EubjPdQ^ zdpG>|eSLiuerJwzZ5+Ng#@D~epAL1g1M=8XXXFU5$);Ao88rc*^WEvspFeZ2#I5%% zk5&iXl|8=l^<647S~0b=V+X*(o zlyxveWFqkg!d!CHFaZF9)a<|D%+YpodIWfaJ2wxSRJbVM4h}nC%h0-71c1V@D!o>r zrlq5coY;~@a6Sq7eL*#v8O;Llz}m?LMMs@AAm_Yq(?u{LE4yq-O~t(RtYA0P=9*Ie zo;#}O>=t*i%q?FZUW#N20ST1l28I>Bm7vo>(nd@AKjeL7SX5o~@1YS%5d@^Wk&==S z5u_1mDd`fBkP;Y5B&EBg8$pI{q*EFsr5mL|;O{5d zBNqRGS)gD9P(L*3GaI&cbU8d&#@Yht~KeYaqpKThK6bS2Lk7zXt^|S0lI>!WK_>ko>3N(9}v7BUqxc`<|Ee2_2&Ca^|;ahu$5G z7l04$Sw19@`=Ma=R#M8TeMQYg3i<{jSdk5$|sf&a&i-blPIF{znkCI{%U%6D~bX zum6V`9P?l7`byG*G5#Nz@2gqrH&STe6g9Ja@yEBYu~8r~1DCxom2{a$1i>5V_GEX8 zqDpQldV@@$P~|X2;M3s~<~OX6JlXfU>6`ock+a+WGr&x(JJecb$B03gf$e$m94|Xj zM3`Sor@7THRG-?(K)W}N?kgs!nFwLygzvl8Him|aQ;Um>AcH;g(!H9m0H_Vz^dNH7;%bMlJN(2lIX z74b%oZl%wP_9F#(IRvY$hlP(N!X!>TzM~P~g3kmtHg-ysY7=e-OHTzO2&#d&)g*xE zM`^ea3?xRp7dPy+e&hnK{G$qa9rE8&Bf%LkyfS^zV`73m+180M42E2)aGJ=#4}9C~ z?b1B&h(`6N-)Afzk$Q$U6BOmn^S|$kbpT|hGFes5K#B;kY7+^<6Ve5k+vZ58ZxcMf zGXs)g(i2M%*;3!yOmnPM3VRQG(%3fa?KK`~86W2G+g8`p;k?X5vso#qu19(`btltn za00m96rPtQsoBU+4;`WhF$)M@Tu^y4DsXO1>!SH*o zh=PKmoNFr}1*si#WaX{;%X$DeM@}etGM_&6l&XSM?NZKZGp*0Fk>HQ{L#2A}Sru6` zgFfUN=JPz-FMON<`wQNu`T7ncbVs4b>_eu4*Kkbqt11MY`m)jtuwn$vq((M^YZ|Shm1SAqYN6*(_ z{3=pPs^I~88!6s;+-_{unJd<*WgLyt%xkQ~bf z{Xh>Y;Et#fbJEsRIto%TUb!3{5p#4e>v6mxCfeT0_<6gHXw@WKrt`z`ki;omVBdc0 zBD^uJlSUTj{o9|~-PI~J-M}U49G{=HewumHh<=ooJ_N)WxiQlL)I&wj?%Qi@#&N$F zTd3bdf<9nPZLDNiif~h3<~TJNt-7P#$CYD|`O=6%tEY5IGg{C=XGX*!CB>&17jBo( zxj)hjZvU5QH*<68NWaj&I0K@z;bNp^mC*Pe^hwY~y8x_8gj_+EEuwu%FgAD_|EXn3 z$ChOl|JDXv;dS5#i`M4@I>9*?6WzRar{*TS0O>`mk2L(c%IeIDEAJj50TI6Xdk#1u zis03cpY=F_S_2y0OBp{GF}5d#qCs~ZQJomRg{szI%*+_oE=HfvJ>PJY&fU%4K9S-R z;7Y5}loPr-ePGFjTlC`W$jaO7A-En`o=(HXwJn&?)i$RL*XzI%IkhXY?=#+%^U?VW zs;L7+R1^-u0MDBFe0B#i~nkLL8Lu7%**c!9BEr}HIY%aZmbwG zR)K4cC7-a+hvB2V+-)_M^y(QUa`n$ki_W>rNu$5rX;C_(*H*?YHSj=sTExj|;WYIf zzxMl(M9iiCJAV$QB6t4#M@NWDj-|X5l<6mHQvHqW z&t#toA`6+hi}ig95d4r`SEnP#B9xl=%LnzqZhBs-)NS+Xy%@{*eqzZBQ`B}%4}v&i zF=pUVNWlkV{ru{!eV^0RSolqw{Q9rVcaEae?qToJE0s%c@B(bCtgKp_Tza`AKg3{DunS5G(IbtDcfE4Pq6)kY0r2YQlpoOjCeS0g z*LM||!kN}C+TQUA3TmNb$jY5RfRRCB5HNg4m+a3Dj%gUBNLRmKE^zb z!vk(|naG6U)bHMmnMCeTf+`xPrY7P-7KVlZCC35FWw#4pW}*4I4dAKX>ARjQg&^Ne z=h|^cE%`?tGW z8ig1H8lS=s?t{`qMPoyy$giUD)U$U?#tne2^2 z<2UJz^TSiIO+4wRFE#CZ`=n=@H=$LkJa6E!jUi(%Eml% z8ZVdSOx^8c7ti|rbgy5>(W}KiKJ*e4K@A5}Z?r{I_etJILlUnF7W89NInU6yO@1WX z?UqW5hOtwAlnLYhSYP^`%QA#S%eR*#n$N7{%FxVg%~ezL-fJ7vUnQ5`Z>LBcySIOq zd=hc?IAj!JROzXJT26J7$JPbJCc?5$soYB8ellJT3rh}@l7g6Hk=^CjAELg*GbEqh z<1$waNf{p)XT1%P1$!Ll&JLBXi+9g(=j`|e*){WO#AZSn@0%KT%`fQ>qb8RVO|YR@ z{$>41^GPmueQEXq4hBw_E<>`uU?1VADS?!T*|xrr=9<=DMo>3HA}cwzT-3KdR6pqV z52~Sgm;UWL#+J5JZBuY>(?o7atp#jNCzH3fKx>B7I<-nX?U49IlZpEwg>e!xmW{4n zPB4_gO)swGN-D_{CwTBy)0q_+cJcG{^vbCSM_62g;5*9jQ(&HRg9+W6?BZK?pSpL6 zcZYFPOlxq7u69@mbBxX!%QnI;?}ePFn0lBMltJT~q{J@Ea%s3)gao_b73gq18KUQ1 zJ5CAE^Pd;!!+^4D9~N-gU27~yH?#AQR2rA!*pxUuU?#vtc4m3ASaKMJ#mQ9_WLZ0z zTckn&jR~C2R90_(Ek)N>!fqM5-emgD`BLO?p)Wt*;9;3k7oXU%T~g9jgK7>^OE540 zVXaa0O`$a};nP&}5^25r?+&_^tHRZDD-_t-(6_W!&btOJZFyEk$g>u6zh_euu0m&O zJUJ^&&GEX)H-d45C=&0Dm`L-`Qa@`5I2ZCenKSev!`{2_x+wx`OH$HfyU=B8{H<)> z+*FNh$`Lg9I3tZ=4yk7)-Fi=lp-eYg7f*f@GR%?9wQWQv#zbcTJ+{!^97SK^zAT#S zVCRinizD;)GFB+>F&LJ`gl^9&=#PFKD?8deHi*50%}9@fGslw4!*JR&$gat=;RhO$1j@z4m3e(Aq5F%fmf%0xKxlaht+?rX8#Jb&<3zq~f@@ zKDo+h>n?Qkmo=P3q2UcVJ+l;Ir#36B$|BJQjU@?rA$b&7og>%`fv8)G171xjK8m+` zaIcrAFRd)tD!Da+^9XG$elB^twgu|?_oJh0fKBl3?C` zAAT>>Jx4=Xr@znQ#EwJ*i|u!Oz|0WiRLzr00tKIzT#C=~Pd>HC>T4|dJerwnn5=y?U9CmV0^M@14)|${wvCqNgPrd>frV zLGWy1DVP`)5_@xS{pER9%PZrQ)Yux&F4VgYIx7jVHlNzh4;K9y26l}%5@6(n#b5PM z4kEKfIAk4O%dxmfmnyPY=EXXh^Cj1P%1-4p$4_V_a~!D~G!$j084R$;w~&auM=8R& zy-TQzED5nae%_h^=VHQbXsib7g59VI)1$&-bLXG5@`3q1c1ty6C%{Lxuz8B+C*2;@ z>|vdOc&wS=4zh&)Ew)D6*Nd)=fxecAa|kBi{>UtJ*tGIGDkX@I#opXHpa>cE%RxLf z#^ZJ8_g0N{=c@}p1x9q(R>JLDPa0Rrbde#Py$0*uk6Uk)TO~0ue9$$VB&P1F80sb8 z#a64~fwUMW)veyCo=A^;{|SVd%8P}%8}r7fIa7s2BwtBL%%Mnr7? zpWke%Zf_f$(P7vGL&EJP>d(k`De#4*2a||}b`^HN$g_q-cRW!;4lzG%(@Cwapn693dIh&N2Ok%iO{!czlmR^!%IEsWP~tv0 zU!h_9Cg+F!jEP6L>$|; z?;6Xp^P;PG^u$ChM~^u5UJ6OKCp9!B8{$J`3kwUUqZ|)6q{5uy*_=r?IoY0u-Yw@L zpt~P=x4=0SBGYf{ops;0=rZX~C~n+(7GH8yZjG{j-?YxY7Km9km6d?5@V>20qro>5 zmEGENx&=`XHPh*qVy(gKK~tz!RI#Vfo5kJ6*dj!2=L0;Q02tB6TK&y}$N=7ri}cf- zZKJZG-bOLHJO+9prPW{*NLDU)EtlZNONW*AUQAKz*VgK0^>VQ75S0GO5nrp7mg~_B z*LKSAtSa`M1|Oh;TYN;8wRksN4?P*PP~09 z(E>X|VJA#GM=Uie$~Kbe&#@}dBT7v7el+c9!E!mg`R$x}UM}vbXY0^t(xB|;Sh6!# zyfcuUqW(CJ423zdk(=$*<%L5td)-TW+k@`0Fd4!HH@gO&aU{^mwe&jmB3)#lrvVv;^=G=`!>xmpxU#8K~`mefE5FH1J+|R)v|tkCvC~eH!;6 zNUPj7z^^tpWf~-7K2*R6mJq}=wB?d4%8MtuWA#|zmGuWC#umA?ItOoYIuB2?T0P%8 z9=DMp*d0LAt4{jZFqQJ+rWssA;9T1mVeLS! z2dyrE8m$L}K&HII#6U7hcxkmm9{t?A)^?N+Eqg5^W#VA=W>g(>OnfpnRjDU6Dbkj4 z9l5olJj-#;cRw`lr!*1-`}l8E>b&m5k4JKA5hm_^p5Jp>r~^=VmNFDxz*_M0`9XVU zQ#*Q7bJY=r+V&Pc$;S|+LJ35}IcVKo6h-q2y{nrKEhmGH(8)wH{BFZBuP9$N`!wn~ zDc;=ZUElF7sp@>reeiWiw9X$g*rII74kr7|A|UF6^Ww*xWk zzuK~{90mp*aJi6%Bkn|rD#ufUzaz8)Vu#qj&-xo5_a>wgEhOVhP?Tr!kWH+FO>W*n zg`jaMLChE+7GM>@j*hUAoCuXYDJ0j6J1W=Z&E1~t(xL@?O$Ja)fBFDC2W5Oh zoN&81qMR@IwB&(}`1>^-sN}!j-UWku^q;6CgJ&@GbM)a z*)uKjI3g58XJox+XJ?}mGu{NP)G)MSczrQ^-KczOUT1RT8L&VRkMM=MKJuE<@yMTW zhVtz?6WS3&6gyLd!Tt?_58{5Ob)_D~-~kJgOJNa_kCSyD++_p-3jiYbs6HY2eGfr} z$QiHbL%Cm0`#lgp`P=sx9AwLpOgiHQ8RAR4p`Dl^gmY%je-rxK(z?11ool=f7Yeg< zcfU9(EiIKQ+0NH=!bL`Oyp;s6YM$VSEOeLb6E-{C05$#jS%aOJH|pBBWE&c;3FXEc zt9$P*{ckERFRk<-*n8pzeam`gdRi8}3RUDazT6Q<$VeY3iqqyNo%z* z?|-}f4wUL+W{a8mV*U|8VXW06#X(-`c%6Ud>egFb8xs?$cuu_$5NAD4lbkc62Feo1 zl=xzJo&A!pw6yfvjmLiDX+>OV<}VQQ%WZ0EI&XGya@y!&N-uu(^jzE8d26hoq1AAv zBjld*#nC3K8d}Wz!M`F$n~jG~f~m5B-cas{Ziusc=$B#Q6R zqhgRsFlZp~=+Pr@3KhG5vP?oRKOTuLK)23`WqH4oghJHa$;+-4#JLUX-|mi#jm6Mg zT3N}xc=5uS!nLjWU!x`-A)$h$rB|}OI>c5b;|B)^?>>A1Xf`;b%Jb;C^k)!_&fVLy1xLp&RQUt=9)&$lY&uN=BH*wK zKv^Dxk`k!~xPT^h-BkC$BnDH2qWN#tWMw@O6&00#?|eJGy%H407{bG6smcus3My7= z2&mQ!E+{BCU+2^dj9>pA(y<@_PKdO65)9?MJz?sE7spGY`}?=tdHS;M!jpis1yCHP zxX-~^oby<8Y;LXu?7qwO0^ZiWCF(yXqshp`-S<-g>sWFEuNj#=(}&`q!7Nor(;#I3yz3)i@!5JFHdGd$Mdv)VE{8@ zMBnpR9h`-6Pfjt|PY^&Fso!rBS(uBHmyroXFHbprVt80(zN;!lwk=t}Zt(1KWyM6=eDUj7sm{q9-O*qWur1Qm zcQfs2#EnmiNV#_B8vFLGeC5ys03wT*>+9=zo6>W0bDb`ZEK|iih4&8|+z!n|FL%Bu z*pwP$)BjO$8K|2r4>a-PPPPqhbO7E?t<_K^XFnBft#t;VI2Mr&*1{A+e z9oo|)d$qf8{kl~}Ch2x9z%Gj7jh(gPc}1L_lTcDlgm5)&Y*xwf^YLYC8hFANjqj6^ ze)B=Wc!WcwZL>QACryPRcx^dMfF400k!kK*1)<9bz-H#YaXCODfr6F9#R69~#BcN0 zwvba36109*5xbDe^FW=P z-1F}@E$HGG6ij$7Q_AoIfJ}Pal52e%0@j;<^|A^1ZFck(-P6&<+rsDz{(BF3iG+tk93&pRJ89S|XN1ns7N=jb$e3!*bjBs!8v4d=&0Emli?d?shtYjm8D{e7!a8N}6;iLuOg7|XcYXQexxYHw} zZikAqiVH3V2I)Y$je(%Qd_1QmIMQu5LFcU)O0Tn}ioN!DYfFG%DgqRm1rq#=;Ba-o z(tvz>H*Cp)Xx<#n3rS=A4PY?l&IX)&HGo-$K`}2mS3d6tpn?dz+1S0g4}?9>yZCu{ zqJEw+F*5Q{Y8n_AOdL9bK8qI}enwfyl74zqdPO55EL`b*bA7)5DJbZc=;^E@X?65` z@&nOGvSYu$+Hg?>9ACx68g||C7)Hv-1)cHV!@6Ur2w`4@6ES z!08o#HU)(^_NfDPee}J(n?VV+gz;mzy_;oSTz~^wvNFH{{;n83ax8aEZ`1|A`=ga; z<-r?I0CcEZPxCk$d!cMPyAN1Q+mF`zwZ=Vjci)4yIxx+ne%>Jq2L~Ry?)#LKoWkUw zT-YQiG;}=cIJDZ}1>y-JZ+EobLP=~U^Iy!BI~^y#eftE8JsEjXwN#GgDGy7hlp<^UpOcoL8Gt<=BAtVtc$*9my8$!~AbzuyYS&)gEtLPr8mO3|C_>Ls z3a5Y-9jjXVEo;tNWKrYLG}j+N#rAo_4vPji7n8z{YoS$k3u$j&hWPbMhH?XOGF11d zvH0zq$(exL_j+fZR-L^$Jvv&!<{Ne9#Kr)u#N(?{c6thsm}?Dyn4Ps-7ZIYYt$c59 z&)262t|5N$?PG^)$b^(f)@id*(p4`_xYXq1Y00iP< z&}7!xxFmx={%fsB+zRDRtJ@?_N=O*ak&ovCS_BZ>CXoLOI94@aza8}TnP;u6t^2kp zlarE09EU{n0L18AsTl}+Yj7q{of|pe21}~PUO>&K+0{q%^z;JxN)iC#e8~HAE3% zJz1*vt4?=GmF0zRe3W(V7I^4T$&qthhRQRBmt<#SquW)1j*jlI&pKzPYdc4td(tsx zUf^3%z=C6K+mg$Ir%Myza9`AqN|0$|0ZarSD+LU0C~vLc=jXY z(4&6b5Ad^QATGtPyGjT|9aqzt(L|aLTV(v_5uIdSlG$eO=BCreeN$)TT#R}J1qBmS zU=cU8+p=1Mx)P+`=}AdRWv_qE$4BwhVT}UBF%X_eVWFYLXZyFsGaUciWnet=c-`IO zwN*ef0F^$pFXRH0N2YLV?<>#;Q0B?NTyX+Foihx|*Mbr-ojlV&H;HoLuHynhq%_9=SePP5|mQW#~0GoTCzyHY|p5rFWc0r|C2tSUVszQ+V4^ zc}TNTNFHq-C_`5W6ej??z5q%WKSmfG{M;C@BZAT7l3L-34X!dQ|rBf%Ar)BZmnLw9?&6cNa-4%n0QDfmzVQosKek;muBVQz<$CMz)**D zymb5at?ve!{ym}??NZT$w0FOHiVcT#k`JfkWR+JHxg7OsW`*X?K~`kOzURKjQ{`m6 z2B(JuIz^}R9$eeMa^4rl&{DW^#8_|H>bSG)BG`eOySzB3JOl*}?3GQCh4aBEnnLc} ze3pR_D$P%gY@-H~?><@^8d7hk=)QVe%Fq$wHxC4Iyz>@c;m5y*Ak1!cOFtPXr%EaV zv0BMW-N%aXyt<~#y?X6YtINwvI54b2`@oh011wJ6vB_54x(kP*vq_Q#jx6T7puFTPjXX}Y+a{x;UPYUx2713&-j`Q;X%hhCjEQD8O$ zSzFlLY=E$~E^8c|Z-7y62`(2x7!lbV<2W_0tcQub<_LRA6d*DI^`tI5i8k#B`%huX zcan%64L{LFl69Xh;g$wPC)ioO0_8l2^?amt+9*V%hP9xi1c7AH*3$!`!p)Bs2y(Zz z#D?m*(a|hmw6D$9+x~nFNHx2rP1NFlD;iVXG<~y|;qIZ_7o7r4pFoiwGtU8FDlZq@ zuAExp+}+(h^iv|9UkA3~;NaH)2j_daK;&_p^AFvN%f(1yKLT>zY`o&^lIl1_^zWUh zX?%3_i%DP%{NX<8!n+24%Mo2zh3ieVoLu0`j#+bH1|VkUD_??8fk0IfH;8gos}}+? z+@3a1Kc0;R_0|f_(><&5-S>dGK_TqaeJBJj1KNI~aY<*%`(hu-g;d8p_W)9FUv2qa zYQ{P8&+BV1W#^UKjL!}JyIz=veghg14j_;zK3xv)17?BD6L$M$GLKul?|pMA#I&ms z#Vw5x=?GmffC@H!9N_YgK((*o!A+H0%tsV+I-`QSU#y2 z$2*&IM z%kS?fYO~XmRns)b-WNFkvD+Ya2I3G>SzGuXm*}l3U>O0S#b(*usyib8dbC|>#N*ev z-_dm-pjD6urv|$CU9pi9`tc(|ZIH$z^bdQio}hYl+(!YuEg1bn}5h_SxE*Q9jqh4+cLwr@h zG0XFr;dAjnm)QRWJ}-&J`$ga|$aFF%(8hTG>g@l1n>p9Hr!w@9s+^0yd>PfT7SDI4 zfXHS(TIorM(*5V-NlZ+Px7I^}b()5fmOA77kLzU;Xv;fM($;zyuukJpp<1u2i*lXG z=l|1X3Fwl{`1%V8E0iI4OV$tJ8~UfHw&pL6x4GoqOA$UNW&MMnbGUK;jVrm5cI(}g9i9U8^btZJ_P4325K6N5Q_Tpi*?AN($?eFjZ335H& z6UY8@i9viZTI^=@wL+y8U^e}lP;krti*8O8b%ZBUx9Odb>b5X((PSM=?my41I>E=T zdf-UB1P&;Jhy47Wq#+&ph0cmmcSAZ9*oj4|GD-!Of4WvqI3tb!+kS|Z6L7oru6&gH zJ>79{S z&rxqDS*ef6ux&xHIv0jLs(hJ$?!pW!DW8)9&f4*6E%*#k`Wu>Z!HbErvBEB>2s-HSitb^kZvu^b09e^kGgk^d$*r(=s)(aJxMFSMmo zfJDnTSDWC16GK%|6d2~%y$u6)bZa6aZC#kD`cizqDHg_ zoY@Qt;)P`!hcEBM(1XA_*l|JK?}dN<_<#H-pC0^2P|y1_sXR>wu?3Y z34=QOJt@82=fC&l6sT$ih?!6XzxMU7c2X8YN&O^Ao$v z=RcS|dCv0rX}C6rre2gbhjG?On}82v(y|lO@qm$Ln~@$7IMKKzOVyX;Z!*+ZehbYm zO?l`3>ZlqdF`gnq5SH8KK3g+LEL0UwjV}S$F5+I^c-J!XYG4aR{CxnJSRt)LrCrF~ zDcVF1o;uCWXx{TUY+eI z2nu7Mz?6HN7wT$&nL%^Lz)Vk$szU52@1K0hu#O~@oBjX<+~OnPY{i6)cbt1v?N-jF@f@xl!!{7`aj} zTwD$T;+P14pu+XpAd9a!BFdu-m@Uf1<66U0?O~L6?%*OHnAlF+_l-LA>mxsgC_{S^ z+JS6u>D4Gvc|gvCB`D1?AiWgv{6#l2encCMj8Drfw<3*KBie5(_X)Wuvk~t>EyW?7 zf!T}w3&o-sYqqyH6IT6i`en^KJsSz)$q%<~QI~ap>#BP8HlEy^=@T{VPO9241wB1L zUSvgmG88l!oBxrYGySb_tybc};z1D`Y*n__k!C3@Sz|I+7j!+B@V3vLYBS=%T3MHj zeH%xUr0g@~kYpgNyVQuPI2_sbn8nbf4Ji>8=^MTxsM_*-i3I91imskN>(5=gR;a7^ zQd(@Cvzfi3Go7fG(?<s$A`C*h22c7dm;SChM4a=TpWljBIrpAw?fB-<9rzHN1Sbx55k}(J`q? zvZJ!o$vtwjI5ad6UxouNnWgocvH30L5n>g2-hB+DvA}JC62`Ln3-(A>dCgCNvlw^% zj+@33G+#kcw!16|Hlp;?jjMHvqvSD3z79I=sxM&F0L8L7DCuIQQOEsqZ%pzjyk3>pm zThAa^CSf6uu*B(`osc5IES7k<**l*Qo0<~aDE1;eS)qH&6-3K%;9R$%vyMRVP~7f> z$+$26H6|Pa-Kw+CR&YI~=su#blHmPDJ4^@r595LS6*cv=u_s`yNuUo($uvK9fT6Ygt3w;m}XIamyM-|z_{2WLP zBQiFdU_Gf{sW@RO?&6fx&q1PM?Yd*MOp-W0z!PZ(NV7s#bMKzOS{x-)JY4*?d>B$j zr`L(}(PNo4e(T^HC{YC;M%CZ1qgTHN`Z1BE0oxnBvv+yzZd19_5ew6_ZZVJh&S8<{ z=SbmO@=EeCBn04frp-0wDG~Mxo#aZt=Wg^}c}Hi7LVI^u#gXmrp#qM z)1JlNy+dY<>%AK!&%dA_sQJ2u>2Gfidlr$}MC>k>kOs4du?Co8T=~irrRG7Wq0xdx zW^n)#RFNV|u!|MYZ_WDZ&d-Z7)LUpsXFBe#J$VK(pP4>=J8uc4$Y13#)^9|j`ateM zUzmN^B)36Ke;4($?n!6yta6^*Ee=YC=yt zM7@dUafeu>=BrMab{FpNOQz02&HTy+S0W6Jn(o^l#W;ilLsF$|e8W@Vul;?YUNh3Z zFhYl2!yk*^yV#!JgU+UFKMmBYPKavs^Zl^zwivC;pndHl7l#hvY#$aCM4ix8R$T6V z=hiqrZSePN!2*JY>myNpsGz9D*IHxsoL=NbN3ix4{YIABr+a-y&Vm5WlC0z*Orot;_Lf$~IcC_^O*13rC|iIo2MHlEr<=eag6@ zmd^tvM5o!>lqufx*m6UgLy99sk=T?YeRKzx^r^@bE4w96TIg<83}>4ahCgCPFM6+i zJGptRU#?cZXVWBsR#bkd8lTXh!tQvw7Jh%`t~jyx%U$VZNLIZ-zy>k+d7xj%lP68h zXvE{*r}DLx$?J=eJ*>Fnz{|qUR46~ZyS%H4xnPu)k;De4Py0J-d$K6RuWEE}DPdh8dmEDs&HuJ(IwM0#Zc? z8byhC#N#SE`hFsHSsXIP5uixa0ur}kf4J?LSdO3_&(BdvG-XFESom&BTrR8yj%j|a z8Fc2OR=$rR=Z+vgw$GN%+?Q$6CX(v_f>iM(${9%_>+&FsGa4&ta)p}&J23P*TSNgs zyNHJ8e{6i*!(t=?Nc3-fBj<<82W5pZk*Jd+aoupx_jM_Tspg0nK}shpCTi$)jYu&NZjUJJ z!sd<-KkXUE<_BncW;~uF6LT2sD@UBT&Pt_#dL28&%;xc0r$9L`Sn$sCr;eq^(}zJQ|mtfXCahY-NWa0$%hPW zg#la+MKfdaQRRv+4Fh1V_7`&=rX_i}gkV$i8;y|LU@3YpU3A&M#|is`{R#TVw%Hb+ zjDbq}vQ%aC@Du|Eir|&bvNfNQ;fJ$qh$T#&4r>A;ap9Mqhj@3Fp80v8sjWkLoF^ z|EjGHS$W7Bhql&_ElHQ1rwh24{t`B8(Hk;aW_j5R1Nat?8a=kkes){zhuTxZRI0AwX9_-J1 zuZHze$I20gDcECu-^b>!*;=U~18zkmw24YrA-*=)9f)mQ*?Y$ji`udA(PQXm+Jh1m%b z;CdaLTx(f$8MsfwMeoEuZ^gYw=(OVcQWXPYOZt*KzWji39knk6sTfs1o-puMWz6v>K?-vsBS1!{-%G{#E5YK3FfrT|2)8eP+K3{@aeN0 zo3TEJAA@D6)8_b)227%bc}BbD_H6Io)%5*u^G3GG_W1d{fP6M%6rJ2* zDuv;nH8&a4BBwgHfi3!qiiw7wt zNcke+d7kL0&m8%KkzntCNF|x-wCjhPxN*bYo0iP2nhL$ii^thPf^c#i!}7up<{h7W z^Hr2VAaxPx4_d98Ul}*LYuiUnl%Rv0mKhDZz7TVr6OAlNh&eC4`I~7d_Ykpg`07{a zGY=ZFbh0p(_gQq5kk_8n*l6Gq=p_}Qmst~ioAZ-Iw3M(RH=)k&cU@$ZM@2C+mIMO= zBR;2)KpIugJS28G&>Vf9@h~e>fw#&*$F!EXeKQTs*-0mi08~3(hzXI!S>kwT>yv}` z)4`+>3@u-J%;%2^eXejLRWb^7n~^`I1~4%UTW6d#&n$EDb_XMf+4oCsNBb|MFD`rh zB+!Ep3=9wMv3cyrzkhe}NCaLxOphYSl7&x8Vd)zNSdqjXU=I7G$1L8`Br>oEY4(SOc zs!DZ(0yAzPwg(O}49{(svfo7%tmsKvJvyl*GQ_}JB*{~Rb=79$PA79&rItetF-HNf z#i!E8Ko}k{mCgByd(h3)kHvzG=WyE~JDT{340U(a&dFoEwb#tqhG0gXK%T86H21hD zAnp%sWpANHP0D(VcwLruW&8~3N6t7815GrDD3x)c?x7c_8QdatUG9gr6WGxBLlNt& zD3JXzI{)MIBsFxF(6!e(pPm-Hrpp3Ao@2emqc1n`*KzS)JVb)hTsJ5~Epi$5%?2P9 zMe(aw@wZU1_Jew(j39}wU9|Scq?%W?oJ{1XRvpay_eBYvHpoz6AiYm?gZXb0Bs#QE z%5E2ILJ)}`(LFW%#K9OxrhrI~L5RxBoZrcEyq`#*-l4mc2X})In@uE#1oPDeZ*cN^ zwx_FC^8yuG#M{2@617hLreer1LWs4KTOU38k$$QXM4b$tFG?aukA$EK z|0k5_(d;@p3dl%m-%1!d8qdSIs{@K&V1v`JWI58YcGuRy&d_+I$P#`~L=I6`S0P`1 zBTEd$YukK+QD`}_sqTa4nyPVf9~CC&XlosHxr);Rm#5nj!rX!okH@BpT04@d86N_`hV6KoKw9t=_N>VJ!7T zTrg0?YiVGs#j||QPACOtK$5R~h~k<)JM-LXUp!s8)($Agc?iwf|K7P8!U`3=}0VL{_&ZD{%)slUtSv~&O#Z(?Rr z>cNJLUUkz6^JC_SW@~%(=q!5#h%>@~+7Roe0drfwpch3JOr0-Q?y2r%m~eP>dBcc~ z2%~JPp?&B;XgQD2%j_Hh6#f#llO|*+Ce;#0R?}`x#CNdu4yo~5?aRPb)`Qe}E%vJF zFJBH_!CLtyHs;?o_wxUY1NC#CdxT^SmF31Yh^=ltmK8755+wQS?Jj}8v~A=L)v~4w zU+(VV4I9@f(qd<{-gPtXPgoYr>M{A{2a0o~m8q3T~O+>*` z;U}+U$w`(xpAi6wE(QV}Dleo*zSQxf5zHg3GClzoPubVlbV!hEW#M$>FMq$y*X+Gy zxL&_{m>OFR4}4w{_GM0R&M~Ne;5G}%<|y4h5ed)IVt;5BN~)Nn%0+lL6KQq9?z0g6 z>a77Ji0B7NqP@ghvrPd-Rf`b%d5^m^i3%qeeyzZvB{A?+?0hA9{x1(iphusgthU|n zM}Z#X4O7Cy`A_9cXB-crmBlv0I*j%)kbO=|;DPFiji#9sGkW36DfPg0TI>3#=?n5H zlA4^)EBb~W6qpXnBbND!${I|~QeAEz30NZ;jVvPx_O)V3hy}e*c36JPWoMlFjsPZf zkekDDGZgyri7aiO96j-S-zfKZ$Cn)sI&I1)(n33o;&0vhh9BA}uzws74@ZJnvlpvy zrOyr8BU?%6jIoDE>1B6Bv{ST`n|o;SBam^IsG*koTu)@p+Joa_o&oyJhoATtl3;Su zSe1y9e(dsKz~SjeJuYf_5G6Uvg9ODAa#pUhJ&~1baeQCdAa4@cb87_;L$iDa6;jEr z#fyy_q>~)cdwSy()U_;90$*3VG_;>D5yjQE3az^MaX(aMjk}I&9Df` zwKu2IYB6~WPN`*FqCl_?tX+|W$}Lt_^3bm3K;VW}PZwXItFFVu8?VEse)7xA=L#Ko zeKT^&=?K{m;w&ECEs^7Z-fg!FWOi!J#qK9a%D00oh2r5MTw|hRa@@}4pM=%#!^Zfn zcbBx--(LJ$?K>thZ2K76bJJ*{MTrdYYaaL|;rE#!sU<|3AbiQD>iOHV-P*e>u{A{4 zd#p0g)khY_#+>RVo z$<^gaTv_%OPv?#ip5&NML*ElMi*z71L|>&= zQ4G4)oKmn=-+1=96C(J`LWTD0_PQo!%8!TuOFOg1*3FSPmSuCOo{ObcPf%WifUww9 zrRAtatt}yQaCu~{Gp)QX1&}JRB(|f+VddWn$B@9p)TX1BGTLM(sq8aBrA&odh9_^Vsz`P^owAOhDFEcyo26k2CUjX!dDM7dyE zBAUG#;Q!5Dut9TV$#fQ7rSSKXVg9hc0@w?*jUVU{yWkWFWHWmp=yQ~5BOzzI!w>)M zx_S{25h`{D5?!0Ci(@A6AAm#m>#LHLeTb&0P9L&t<_Ds&$GiA#^CfkH@&ayk)GQU56Gw0%D1? z&U14)Y09V6V54F0a`W=&qAu*p7qplo7pk=wN@fzqi*M& zqJAK{MzK8Hols*?KWCO5h^$wFJifJ;&RV&H8CkSoQF)u9Y#4eyxVG9|^#t zExn*z+?~?xriQU&RDTE-Yv!f%vt+fZkzszY;(b62ZLAy)I(n5@!oxvh{tscFrS3N>A`Qd9aAQH1!#fC!>iju<$k$RyHaX;Euj-Z!8OJMzw; zxIw9;9C%Y;?1v5`V^)W@ie*6;^=xX0=y>Wvd{ki1OX{?^NxnKD?)uYB&+*Wax95qx z=uMx%aq@P$dby;Xm+OuG)#g@z{HNyg9`38{@rAMc%nRP%>)vH$Rqq&9rBmsxt)c5|x1MdLytO!5F1Y5aoiEwfduRSiSMBfRVlgv(IIuB7^fhcst};U5 zF;)ss-}_C>d}mX=1q(haD!Z<|{$wsQFJb>lk;D2()8NYSYFzV1_h#Gf`sG}Je|3OP zWr3;Ugz1@IS?Pt`oS4kJE2zaA6Z5&X!DX}=#YLwk7UjK5J=L4<-pONl^S^nv>uyf} z>=4&+*v;3nc8Eu4Y5baJ{jntpUMkx8+^htB zU+`@axqa^LcggCL&vRdWYIl-}t2_qp_l zJFuVmC%Mr4Z}ULh2n=e@%qqe#CYn7nu?yB4Ikbr2edb4}*dj@d5u~gue&aKJIl@MQW(HDK};glTd zv@w?vr}@;N+5VCDWW@Z{$J@VLaKdcaj@+G&GqvmLjH-+Sez=N0e%W}hiA=_B;q$VX zaT$TCMpSZzW6klmO|PrR1LD5B{l$5k|3DTu>x0G}@N;iUZ zcXzi)cXvsbfONxo*53R5jd9K&a6X)I)+fetI@g>}+|OOtb(Pv^XfR5wA6wf2GTui? zrZ=_ZdD_L4D|LjXO{scCFDDjRGmj$X{r3mV4@_^SpWUjy;#wE~q@ofhGrF=(C_ai^ z>|9A36evOxqTnb|X|pkQZ)QaP-)GR>l!RwINT zy_iNwyq{2*20RT~U>+nbRhmU;EdK7i^mA#ePO(9a>)d@b>wL>!qq%gS<$U!g_WEr} ze8$1}^Q~mHCmH+4DLL1te}@J;xQ9HFafcmHjbkcgotu>NXhs$^gIbUd^ zYO9P|Xax~F)HZD%MSRbj>nqr(-?{t4vVSnI%24mSwu@4)aDV^sbumY^O9kWTWc^@i z{!rPFaD@AzbV;(s1^34EK_1m`r(H~r)RakPSlm;5N&<}8>ZE>7A1J$f!(f-7rsQfO z?DaPRO2g~=${i)X(eK5R{!{NX?Q#N*h`{or@TcnReklD)*2VCT<=@i>3sRhe!qjh^kyyQ=z@OzWM=Yf_B%k zNZ5qP7nFdxXE?hvl*g?0#{_{#jr9dz>RvcYR5G*(8G31$ z>|YI+^}gsP16`64ncu!4u5WJ292>m`bh4*sp*Jhh`Hc3qwz&W_1DdR*`dkJn;MZC}qM>ke;%h7bt|1y8bp)<9&T|xS*7F_1D z`1gvO24}PMIT*ELXSWs`l+9sPw^zhTM3!hzch8aKdFhqT$AwZ9_trefu&6@~f=}k| zYo9XS4Ko{sF}gM83{6g6Nl%1abG~vhaAvQi2xvo8Qn%_j{DFzgFCL7^_C5mL#C?1; zHNEk#R{Y?JT|Mtzb0FSAO)kZ2ZGr<)rGo03eFYmO-88*QQ}aC4O_;LJ9}XL3F4c1i zLhRxm3ghj6UNqgdHO-F1&#Q(zMA1$0(Jno$ z-aBPJ*t+g=-@hWTxQWTBWZ=7TT5`FV`Qggb^@Qizw9EUOG`8Crd{5IUEXy4c^CAK@0q#l`BBofc{d@ zG&pADi2F%ZH6Pp;G$V(G8qn$m@7M3X?J@-|)v|z}Fyp+@`P765a2a1k@?A!jZZh0y zcBR|{xtP58goL(g3-B}rN+tG<#O=cT{Yym*dFXu4cmZaMYZ{ijx=FhswV8 zVQ6_k2xDi-qR3Jetcw2Z!R~ z7NJHTeX>!MOWFSA%Y&1hO_w2&hZaL@u4xtv;Rp50T`kSW3aDyZr(4PKh;;W}byrte z3`~pT2jMUAnqjvG=(}&H9L(uZO+OutY$G%^x6Cn~*j>RFh%LL3%4Cxm!P)K)%!fZS z8>)>yenMDASul*K*Yv3oDp@>n91rbTEqaqTiq9$|#a=o0sY*H_rYNLg$qk3Kz9d~Z ztXZjqCuaf0;e(SVkDNxqHMNQQUQ2qWIE{FI2C{7(XD1|CttPbqA>>=^NIMj zq3l9xrUh_FRn?gk=~WO?rWv+mnJjpx^vx}~PoADLg+G!`=FWppAT8PxV`JkH(lG9H z(E2LV)-4RsuBL$fGITXk_}f5sHU(f<-vYF%lLKr^1m}qWSO+F-i0{5}b+=~8z2R(c z?q#)yjZMX5vE3clI-G_0y23yuzqmM*E0jzbbnbp;l1>ajMP-bd&5%F8y^A$rHzpHu z)j8k9x*nkKndp;=`e3HX=%HC`&#jn$?Qvo}jb|@B+CsE1wmaMd+w5%u$I4E?>{1Zk zk=y1Z;zH?{&^y3(3VZ~5dRFF{()}avPOLrLhP~qXF~fr4-H|08&7F(J5=|Bf8H>c6 zawn(Iv6N?f>LGD7dQ$`^*SDBdbAL&O zDqwA&MyA}fPgKDnGG=q{0Bmu5qR!!9aj$lvoOZ!>ObTnx<2Yf7eSc|m6q^xz!!GBIgaSmF{o-zTh$&MjLnJU*awgVi%e%)=u@9D^(HtW%!EiO%q}iQM zlHFEwi%yI>{NZR6qcBi4w?|t%IzFy)KHVAFsR39Uf8&Zs&=qe3c)^0EiQ?a@tEufO zlfw#n88Wl7$aS5ME=+^9TuOmj{YqmetZVph^_+Q-lUruAfyz?KKBM;M>lBpwq@4c2 zK0CC4Xr+M>U%Qxs{XC{7jTX`SqhIf*(k6}!zHja(X`e6j=m%GB@;gkwlNb$S>$l;d zbG*Z;yV_5R0oIVo2te}ZJ>Hx)*~Q4P!xH(x`7*Ei+hpl*HHyq5*zVDH4JK>VvZVu$ zS(sehmQ}RDo+=OsZ=XXzh?Tda#M~+_U&opgl#{8c*-skAa~$zTWXaL~3x>_!wU%Dj ztQv=0;p%#QNzRAO)hrSm(Um&egtLcf4Z7=PrW+GU+^ID;UB8N}!0^)bO>Q@T_^#mb z!D+E}%6Vf}wX1ZW?w%1`^wdi0NfY^%c}Ee}*DtyC76Z#EADSjN3#vA))>O5h-Bh16i6_Oh5SkwVk8FqZN*<>rY&}Y?n2Drdc>N*kLt9L z)KwX+iEf}L^WT`npHErc37iR?yVVeUcIQlbY%uTOxpYv{c!JEW1B4t(vYol1Pw)eL zM~28UQM?hpUX_~#)bb~{!Iz6)_@c}Ao4{E;#yCE^SkGqi*B&_5OLr4tCp%JMA6U+( z9Ve3VEx3Nyo0@|ifqAz<-nH=*c%>&>E~mf^!y9oGj8N<^2;}SpGa8zvTX@YmHbX&T8xr#Q!^@>u;mHfqiF8(sSJaXwO?&vRe1-5%tF7)S@* zP7xlfl&?FFdC;a={o!y^mPs-~ePSRu^{c(IrluwQ&owsLpz+lnK#tI1*_Qkr9v>)CbspLIF$&4?wo-rS|AKqIpfh)-*jFBL zsed;N5b&5;U?wQKM>~MxZ$pd-Mw$VA# z>H`iNcJouMbL|wOn}Hxs`>tah0cjA`0UydiWBno{dcmrpb07F%Q!nTO9p8e3RsP4O zU86en{?JpA<2NMm8#E)Ez>QTb!f*CojR)pHmfZMcWbw7;fET_P;RT-Kx#_2gK znzaZY&lec(K&0R?ve!q!dRYT)TA(cs4pEE(Gy*Mh_Pe17bygvP9hjayopR~#!)+oI zCJC1**>*gqfLiAfTAq*BRhePu4(NGJHnMw3f~5BgteK0cMIb^Ps}~>cx#uOs{e21I zh#&{-Cmczfbf@awg@y>;`tt0K`}1|g)77`yOkEh5m=+SJj*w(=jx@`bq*l&5r6Tkv z3yJFBtb^N?!%s%loBcXX?pK6eJMSn#H2gt*6-5sf*71%+nrKc=<7>j6fpIEAW}~|OFoYnI=XJCF%nxFO?plc9 zg@>VjxAT^=mUz5PsV^~;Lg&qgz4L1tvpILhI6b<)*$)#W;c+{&7)BE%?wLF}&(!q( zf(W4L(^7G3!cyPy6z=fp-=jaqDr8Pv#3SOllJHMztuia3^4cOnc5l=+ z>{S9gl4TiU#kX(cGATh6I7#2-KCZJjm-#aaqhJyd+#Z7^NPKp6k=HGX(MmEpa+J@J z?~*T>K;*ylm2C~~{Du*86@&~WkL{u~_jmX5LjisMy{!_vXw5udi0-IeFSJ>t!f!Bo|MJ6R_lhl9IdB_5zA<-f(zW)E0|2 z4afaG4UfG%-n6&{Y571Nq9L#JS7NicTE?;H;DjK}N0CWnPXkAUu%sI}5kQoX9I7+; z1~^=geKf;X@f`G`2Kp41)x(N4S=4A^T%Ku;nX75!xLdK8r6d5iX=~?0#pxAD=y5#| zRl~)?z|+(GE+4m6HrLzN=SD^N)YI6ixn#zG?!s1LxvB(_rlV0-P&UQFHUUFC@hT}2(WiV)uX zUctxoZqKlO`RD4#sWMcsX z%K-cR`8)tgj@^_I5)cqjRyGd)2%=zR<(=$n#&l9xyJY#Mw*JJ@O@#Uck3#mrqSH+9 zV`TE}bDGa0U#_<#^-=Ul5;FJh*WFw0TOU32il{6qEbNa-UxvC#(pVoSO~PnFCZ}z* zTe>B-RvtN@InCr5|IR-v0YTluq69A4kRcKVv_S+xN(FepHaOK9RvkE4o6Ma?0DV4N zXOjZ}VdKNYsUk#9Wc$wza!Tn~st>z}{*A^>jEz5nm=0WW(x-LDwC}j^Sc;Cw)OBhD zME`Uui3NY>3PuH>VWu;YV{vzF8t(B81#~9u-YOQ(* z0!`~A#?MOSU24%)1%-vQd4rwM=TNw7Lw6kg!G0o1DT|O;*h2a>hr``~khJMJ@JN48 z@3DVe#SgK?%-olnkW6~Vr7}d+{ldq`N43G;Oz1D1hrn#^_4N=)7IhU8(Y|>zyjQbS z>~{U@UK(hr0siI3PsV|Ln3$WxKXwIJk^Dy+yuCm-x> z>3Waz-wY)j;(UR>XVqTH56d2^6LI@|E9#s#GBNvUsV}H+co$Dv3O0%{bBu+4_FDoL zm0h?CHubD=JzDKYN(%8uMm7+q)PuL0n4c%8VfqV?(EI3H2$%_IX=%sdbOZ3{e#Nz{ zQ%y}R;i!`w2?+`Q%B!jY(FVo?@vKx$n(s)!LR8oaNt9)O#~!rar>SrVr0jn|4aEs;&Cw~jhd() z#%hV@%UIMH3)l7i@EP#cfVLXyjsFg2#g&fBm3k?*3s^uD$B>JI@gbrx9fi%;*~Kyv%Q z`NoPeCw@36WDB$3#q;wVqC&k%y_OVuW9Kjv@<_o6q*zH2JLplaPXrwFMOhT!!7zyR zbmq*d@c_hb;z`ZKay$ zELz-bHGZ+0PI@a~2tSzy`=!cwi~{V6e4v+ROKk;=$>Yb5EA@M@!LWxZvV|I|C<Sx#PqTLGw^b@UY)|7A*k}~J3@5=nh_JBc z5+^%d3T~C#?GDuBGe8}~j;G4;xV#E$s({OhpO5o&Jhvo9M;rt8?G`IB9}Z zc3f^3;kJpL`s&|?AakiV1dRO7Dx^6OD$;D?{M@0HzTgHXKtqWkG$!BV*I{d_feMw|XJ*1y zlJZvIbWzjygycenOZdaP*Db)1aMS}gUhI?0R5&ju&(q2FHQR1VyT6Leq*K@>$RXoWfWI?)dV1-bp@IR0{cjnMfv1xTkF%^RBUeH68ybu+`1%~@ z2t>NOz!kJTO6F{G=z3yOc$s!w!9#fY<)9#U1Vpdu8kDnBw9(HNzRTs1Z|6)LdXzPT zQoz2N{4<9ZeOt^k4&Lh)t>#y0$H8uAX6NI1AegQH9Fgt&AXa73aW%x>48%o}RQ79|0#_3 z$Qm9GO*dO@6IzvUSvSr4%kD~QZDvX?NsWh2b`SQO@6)UiM_Zsa)c7o7gF&}0oObEE zPhRD?n>z~0_kVT;iUysBrY~eFSxFDxsu3L%v1!`*-Kl*O&`3{Ac5Ac>VdbT5q8%y# z`2wnKM>OpX#?CDuhWSr67lqiBA-$j?Gk5ptYXY$72Bv5Xw-1Qu6`TUDJ$IQ(MBs~I zTlUXxRK>}OuT$BB_ZhKrF%~yo9J4zs1jHR%xi=@B*k;Nv`Sfj>TF8{{k7q9Hb9}ek zyT6RRFw1!j`(f+a`VA>8%R~~&hR4?SmHR=lp;RFcy_bq=;bHjc)wY8z@SH)41H{u4 z22Pq9a+RVWx;|O>z*&GSMx&O2>6U?Em-+r1;M8IdpIq4=wdR|>;lBDMqzE?Cj77=D!m4CN}U{<>mTDk|% zVolVfOq@+N%^vW*c(-sKpZ?fP-FedpDP?SY%@NFrE!zD1C1yLy^WFZegDAlLQs409 z(&%&n#HK3c6yp~Z@gRo<5+{-lVQS}A;EC!R{s{9-9I5^qavzG@T55yJ)kH)@`ir{{ zv!(QaN?L&0w`Cl6VbTdN#x85qujaY&&+O)f@r*xVXKXI`+I7_bQ%iQy>{6xgG%GJ}GzkSZ zwHwbXmNA=-Ad$!Aj8}>lb^^HDO2j7|q0BH)O{_Q2R1ivi9pWtsibY$^f$cfpn%DWV zfn%j;?E|*fHmDL6cg7P7H@>Lfi1Bjc9B~8L*vtIC(L(62K)Bx7I4SRPqTGaM>JGzl z4Q(meZ{-DNl%3f~dhEI4OFi^%xWksT&L*T@0PKKaf$8ZNyFH^?oX7TE`M|W&Y$%YA zSJf7qwY*ceYtYLDd6^a1J3n~t3H+0kIQtv1B|gOx)8<>r_TySmPPZ86#yR{$(|DX7 zy8dq(A?b$|Vz(>E?S0fLC@!9C!{p=k$>>qPy}j)K+$Eu^p8O2{J^Cu27Uafi>@_YM z)p?q%l9O|9<3rsXKN2VHD}e)J2uW?7ypEctXRLR9u~eL=FJjS5nS0rkx9B@{Ycj7I zsJO%H>2@83wQ^x?gJo8W-b1h4s+xa`PzCIO{N{;d?s_s0RmtNCHi>Q^>a+g7%EH@ zdL%DQso&3e`^pugqM|N8v5-N=xh|zLAUF8|Y#Bc=k0RsYRCi9_W=Cdo>#J{z!)9zY zN<(d{I?-y8S{Y00-p1! z^_;ts&ZHx}?0@s~87QT7PS^?JSV#{@kM@AV56_im(^(=%A-crD@WOf-4KrwqxFWHn z$aBHJMq^Vd3cpw*&<@$X&*rW2aR+M1RIWj?eVk&qV{JGVaI? z31Sv=a;iJLzV&c7UT9^F(4RXCdh&wS(KX%=8Ty%7n!WQLgmcDv{gaOpUu&yOo<8*I zX6fMC53Lij*dJX=$WQA>1=b60{y-ZFFpylFJf~l@Vwws5jDZKfRZ}wk^i{iYS5vK0 z)Pp_yuv=f#B46F!*sXQ=80`_O3&)5Xr@^_s)JGrc#$(}>Ww6CT3yY+93FBg5ME-~! zx||zdUTJzJ^~#J)ht1cDv}$_YW}@kvjUP=Gz51Y17fl8pru#*9W8?hoin{Rz;2Z3S zV8Q9bZJH}(-@oU|i+%eD>gFx0cbAZRj*%1|ur;jdP)`=EG0qYgg06|_C&Ye7{q_uBnKi3pHC!_&{J5EdjzCg?A$NIsxeClS_lx`&ZUP_@N|?R^iKmr+rKaKW%LeD;!4?uprNsn?fB`Mo60}lbb+NfuR<7jT(8l zpyThSH|V;Lm9I9_pWCo4bo2EEV0b717v^8HkxE$=5=Y8qeE$A5x!e%6+L5G4+rVY8oC?Y~!_|8)DXlT|`m5nNE2)ui zPZk|lMy={N(E5rCSWA9p)*m9IFh=VFnP$-OiH&n`fT5R_s|GIt4Z|u-UvGlAEZCsS zJ<<>vJQAQuRkIn6-Os~C_5AXArfe#IzX3pRAW_LiONOb@yhnasDS$){@0$P#a$lf^ zneH(KCX}sqgK0v$Bp>{M-@d|6&(Eu^(c&RQ_GS@9!fSNvvaUk)4clxT7*TUy1-(2{uM3%}JUj5$qP z_7f?18nNGpou1EKOv(ZP;tFDLQkSNKAD$S9vP|~<9m92svhP}N&XZg^bs-*B}4HlIKg%)ExE(fzGT06z#kvD z1nZT({E?cNm?)@g!br9RsxV^gv8k=g(_?|g;)sxChR5*M`^Ky{$S2KfOaziT47Da8 zq0snI=!|jlno%pP<|!|veRR_G2qi@K9!y&KwJS zjjn^`zpY7*_=A?~H^a|1y~z54-65h1YX${)@WadtgL~1vXJ=8#M6SzLquuA6#LEZL;leq&?XFhjaChW1j+LqZ$gH`aF zS7hXIwJRVk8k9&0H+uM72%c^~COk&yxP~z!BJ0M8=H|)TkAo`pw-wdji%)hKYV3_T^N@2!~}d~A&oI01grD~{7!S{_3BDR%-kc~%Ox)qhH}buv66GpS+yWPlm7;Kf^Y$2W0$ zb8~ZorfAZAr{CiO9d3v>$)8m*hHRQ@It6Y@u7^V;rZYUZ<$O;%XUU&V-%zyTcp4w> zn+DN|X?0txkEutr=0Qzq3`qNM{k+s0jFDsekfDi#9b-r40pd#YSQVtv~f|cPub5k|?_Q{S& zKd@0Hgi)&|G9($ArW5zXirS2} z!c@Ey5weO+n?4{U&s#laPntr2n3G$W-O1|=yB9ZVdDb&}5Dg3rc(X{RD`-upyivUR zBe`5+o;O^LW3(3-AwXoN7eP-B9dyMRJz17}DncbVpPQx(4Pt%-23w`jV>ma5Ui15R z4%=Jjl%Z{$U#(=g&l(z+$lVXS--B`8TP-^)YoPxGE{-HKpGd6?qUwM~J3^{*f*Cg` z%g%6#vYnqo3r6*FX1ria}uv%=p)|w6s$( zB6rt+G6@_+`q4i(Zv9rI=-%3sCzP>(1uh|&; zwj39c>`j?lB9jlQz+K9#U<3CGsd_DWrCRU~jAs5ANL z?zPQ=8!;AhK9^`f^k`aQLuy0td}LK|rY)0F#E&@^grJ>$#m!I%PM;Fi3lYqV>Us|1 zWfF!x$$4=cB^-JP60zcXE+LPO*bSR1IZujVlxd$k_<}MZ25D355VM#Z1ZuIuN(2vq zy39|OCtnsl-e{ht`hIa}nMZ_vO>feJs;lo4>LynBE0vTsRVDEaybuQtyJPJs_ z=+Fah?6!7yy9Tth-~0ys5E&Cv$H$8%v>Z820x=@)M&~Rb)jy7Oymqs97v*WKB@Txm zChvsp_J|)``ikV%n!f}{&>!A8x5z|m2G&KIvCL1feIP~j>}R{_K%%<)!I_VYpid^j zNv5aU;_+kc7?Y_927QKV#scW{_N>Re^=ZKkG}iiOsZ|(X#=L74i2MEXIXWuD6tgf! zKlbc=)4&rvN8C;)iYtYo=-dh`B`%D`giFGP3+lE_Nwi^0nvQJ0Rs%PYdPrn}CVI$s zOL#~7W=0Yd!86y_ccEy^WS1QN4CN97Vt3q&?mTYQH%W&DQV_J=v&5RoUlO$49z7Fp zG?5Boj?Ac^8*DYbFs=TiFM{W~kiX8wlb2GW!G}o6t~R?r&`=?YNCVd1T*gK7J&i&> zR46W&(gRi+*^`g{`!);8hY69fzkiE*VMF||hU;`NTE*^@i2M|_hhOC=47+dY z4Az2bEaq|^?1V9sqeJ1mS12K_cNY?ZWL!z?-j+{AnZFKPV|JKHzYdX%%Wz?YTs!t` zb&Zv@kM9H{xxsE?g0F=d91rKizojRjZ4~bZ#E%Rs4}s@@9g8IS9vHEdDAX@x-tclP^&!OMHKz@thrKYOzl8DKRfPcr zXxc~54{B|))||K&-4&N<*VR8VAc1K;VZBi)>Dz8U6a0E#&zDyPEcyb7@TMb9XvAn% z_pYnb2^69?rLJNjC;+}gip`ank;0agU3RVlG{btqEY&4xesMe^$3Oy!(%YYZaM(q|Ta{b#DT?GJfnqC}CP&@U_4Mw%4hr*3OnQ$<(0UeuCwBY?nvHB(!B zF%=PLpi;IZD82*EQwAe>_3jdXh#^Y41~Y&4iNgop7|v%FQm3#jMKt~J6+(w5Iu)AL z{n*`)`#0~YV%Qv@&#~ZDGFHFRSxDEq;6wW5_Y<+2E}P2m+QPcyg#s2t zF&1uiayTP!dQ#`8Mbi`Qbed4r>_?$T`{M;d9GSzJDjT{ui@w&?dK?v~(1v?|6IdN9 zO^VnzgTi~7Rd$()^on9WW+dERzs0-l>UsdIS0oP$)U65*{~}_{S`1`^ads7;eaVih z|6>H19AI(ErGB-hs&sGy6S~rgA|03b>Uc9Ev|;ADB$@^Kj~Bp0dw45j?7_~X`kO2q z=(j4!;_Y@lz_Yx6)qj}ys=}SA3x31c?^^anRpy*9Kg9R-um{~nw}N<_*I5nPvX4b4 zRlf;9s1CP4@IF!C*sKP}`+=7*aT0Fo$45{mfOw07zlTfr-oh#kGzV~53cwK-bbo#S zur&N(cv@i1l3Nn(?cm;BGR@ICRU%KEAfo3FaHJbJoQl0&;_unI`Welpq0kBOzZ17b ze=kP;_QXiQIQjGD;VvmeMmlnel+~l5XUXke{{Xkp@j3~!=csYT-ec%sAZqidE|P-` z62%3ZWJXKf(!+BEelrKDSx-nC`Et)RN{z11d3Nuv;)pgXW^Ko`gQf9-r_jkmv&$Q= z07=s5V5eKw`dyhA0vQQD=btrp*23$-DO*3$!j1TI)x)eeUy9ec z9z7u$ARBcg^9G5Uh_gA5V%Pa2!`P~&#w9ko1Jg??jDC`ixr1hDrfYqbDD$wg z$A(m!=Nq3splV-DSNrlk>3xW@K&%%mQ$7i(Ecs0c+_J2#G?S~8M(qNKTsqe4>Q_uT zyusR=3^yCUyeyqXO~W|3+%%qasMgrRktMoRTZjy%A&bd)`?34j3ctPn}56 zFo-w~k}L_5zw*R2O;1Z@JQDkSOU(0|>&Hy7Opf))WBIxky%Eu(>HVnFOzA`Vz%y^` zS>^fxo_OtuXzX|M_Fan`W-YxP;b@Q!wCJTrpm~f~F{*KNgQ*q|ZC}r3k<0iRDRDZ%2i7k(N|u zAVWJ>*XYz+e3gV@Eq5 z^YSfBqttYl`9{Haw+vHmyl_Ea13B& zlL3x^)6p>*<`bz0Ds9yU9vmcNY~ij^+NcxXpz!biES-b`KMGPZ0-7$p5MN(lmj;?o zEX3kjP1ly>2@q#!o*$uON<8qY_Vp8~Mn}Wb*t*{tilh(}Y<=qPj3|2;+E61tiHW^$ z^yg>qP-GOJL~>o7{BuO+k!k)?4@X^vZ={G3nvcJ@+w8WZpg^&2fwN!k$Sz{~iDLl< zjcWS(eR~BQcd{&_{6qH1KLp0o#oU(_!gmSz?@5lQt0nJC|NQo3VcimXdiMHq1C5yppK$;nG5tu&^oEd<@e3OkHuDXprb+R_R|(y=~6E(Hb%wiAlRLp0^Nl zTUP$h)d=anE!n}+P(wW!JD?Bt%nVU{rut%G%d_w^M$e}VXZl9*DDVUm?ttv$@Hef3 zv#&xP0bN8ywW8(6RQ$p2sl(O+=`l04%L+p~JI=lE^>1WV@UySIA#qg;xuIB>(aQVgm$7eJWB=_r^nr?5hJd#uP51}~!|71}}GXClF zOpZC3p56n|H|RiK;-vFJWcp!P569<&Pq?>Vr$s64?)4Z021y2ZPxzOfl569&;h%l^ zzk#I={{ldD!4v=U7o`7x6dXw;{<|K6RQ`{^WA`U84F7!iqyNVu4we5O`MvP0|NoZ% zuZ^Y37|*{`@j3pVoNgTRCJLx8M^Mw>KG4ti%v>c3%Nc7B9XLJa=6gRtXp#tjEh`GMR zui#;6KtYAfuNS^wAt=H!&*bka_~G!kSJ&6{TJQul_~mxle-$bDv4a^+vt7{mIpbJO zUn~SMKV2q)|2OE7iG@WC+3g{y>xfcD8@QR)cX_t|_qgGSyV)*)dOa_3AA!W`zeP^w zCrrj)+q+E!VgHXc{r}4wM1J2E^Pho&NFo86P<~)wKxa;|M6+mTXU7;1>-YbN-hV!< ztf~S!gH&JeTS=IZ#{SiB`@dU}|H$0KJHNH20>QCdph2S?AulVN3SfxvZsUl|=;#*! zAnHw60nodkLtU81P(mUIlF!ei3il2npXFg=W7`GY<3vsNTge3l1yP_u_O!VRXlBBAhLk0uX-C+a579x} za{Gi+xZ8iWO;iq!OcoUM`qrx54#zw5+@6fV`z%I+&92xEdQ_parepVwabf00KQRTV z>=G3W?GdsU94_hH47%I5>F-wP(~-UOO5~K3azJUW9xo=-U)!?L*B2S+meOtTrkMhj zLgv?=l8XPfoUg!cLUA5T5j(A6b=?B2>*kIrbqI^pWl+mSkbm#{{{%ZcS;of4pR>_M zHpf54m&vW~r_v65XEq_l8qX+v{}EX~*$)p~0cy@ro{ErxCDFU@&alBtfI|KLdFz^uz!NlmZ0@~96~yY=RGQl z8k3Sz0=i^1z%s(sxVLaM0{!F#fOx1huMU7tL#HjaN3&)D3DTAwzy(x-aVx#GKUn0c zpVVPVyA{-XvS4UzoCkD`*6BTPt@;7@u`Qd9jfKS<$zLxez(8fT*{y*rudIymb-&xi zvY$u}_RHtbgUu~WOaf7DK?5o1#?2ma;dMI7)O1|-DJ|Kf_qde!XMv=E&B(rD%I7Zb z2)ciOis7&PcW@{kfC-=P2LAbvi{mT3Sx{7j zuGaMyv`(8&7Jknx^<)_LBvTAuV*yCm1}TqIB@Y}d%@EMn9UKsA;tHCr;YzeRXC}Ix&lV&65}@(#7es z6#jUIPWE@fu%+#HnV=CHAX6p!Hvk6aP07@5{fvKREjn#9t;$L*kEW7(&T*$IrR9xV zyV@p$YS)Glu#22v?f|3#zVm>@SP*U_McZ~V-`&M(uIZc5md#-pSPga^I7-3NQ5cU{ zBUL~%Ke$~wxkPkuF=&(j-+_#W%cn73UYI*3K-U+D?-_zQ@}AIg;p$JtkZyBwzr2E8 z@T$|ZM|6O;8yLQRJCub)Jz%B)TEcol-XJ}(cw~OQ5G-%+Aq8dSTofWllJt)OEZEec zm@h2q)?XB!XE*MSH!!^!rz9z!IMPZM)}lIVU1CNIJN@J9hb07al~#3X4E}-d>g^f`~U%D2x(P zftR&+hC0|kd-m?ybF&bgXD}Hn%g0VtZFpf%bLGXKg)Us3l!+}^=p7@q(_fxs7JN&$pZMPPq{q2Gv)Gfit|i&ZdLoTZpY<@j-|4wB4pe=S`IL? z)lsrBfBox@9CfcrHgpHa)&7WI!EjyUQM2eXq$hCE5=9Bu-+$dc`Wy zP*8{h#~6TK6c!ZMivz*Kajm;`t?Z(Lzxf5lf$a9j18gE1@q3?-cE=VMiw*js^9l+i zP%sGKa-zDEb2BsGT)E1;qzCj5tv?b122 zVHC*T>LCY;gc9Il{$Q$Gn>0IUP;Qff3#(2{;M6d`B{DT@7c$Zdo-Eeb#p}=3yF7-~ z9PC-RA1CB)hPjo@H*b_JQ|XoTwI3LRkx~VA(f!tWN~~jMS_{j^>tBIC=%_n_T;g^1 zFa{c$QJXKi^GX0^XH1m6ogG|~&Z>ls!@FVu%7il!S zo1mx6FB7YO);0it0fTDLH!=!0ukVXCHa<7~{q=-uVUF&hExtK6-47{mHAo3yA;3XW zN^$_W{YDm>TuAFtP=HLT#r<9jdV@4DYInyTcSGfq05wJW#WRRec!>L0^8EvmY^HXk zG0bHWxCw%sGPARVfslG|?3;NYwX9H}m>hW9P2{BD4{h>dSUYB z;?OAO*3~&R3Yvj-4V@v@PNR-M9OIjvUnOpESWjL~G9KSMAgu`cwPoS6rlrYU&24Wz z{VuA0{>;u5sCJKoB?KCefJUK=%lW>PV}8XP_BH?n0@jqg`g&(q@xRkk+;uj`00{$5 z>i>(lw~UIa{lbTbP>`1HkPzu^0RfQ|1W6r08b-PsM7kTKL<#8zX%Q)rj-e50hVJHn zkI(b`{_n^4!@JhYT7GfnoH^%?eeZqkYwrtw2KU|^d*tHkD(~G74y`zWi|N0P26fHE zCOGEC@O}5t1cil*i;9%M3_Dy?f|X2k(Wp-S@22kI2WquyW6T{yXrF=igY@o#Y1dFtEF|e_kF@m>iYb`LI90q)A^D z*K6L>x-hX6p3=qqYQOk>Kf3j41ZiPQ%i{+%5vPK1I6P#Vqyg0AgmV38Z-0Um%H@S+ zdeiv-tK!$g4=gQmX&B!%0wQzBwIH(yRaK2biudKdYX#hw$PfjfOL)-3sEFHb30j%( z4@&UoT}Iz=-Uos8Ljn3c(8FZ)m}NIFbqj_#X`%Jnj^994S-s_FrTtgkJ~m_%fv5#b zv{biJrtQ4nuB!-FVMAbkitq(+I;RI9jd_`j-04=1zWqq0<1>ae%QzG~L8S z(-j=>D_hFXyRj@CxxT`hHNmzl149Rge*%LHVlMqkAgS_4*5q+*|uQE6yyjE z5ooNR`@vikhL@izvaL-?L%;`&Y(~g4(9#U4lngdKQ2%{tXeiYhVvrv>R}R#Kz14rd zRPly_ms)b&Z1Cw1li=2br-gTQ$yep0%C6D>{RIzmjxWd?eEa54%BfGx+pa@NBeOV> zB;@fsIQ?yid{|LY(NBH&pHyf3b%m2!RX98&0*)61`t>3&_m^gCq3sFM)TQ{ZHl$68=B5L_PQ4w}bdU_#Xg6 z{67HZqr(3Hn5X{(U|9bz02B8=0OtSgEj32d5vcwWquB`5Sc!j6uYdpV(4h=l`ryCQ zS;NHSbL~WHIzr4};?VQ|`AItv_w`FgQ2KitM{fLb!BzM#D15}q&Appa!wq+VoPQ%f z)Bt@i?8*@T_m)qL6 zy5}m=6YK@aG|l>m5QOOB=PvaA2kn<{$>%`@Q@g*G`2?9ndno+vC<}Z zXD`6ASzGH*?U-tj=Ir{(gmU=+?bZL8I zV}G?Vy7!=#i$3As6(OySjvC+rc!`+@2M64JeSK}9-20UiG=b^-+IQ*nvR?HI%ngkA zN+TCN)qmG?fzM1z=hzBuzAuKTWl5YzP$w}dVxgk~P6@Umf`!_^s0_D1Fi=dR++L;YkXh6e$~lqnMVP%!H}!5P#rlR$*EemT zcNms2BsfhgevFfKjhyM~;ng>p9WPE=3m)hi^Ek18Xl z3gYA3F6n%Wh2eKV2iHHbf8W|33>$rE^B|X9f*?C6<$FWV!|B5TTS%(RypCzD<%V|x z1G>Qp{X3*Ii}e1FsPNHgEJ!X-azqk`QGE+5ffl=Z2$QXIU9tla}%rM_A6KvO-g(=4P_Y8tyPz%87UWj zXSXdG2qggLAH}^z3MVoFTDkP=6q)vr9ymv`qg|?U1@u#vp-JXcXM&7OKuXPVdys@k{mY&rQrJPve_^rKA;;Je# zdHEP9HN|7@Y6Ih5UeL!e#2xvnvDCh!cL^90az5^h09lx{D#El8!op_LBXa+YDh75a z=1&9ixN9+1D9+EjvT0vQ7{Gg{R$2bN6lvX;`0is-b}ML^Ihwo6%RYH0{n2RBNZ|yr zI9#!ZSac2!x~}px$di>q__-VUHF9Br3G53adKj5&6TMUt4+I|S{>WRJ+RGU znLO%eYxSe2lB3m!Ub>r_CmLLICqy4WmQlRk$hq$QS(hSvHsa80KvA96&J0$U^bWY{ z=>X`i)`>N3qnRoBRw7zOwc(f9iZcf4({FcWs|=yBkKGt(Xo6Y}-rHIff;Wf6`n(U6 zjytf09kS`xDpnyhT7n%SUoOTiWj+a^SO2jE@4(7zV)fejPRyO#e@*3kZ0cErJVatK zzd!ki>5&bcXAu==KG*PQb%<2435u+Lcker-YsyH$_4lW(sYA^TmfQ%W)6w(5w-_Tl z@^os~R=M`okbB0k`d*cz3`m{@4M1bBJK%*O@15@>@2uBucPbsXXZvbLb7+du%Asmd zkoMVXIEAD2Pkwa@|Bbid3=Gis5dZhyHKS`(kKo379wd&WRD~0ih=e$}f`MX{P^}g} z%KHpRh*;$iM@ z&nuidBXP9ue$-WAVA@LIMWkBdK*$e=pJ&_Us@PzOMyyQE5XY;@D_&<{8&Lug#^LU& z)dWJK3(LuMBGir_VEjj47xjf^PCKTtv*}6V(A&J_VVdzX$f;h_bm!}fMER%-#X{SS zQ%RSxX2e8~OQ;cl!nH`(Xw z%#B#LKrA6Om}{H+EIK|_{w+15(>adleE2VtyAY!H7lQ?SQ>i|fC`KPHEDHvy*qmq| z5TM86P87jKPR`>aJXrdY#vh0fMHVdnepHa*C;t6J85p)sWBRJ6TD;sj26O=PjcG!ex|u_W1x- zl8AT$g~XrNdW!Y*{u$WUi?E>9E3veKBX(1|^@TI@7dH?LNYKvbOvzn^H0`ulJquju zJIFy`?dCCg(|Pd8{3ps_V9Hd8{!XIarN*ZCw6c?q+;n44bEt|tHTNOw~N zukJi6X_d5dc{90*z*gLTlUZSW@1BPnI@ar|_K(Qn*ZI|B^c2G5qR7q9LXYm>e2iay zX6t6^8#l1h!T*zH{3V4}vUpLMBjydna=m%Hb1`-RH^|wRrzK#~8ULOK#vLqOzV?q` zzd`ddJ{;ia+<|P|-;^pJB9jf13IOc8gP-l0tsZ%Kt7J+YAL9tSdtNPl$sdV3<$K0l z;*Ubmt%Ks7%Q(+dOefE6RjE~m$BdU68JoU>FZgFp~ z+u#;Di$*SpbZ*h8~Dp@>ffoIgF(z7W3$LQ}MeEGMtp{?uqg?>g%D=^(+1GS-f zNt#AXus~6l=IUk(J|0FB`wf!5djc7K8jtd(Tq0-Y@6p#`h5}(ZGlE~aRJp!l8{~9| zENiTiJJw08FeR6scuie*kd0S#`&xl9XRPsFfh-xrZlg2`139|-uXudS5ZiU6xy}cE zyz%Mq^Mw`0Jo9>u$yc%J1k#dUMc%rsZh?oQQ?%)I2-tn9U=1OT|2XvsJi}AA-czCM zcl?CYDI}2CM&vs&Ec4Q_^%_4eT-wu{O8@r%&$>BPcQ{Na5f6ysY zG0Bq{kF{O5bB%coh7@;}`3Bz%VfxEBIZST4J>zC+8T&eP!T_p_ z4l>+k9JA$>B$Z709D2uV`Bs>xSg|3F0|w;7p|{0ysNa1(WQ2J>f_k8xKK&GJ3dG9J(y=QiylYC)jl<8IwU4MaAL2fZwF9tqtP5Zn>6TYb`JR+aJucsgO0V!wVnl7;zQUlGhs*xXF zTP2mnQj>zapJQAv{@h4r!@%03J-j)FrYC-b5Uw3iI5v}{abrRbA`2g-IGsnzJ%?`j zoQNy#M%;mryI4KgUrKFE;?flHym2r0`q+7AXc7KR>x>JjWaTcQaTa_*s z6`_alii)?)Odnh%yn^&R>;>7LzEGeWqGB=sfzZdqR_{;du*dw99&w@A5TI)D%hVV` zzCtcqZA~aTTJSqEHy?&7U3u}*Bn!x}9BDs1@8g3ISAh4j8(>eU7i(|11^wL9O4$UP zVQ3yK!z(`qR`{1+#|=rof~X6~T(6N_7_;7lxt;|B6E8L3{S*TBZKjgp4$*t(}?=GlJi8?c-^I%hh*|ZwiTp(KYi8CVKwTh8g?m za)MApS|ZAY7V7f!>QPr$kFbXIiD^!5Dj=7869}X)W&RmU?sGq5#D;`{ZRcps{wGr& z$Zja`MNct(CfOf2)J`D)w?H+Ar~G_HBSoJ$mW;T@m~tZM;oYrUk-U?crMpHR!&tDv z_x%Y(LA!^|y~Yr0O4p+Dl-~c7t9^l;fmNX>;coNamBFp4IGdvegw!c1DlfXTA{s@g z4=r9OHq80O=A!h}qBq{{C*cRPOivP><3#{57CHJUp}ETINA}P4znH;TnI$--(R+44Jkf}Xtl4?z`6lq)VOB%dYVRHG3Cg*Y=%+~=^QHvLZ~TzyT^ zd4U7sAMruyVLJ-*Y7Vdi-f88m)Qj}7%G$*yk5%CGfKjfQ@J;<8^3u4<4FIgOvkT(T zqxcJ?LJf<49(?7^aLX0A?27N$hnG1Bl$kO0vYYU_2|@>6Pb%#5zrsJvSBG z2jatKpD7`%SpC(?L{okh+jIu|wPraPfYl&fM&4%tE_*s)wxZEY*Y(!YMLQhDpIQuz zlu4w=gLw~Jmo0uw$i_?XuI^e7W&}*@k>Yth0m^|2)5e)5%?{$PA&_1LOUFv9L&>IQ zPO24%beS$pr<+RVyQ49rC$gC9^h-XUE44+W_Z0PWklCvc7gGR*fU|2Te2eXbxY*5VaNGfyDChe-`j{6H<;qQIQRE z#x-!cgB)92W@dREXqxn?QK7a)BPrNDh(FwqKy{z??5Tb-DO##I;Re`&f<=ccd{2oW zv3SQRDL=Mp69w_j8u7rlBM9|0-g1F}&|gLQcS~|!CnTQ)wvBZ{iSB<320*E7H+zzNH&sUj{ z#zHYo(jz_`RsuS8(silfUo|6kd!I8yV)Z(nAQE2u!k4gSqXDvs^caM*UKN}Pfr{j^ zcTB^O$K*SGY6P_=5^bC^Hh0hEHp%*P2v7vW+uL((J6AR~uE&ob&xePH17#>1$~ni5 zl+kywHrMfzuG9si`j?)6Q9bMr0}^?aflm}wSnv8%7qvCZ1&p?*uAx8He`w{zvZJ;} zAIsVC{8dRvRCnAXi$@rM@|^a9t#by__`|ACp0ksQ0Q1-3<@Ii2N{XLV=^RI)DtcgN z4|^deG1g{%9h;`rd`hEIoj-N zE-M&4LC<Gaz@_NcGDZ>~BF5`hzXuzTLIm;e`(!+7(7oRNa48cl4TPY5D?@_M7cf5iBP z1yaS}F6qj%?qDi$<$&)0R(bsz2%OirW1$9oYgT6ktSnP(w<1l+jFxHnjAE6vXUW$IITC;_z zfQF>}(P=1s#|HygOPoX726kYcyA|;Kh*9WuBD*PkxTD)Ib^3g!3eBg(t^sJ7%?-ap z%vQj3lHEqJvVU5O3Z6hQY&h~@lA>K9$8;SL%5drZVw$5bX4%z%*ND`7X^ zeDit?GdJc~k=OUkxSR~q)9^JgCvw%ifDn?}s6NEys$}9P{rK~{>?dHtK{S~pBG1S` zwN1SHOx&lHigtZlwfmZn7VqTf$X!`UX_~s*%8A+{PJUE1yH=Z-IZi56oaiopVq#*? z?&;|(m={g=x$cOh;;xW@z>#^QExk>KNSk`{$Tw`rIron$W5}HjcjMht3cJsMXtx7? z&c}zrT7)&pDlT{4d6u1V(MU}##;q%oqF5$p2+Qgx(aYwp)Xs_N&UkI*5wK#VfDO_d z=|c}~1Tsq^Cc^0XKhBILxAuC01NGXf`P$Hg zdl1dP*;tDTrb7jwQ2N^jjyl|wuCPw4^H+;!`%5-REId9w-c8{6d3!E60L7yZU(gdw zQedKcPc;k2lB%%Z1sK?et82dl3Mz|Gjafq=T`WpwXIvSlnO^3l`iNS>;m?mhuY=wt z=)rWr=z-0h|9S%D>t3hl&|W+8IZ$6o42fcXq!)>fTlz7N#W2sE0TM|Or%$2<84MeD zk`5NI0;G)+mPF_tb?5vxotW+i?I=RHWQcK+NzkNs00^6E;68}zkE)|hy1O=dWaE|K z%yu6?-nS_QAkH$b%eUfB!hV#IsFSMd@o zw-+(pqeM7&2cyZB_s8xAwtGY`am3f?ZeTO}q$ID-X5}MLAdX+^YBX+ZT%(ybzNHO1 zzQ&FH;NEV;N_;WZ>~VNetxE{*9Io=qerWg9PV?ejy8&#wZ(@3&xV-gZNiN&@>TF-A z+d-SkWM_CR@OvG2WI*>@TzO=G0g07`l{aWol{M+4Bxx(fN8@J>hhIyLSLX1GmfK+QUIITNEhdMH0Zzz4{q$%$g;TJOB}74k44*=Y^O3`oJE$lNJv}{Q z-43)Llz1nm*StJE4FKK;X%Hx=dY-9F=j5}+jc3E%`26duP15zXQrP!s8Y;0;lFRI9 z5x3`NnZ8vOkGCebeu`bIA}Ha90p5uUJd=QUckBRun;!ZpI4~udNbPAGuQ{}_@d(hz zJefr!IL)EK0Z#}Qd{M4dF<=6 zJ?or|W{RvlqbDhEb(9H(q4QDt`bm%3*(pqn@l zO$VRtj8v#&ZC=vFbJ=18@j9spEs|(qv=HaF>ouz{2XMr1wiX|Y7GWyjv>PpMY;KN# zEV7oNVL6hu)7mNtXGVcw{o-DrQvdzSezLM|f(w;z z@M|@g&7mXIQ2d!iNJ!1+OPdzIRr@k!v`-JnIV9R{j>~IZ5EJ{i*Xava^4!gP??4jX zQI`brF;vYL5T%@LthizW;EU@;w&|_av<&w8Cc(6a9wW1p=Ur34C>Y|h8y~U z6$G3e=ilE@Kx1Q$XId!Ixd0Tgy)y4iEF0#j7Em3B^awFJ#qiz7Q)+yeg#Ax`cPmq0 zw3mvOcK)@2L90@{aBqPpXxeQ&{1}ry!q$O8&Jm1!D`Ir5NPRZ8KPG_b#@TEW8 z$1xw6j9&<|Z9O+fGFCX@*|Q9mk#F>P{hLQcMnCt%%)6xSe{&vBj?U|ho(a-yj9qFL zNRrVI1ZSi#Y#XsZ82M5|qe^7%7$VJa+MqK1*nXA2#Z|!Z-aa&{bc*$!bT9#q`2={Q z8X&Mz;Rm=;qmDL!##Ew5M0(fva%MpraP0KU0DU@^i;-E$r6+F0dr zv6teApN#LRYbYnq-ObO{=svD>at0e`-xCbMh}|IVcmRJkvrDPU7+I;j;>FPxFAbeo zsoEfIx<)3e0ho#153sUfzcc*XCv$UiVD*(&L0YBdxu({uD>wUTS9`&nel8gRm^XrA|CV>aJ)K1>( zk#*GbnFm8j@Y{2f4^R?Xg8Ay(f_ov+^<&V2XJoU*K?ySL&hW55ay%kFFSWFWIWa%3 zqYt1nMr#e)ztl|f*5(<9`S>2a^L0Bio^&b8)XeWnqXoN4qELSTH&|fLh1HgG?XK*! zo25FMEJG$07PHS_*lFeP00q|5W+6=Htcy)CJdYkgkHPZ z{raX#xqWr%fuQU!D;lj1sXjd(PuAWXzQa!Rft>TYasb$;aT6$wC%-*w2g#=)uvq7T zp%{n5*Bak{1?)Z=n$I2KfJX#6M?1T@Apk}1%^KIMIu*cL0{^RNVNn41b4-c|c)1SX z!~rrrxkC&PL6y(co*f_0D=RD8M*yBSY^ur(Fj9PV=Xzi7f_qSslasg2hm){(rHJiL zRU|(w?Z-xzIOBsS*iM*$p&c!}k1}0uoC&$NyK-|U_7jlqZJF>c6z?hGi zulXp$EjbZ8Rx5edU49%`pcX{KA8+7Y@}v~|6aI_+Y`o3!`hm@;YqX`sjC8QQCuR}j zHlB;zc3w9 zmVUrD`*yP8x4~o2xu45fPIE0{UJeSkkFopQ)?Q8&I&GMgDaS9RLG=o5k4jyp52kiX zg40+z&;!#fO2?5CH$J{{ai%N%P%(m(6AK%oTSnH(S*;z=_j~{~e!9^GHn|@VfTE?X zz4h6mPCA%%Df?aO4xBHp3BTaO2!GZPjAmgonKx+POMO=*{9A1HaH1!IA z332h+x*iQV-m>sIx=HJJ(`{Gy>SLSLwNrm#M&a~`(sd1Ubta3H-sBD?&J9>X*I!{e zaz54Uh@)%KdSHy=0@jd2Tl$TGM)?f{E588rs$rrq(LTSv$qgxBaB|(APD+L`@i8=j zm6r3(25|7e^n}5cYvaBU0!A@#nM6jMlFU=4~W7 ziwPcNbkR`q^TkY5()}lMO(DUAcom=U$Qw-_wKYed=nIlIKh?#IQPo zfz;lEXw?(Ld8$xK93k-aC1Lii-Q)A&h-o+0gVP|x9lD`fr#`})`5b9ghu^jbni2;o zAjXIIr-K9O{+xe&P0M8|D5A)EyxCZDmOU}hJG0aNth@;>=pG@6zH-eUGJbKwcyS&D zb2f}@XWp>)*`_|V7qVCIbFd6)b-V|S0S$m+V;MRn2y=_9632(_th3DUpaUW5MyHx>VGU!8xWE~=p1W;QrW;VXRiKLSvZN=9m zJaCUFL%Tmdpy#w;tGB2@%g8!HSg6MAEw`*`kj_ZQL$$1{qQXKer?QClqrk&x+a;=(kBL-p}|*ur+1I)o7y5V=j&Xrp488!PSU;~ zv2M&DHQClWf^(Rs4lNPRrY8bVpOA(>5_T4+aK6X~PflB%HM=p4zB8F9Jm_ahx zrXTSjnAQ2`1OLpE*!k(!UMzBx+&8z;wYM_}e&NK1_;lAU3`gVKjc#Y?cwV%zb;5_j zPVDMO9{c{}(8ibc-*V+NGv&aHgu-Su>OUHtyp!uH9dJSE%r7dkzM`w|{^PQ)3OLd` zl<)MY+FsZk_!C(PkY{}5s0YMsBsCxKSrO+%yQde3@@Lf9fMpBRG5Bt3C*W{Innl(y z;EbExeQyE{C5pOJOjN)PILQdmL_6$Dd380vt%$Cc7Ky`S(`??=1?A}sgc#3t4c8a- zsJ@XuZuqz?#MTX*k6aP2BS`({T59BDY7FlS{%X9Ou%2^=2Ho_J22>2~6f4V$6G9Cn zNO9)DSJEfseJ7!sD{?I<_fwDBpZtffDFhfXl}>1l)pZPuGo7u?I) zyPlD_SYaf1N{+af$a5={!1*n?Li0|0Z;aa3Ub=S+V6wj-M;?ES&!QvM^j-NUXztP1 z11Ij&#En4g!68^56m_OQ6j%EgssHl?}em?t!d(jvKw8a>eJlS+huSc$OJ=mTnjE~YwP)A+c!IPcUd93#r6 zJlg<7{0xZqK1r?tn*dEAz3t7&^AN`D;B@!8J$eH#>#>}da?!oL4X^sTlImJv2#{)w+mrzc_4J1P1i(TuQ_0&=zuiIImxvnFuUxi zFB?-XVQ&6@9Xk0i7$;#*Vwt$SQXwvc>p_LnP=2St=(Bfn0%^JvNC}` zIlWac$IaE$)(r1pv?nw^y@$WOM%R8vWZ>~5iBdn%&GB*siVab5UdZTSt`B=}QQqK8 zIzK-7p{{Xc#d9|!)TAx^zPJ2g@i&YO)`#zAV984L*)#B$uke-`d<8dITIixxYBpQKCrc_oG}Oh2g`>e7sNI#NWod z5?~4S8fFq(XBs{<33A#X$BEy9r#z_x>Kgh20+1kc4xDsq`A&WPEz&cn_R6|fy?kqd zkMEVtk4h>{-HT097T;7O{|S(Pv~H8GXKq0=v{)@~gsmk%-MM7l2${L4X}!jpn*doR z5@HO-hbyYg*kaUxaVrV4QBwAJ*G`{(fY$S+iBcab-UpVs$d1!oGO66)J2+kM7T!11}_2R3FDZ!sYK78+w?7Vv`r3Irf8 zWSdOUn%qaDfM&EUG#u0y>sOg7em}L4dh(HH?4_8Z+q9~$FURSe9#!S8QAu9st}pTV zyz@-djuiJbe#%a(lPtsaK}(NWZ{^phSh19xZ(QkI#g4B}eI$g5M^tc(ke z08v_JbJigL=mO6T3V9W5gSC<%AO1#*2tZqE3v5$~{|;sq);!dir+ zq|8gI$7y${@b01ps7_3O>2m`mtZ$z?i9!hS>uUIn|Ljff?+!Kyt{~<$>ZOI@&=k0) z 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%0Dl0)fVK4U6MUl5WZb0& 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 DG-OT) literal 0 HcmV?d00001 diff --git a/docs/technical-manual/waa-nodes.png b/docs/technical-manual/waa-nodes.png new file mode 100644 index 0000000000000000000000000000000000000000..cece1aa91ad50241c23557b6d2acda4787dbbb5d GIT binary patch literal 50450 zcmeFZcUY54*FK7(vaP6~2#BDdi1ZSWPz6NkAiaxpLg=A43q`7;f^-DwCDc#@HbCh; zl2D`*LXj?^ok?K7@3;5&JLmj;uIuDdA@fXGv(}n5tK5$-)m0yyJ;iW}f`a0#!b7=7 z6ckhp6ck5#{yG6#aOe&e3W{S@wpw~_dMe6Lb4NIjnT6vMOCC?S6L?NRAui?VWM&St zbYpp9X>IEuabu~r;RcJXg~SbAhzg&IldPqU?L%)DOHFT8Epu;}xv0erDaljfo=`9W z+|tdA#S?Dt;0pDWxIvy53VtUw^WI<~cX5MB+|W}|XOVSuv1EbpKzR6WNS)n_i;9Z!@(J(?2ylZQ+^${@Zf2g`4z8@E zDGuh4vvf6gv2}8@b#!1M&1v?;5#c6r;|6%nayYr0t<`^LcW^!2K41hd=?O1C4~864`+w7&^yGhacd~JGb9A+Fbo$Q*{;|Y=9{;a_fPMexA)aPV|EJC>D*w;j;qZTK z!`1D+J7B~iQ~yEJ-wU{Ec{y3~KC*OmM7Wq+-gmcjaAPHNg2Wf70^H2nQrXhM+RaAd zhLEuEzn=7hkXgk<~)dl|hNbkSTz$hUhUjF|+)Bis|Bk>!m zZtH1juP0{te9|HuPMex~AR0Uind z>yd)2th$S%m90H^=KAP?42!~jSz&%rVPS3oo`Y$C@StF2CW|3$nwxx@cBs$kqf8!!RD zmhk?^n1C<;F-MjTzy`ShW1|s(umA?_sVxx8{}KlU#qHOK*Q1=zHLf&E;cT9ytMeFf zMKVe;*g0p|+xGP&aC~jwSd7C)S8fjXxa1EN-FBY991l2B*;4UHPLXFSq3RxT1_Jq! z((9Vixs|dip1gXsI9O0o2k9y+tlK5yk93tR{Z%gYBs=9C(Mj%-aq?d$DErDQ8M&FDE)+I@1f&@yOhs;V?TeZ`e|^=6cugM-eKN>4iI^&L-Vfp#`Y@iY9l z%0i9=)4#n0Ss5PP5}drL`&vKqMUwzW5A7^n#qEBYi*M4|Ei7)w@9yJtB}4^`au^?WETMP7~lxz)Nu6MkWO7*xlq|68+)j*YjOpnV?fYBKY{FgAPxzcZxHF7TMHpv_FTwIn#7U_>p&s7^=47d7@MbmAPe!NIaAg;jFtt z;b9NEjNp;s{QdlOcg4F$)LiqScb+`{Ud!p7FGx2v8 z8uz!JYm8&?J4c}|4<TlaY8-lagK2%21=A=(_SE672e~3t%w{iWe{U ztRMcxa3n4Ag78tGS#n?mVD&9x>U9>IV}ZgCe*OhmdR}852tfM%Y=DE{GkL&URubRj zN~pjOv-JJyNH7CM3|JNny%Qz@TK}Ti?S#v8{YByVtoQ=>em(ssC76h>dw=M}u}F%* zQ~xF+`GU|<3ZsK5Zv1Bo=nb1=6j%OD8iF+OZ@GVwHgMz~D@EF!FMvLZ7okt6!H+L; z`>x?6+7fTr1J#fYy}bciS(MkLDg)ClQ2aVg-iXY9Hloi8b{FXQZ`#h1rg(b(Uko}z zqWCE5zb2*xY@_IV@vlv-^y~hL_0GIKg|#(+Hh8;9s9GS{hnimDOQupKGjp4~Y?kyP^==34*_MaxfA7lvpgs5IVY|#> ze~W|m{HX)-vt$q3jSkuiGY{Jj)wn|IpdD#>*nTLy3T1M;n2gdInNF3+L-xER)9)U^ zG3i?q~iUK<0lj>%#ZL0f)-uey{_v zfhfYkfUAEGAalAnVqPrYfWW^bWq7*wY3bEa5 zyK2vX4n`fy0OCj9Gk0e00Z(HOWdJ$QAnt72!}dcNK;Q@cb2$#%4`l$cJZR6yAMiBx zPzDg=gZ9GQ!}dcNK(r6qk+z5JhcbYuklW!OI1bwn^&fKopgm)ZOuuyNp)p`EA*-mN zt<8aoUOqgu2khYJ$sv*sKpOn;;2$WkHtWIUs6*lpc%=N{Kt16h2Vy^9^?R8E8DcLU zDER^Xd_eNG=Fp(CwaRlRZDh}wIRoaCinRlfE zR=UOZMOSbE+K=$E3IWXH`V~SX26r{xAU+Dfugelsab!7^3ldYuR_6oc=W}~A<&!Q@ zJWhP4h@9D<%Ud`2Bvn7O<pBY8ZBpQj|J zMiQwa4$oo1-zGW08PXiOh?1J`G7rpvrHREpq+Wbw4d zvT=)fMGn{IF;dSk_*xxKiqe$p@%dqPwoy|JwQJ!l?>JJ6t$HJ)bVcRNoc&TR`@Wx8 zYIm#0h}o;tU0LpOT|XT+=fs9NcFks7v1p#hLpFmyV8W^0%}S{&v_V=I1QcP{7p5kQ zV+4`4l51*iw-65d_%Jx8;eGEPKab|GYqgcD`)Rr}Bgl!tue`mx-=U((%BD7Rej?#K ztplBp%!?MNtV|p~4U)+V)X}_lMm=1nTVvpdt-qy0*_Luz!i>7OHdv?UVP{~hu4{r` zQZC;2d{pR1<^{1rn$BCP_M8d{o=%#&XEu3m;J-dc=JF00V^;NC{0lYl4S7E|LXv-k zz0!y|hdYVx5lpMRoMk)%N%od?&Y$>!ic$2&cZenMS`fU8#I)ruw4dEc41>dv-L(n4 zT=3RiZ|)@mPO_p2W?fIVfe3OmE*k^M>L4)*r9KiVG5WTK!j!LruTWWr{gg zF5GU`>l3JVD9Op3NLEm>>b->XVDt~z#H8{s;?`=Ht)USO;S1I^%*L^WuG?xb-*B~@ z_>JK(?AXDvm(Gy0E0S=th3kK$NrpYu9QZ+aWYwaLb7j^bs(Q3k?YZ+oAKUce- z-olc!kTda4EAP)z=*RtmMabUb>0u7tuG{x%1F|y4>lC9qFPZWWS8m22XSIl5VjsM8 z7}{*|!JZOE`C;|`Bna3<+~+n{JxB1gc3Cyu=%NdUIsAef8@=zzY-^&f+EBk;bm#K$ z6gCCna`tYb6VSb2pA$wn-s<{EgHa%iqJU*3`OO0>R37?gg1)uBBHO2zLp@G+HRig9 z_nKi=r5s!_X0O})v6_2@B*a?Nuab>krfmSlJ5b1c>qPwA6)~CK1gkcS(jFx`Tp$DStcT#K}yvQ@9Q<1pE(T#RbbvAmRBRQLnRvqv|tfTY| zCf|cyX!&u|dKpjtb8q&pae42y!INEF2{&va3{mplQS<@s0)E$jht`Fk)`SXL`=^gL z|9JgrhHBmo9IDNjuLr&Zn6U3LyuHzOLMuOOaG0VT7zCi`e6NnFI5gCPy|1EmV8za)Rkc0~?*!~5WJoaW z<^9cGN3)y(6k+Sy9YUoKXOwomE>1Maev6^DAS%rLQR_AxnRAcPGS1o}^;L%aL3VOI zl^bqednyukPqJ!eGtx5#eVep9Pdr{CMqcYFDtX7qWpHh+}9 z(H;2FkDj)x(KV~nRckI*MpyA=ns;7v3R06D z-h1Bk!BbIom}4C`eLt=>Ho1<+D$CA|+JW!++ma*XMn^rSI@Q;L18f^sN(!)iI+Srnm(goJq?j!AGK5 zJX$+?!k)I${_N(}-qXXmd6>Jvc(*)HEJ!3({3s}naw@byMMoQ$D?!oWx1;cU#>ag- zVy)!<$UyW)>VTQpnEn#D#?Wpv$?V7ffV)@4F~?)LWB0l0uzLtAJM>KveN~-5hV2I4 z#ic5JPdjF9Zs-LPs|2o$iR4bWLFem%Ir}$Ccy5e;e!r|#N7lNIRUV$QIrIi`aEu?Z zK)e&mk^6D7joR)Mqsq46ZbuWbXw+w+^4f0B?CcEnjPEEV67ykaZw_UTuKnN_8(PD)#FyHGn*k=opYsO4TF+>}Wrw#Pe>>Ax z(jT=tEVg7*Upz@zO8C6o@zFo&>d0DnZ9z8%3p@oEmv28MRf5NJ*)uG8;mcpGzzk*R zY`Z-l1=2-ZgIF`V-(`{_vu*({q>Y7U9qh(WbbigU6HnLj&IoeFnJ9KKMU#ul*0s$Ww9G9cfatS|mAo4>D?nlDdqcqB7mo9vxdiz4h3Kah9ZIfcS|&#@m@z)?LDJTgy_trNL6V_?VC6^A8G_`0km5psjAAPHCc)TrxqdP4|U7*RfF zwLKb8P|^2(>6J73gJ+8e<8R`2VQ@r`L6-vq8tcd|G$i==PdJgIA;b=jlalz8&~a<0 z;e_mP?x#)I?UbJC-9(S~XkZYEGDmW@7-mk4jPNg+lC{0-%nj)Byz3=H#Ye-kx!vE= zkgrO{9lfa#-xmuBUiKjp8u`=S1?88Wl(R>ESLm2efW|{f`OY?12zB#lGfZvd!7Dh|$+q%XX?>FT<@WYdwh9e%33x#Kk?pAh6;8K|H znKpo)ZgyPi*UC0bmN!k-R?uil;_qLur?`he;ds5N)-$;KVhvTkK5wd*aK@k*O@|6` zd#CR=S2fs0ZX`7Ko(LMxD+=Jx=A^cA;}^^%n6}YpbKVY#?EiKxpnI)x2xBTau+tJ_ zRu6{iAN7Lvs zQNotnLR>dfu%a#jW6v|;Rn(&kPm86liR>MAXtbBicuBSRVQps9(X^JM5{{&{X@L8b zdvxitFjU$bOY+TctpeNHn(x()l8JnWQKp{9Mfm@6uLt!zjhEV<-&^~%VF?gKekzyJ(Sy(#br{#g~gNDjYbhVhgl7VXih>Tt4<1%4gl=owr%FuxMhd zr|N^9kYt5joBi;oMQrJz{y)X^D$_hOXJ@yYp0bS&4LoJDrg@%nWQ&M(s32C2VtCU- zvqb2n$Jb3>8Zsr;)1kF|(!MM~{DIpgb%swb>m9^*PD}IWvOZJ8)teG2E@dv0UG);v zuPkq;2nf%SgyU@1@@EXvBlwGf9{4)iGfI+)cP+(L2Y%omSs7rMJG8Inlf95xh9j54 z4Qi6E4!kz(f8H+t(hz0sE-re&=Om!sBL=Jeraw$?o>l^O<3Ul!686>VOeE`--2x8$r8(>5B$H$xQ}FcC zW&!VIAkGr@rOZ``xR*e}6``oGly*xuTQe*oaw?ay6(4PYN$S+)qlN9&d-a{u z0tszdspW-weUF9!Q4dvGC>4S?{V#FFKDg>1Ynrp|V$SP%h9Pf*Sh#;RXK1euT>RRY zw`(m5by1ZivEn^~=V#a7)=3d`!tw!eh0$fU7|H!&!Q6e@{qQ2#)di+L>uTj;tJ$Gk z255RWXXd%moD$|NUPGjLVm$?L4@jP<^>Z}vtCxLrjd5z?u#^;t!(bEpQQ!*$9#-8t zd)9042cd2Fq_IHTHt=di7IJbxh&2K8n;dv=?ZjCaWS;fflh;kkbko&nsh~Nj*LwPv zIPMcTr{F3Ts-E2YBHA5afFH3)PKp|0!2H?Gui0}_ZB+#>nF)w9e`WWby5D@oeWP(4 zYS`TUX38&NL>Aa>yan;u@=zRbVtAD4F-lxW4tyiwbKrrm3nF3X;j%3m)Hs^f?aw}T z8?*bZ>m8LK)C=bu866f?#MLqTz0esmV<;+3s-aO({LE=BMF=}926(L1_hfs+ zK1jk|fHU01YOzCTd#mczCw{BUmI)2n`^cjcjlWSTD|f-1Y2IW^7fP%OwunKj5O)n7 z8RUiTuQ7d%dE6hR9pfh8_gT-}SIbQ(UWEW+y*F5mvijI|`$DGL2edq*4d66Qq^!?w z2Ngt(FnIK5us-^w5!qw#_|SRpvJl?=h!O*C5b!BIAA49O7Sj<4U{lz6nu)9N1dDr;;W@EGnL!5JuF&o_y^UVU=i35P+n%TMPLw<((Tm zxxVm{(*a?#GxDH*ak+U=jWeb1)xe@YmmR!y7FlKKbE4fp#elvcKpTW}!)g$=69~Yt z3FJ_zswz97_|<|paQ}HMvy7R&StTfI_8NH!1MbRd1D452d%g}&dQlSbDx8-qjJ9Vh zX`mLH44vIV2Ev5NHRIH!OuasrijwyCUuHp;MaX}2xA?O>P$=NhBFWBa^t{A0L+)v^ z>L+wp61*TF+R4sdr z5_0WKmE?mPF(&veN{s)`wI%=S`AeN>B=xy^)DLa#xk{R%sp8lq(``Fe5X}O|Y0cw7 zr?&6FI&O5Wz_c36&OSN-pr zO>458DzT%N!}5!KN_`Ta$s;L#I#OuehBlqg9aTM6v&aI!9!iQD`??84mO@NlvU96& zk-jH~;DXN|Z_(n>s#Kcf&T7e=?I+F?Hcc$ewIFI)a$`Q%=S&cB>u5Ua8z1)|Q^#Ge z0JSeb_Bzvs-_PCd=Moh~?>}z3TR5u2v?TrutMll`-P!~NFYapmSwxAI%kG%beZ4-q z@a>|{ehyc@UmTq(QUXE|X>*A~_%DcE>419a7VGEpv=-T-KgE1(fV7Pa%quTciKll( z$9$|5=$%vZTOWydNL-o-@uP~wH2kqNmTfAnu*@x*d%$?YHe&6}tUS|+7&$h-=2wOw zn^8{{>{T)R8oijN5jOj+tP_s+1b1llTtUAa8|L9;g^z!`2ctd*)O-K4a-(19L#RWJ zY2}+>)ntZPmJgA(C4;Zg(?M!5=kHy!WvV<~A)UZTzQLcqw6EN&DxBec%x2v{*@IOx zaI#WRTR2gK_Z%!?e>gn#LU{#LP-G0+P{DB*B^hKVfkm+m|M}v#o8>gJcKqU+XHYuL z?CG?t3`mdN@aj(sSfO^XV*|482bMbI%YqaRh$=7$`pnRe=D|)}52aZ_ito6&ae@Bv zc!E~^x6dP3Bu`hlU2I#p`%urs%C0%9rbI!Zr#-}jl8HAI*oq>Jk_!rP zZ@wC)$W+t`&wAgCC%o@}FTd54UTd@zUUmH#wRhdGr=H)Oc$J5I^I^_tUhSMY@u$4BdFUBqI!TrL0j9qYzCL;dEr)ZGJL%lJ$+zSbQ z=rg3blE5Xl1*b2p_TSIq#k@{=Ds7hY(pII%0E@!oS$cQ*{XS#d#AS=|jeReXQ)p`< zV*~sk$6t;F$tzM=pl_$V)PLPbkH(ps9hmIZ@U*823L;1MRaS8{kMYU%dPC}+T*r%; z+o$OwD4(dSeAD*ngjO@0sDEn&kw;=nel#IY5pvn^+Fzw(R%r7K%?utDsT-X&a^$9E zGFS1NU2j9p@MD2Q;iwdbuxue(HCu3? z>>ap&Zr2rmwEsk%$Mf|Sahvn=*8&}ly;qCj2&uhMra7!7p*Z<3#Aowfo6-vE#$SY> zguUtF-S&hIn3$)EnzyKE!DJf&LY17gt*wMJIc+$sISt0@x}BLr)UlYA+J=o+^?v|~ zpa%5&z0^Rzh=Zy}8vHHgbGz#gs~z0Gt6pLL?SDPx5y-h{;!hiTJ4sxwh#1rB`tW0WDcD=O2`9Tf76keZN;Gmvjz&;oJiIYa?>6~m;GcP;$` z!GS9jiCFD2BkRHV>XUw@W@aOLeLW#mrmGbLISLZE4UlxV+Ld`zM|KvH za`RK3Xea>^qdX+D_o6_m%Q%i`ECGuc>@TG9W9H2~3!A98rTHBPN6dm$1<&?quUthf z=_W>*u_%zj4c)cv%4gJ3Vc&O%?R2q}sfG$YQVdbdKHjSs-t9yKtgpi0=%nxQ$gEB4 zhUCBMOn8PEpolySoh|yo#`_ejoXZc5jNY$Ec(?-jf}9XIunCNwAVsX>5wJ^dx($Z_ z?tqY#6xouppv)_wjePM(*NDpI8d3+h=n{5>6y0hXI-&a63kC!cij^>UM{>`R6J)cp z00U0pl-SahxOsX!eGVZZ(y)X^elC?aU&-Z^}k|l zgw77{#X7%tT~O|QVkJT^;O}gv2uc^!bwBYS5p4LN)6olEs{^8ezKTA*G2YO%7QnT%}7dxdi_=Di)lJ%O*58>Urp^SmP*2aNjqtrH}Pt5@0`b{DeEI6 zmh4DDkX?1(&jZB{rc#=@N}qmfy=*uds2E5mWJ#%}dT^T%XHx?vyrF6F!NwTe_mLyX z3lKF~&O#L?r-tH>U<&YOtC?UT>Alk@%e~5qQJN=INQ^!9X2{S_m1a0_qBuK5FOwpb zyT57k{Aa!ty-Q^uxAWqKBg*j>!p3pATnQBi_0GWQl1Jp&qbk;KZs|`PLQ03r?s$=| z5Fs=LB)oq8B3vWM zWTRo3af7UjBM#uYvmO5&5scwppzX%y3@1|0l+W)pb1yUuWTxzt8~57$7&hJKwS%UZ z?x%i;Y@;hBdv&Gz$$aTPwL;=Hb<4kt#B9n*?p%#bjlr2l%_iQePTymZmL#`bDyerJ zaXNPc_p*7_lRffY7TfM!(R4~dq^>=#>S0cw(bpmZ$K?v49@n-C^zSO!wKt84O}v5f ze8~^unz1Q~{C=-^MDXK3g3>cU=?hcQ$9M_Xs+n`5S}F_0R0&;+f7TGKl3X9D*^>~p zX^QEJheUdAl!7!vuxyb;f9zDQFj-H|M|GZ6lIb^Sj%h=bRQO@<96O_Hm)rir?6&_; zJ;xedy<%?eCa0TB$jG^CA1<(|6(rZi1XV0h*RO_?BD>DIt|Y*f`WaK!FMNTWT)9 zsu#B|_Oz4Z#_G%`;yN9Az_|dffDWCZJjNJ*EvZWPPlI7i%nA9>wNp=XA(^%AabQMap80BKPAl7l9Xd+3||S!#yU_aD$u`;U=q5UQ?Y%;rhg zpX>e{nxikgf!P6pA^(e?RXk&L*Snw^U$4%E%$r~utjrByt^$eYXjc~Zh=S|lH`Q8I zN2;z_aKnoSa=jVx<6qwLa|K-At6TDWk9FtPRD2ID$qJ#Y8&SH~5TVSZ!YguZ1Od;# zIdtGXy(awmzMcanN74QVNi+5?%|PO49=MVJC$N_?2NuKhJuIZ{49 zc@}0Pzm^=>D!YzDL(~ihi6M#F(7qXC2-+~aVsQb zr8Xj726C^%vHleTEd>fC%9=)0VEZ>gaH3-V&;wlbo{^?WB)1tGsLx)B7YYHjn0h@U z*=4OX-V0Jk67WAuaxU3$e*tY$N|D+-wMkbub~htwEx2p_#V*Xz&8>$lR=;nk#7L~e zASRmYBYGkW4uW?;cmcBL+zVgx;Z(Z<0+BMpDjD{t9L+U2)tk9N_|GfJn|0ymRBp=i zP^oJRZE;t~$KtOqFt-%^Q~L%*PzbLkaP7L^P=~wU_P?vn-6_;>`Reh6*pX{a(Guw8 zI>ma7X81$sv=5)m54~mv{mS5!)QRI~Z(Ucosp}__jy)j*sxxACa<*Qlw0os5Hh>(F zF1jKX)45jP25oRh^D0vYv=Eetmo((8Dcd9TT`cc{pja__f75z!xnnZQjultP0Rj5t>>kDW9Xky@Na7UFXwd0IDyPdw3Ry%Gc53b7~Ge{RX>Ol%en z>};CAw?PebAMUdh$e*(dl9(9OmdwB^da{znSNM~rL=P5{aQW3aX#eWq$h}=INETIq z+x>9x7xT!`-s_kMnTs}ltUgo`kQaqKG>~}o0`{_o+|n)6<@L?sSf=7%jw#S?$F5?j z0r*vmQl@d8kfq{GHu?v#3ojRYsQprw>h(NS#`N-W_H2#{Ju0$(9=G>=)9xJ&_~|7L zvf9MSS+6P8-AGAMZxiOLJN{k0XNMKLNw9*#ttF)x2mi5aTr`vG1eAT`6+3*^Z}i6) z=U<8qyDFp2jDy=$Pvt%1*wB{WLqL`Bn0L-N$g6{#HujCM3-@~P(xykgU09liL1Ile zErXjr3M|rrnQf}FB&<9&1Busy;o!&S0*OV?1ZOYElHGe&pXe<+i)OJm^})xKri;r! z0)`uc!k5U?Ja6JnXc)G5YsLB=dYU~&g?K8tuBc4MKjmmbyLANo_YO$xcT9oweiJnc z#Fcxhr6-N_lLmW)kU#>UIdNpuzgBl)}ny08M?5m$%c+m<8htjd8FeQNZt{i(qX98%jK9 zGmD!4)C_}6Q(#Be%Cd+Ka(mXQ^C)|e?M>CT2X$;ewGx~SDa+}4noga(UHBFiW@;1pB?YBEA)`L8T=B+>j$X z?Wpz=N1;b-HlyEBLUMc}@Z=c77^latk)w=4$cwYA--u6!=WsMg=oBjS4YIsmgwc_t&)>w-bRDaU#QOPyRRwNqE`X1C?MMt@%bT0 z1bNe_w%aD~l(&|yo4Du!W4#6wLYL{< zImz7cLL}i^`hY=6z~?l@ugQBIlH~#MgZl_OBHGBkZBin znsTdt#aP|dIqdnNo%>JJed_C#Fb#T%*C60Ru$whmxC=6Ahd_`~o=oHQ;gudM5V`BB z>qN6iDeGRgcAM8X%2!Y95aSkpO@|h<}y0{f2x(+gaMoI`yTk> zWWK1Bw;zG?wJAQ(7{tNkvk^#uwl;&xg8zyoO}K$}Lzik6uNJyUo`aUjIN)wJ4P%z; zT_DAm9UrY$LAl5x)%U#pneZ}I)eB#rv;&XsZhRJGczfxAktU7@;HpNqzgJCLiq7-! zGB5&g$55%nQUFlVCss2NR$Kr-(X{#0v>&?x5Ki7&Zf^U_4@ax|HP-i{Mz?Kxqg=L* z1Vx(On%az6>wlwOk~39f$~)|(i#aNBoIP^5Ay1YGT@+;xDvH@uramr_z>U=F%kxgw zXU|^Vu8^`lFdyz&EQAfOTIx0rdvOHY<5L_El?9uR*1vxAB>^}|q6e@3 zJH!g9e&XG!KFS2r{x(a$-Vhx=%-(iDXcaD-v?(uzT9&lrNeKOFCZ&fTSz*ub>8sfl z`V%HF%zKXjPij{fVWr&2MK*Xzy#P) zZOOnk&Cjs_<3mybMI5%QkHx{gT-&7|-$Lw=E_Z{o6$VKDIRf)BT{>%`ijC z_5&nF&A=u9(z}`!JgDvEz|hEwsF)V&2&(+4{+gm8;O`2b<+~GImM0=H_h2ev$>xbV zIC)@`_2b0Yq@&+RAw)G!7Dw_Ja+6$e;#b!xDEr}bjx`5>P zQ+*u?_qRSgH30Z2fg*^(bhf0wSX_7f9mShAuiAJ37B z_;<~a7Q3gBcu(F@FOO!-rAoWZS#keiG)@j+Z}K9n0NAAhFIgAekhko^yzu>1i9IK! zf}%lUH+*NLKVj}GsP91$KCAS%T1!<;K)U;A@)FiZ@XyRAZ#udxyN)zaBqJ^+d$_wX z=4NLlKoJ=L)AD9;?akQaE>nNDGB>c84Ke-i8sifhezpK!<0s~`8NgWrur_;Wo2PJG zxAoTXU5jO_w8yR>4gobW05oJ+r0;rZ{iZ)40D}Bs5B=@Tkb4yxxpuYJqlVf?HJq~$ z9W}9ubP)CBpi1KXmbxiEZPk0n2pB#Bs9Vsg0eFX2|0+)Ro;ipo_3nP@L1B(#Fk*TAZVsU6nSTxul?noF^p{8d#1?y=)sH4z zg~wIyZJ~?4OjJy?AoA=GpY2ZFe_+0e1a&FVzIJ;0-Pb!-0X0< zq-3?5A=uJDUvOD8VJmSsKXNlCw)5#aaEaQd1;K^BW^YYc_~v`-%mtr0x7=>_F${3v z{7M$X%u1)Wyk<@-wLrJvwbfI2Y7BL!pOza4z7P$?T9v$kD-egP?sfB%k*=ysa#Lpg z#yTPH4^q2J-;8b$fCqM|5?U^1C!sIJJ6qDSttWMHMWegh_d25@m1FklTDarYdQ$)c zWBRSVT~L+W*`Ji`G*T2LS{(c--`o zCa5^t>|7%pqm&LO!O?}M=C!3W{)5J!x5!F}(v_GA@@%OP9$6TYn(bS7%o;tX(TY=! zvxJ!z7>YF!nTT5XMG*g&>iHl0$N?r}RX>|?v z4g1J(_!6RXy<*oJ`^V&De*N6LBeY?vt&@VdwDJrRWZj+Nnfxa>`iL9^g zUtHTy;`5KT0ub?B*^W%8^_4b&C+%2}TEO~SIiz!BjW@)4e_fxPP3<(!ZH3ukH$9f> zh9BL3;kvC_Yb8(LP`NQF9*C0d`paIr>q?RLaq!w0Q$}B(W;V%unn6e6`#^TVIK0gV zUpmj;yVE%>=#2pLX0u5w@=NT0h#JZIi(#VEt;%HephW*O_CaLAhB3GJ$jc`h`BO+T zn!YQ^vxW4U0-P75x&V$54=&?XCIN>&kdPGl4UnFy{af=W2e1CC{*uI*(HFkA_5c9X znf4YK175edMn?SGz?@qLNTHu+wpX92Ish<`04|qcP+{!5#zcS(4<< z4+4H>wW<=YK$r+%n6PpWbpSF3Ux1P?h6c^^k^!T7nyL!qYNT{6`6UEtoHwuNJiS22 zv_MTcUm-xvtuAdZyy#d6&;aT7=+xwaKj?gf*l3|184{Y_oqa(MICLI=B{-pv9l9wR2om}Vl!S4HQP~ah1W(>%eZ(RY7vc=}WlrK)dA-T(` zQ#wQK02u+q2;AjB6iuZ)Fj<0o^B}i!s|+u_4Jvd~^ukfMPEe*Y4}th5OgDa<^hz&y z9V9we69E0Y+=FXUMO5RV{!6SNfe2Yx1qqgOh_xDd1%QV@=)3<~KMxS{yY4a|KnI1G zm^A>R^EXof=jIM=@~3UwcS6$6jsw8vlAM479Kt_!3K;A}X3!mkKSivSO>^~*4K14% zyM!`hj6HO~_j-QkX(e2ZiRF6?Aljf4KrpBJTj~|N{5gRMyiO`|NaN_k{sE7C8?O`4 zBEduenPR2W(-)!%jQ~x(d2SZq5mR1xm;jFxcrAXB_4B3n!%UvI3bZ+~(|30fz-6Dt zWBgJlz%Pw$TEAX@AOPD`)P({Fl)(|rdXrY~lP+q&Vi~t6!#Vz%uk=9(^V=j1RHiiR zBYh*m?cTI5-wN=kyE|h^BY6`gM<}FWTU%d6!FMAP^UvSNQU!QmX~V(02Kn~$d!zPV zqd;KJ#?5=@KpNT}#6FAX>I%C@KC4i2+b%#!sAG>`3td^Xk%pwe+rQTpkW2!R0~Fq+ zRcG#7ZTTbs6tlwzHDmsK{g%tk%t#V+@hX2h={&-xJZFwzPz7)DapkXogQ5D% zX~D(i=iQc`Xz(Ths353jbvhDmm;ITTcrRnX3(6jn;(ro=hu7!URG6y?c%O-a0vl%QHWtadH~`-VSz`cnC8WlIX|2EO0e=qQrvHmCTkMYM zF_>L5DMfwyjw}NxCKh{;n=p88odGfeV<}G)qJ2QP8Z>U#hyt4#{Pwtgd#KIo`UF4> z1J4G4#W2C-_5ZG}SubAa^SXAgeaRHUNmIXibw%TM0D^kOF0oZ|Ob^ zT2j-e^?@?hX0b8>5coQ;zy2m+y@nm&^ln|-k&)7`({~<@*{s+EC>yW_xXIVWN7Tiq zM78IDfgpGSi{&r2Jn0EEw`g@x8NA9e!iR=cz{Ks@*Bn+ZVIb!kQ!#+!b0=W@q}P0; z4WArHA{djykGx=rYcG#A1z{K|H*4{hRLzK6tjpN?5<{?_;&ZAU0Fq@ zw?8%Axt2HIx!u{-O+?kkeAHd4Upl1)(B<66e~?r;Fgcw3?wHLhfM|-loP}7~IKHsE zIoeCIJ3$ds4Z}HDXn^6mzW--THQ4QhTUKquUR!Kb1MqeLumbC!E~sDmIw@wT+lgp*x+9vzz^*BYnR|q|~`;2tQDu`~~b#V?9WwRtcMTO#qQS zeKxRnZj)c-hyw&55H5w~U&45%uZsGa?klD3lIWpumy{>JVs`|7ec2%Rnwypu~gh8j^GV>SzIu?&b--j_BFz#X~8&ln6@`|1H7OxvXw;K_y#cb zOy2LSG~1>H6Fm7c#?bEwXTjx5!qBT|=J<17_JFq*4Gi2;HXEy#cy~S3cFlAw6?WQn zMz=JDl?@{pln7p#iL1QJ;HL6ogN&w!hmzF`{*_aCh>ozqBsAiAcZOas7GVD=lQG~r z?Ec20sdmB%6jPg1B+{zZ`?1mg5es=|)&LI7c(Yn~1Y|3;14ICh;NAplGzUNMdnnb60qk{lRb~K^j!dQn;cT+c zC?v=ZRHXqp9pE-=?pdE8%_+T2`Y#!PRfz}CvhsX^pM<4ZfH+ps)B~t2XJ|Wkr)KR8 z{b>LmMq0c>xE#E&XCs`d@nLpWqgx}+vJ%mCTRT+-0RRHlTS43lQX6a2gli1@9eggu z>IGMsY)_?-{7%#sbOZxq&V7$?0oDPtvq^w_UHeL+Akc~YVrN?6kmud(4~s2MNbs72 ztou^z^V|x*Y78;aRNn;-YCqsbnQ9aOrQW*MHalUDb){vi7a-Mrf#-8{1UR)vRX*6A z`Y~Q>m)J5;J~Gj?PN(wO)mImOFJqKLW9Iat`=6XyJ|g)ARrRuL20xPHEd72!{&)>$ z_1UQfH(%Llx9&^%Y%=1S;%83owFc9(SQfNWzBT!N z<=r$3ld3Wsns4=sdBSITs&XzwGaRpCAvdf*eV&7N;AOI{tyAo?L|G+=h^yr^J|=Q4 zk86f!l;4WPOniiMe!|v%z^@-s;eE^@%fw<9(in97#kXW7JsswRd#fhuH{TRwf4>v9 z&*mp3ZSqmBH1GS9k{w^drcVSL&;3GeuM9Z)6}khP+7$VbFYvN9d#sC532NN$RRq*v z6vi_{n0MR|^DjsdKCd>#Y+9;&(Si2@`SY6Q48zON&`_Jvcw^JjLOa^_lGzt`IKJdP zY=3yy;YS|*$z}qlhuCJ`;QGxMr@x4ctd(Cc5Jwo|p&w5KjaiMM1`Cn!u%H(S!1<&8I?PJXJK zu#mnq!mHmKO{`;ts*^Rs6fey#B}#A^vi3#F!lIYC?qyCxs0MY<>J;ZCRjg zo{wHt{ccB5P@d+Pg_hnWuYRdV0-Cp9}t0cn0IJPXTA(@al$w4n$%W4S$e?Cm+yK2JCZ zb|1%iiL95KF-Dk3KyRF>ZFdr@d^P~Tl%2Dp`7Yr+9&KM6J?*`8N@o3xfY>R|m!UQR zzt-6=T<9KgOB-R7mII%PvZ|^-x>V<`{y7^MKusayII{D*FIRJhP^-O8BNB6$sU{Ri zSv~XCnEBB#>LZOaa|j6Q)7}r^s3SD53b}eCURa_hn4WvqF0d`KXXtq)6js@b=ABNA;*b3st-2BH6dny2KRzJ z>=nVNg4+EUWB$Gn{OYkRIWKx=HEnnP#LBI?opZwT*wav0Duq4dOAKPNB$R%!l{6?> zv9Z{ZmsKKbCoh{nS5aB^#PvzD?jKzyusa=`oeu(qot8Q!R%ebk)-(!K+H@AnP#ag# zR2KEXM#Wv0I^PXUQ~tVz76)_PEvaZfSH7w*ydJC^Ew3Ievk=qfHs@_S*q1ito29P^ zxwe$(^$#XL!CQ$wcQO86SfBxxLN@_1|Nc+yJ5Pq3k5`v7$N5kapRDSrw=2>GrKETi zSpjko;-b>K)iCMzpLL(h4Jl05Td?>17DozPyx-lBkRjWbihPy?c5nq}$K_8~a6u-F zH|Ix}Iwt8<>H%mN%2_d08iRr#yzUUea^P@zDl zGXG)__U*_!O0X!M-NMrS(@y-Ajc{lmEQj0t5d#$3esk^HvOSW+WB-VOVU~)r+1*z1 zw`9Rwy64GGRQa+Lck{e3D*Pfm99iwZ#RXoj=1ln-Y}pv&ni}IeG?d=sO>#`5RFQG^ zWeggrjbDAuJXIEhm3&MNo;26EzTa+-H@l|WutdK=P4nAC`Pk(7Hy*~g^qJAnj+wXw z&oJ;aH7%f|?W5(o|~ro6{-ZQkr5k+7)&;RPIUlcy;Q@ND1OBPY8^ z#7k30gq>e&Kmo%xY9HABdubg-C-n~q5T4aKT?b|5>|cgz-&u%-VfIZ3J5nGpXe~?y zHTGhj^3E>2KZuorBf()^*Aq{>yb93N;-7-zJK}=j>-O z-;<&~6ptJFLstcbIoUCGy)GCQwje1U4Zo;txS~uwf}AZS5r@;LyaAcM$~nIPDQ0sn|%_CZ*-n?rUS$1@WOj zg(uY(dXE<>K$v`K4rd>T>U2+&Pno6{2@4w=DXFcQoz7!Kqr|rP@UQy%mT~GwP2j290l4D&J zs~BhsdB8FgZAkSW-+liTd%$qNi5u++oe6Bt=WI;p`Jl7Nh$ph0`#An3f zYSE)VZa8ROuZa-OW;DU>dXR$Bm(YNP5XQ1oJ-_ZqT)2=W!vAh+2rJLM-J$P#wn z8hZJ}b!d0bz;qR2+oX)Tq*?*+eHQekjmPB@f!7l{CReU_0wwEma33yEaZczUTaTeW zZtAH}=%v*6o9#q1UEv>)%d zu6V*&7BOm-w4E6RtMWvqzO;l_owdC?OTEwri_ueA{kIO=T6E~}g9>R4e^E<4SmCxx z{ar@Wj@BzgL%S#X&O(e|kdw+M9joEXKQbPaU!YY-m33Ont)yK$=UIq&0yJqksMyOS z;^>PljUmBZs?t-VkeM^u5nO4IH(wtfxKkSm$K@$)9Q4m)0~jI2L)bNyzC{_<-$ET( zh@(5BfLemOu6{WN=y~E@jYk$}eh-WWaVc)CbNzbReJ2LmG~y_+Y#1$b(WfiF)-BGY zIE1ao(Q=g*7rNgMQeinKJqR#?54xj#gLd>FrF7?SLf3>qppe`s^fV+yV7;FH=QFx1 z6Cw)RzhzBvr%p{P$Mlaqlgj%(RwhIdX}yR0lqNa3C_SO@#YH@b9KB?cg|^jXfV@a6 z0Sd9A8(Pz{B}>;c>YSVXVBsx!u-y2wDi6WHP>p(1@Ujgx1=VSEVe|)pR|q`o*&94_^6sZ_m7> z(w#K(iZ^6G^D6*wN<~k$V6NGy-Oyf41qt^vQWL7pP~;l5v;Ts(DO+5!*xtf!1jXhK8#{8jeO7wwe=^b0K$@SMB?MXI$VBHr3aMD$;ajKuo3t^;YmN}1 z1)|R92!!IkmnbCSx}Ai71txKQFk;u{@HYe+GC>Hqf*;7S;!b-rl_Rm(e0`ar4_Q=M ziyiW^r)8wP8Ki+1kEc>Uyl(JWw&R=CHOPk%dJ>K5axDccdivZG5^|gqU;Hf?XI(l$ z&3U6e#Ud`g?%~&NSl3VWG8FdKmdCMSY$P37(R>$()lww~zOJB;>fAj_=!tn0%aPR} z%8~mEe~S)m*2V+?xf>ZyMdBS|tUc8MxRjgq9bMdM&=gsleM!|=NANX&^ZAT~eevhl zpAnCSYcU+o8LduypEOs_Gd0Po?}Mbe#ud7UhvwE_)=1F0v{~ixr8$iK-N;L~TPfOE zTK+8W>{t^#dwGGeCnG*%Trs}^0Z?GDe4e=lvo6CQ7gaKoK>mnt!#qk> z7HyAha<^k0debY6@oYAL8p>{vX-vFIzR>l-jn86$L{af zUu8ye*k_6K1F<3}v5N^j1{7ef#kXr-n|4S6zY(`lZ*Qgm!u0a~P*Q?OE)-&5fa=eb znC4-w9MpEfB6=lmWG_a%1P)cv-tyQ!=wNHaJ_BGQC3(S>?{x1SrpX;9wp9|k3ijoz zwQcy#;^Gc0DafIJo_2;cX|5tGw(iz24Bl^~1HkY>ne+RRx>wS7lW*mto3(%au*#xx z;$Mq+)a!R<202N*iF)^=LeX)+bWdu4ocFbu>g;ZV@80|MmW1*mENn zR#M!a5$@xB_@!B^S*7)NEORCm8uj3aKgRW$ zm09Etdp&L0HqrGbq+-tX3OMEG^8NbqZ87yHpe1PfC4PGZd8D?t_F>PTS-MOLfO(Fy zGtYk8nZ2^H5&ZAF@Oa8^F-v#CG!J@Gv-v}k;;!T`%NxpWzAGhLKy&4ih{134YMwj; z<-2ifox_2G%Fr!Q=|O1Mx8SZ(dPOIo`N8LPyH8H2AO#K~+WqEjoB1 zA;80GWtB0-ZQh7>i?kiF2r`R_iEYqVD1A?r!f@U8r0$U0o+MW<+SORsg}>Pka#;IF zga5W@cZw$Nar$=DpX=F!X4Iz>WOg<}9;~)YU4A;@mYel4a|kOEMB_fr(D|Odq2*c>8A>)!mUA@%|W^Wo3m4j z%HOp_q;ZrW7D0aj8oLd_T#6ICH^p|-qc3{4dexrDm(p z8$KZRMr)n)={m6ULTi1}_}d?#k5=Z{;BI5=_Z%>N)u-Y*re+>AWY1t11WW--Nu#Bo@a&i(N);3N&c$=KfTUUK#~EKw&S4JMBQ;7Nzi8Kj*|+wLWtDB zmvxwz$a%(9ve3j%eZ{_fnUnv4CLyb;Su$Oa^WswMMqEiK*gEeuck}BP2b{WmbYgyQ zjS`r;>oP-Gn!H$;Pt}G57PvO8{dv_p%Vmzn|EFaNJIpj~aCyjA*7Z#5xX;M>1 zrN3N4N-NM5$a)-5^nqczAo{T&X0_RthuIofj+~zMuON+smVJ(J?jycAWbj5d;Q)zB zEoLF>jecC9WSd|`k>j`zlr^eXY_swId}s6XfX1aMBE(lOr@!ZfzuqoCe1#%^))7 zt9~*t3+A=6mAO)05F@mFCtJ~#qtTnR@KVG`MJK`anpxF4Y-mtpzTcW+> zFoFG=6!oC>dT_Q}anvip3Qsd{*9!}@xfw5W3cDQ%m)cFy_ba*kGtJk{zXO?twV83;yCVzNTn{=K z+2$fCuv@9lWV|v#Apj_6)Wm5J6o!>VcPrPY;9GJHQIgqTG)^BjM~u||Ksq~pXZ>X0 z*drfw5Qs+Za6vB~!7*K4qNN@}?_1USNkcmnp%p%!Qgx`=J$J6&cSWc-$wz&4eu=yR z(ag}+G3@&iBdrzoph>MEw@MrETFF9FD?zZx=lGzXb=!nG?1QFq*~AB5CoWc3We22g zI8m9kkq2DAvHW+rX|&*`s&$OwRlsZ-Cv#sYvO-t*8gfjGMMSU6i` z#1CRQm0Zuc?$Q(U3)u_`pmD45~fYuotr&Hcf7+%Y)Guw<=?`7 zdLRHVNIw6h3HTnj86(l*GJp0~tWr()90k4&c{W*1gb#DGZq?hdUB@mLPHjYeiL81t zZU1gaAz=;q3@f&DFQ91>SD_2-mxlv4G@U|d;oEKOGHY*8NWIo~|2z>HH2;8KJ1Sdc z8JQ^WZN_XVt6!8mTJEq_#zX*H#aY)i7(Bg?{>g-d<|cir{j1SeFM}$yLO!PbzT<=0 z4-OCN)A*mYw1>c{63URJBfW%LMc9zKW1L`RHEUe7^aC14hX~fS;upMf7-*=77Ksxl z5NC3H@Ne;aZ@&qZ(U7!=|0$DCoX-DQ=Dd80Zq zs>{|mkPotDh%uY_pgVFjGhe?*Ry~BtY=-rd>NCA^J#U@{kncE!Z0S(6EWRXM?f$Pe_D)n=VAG{n{(-4`62;E1ax1WSWy_8QudQ~)k9@fD!~iu z`|>Hnt8SYNv+a~+ws&U~d@Gy+zS&}K7JtU_ZEHE(6*7nuL@Tu#3^Q2zPYZBtT2RW6 zd+PDWM2JXRgm0IMIGb}Z((mUFt&F6V1FwbB6aEKUKk)1>A&RmEJ!w_k_up!07! zXWKA4p@|U_o457UFWj#5y4O@Oqi_Ao^4`onHCF)8Ca$IBN0^H1#He^lwXI1Qi=VlP zm}C^*R_0aB-YiFF^0X!{FPS@AC|3T%xZ-hxhPBuGn^Q>2IXRmj9QXFbr?l`Y-+B?v z6Bd=t?MdQP}B<0gAH zP%2fJQD44(E>0GG7fwQ$T5VQZhfl5y9g*W{cjYU$A$xm_+uH)cF4jf#8i-ibmNgn8k4hy{6+qE5uvo1(> zeIB*Tz{mx#E+Wos$+dy&rxJIyFWF1Lf8F{58d7N>SO>*2p8!*6H|$kIQM zH2myx(HH1`r4QBql;u+Lcsz#ch0;NkQ9)f-?hk5o|5wkRuv2o9U^*aSMso@{@DIi! z9>&uc^&jT$3=IUqqF^Ouu%>MOe)5|^-F94$95S^wn!*!|@r+G#ukWDCe)ht}hIW8( zhLt}1=mtt8B{UIv+zS6fK6>p==u&p5&a8Wx~ zOPE0U9^bb6>zE1QllF4G0kALC3tyY%(*#O4VoB22pUaxbt{{_kS=Qek_Hw+lfBR_Q zOhe(yVi@W5nr83lwH;7*n?;7pnnmInU9ACj^!kqo1=>x1^sb6&#z>8LcFaGR`%&DvZ!|-9Ke9@~0Lj`$(6A116kV^?F#AUXsxIz@C@;G%M%{&F$I< zL=|MJN@4vtP`ekrVWkBz)rVoy$;{KD)(ONO)_Gwyd%<#Qx%rC9aHgs(_53fbuU_#= zm*}8W%PDow!;msMrRJ4rr z<>~BDoZ#=*>U)4auEfZ6+<09UA}xYNX>ea3{jz`~wk>jPTSWYR*!b(7;rD3ICa#+I zyS70;(w)6n{2TDFVwi82@A1`w3DcGUSViTQ4oGNpeIOIyNP73TN-8Yl+mK+1`AWf0 z^rJ83C>aJ`Bf-#3qGDcq!UUr;_?zv*Y21u>-TvYgEm}p0_~@ly9-=MTV(mdpH$UOFmnug3fQrKxFBZLhAKHP&T0IgG!1ZjX%^(Y!q|8iT zr5eP5bmP5!h$dKmdKfFA1|L_SKU<&NTdJERdi8kwlojodz{P0JSRZ?4^qaLJOPh^Z zEE#WUyU?RBOzTqrC$lwW8d-!_sao5|ta_7%yK(oERNYE7MIMZfg{>lspnWM+YvIVK zrSMwPBSwsm_e}@JZHKXK8dTQ;ZV+@cX$*G1G#uDNdkNg(Ccrc03FzQZTJJQFh zvA8q~k0=HgjmpLoIMTg8|DaHTPrbhdhZQq~|jX9mjMK%s_=&_URw%g1SOnTWDi!qOC zgZ7a9mV-a98K}scB`P9fxrP+RuP$EQ|2xt*)oZ8Quy{1fPR{)gWAx12?FstQaH2vV zvM5x`h5`-rl__;9XOiBl@rSPY&%Z@G`I#BxN+}^)qg(`Hvp&Lv7a1SY77U?z#YeA` zu>ER~IDYu!<0AjDhg4-F)b6fX(u{UV13v@cDcQ|FI->6amJxiBzK{y6p@Wgo3FDVM zhOFr1KVie*%-z^EH=g*qz_m*<*k$59S z3#K>)(qx|Qth&&)sQ+yZInVXFi_=iZLrAo9;qjdXli`JL+wU+d^n^`>N?3?~S8Emu zEmuX~GJlO{iUZjhd1xTjK-NpBL@-pMJ1ofckT%bcg?!*n=~w!Anuhb&e7=cZ0iI>| zXo;x>0D(_p1gEVzL_6Is%T*JVSiM2`$-8`ud1N`2@~mZoqfu7<$K!^T#ue$sguJdL zwWRcYZ39m6LMbLbkQo6&=WG&)VS6z}8rsf=zrSBpj9Y>Bm^~+tf+K47 zOBj(_n1FjN6c)1BlECjtCM<3HN|(ULmyyBCZ56Zzs3{{`j*-DEndehN1phq>lmw?x zo18nD369~;3`v{0CPX{OS)V2vD%psIxsOUO^QRhopbChrA3)FDi`Z=PRl-<_)yfJt zFziU)tm%tYj%<;)KoeCcC1o1|D{v177u{j3ENEni+Kz)7b256*-o&gxeC+wD;2?#w-83YqfauP-Aa4t48-Zd6Fuykjf zbHnFJDIFw!Z>5qBL7L#t%~8aXby5ih_Dc>|V>zf?_y5xP>TpP6NwRY>Pnnb{TvIG- zc2qY3v(v7O$xwV@)?bM@AhH*JyC2v04O`X6uH?^eFZ@qB8F$^Zp++{#$~%^?j07cC zvzS)i3OhbJ>f*&0^(rZwQt#P%Vs4*6{D<#?>$Cm|Y`E29@xaXPad7n7#rnHOMk8jG zLky@~<=%$%ooa{st{WZb)yuP1RS@c&dlwmTE3;KruHV4)TKf}pu zTU{Fd%sB%URTzHHx0ugPS^tt-Q7W+|ic#CgB*RQtf{PMky09f=pC^yQYrQyA4~L@N zBtww*%m!0T()ea4-=FC!O!L?Ou=cH3bNvw=lDG5}l+Sx-+q;l}7rIx}8 z(xyrm(ZQUi*mv5+_yM4^aUf5pmFcT@lANR;e72Lj4HD3Ht(J^=`2X@xua#yIHvTJ> z9-Urpj41{_Qw1?o3i+aMf~u0Y&s;J=PPX(EZGC&Oh?ru70`HBRwo`#3s_`t?w^!?bCVRpQ~h z`gVY_Oy>GUe{3-bdmeif2J{j@4FJFCfZ9b+EG`d)(`mUGM6$diiShDK=lm0QtyXxS z1yk$ASIraEqvV-+o7@7tM{t(BOP8w9D#k_vP02P0bb))p(Nipo`OsfG&I@b%Jomn(a|scet>;?S zeS^6o_x$v<0q#riyDhqC*A&c0R`J_X28#LNMfV=sU(Gyj5S~5ZDO)MBU9XD|c20TO zx5|(iLF&+PZlV}#LyF6iBPOR>aOkLh(it2_z0-lYViU#7xNK`*i;5RV>|(hJQi}H@ z6U}^%IVBp}+qAiaGp0Wp*GgX>PJ=rA8H{fH(7TA8)qvYM%dc5kzD7akgCJM>A>kn53^XNG&m8IgqJN`ofsIUfc1Xk6X-t0GBsMU&` zI{BCM3#$fvluScjK13pZS*F(Zs&yWa+Z@;&Xh1FF>H8%V7Ui8ehe#uBHSR|W5>z2o z8_wB~F{@MP)t-JNv>iaJ<|8gu4TkRz0;XUh7!4h`mbn)@z5!C?3h138397}-ZAqG5 z-EVDVrylw~zE>orb8~kZmR)H3|2-crB_+M!3Zi~oFld`er!7DS7Yq{!37m|*RvO! zRf7kY(a^0^R6e!%+W4LVI+=$kHo6tvvaz!n_5I zbP>4%)2hnlhKOYAmZ+#clz#SsCU6Et}tW~xV3=M5SFXbBufS{S}cN>^OR17gxf z*6&o@-?3o%k(9R#3LGP;Uc3~)*3@+eq-pNO>#26XVCEd*Y2#=RW zfE47DdJ0Kz?`{T>5Vh_%vSzVBHutCeEzo929()2)ITby2N^WD8!&PiF0G%K2NrKW+ zi>1Qh*v*_+Xj?-(q|jL0IB?wCjqk9*&I|tp#D@g0{A8tj0abwKNtk8W?#LWCVnNH= z!%+?91F7tzoNe*h{83@bYjd9_vDj@+s?|40m@+dpC?ijACq+==N&h=ayQ1Yz@9Hb8 zbm2_F?0g??K;2i43z5E$FCbY)={q;jPXP8-}>evlB zMEtU_wCbBrUH*9VBham}?^X28oxHhlz({snUj@6wbi&FbgRnkuz^I^*a`e)!DEDxN zkw^AKBKOou&foi@;|=|)W7D%twVC%N+OSR&@?;-BuwJ zsvZb7C~Ke51va-@S^9OCJ(u`vw5E$VrECsdIE;-bC}61K;n zkf0doYUB{YS$(q}fiL*1W&L{kbnZ)5r(-&9<>*i;2TGvUEMBqmN0%PRcm5yZ;hE`g z+kV{L3z9^v36J9=dx)aRs2NGKw?uG(O`bLRBNqt**^&7Z8c6q8?K?19dc2sUzRk*? z>;-02PHfsd?eKESQcMTCx%$jAUNgDd=%3z~!w#eFhN`@$*qJR5#%4L9GZD3CUkhMB zNgXK5b?>5y8=C|L{`S)Yr*PQW?`O%kSP4I#%^~N-3Hd9J?Ec$fCSSh;!Y{)pj+M=y zzU9)LK>Xe6;z@W^^=h<}D>GunZF40SJsdO?*Q3=UO+6Yfz>h*fnsJ#@lvpz6t%xH%i?u>5}d z5~K6m;W2bIHoSF2q&>C<_dPcnkTP%tO5e>4GX*2MOMsBb8v{q(-P6vl1!*8WQ=ULd zgnqx5Fz1T~$a*gprUzAuAF?w4GmKFQ=U~WuYJ^9wBGUkK8pv|oNX~7uL8iJ)BFF8o z^a?Y}?a~wGXcIlFZ{D_yv}e1V3@I-xEiMaktCe|u3|)TNCIB4;39$K>wi4A?V}gtz zMcL*Hw%pUh{?9-+!s;_f_z)z`GPGvoV!{X_NqUdt?#Ma`f@ z!+0Q%!$WXM5)=-FhaL?$5)z%)Jaa(Wu92L5Y{lp*f`f@3gPEPbgqg$^_Y5%t9RsLBd0v}Sn{N}ZA z^V`Zn^T*uah`(SXcRRx-kG{qF0YMUdB6aIDF$;*(l9Z! zYr`S;9hvu^?MGS~()e>Ju&w2OPBcoU%tf_`X?PECfA8%0lfBKA4J153IO#pG%XCeG zeiE3}uoJJ-h20ZQ<~pmZZB|NLIIOz@1h3^~c3pBbal8#&o~5IiWHpp~u6YCqiFmQ$ z6*(83xEzuJDi*QWmzwBl=*J5|^PQsJ6VC-H*e~vXbQxd$tTk{|Qm5}1ur;}qYCSJf zl_HTMr@6f+OrXYGYCcEhA}#mu0)HClHcwNAr5E#Wuqr|T#%o_sar(k%EwUFW^vr{~ z8LbRs>RRWD5$kDtRO{r^^20x`fA;A^myV!#*u@M^_i{A>hA3mo$-yCk(+$+UDvwR?12uhewdcN zl}ZK|;2hhw04F)*cXM?T7<{=sf=|J%Jsj`T<#P1F(hpXJ5=l@1&3Q`*XekiW-d}sn zg2>`zWPVM9TkJqF>*$MtH&-TqfByimm)RvJ{Az0v#rvQX(>~JKuH-vr^nDEQ+|JWM zD)nG3JSvO5uf=U(aG}OXK~8=OxjU7!vE8QZ+k>5tt}zezvj}Q-=;*3orljdzv#kVRU)cCVpu2~-u@g@>6>A=ha>gDooHmPjik zJbTfX`bP<A1 zL6Lr9JQblx_l95ewcxkt zJs?o?=dyleD|xF&$890oi6Kg7SLN_NA9%n}>a1!OdwLH5xklYJ1SxvvfIVjGmE9}y z`^np~U*{5%N>hP^58Bp)q9qoQd#Yp104NgERc@02wi7_dU3EY`d9egme#^89^N;}I zCl3egEQ8=z)jC0zkATD;==d2j*&x@n;Nw`zflQRSVSVUR1Jn*sfBHcWM@4-dA-6#* ztpBK|dKf4rEg$VaY}z_M#@TRNcuCduVBohIFchJ5jbdloaZ+}b8QDAfUz(e00s+3P zsLTG|@>>b1=%tLOZC@^@%?k+01S$$NFbIk7Gp}F-k{1AEc?eInr}ooGyDUQ;^+89L z%q73@Ko$s=pIOi4YrEmecZi$p4dt?+U@(VC#fc>_79prW-5LB&1Rl`?P zQ#&xN8-<1Hw9PK$

aRn|R>kvffb!U2|ewg_-P>qGJYguG>=MN;k+jh!_Rg?taNc zcLRuo_pdEJu(wH!YIZzt2m*V8REK_W6o?6Q(!!Vhegdr=#85W|j1pxD8t6j__B zZYYa?YG|qC2OckY&Bh4I|7F4vO2wa-j7Mto;z!3(jW;JvCy7B$2AH5*8&u@quA&6W zV3eL6ScHI>o&B7g=h@H%12YMnNvm2LJAES3ah`Ophtv154or~?I%TTF{oe=Zj{fBm zFJghFmLT)#Y&~yw|8U~8x9(6z1!wLlFjXzt`UACg%utW&OT{9_bzWNd{L79bwGItnO^us3$v{e;-@{7tmNZF;KDC%H1Zq&vK*frc5q_6zKS6OEm|yN}vzX z{lmrD9QN0x_&BCGDZgdLxpu z?lSd>LU38&VoxW}4L(2lLnFi@x=`&JXbkLH*!60U|3(9}`Sp)~f+5W4IK{|7Rm9a! z36O9cc2xXhVI^p#ghyn;`M~43oCD;-N94)XY5ex^9)4jpKBxg;%YjhSZGDirs6Zpt z;DQoVP=bJk#DOWU&lrHftGpBY*Ykh{3fit0{O^v5h?slx-zOH9ZA8(!8V*@E*AXy_ zSi4DsQsf|^cYK^WH*C{*s96)zuvU9(UUj|hhezc+a|xK`&}exA(Lbs#Vi{M4#N6r$ zUsc2eP@$xL{-4Jz!|-1Yd!WS|Mzq_V1}Z|fir#EkGI)EzUr7MQK1V8|8e3}3ACGU6 z5PspB{?IL1MBJoDvB+}0yFHlJ0Fekvb{5-qy`BOf8fq{{pf&_bAXqf0zjVMbCGGFu zntBy=X9W_;B)t8`*Z@XRL$*4x*Y0YgX-6Pocl7VCcch4TLs$tIbfo7L-T*IIG!OwM zpU@qcxt#fA)kpoa9!S=I_a%gyP_yVaUpl%PSO%ND9=HQjC|pW}NcHi%AVX&+Q&Usx zrnXp&{WN6EV{5=!e>X0&gz@~nGy;PE{HsE0?+5?2IhC{QfFTk<34SGZ;Qh*0CJ}hh z{_!j=Ui~=S23K$G_eR?bq%Yp>R@1Ql^Dd(*&;Gr`yboHC@ND0O(N)FJXpn!L-jINY z-IyQ=ZbUE6AzWYDwz3ofhsYe!V5Iw5v?aIlSy|E#74A^Sh|t^44=C#WlWU9l{<}HN z;w>;$h(+)JG?@~*jl~27u@r&w;$4~7L1AnIz#>BW!r3VDDGtT0B?E2Tf7YB}&Gg^R zB9~vha69T~;}0KQ9b1NxK^KtQOl?2*Wo>?9&om24vq?E~7~Jza>oO47w^{(-tm@18mBzfC9E5+ zB?7~k+1gzUl#_u5om@Tm6{}$xg^C8h+3+fxi`cO?xpnTkD}nbdMdsQFjvD1kl{q zk5Jj-y+OcgK+-`O4;b1~xPDIBU7Vh!3U0Q0>=^c)ys2#?0|dIHA@FITW&IO1g|q)X zNhsh~h|XkA8-P_7P{tr^R{_NcgvhukL-cu^)zR}_I2C~@u66=kl~ym=2oQ-1b&w8V zPj{}c@=+Zc5d_I<)`J_O_x|&eVrcg`@R65N?tIh&tNR*dl+r$`HyspseGxEH&OW2f z1B{F%@?~&|c`i82e5C)KyC4s=ND~r3)cz3ITD7x#(a_j2NiGY|@>`L` z%jRyXwW@XSaBz$7KR2c~RDT49-LkFmFRg3r93?WqHJzq)VcVvFskn{x=PzKG|AYY_ z$A5u8RL;4a)RNfceS|Z=5Yc#_LKzT1<>=i&0WLrru<2tI(_h}aH<^g_2M05zbGWM& zJS;#ZRsXW`2h-4d#o*?EGWSmtC}eh^*3{6 zG6>u5$fr+IDs&vOoEfoie)<|5Nh6_0mHs6fuD$?V2|LOb7vP~>&wv%LmtX>BX)1gP zH)4eh*aDDrDj0B{$DJxUE(ZT%5jXD8IAD1Na!zp^pb=D>_$oBI{`#EtlQOEnb6^?D zW4mXy)kXlW+*iwGzqw!mE;(+E>_6VfqcfHpka`T6QGGu4*F84}o=3`DGmWj|mz#*j zgwCjP#Mv2g(0{5P*+9D;fVxk4z;S+y`4!vs3!pS0LI=6@qHi}Eco8_M#X~Vv_dWBcNEt9AOLFt zXbvUiz!}!sb8B1Nq8wq@d_jTgkdl6b(bn44D|B!ofpPg+E;DH5U;qX;u(?v+0jh0} z#=6DS3*!-Yopb-imsBLU5hp%BUIOs72)wJOh$>gCr$`!(xAsiv7`J3v3<8hTqHA;5 z&iw@-ESC?PUse6!`lmN453>Dj2%h{*od{_5k_{^`J2_jzlssND%VPj*A7EfOwJz(5 zJW$CX>GzLGBdO2JW`M*d1+dyv zQ6n^*xV=)WYm3gJ?o)QDxz4p^Q{0jg6`_K<(wa{Iy`N4wXz--~Z}4C@-r+Askzc4L zG3rM6>l!@=ivSjw5z&S^*Z@onVeG2Pc@BU9;${5WGK@;Q-@&W`yMj>}csoE5rVVc; zq^%sdHCpcWg)f3tUcTRFchUVG%G&H!opQk*sZ3Bjg!GZ%%>RdJsqwDqH1&M-5f+XIH;8n>5_pzucYnMs4sY zTGq46Pm>QpOP9Rn7^H|Qp0g&RXe#`m_Ku(dA#}m%%L~gEyg(rufr!FuV9>D$kN(2Z zMA9LKdxGFJ{|^=5K7_gkny*nIN27RvjvJ}|6p(;hxY(rZV=*SAA-Ofa(Qu=33~e4onXM{ zUWN@iSRKHm-YZ(k&_(aZ1)ZR~<}-CRa}W}PW&SZFV)aJ!Gt4Cb0EGgHG_3@fPGJK7 z#FN>3)v~4(=8BH0%vSm)3kPMG*wC~C-9w}1=f{J9AO*Dn3~@BH--ks5@AdqFL?bHU zTL=22t4#P3MOec`H$~n!#E23ZJw1cS4kc7&<3r<@AIUH+9;p6coAca7F zPS(~z1sjO;K;ZcUBJ{sz`_tDGGk)Re3E$>sr@RM5(Tqjs-dtqb^R2$MI0Gi}*ePEd z_gl;63+9v^fb>hO6Vi*@%WXl|e23U%Apn?-t>voU zT)b&F0j>DLMk7!IP+`Na_B?&&goGyNN~t=LMs!UT>~OyhnQc$ssfhWFx>DGFS$Wbi zO=-_L&SFOwE}APiogmL}-MInYQ2_GyD2n5Cka_kQ1I+K_bNkNmzZrts0)$DYhAogY zObS#*R4bozMvEIVlUdDq>rUO(rz`vZb1sPBuC$j zc=LM)GaU^c_olOoS;DQ&etN7Ls=#zu5A?KF>pWH}?vlK9>=3~JpoVt#)ilLEYii<= zFTQ>;#XN@R404D7b-Rau$41@Q+C01d?zliC5Eg3D)&x|6Yy;wBz+jS~ie|p?gE`Yc zUX^N}Gt2V|eP7n8>#&;-^uT?YgsXrtv&e91kO!v?oY=`uJ!|o>l1J2mv9JRCMWziN zDW2WHzb@Lu0YEg^N=1N4{m+H>4RDp@ds*FmPuI=Xj^FJ<#G?|BiOEEkpBfVekv*{- z#Pb`Y^@+*X6U*lH&`-o#>ZsFX^o+eqXRv`tMZ|;SUl%bx+P=d6Y?J;Y+0)dVkS|Kt zr%h+A>vgaCPO6Aq{jJ7RLdlZWN>%^RaEveR$OqO)pmH0SM|oRO8CSMDFQN&*Hzd|v zX#;_pHIM}XK#pA?8}JDr&j#K>J(L7STCe?{NbFkbTDq})Nzv;xk;%l1#<{Apd)%l~ zQNV1bsf(WedvXrA`l?z;KU{dj$ZRD#9R`gO$S^z zc?ds2xGjPcK28!xp|B`ST7cgATfXw3qmC;WEaMbfc!K}7D)v@%ea@f(?Z4(@?^dwj zpDi0C93QdrUxi|>1mUxw=M-j zNI<=T4glrNr9lt%+)kaV{R-Lxfjg!apMZ`4I9WmN_hRDSIEQ%`U${rtXR%#%X1b#o zE@57TwGj#(u6;C&yTIUDK76zXX!ZwVM%|CmSrdii)pPs8!D&1+cl!W;rx-~$15TW% zHX3?HWK=m1BuY_LK`lzcYuccF{pB%ZRC_l)^SEu76{{2N#1rS@vZ ztBW=BKd&7Gz0lJ6le&s-1bSw`aRLkh1V@GUa}teU zcmJ=wZ~tey{o~i&U48B(x0F+%a+eMc$*CMm4n-&=&Dlb2=4{T^;Z~$kgv2O9a#&(! zW^}+3OOC^g7=~?zIgj~X>+}8L`xktF`t0%8F(o9%Fdy}ga;`B@Cj3=_rN$ibqry zo`T?>5pn7-L(KOY`p3J^fBxpD25tmry>qAiQl43Xj$T+Cz^vqNZu@xgQHEu|QT<@$5h!l=M!B$M^dj#XdgpsUE9fs;ESm{_S zTe#IA(UFBE&my?a(u z1JK+Tz3cy%iYCZex17!W6}r*gdhXv{3iU-hT_4A9QsP|BpUQOvYqV-ww9p*R0d&?T zINpFVOT`zbeGD-dlagm@kgb4-q`E7eqai7NPh;e;0QXrREnGy#5A&1|^^mVRfcB0Q zaXG2s1IN#Pajd+s9C1oppe2M^I?!AK*4=NUJ?yUeQGMkJ&md_$sJx$`^@GEyimAcn ze!`Vyg;0Z?oNaTjqP~1|k^CV!T`7N4{Dl1K>+!+&0I+Mf#Uy>ghy}&xv(r^ZHV+v3 z$l3$xYY%QGm<00gSb0|r0{S2LJkzOer8Sc8K&&aT>E2S*ZmgESGd;Z@ECpyGv%)`Q@d|%rJZ)4ddv^N3t8D}N zDARNspq~o&F4JH&nld~wr#J^er?GJAt9I{Mozwec_Se+y_{gOVd{eGfo!UYFeWXD3 z(10co-hmnwXk4+{9KO(Aq3Y=qcCl6+tPJ|Ha+-$h(_0yPIR5_F9hF{zLujUT1y~eh zhPrHaG4PxOC0aZ=ZqrZUvtO^DV28!fs89YI{c@ zur`fD`H?5!l?~1ZC5FDA{~44p*{UFd@7gA{+o4X#=B`7Pj!=sLCLWZ`{3+ z`A4Gc|7eS`r%Wztt9ZC4B(jla?lMI(pCBFwB14Xp^qXJojd1oT96pzb#@)w_lM>54 zCOkMzx~C)6Qymh$Uq9P_IK6y-diS+G^%vglO#SGv?JsSM_#H1@9H9S7dXSeFAIQb$ z@F;njN6nr@<$k(+_ITuv_MGz}bAliIxFn+jPV#g@HeAV|mc&>C=PHUgZOTbh@`kuD z%lH#b<9QpL{B>WYs8Uo-)rTSt4b8C9o!85$UV*P=9&Tqz9pF8FQdol{I9O{Q^@86$ z*O2y(#6F2S5~mSDK&ZmsIa%%mD?pe&MPszYSCd~5hG(ulGN%y_Y#xk2{7Ry%xpq2Xz3P;;(UeTicBW|+g zwj5`f8F&GGsLwfl-mIe>lGmmc-2NEh9PNi}MJ`gpEpsgeq42m5gIbwl&SkNWz3{VS z8y3>PevNNC+R$QgRh9S@k$?=ozLXC$;W0)bpmRE=4k` zP)E`>$MEHzj}^T>jJXNP%87Q(R>F6_;<^{DW-hpLKVMRH$f6=A5yRYj%vMLS7alY| zlNB+>{>;YMH`~!Vw^M%B+$hwkI*~Jdb8S-Er4++>3`lR&27G04t)P{e6{Rqp7Th#i zA{jI{R#|g@dIR!cKyv8L-E(WhZ(E9ya4xs{9ny3Ks?$p+I3!6GSF4?+SBGp|qkTep zAy3s{3}5D`Hg0|9%tw^O%0w6A@j3682>s{dPV99v$T*M8CNFlUS)?`6N>~R#@4oIG zxr#{uH{1NY)OXAdw~q>|!keGhf#<|0thQMbHO;oB2OlHguY|o;Jl3h%(K0{Xto_dC z-it1OXG$!+x2R}f$T4Km+qg}Qev*RH~ z^{x;e!vXaw!eFfYJE4LG@fY8-KP{}&#uZgUoq}-kj(WvuEess8dJ++4&I*T>mQ;69 zAEDlx)jOC-bpQtK+a~QQ>}=8sgo;@5f_n9QDVzz-#~8AvTMKl_Vpqx8-Nq>orIU}1 z6k8&W=jIy7k&DTUF7(>mbcA<(@!m?Mn;q4gV~88&)KotGE}G#Z4xZd!PVG;LZ8YljWh=&3ZZCxM1Io&R^9ZD_MuyX_6Dus2J=WiDlosc+n7 z2-fXxKYF~f8TQ11$|KP+*6r2q((~VX!_RuI0o!aFZ0bxhG2NJ$s~8s(!PE)tw8v9s zLh$BFOn3W`*qlTB(qm!=!!(DT`%HhnoKZV?=me7EV~Msd?}HO;&yY_u3?HXanmsi6 zT}4SJmsaoZWhh4vxD`_;fe9-4=Ayx<7)z|$F+1e|d7@Tbkm!@_@X+ji_lLhF z+^_zdP|kk|>G)tD?uBzpD(+B3tNP|93LD~c9-F&XRh5%_a2O+dopxv&ZF)%{ofzdy zs5R1*BS7+qz16(M@y>GwVMmYqbR7|F-_9Bv?{_zg{A(t)PL^v3KYHhBLc9E55_F5} zMYAze#n~DM&V_Zs52x-n;XMsQSdw(;voG*4Gn-QieCQ~Dzn^u>&46kc=_~xuLdPG9#OuIGKe^hbNB)K z5cwgeM0zqd4xJED;N5lO$Wi2C?I9}twq*H=KCdt?%yk}yPP9;t#Y+jQ;2X}Z zOUm}R)y^f%!CixBYffPWs$3^qR+DI#t2iv*f%u-RXcRng)g6-&{isEX6pp=$c?z1A z)LY%1vySHty^%T3yBe%FBy2T(CDik1T2^c)C8Fb&=0yfAG3y7trPyd~fSt&vOy@C& zk;6-(_VOduT;2vkQvT>G6;bh9ygIvjhb2X>2$Y3qHZqFp*Flk55s2r>Go+K zr5k9C-21RF>WOVQtCg`wR^4HQtG5^bH;&@`w_{iFmF%k z*<@7hN(*c6>|*+tYFq~1c3Sf^X}eWnD>HkaJqPoeG7P{L_Iv;Ltj#zt6nqN?ghJuJT zEbut1=2*<2_I^)AUDQ&K(~+WDtWBauw)m*Oovrd%aF>Z`R%3fvBmw&M@*z^)YVzB@ z*MVM>3zU&Om!7WS*_Q~hH>FFESt#yiiTZnino)LMN;4VGs>~d8!?`)vaLRSUyUm1W zfCsKziMLo0FKcs!=G^?Yv4w8#%Ga}b-;#lUG?eGp&8Dz2NWXdsdJ82v$-9N=qapxG`r4@0M>RB&drctow{aCFYwm^x#TelADgD z*gfdxa6G%sz{r*VfjLhs@yRW6qvqCM$2Qw@Nm9OeQuUpE?#0wbX)t!J;m5()U2Sfk zxO$j3{>UOt@kvi&W8U=_o4Gye^mn&s|AC49Y0qIrO7HM5rs5>w}QA7o`X|=6uU5)XQjI?K1W0dPuc(FmI^bGIaQHD zwfN|1VXSbcgmfFOfo||W;a*O+&6f21mAl}S_n9+e=avckkJIp77xHQ5Shgx$)QB+J zxyrh)=^E;5fA zHar47Z<9=V7HMGnt^bPBWbt2<=`(Td$)>Yt|CVU#tKD+I8pqx`wCCKjgWB`u#Rxfi zs!`Fp02BR=djMb%^r$*gR`VN0^q@F?sXR4gNK;FS>mPl7;IDpU-lPxXTe_x3Ct2E_ zm3fP$L6zHIW5n!}oh>MA7F8(jL$G}}ztDn;sS+;VIjSqoTEg453MlV0zf~q75rBJY zgh=ML8`ad7ypuqmi}Ie|=UCbq^DW}AeKA}2wx6_MxvHH8#%n$t_e}pz4<#bN*&blx zqr?7L(}$kR=e^qfI(aeDL~i3M3M#t`)yS9jey`60p8r$`e#DF*r`MQ#vxNcPgF0wH zVeD_(6$7EAJdsD23a&iOL32!ynvM&Sk^Q!XY=JS&A?t!t9Oo!S>X0c=+AVkSA8RsvzYTq3`=b*Vk{4SZ!0S z?$V~gL#iZ17Ych`y=WWdu$Y=loqh9|>Ba9W2_G9N+7zW9G0lF`j}?C8Mg`mVDf>qLZ4n<63jIA}qBU1>pf zk4$&x(%1R#6KU7gGR|J~WK}wrQvp!_IFgxX1qM36FO@4t{*NRQpbufHwwlRGQ3o?=zw3;1n1wVWKy=6&ct89PmkZK!MKhq{~B7`cxfu8)v>tw@9zowPkS@XBX-KVeUD#2l) zFjM3vF`>CY#r|{0wD@tp(Jx&oD?;O`X#!tm921U3HX_+L)*vX;dd4Z5c+4PXp^7J;T8>GTXfxK) zURvRw|Dp7eWp8fk!sm=JQ5y3NDih0&>o#XVkKapw@!kxrYXLdP-xz1+7# zfYsBS(cKb8*I$v7m}g}`9FH6|%6oT(juv%ulC9}IsC6(&S@7JO>BqBw~Og-)J;v1 zelVOHkyEqWbE2=_*tpTgU3I>Hj0qgVk64LoY8g>_StBP8CKdKqdj7LK{-4xAI;IP8 zeb7pG0aMM3yQ@>U%xx7*@li{F=u2m--8h}K5;gLWrr8ldxYSaFH?G}$77YT~n&7Ql zsXob2*zwU8dM~~?cRx*#QANx+8E11>+xi&hR(WkVy03;^4ol6wb0eW+#fe3;A0Fh% zg_T@T;qsE4Vzp@rq3Dl)$FD9>2BXw>Ur(@ysyBgvF|^L?=@)fXUeeB&u)uDFc0wr^ z477EY01M4snYNkPe>M)WFqQhW-#QG;VH*%U|M`C2NS9;srHAqd=dDp`H%^>Uu6@Sx z<#74TYYm)^I~F3XIqam;3}RvtIOw|$V{Y};5T(IAnR1LAT~v1lgoWVilT1lPflV&0 zT0hPJdAK5G`m)Pr*K=XSBCE9v1qQ_-1@O#eHT6)4G`HkZcfEZ~e50Ik1RESm{;eUm z@g6O96bTC6-J&7_sA-(94=2Wr9AKJ=?eCOsc0)bf6$hx@afZhA(VeV_ zElSuPY_CtMapTq-js{_~_cu)F0&p~#Uy5HcQgJBn@P1I?cBwm7+PFZ@Idu) zEMwl7w_`ISmGM-BpZ}?Malu~;N@r{Ukn`!?)Cf69|<9fSZ**w8(C-d`1Ax$_bRzdvXZhU&mh6Lhp` zJqsq`gPoO0M?<dh$|B!Wv_amZ<%=qQapkX;AghSS;XNn6k$= zVT#N3*gDy}5bcXlZ?%I@w6^1Xq8F3Jlnha;%09(stH{omDhi#r6_H)?hfcJBf}Uh5 z3}mnU4eU@DP=k=Pw$|j2jP7YQY{D8bDEWAu{K^aR*v9fr+Hh=T3to$$Xd6meH~?fY6O_d3+?GhE9L#j`j54 zPeyGC3xcsPsN23NaHu8yoK_1T2o;qe?no~mERS7%NX`Z3Uul+C`T__&-U@I9W``Kp zvZ1J;&{{YE(xhJjsc$+$x$Uzk`7EE-wrqozzw93%IM*@!>BF^|kTZ>xOep!$b7ySl z*VyD9kQh(IN$i#UjdEOOa8T@3e-sYE@%D7pq^ z3iU@-{YdjA6pnDL{(--fkU-UW{sT%$5cUda%}T&|*r>qh9Jy%E>t|l9?A(%;#8pl5 z;46o8#4{L6s{B-Kz+qAK(6-}Y!39Q{dp>+`Ui#g*(89=^jTD6XF~M-apn`Hah>A&B zKd={->U3jN91;>5hpP2MHv9pAc3(VaU7l@o1X`#QQuQ$Gru$YX>*G(@?ApQ~}+& zVJAe1gwbVJSV`FELS1OVzQzhi|F9u#i{v;5EPZ#p9$g}&Qjd;f&3tNx&abXpXQh2_ z@j4kd*RMNP5T&X%?7|2F!t=z~dJV0t$7c|kZkQ`GEvJF{W3bY5yoOtM&P_JO@X|nM zB_KF2hXgQ}_l{#;OPkIq#s;g{!C_e-i8TSbbX&nVcnQ<~@&Vv`<&DOQeU#>3pX&N+ zEA~5EeNFl@l9*NEB5JMP<6>V$U+wMd+j8uUQa$cdoLUg3RJSYrBoO{I^Gb!xTxt1D z9^1A|0+J|H2FXSSjd#PrbETZX0rGghiwaJj0s)1NwoNnqN0;5Q^a!_U`p;_n;o2Jd z2LN04xf;T&>J43g*cvgT=*C)K0VX5j2qY^VHy~cF`g_?y;G9AT){Icw0NtFJ>uIgI z{{_A75)-?CULAh0ZfOrjg5pU1bmJNySdSTva6K=n(Z34I(ISr8;c$~5A)G%xG1BMX zwNt9g+X@d6*1K01t|3b*Kxo0Hu#++s2WTObW%Etlc^ofbv7R%51OoQz#;%C+#doPpDq0PN={=- z_Ad3RhTT$T%oANc&_I8T{$h`Ixz-02XO1tdD;$fC*J&?5DasDbCfNYIFdM=|XhNt`BjHHEn zt$XYYBOKLv0HyM4_w_hr@-2V=THb!xA4e%N;kK{!g zkdB63tL^shfzS6uW0wq;L+81Z`ZiK9(ibF%X!LM$XDAS__d#)w)4F! zZb=ws6t@;K1$>fl_9rMNs=io#%I6H8=fVtB^D=s|J~)0bsUZR6UjB%ggf9V_^YZ1i zYG6CxAUBGOOQxq;XZkygl>(_}mpW3hY9I-@&6mjd&?KUxmy&oj$J4HGPb!cSfDc(^RH+Xm=~WU* z@EX#;Il_v9yYBD3R31{OT9zGFYBBO7=;P1%?}QpLjmS#_=BD+*=k;4>fd$gsPWfq< z098NvD}#P>ttcOyE_efJ5UutW8~;!KNj53jy+O9D1~PFKJ8 z=2Y<31q}s&IDpF?K^5e2aLLfssmT*vNxjpw!N)!aQ6V`Ti=~kkT(-LATbq~ zITE%zjs)C;%ps@n(v~4xRTb>SyMYR+5^r?I`ni*)M}GM8C-YDGQ95h!l6#Xg9zg?N zM_44^?|qFcS_f3QHwtkpc=zmG^bL6}wRkEJ3{{q+{u>ygoDT-QaeHVka%Z2GNgt$9 z@aMe)|J?ak>Ol*UYPSPD6#M82i;+p&LUuyYcbdLs`R=&=VO^_lqnSCiKH3hAgxvp~ zEb`t2;K#Azhz4)lrsGasiaLBHQP zk3XybXDH6+VM}LMkx;13BBL~-BX=zSPIxGChk5_LR}8n(cOT`FB23}I4fbzccmg1i zF~NwsIq=b-BV<&)Es7jjAwdx{O}p9hzsvbqU2Gr)M3j8s;S&;!>2r66jIX&YY67pv z_>OYFPud=ZJN}>r#3}HjI>FFs`{MSX--X^g0N{$UvHm^~=4H@+-Pi03mpwsb`+=cp z9L8&F=q=5gu6UIKo|^OQcTpbA2gc^Rijs{rT={2^XcTKNNBk#NyR&n3+}C1IK>Y8} zId&VQZUoNhh6jjiJ9qWrHdll**CnV3RipRR*``BBmU0j8gQ#1p%{cE7BnH`(HU7-<4qr zeqv%8Q`g1cCIWZ(+Wb2!r-;b2?B97ZL5bh2g$X)^kMZNeWR${3u;lMlq~I0dFJbOp z@VGbhJN+eib;19E`R|WJ4(tBTtq9)y?{fcZyZ;%=|3<~{7Wn_+QHa>T<5zA*DT{cZ z*Ly9wB4y?k#@jpwY6bsF#jkDEH~w~aJ=j^kmeFS3X_B1BLGK>yq;A zSiLPpYM#i4J#z6a*xA!DcB{jd-2aG_T|tlNaq9n)N>Yi0Rv3TKu@b6I-#%~_- zzbq+>s8miaF=Hhp6h~I(QGTXF0Q~%M5BFLkh>`Y1*|uyk?h|YhS|>1DZhlrdJ_QdNc6g|NsbFjA$|yq>+{|m+L{^8% z3{x=II7^pZ(MC$;&ta9uMusZo|GY=MdSo{j1zBD6jZYCB$=j@g8aq#K-Y2GmHkm@R jY@OtUSJ0(b7;hQZ3|gPwy~rC9n$8t78`BCCj|cw;OU&&` literal 0 HcmV?d00001 From bebbe30b50fcb0925be20437a6cc6acca3d69f3a Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 15 Jun 2025 08:03:23 +0100 Subject: [PATCH 72/74] codeformat --- docs/iclc2023-paper/iclc2023.html | 1696 ++++++++++++++------------ docs/iclc2023-paper/pandoc/iclc.html | 135 +- 2 files changed, 975 insertions(+), 856 deletions(-) diff --git a/docs/iclc2023-paper/iclc2023.html b/docs/iclc2023-paper/iclc2023.html index a83e075a2..add08377f 100644 --- a/docs/iclc2023-paper/iclc2023.html +++ b/docs/iclc2023-paper/iclc2023.html @@ -1,458 +1,598 @@ - - - - - - Strudel: live coding patterns on the Web - - - - - -

+ + + + + + Strudel: live coding patterns on the Web + + + + + + -

Abstract

-
-

This paper introduces Strudel, which brings the TidalCycles approach -to live coding algorithmic patterns to native JavaScript and the web. We -begin by giving a little background of the first year of development, -before sharing some detail about its implementation and examples of use. -We go on to outline the wide range of synthesis and other outputs -available in Strudel, including WebAudio, MIDI, OSC (for SuperDirt), -WebSerial and CSound, and introduce Strudel’s REPL live editor, -including its built-in visualisations. We then compare Strudel with -Tidal, the trade-offs involved between JavaScript and Haskell, and the -unique capabilities offered by Strudel for aligning patterns.

-
+

Abstract

+
+

+ This paper introduces Strudel, which brings the TidalCycles approach to live coding algorithmic patterns to + native JavaScript and the web. We begin by giving a little background of the first year of development, before + sharing some detail about its implementation and examples of use. We go on to outline the wide range of + synthesis and other outputs available in Strudel, including WebAudio, MIDI, OSC (for SuperDirt), WebSerial and + CSound, and introduce Strudel’s REPL live editor, including its built-in visualisations. We then compare Strudel + with Tidal, the trade-offs involved between JavaScript and Haskell, and the unique capabilities offered by + Strudel for aligning patterns. +

+
-

1 Introduction

-

In the following paper, we introduce Strudel, an alternative -implementation of the TidalCycles (or ‘Tidal’ for short) live coding -system, using the JavaScript programming language. Strudel is an attempt -to make live coding more accessible, by creating a system that runs -entirely in the browser, while opening Tidal’s approach to algorithmic -patterns (Mclean 2020) up to -modern audio/visual web technologies. The Strudel REPL is a live code -editor dedicated to manipulating patterns while they play, with builtin -visual feedback. While Strudel is written in JavaScript, the API is -optimized for simplicity and readability by applying code -transformations on the syntax tree level, allowing language operations -that would otherwise be impossible. The application supports multiple -ways to output sound, including Tone.js, Web Audio Nodes, OSC (Open -Sound Control) messages, Web Serial, Web MIDI and Csound. The project is -split into multiple packages, allowing granular reuse in other -applications. Apart from TidalCycles, Strudel draws inspiration from -many prior existing projects like TidalVortex (McLean et al. 2022), -Gibber (Roberts and Kuchera-morin -2012), Estuary (Ogborn et al. -2017), Hydra (Jack [2022] 2022), Ocarina (Solomon -[2021] 2022) and Feedforward (McLean 2020). This paper -expands the Strudel Demo paper for the Web Audio Conference 2022 (Roos and McLean -2022).

-

The first tentative commit to the Strudel project was on 22nd January -2022 by Alex McLean, with the core representation implemented over the -following few days. Although this was his first attempt at a -JavaScript-based application, by 27th January, Alex had managed to -upload the initial version to the ‘npm’ javascript package database, -sharing with the wider community for comment. By 4th February, Felix -Roos had discovered Strudel and contributed a ‘REPL’ user interface to -it, and then contributed a scheduler the next day, so that Strudel could -already make sound. At this point, Alex and Felix shared ownership to -the repository, and the project has since proved to be a productive -confluence of Felix’s own work into music representation and -visualisation, with Alex’s experience with making Tidal. Felix has since -become the primary contributor to Strudel, with Alex continuing to jump -between developing both Strudel and Tidal. Aspects of Strudel’s -development have therefore fed back into TidalCycles, and both systems -have maintained a shared conceptual underpinning. We plan to continue -working towards feature parity between these systems, although within -the syntactical trade-offs and library ecosystems of JavaScript and -Haskell, some divergence is inevitable and healthy.

-

Over the first year of its life, Strudel is now a fully-fledged live -coding environment, porting Tidal’s core represention of patterns, -pattern transformations, and mininotation for polymetric sequences, -combined with a wealth of features for synthesising and visualising -those patterns.

-

2 From Tidal to Strudel and -back

-

As mentioned above, the original Tidal is implemented as a domain -specific language (DSL) embedded in the Haskell pure functional -programming language, and takes advantage of Haskell’s terse syntax and -advanced, ‘strong’ type system. JavaScript on the other hand, is a -multi-paradigm programming language, with a dynamic type system. Because -Tidal leans heavily on many of Haskell’s more unique features, it was -not always clear that it could meaningfully be ported to a -multi-paradigm scripting language. However, this possibility was already -demonstrated with an earlier port to Python [TidalVortex; McLean et al. -(2022)], and we have now successfully implemented Tidal’s pure -functional representation of patterns in Strudel, including partial -application, currying, and the functor, applicative and monadic -structures that underlie Tidal’s expressive pattern transformations. The -result is a terse and highly composable system, where everything is -either a pattern, or a function for combining and manipulating patterns, -offering a rich creative ground for exploration.

-

This development process has been far from a one-way port, however. -The process of porting Tidal’s concepts has also opened up new -possibilities, some just from revisiting every design decision, and some -from the particular affordances and constraints offered by JavaScript. -This has lead to new features (and indeed bugfixes) that have found -their way back to Tidal where appropriate, and ongoing work that we will -return to in the conclusion of this paper.

-

3 Representing Patterns

-

Patterns are the essence of Tidal. Its patterns are abstract entities -that represent flows of time as functions, adapting a technique called -pure functional reactive programming. Taking a time span as its input, a -Pattern can output a set of events that happen within that time span. It -depends on the structure of the Pattern how the events are located in -time. From now on, this process of generating events from a time span -will be called querying. Example:

-
const pattern = sequence(c3, [e3, g3])
+    

1 Introduction

+

+ In the following paper, we introduce Strudel, an alternative implementation of the TidalCycles (or + ‘Tidal’ for short) live coding system, using the JavaScript programming language. Strudel is an attempt to make + live coding more accessible, by creating a system that runs entirely in the browser, while opening Tidal’s + approach to algorithmic patterns + (Mclean 2020) up to modern audio/visual + web technologies. The Strudel REPL is a live code editor dedicated to manipulating patterns while they play, with + builtin visual feedback. While Strudel is written in JavaScript, the API is optimized for simplicity and + readability by applying code transformations on the syntax tree level, allowing language operations that would + otherwise be impossible. The application supports multiple ways to output sound, including Tone.js, Web Audio + Nodes, OSC (Open Sound Control) messages, Web Serial, Web MIDI and Csound. The project is split into multiple + packages, allowing granular reuse in other applications. Apart from TidalCycles, Strudel draws inspiration from + many prior existing projects like TidalVortex + (McLean et al. 2022), Gibber + (Roberts and Kuchera-morin 2012), Estuary + (Ogborn et al. 2017), Hydra + (Jack [2022] 2022), Ocarina + (Solomon [2021] 2022) and Feedforward + (McLean 2020). This paper expands the Strudel + Demo paper for the Web Audio Conference 2022 + (Roos and McLean 2022). +

+

+ The first tentative commit to the Strudel project was on 22nd January 2022 by Alex McLean, with the core + representation implemented over the following few days. Although this was his first attempt at a JavaScript-based + application, by 27th January, Alex had managed to upload the initial version to the ‘npm’ javascript package + database, sharing with the wider community for comment. By 4th February, Felix Roos had discovered Strudel and + contributed a ‘REPL’ user interface to it, and then contributed a scheduler the next day, so that Strudel could + already make sound. At this point, Alex and Felix shared ownership to the repository, and the project has since + proved to be a productive confluence of Felix’s own work into music representation and visualisation, with Alex’s + experience with making Tidal. Felix has since become the primary contributor to Strudel, with Alex continuing to + jump between developing both Strudel and Tidal. Aspects of Strudel’s development have therefore fed back into + TidalCycles, and both systems have maintained a shared conceptual underpinning. We plan to continue working + towards feature parity between these systems, although within the syntactical trade-offs and library ecosystems of + JavaScript and Haskell, some divergence is inevitable and healthy. +

+

+ Over the first year of its life, Strudel is now a fully-fledged live coding environment, porting Tidal’s core + represention of patterns, pattern transformations, and mininotation for polymetric sequences, combined with a + wealth of features for synthesising and visualising those patterns. +

+

+ 2 From Tidal to Strudel and back +

+

+ As mentioned above, the original Tidal is implemented as a domain specific language (DSL) embedded in the Haskell + pure functional programming language, and takes advantage of Haskell’s terse syntax and advanced, ‘strong’ type + system. JavaScript on the other hand, is a multi-paradigm programming language, with a dynamic type system. + Because Tidal leans heavily on many of Haskell’s more unique features, it was not always clear that it could + meaningfully be ported to a multi-paradigm scripting language. However, this possibility was already demonstrated + with an earlier port to Python [TidalVortex; + McLean et al. (2022)], and we have now + successfully implemented Tidal’s pure functional representation of patterns in Strudel, including partial + application, currying, and the functor, applicative and monadic structures that underlie Tidal’s expressive + pattern transformations. The result is a terse and highly composable system, where everything is either a pattern, + or a function for combining and manipulating patterns, offering a rich creative ground for exploration. +

+

+ This development process has been far from a one-way port, however. The process of porting Tidal’s concepts has + also opened up new possibilities, some just from revisiting every design decision, and some from the particular + affordances and constraints offered by JavaScript. This has lead to new features (and indeed bugfixes) that have + found their way back to Tidal where appropriate, and ongoing work that we will return to in the conclusion of this + paper. +

+

+ 3 Representing Patterns +

+

+ Patterns are the essence of Tidal. Its patterns are abstract entities that represent flows of time as functions, + adapting a technique called pure functional reactive programming. Taking a time span as its input, a Pattern can + output a set of events that happen within that time span. It depends on the structure of the Pattern how the + events are located in time. From now on, this process of generating events from a time span will be called + querying. Example: +

+
+
const pattern = sequence(c3, [e3, g3])
 const events = pattern.queryArc(0, 1)
-console.log(events.map(e => e.show()))
-

In this example, we create a pattern using the sequence -function and query it for the time span from -0 to 1. Those numbers represent units of time -called cycles. The length of one cycle depends on the -tempo, which defaults to one cycle per second. The resulting events -are:

-
[{ value: 'c3', begin: 0, end: 1/2 },
+console.log(events.map(e => e.show()))
+
+

+ In this example, we create a pattern using the sequence function and query it for + the time span from 0 to 1. Those numbers represent units of time called + cycles. The length of one cycle depends on the tempo, which defaults to one cycle per second. The + resulting events are: +

+
+
[{ value: 'c3', begin: 0, end: 1/2 },
 { value: 'e3', begin: 1/2, end: 3/4 },
-{ value: 'g3', begin: 3/4, end: 1 }]
-

Each event has a value, a begin time and an end time, where time is -represented as a fraction. In the above case, the events are placed in -sequential order, where c3 takes the first half, and e3 and g3 together -take the second half. This temporal placement is the result of the -sequence function, which divides its arguments equally over -one cycle. If an argument is an array, the same rule applies to that -part of the cycle. In the example, e3 and g3 are divided equally over -the second half of the whole cycle.

-

The above examples do not represent how Strudel is used in practice. -In the live coding editor, the user only has to type in the pattern -itself, the querying will be handled by the scheduler. The scheduler -will repeatedly query the pattern for events, which are then scheduled -as sound synthesis or other event triggers. Also, the above event data -structure has been simplified for readability.

-
- - -
-

4 Making Patterns

-

In practice, the end-user live coder will not deal with constructing -patterns directly, but will rather build patterns using Strudel’s -extensive combinator library to create, combine and transform -patterns.

-

The live coder will rarely use the sequence function as -seen above, as sequencing is implicit in many functions. For example in -the following, the note function constructs a pattern of -notes, sequencing its arguments in the same manner as the previous -example.

-
note(c3, [e3, g3])
-

Perhaps more often, they will use the mini-notation for even terser -notation of rhythmic sequences: [^This last example is also valid Tidal -code, albeit the parenthesis is not required in its Haskell syntax in -this case. Tidal does not support passing sequences as lists directly to -the note function, however.].

-
note("c3 [e3 g3]")
-

Such sequences are often treated only a starting point for -manipulation, where they then undergo pattern transformations such as -repetition, symmetry, interference/combination or randomisation, -potentially at multiple timescales. Because Strudel patterns are -represented as pure functions of time rather than as data structures, -very long and complex generative results can be represented and -manipulated without having to store the resulting sequences in -memory.

-

5 Pattern Example

-

The following example showcases how patterns can be utilized to -create musical complexity from simple parts, using repetition and -interference:

-
"<0 2 [4 6](3,4,1) 3>"
+{ value: 'g3', begin: 3/4, end: 1 }]
+
+

+ Each event has a value, a begin time and an end time, where time is represented as a fraction. In the above case, + the events are placed in sequential order, where c3 takes the first half, and e3 and g3 together take the second + half. This temporal placement is the result of the sequence function, which divides its arguments + equally over one cycle. If an argument is an array, the same rule applies to that part of the cycle. In the + example, e3 and g3 are divided equally over the second half of the whole cycle. +

+

+ The above examples do not represent how Strudel is used in practice. In the live coding editor, the user only has + to type in the pattern itself, the querying will be handled by the scheduler. The scheduler will repeatedly query + the pattern for events, which are then scheduled as sound synthesis or other event triggers. Also, the above event + data structure has been simplified for readability. +

+
+ Screenshot of the Strudel ‘REPL’ live coding editor, including piano-roll visualisation. + +
+

4 Making Patterns

+

+ In practice, the end-user live coder will not deal with constructing patterns directly, but will rather build + patterns using Strudel’s extensive combinator library to create, combine and transform patterns. +

+

+ The live coder will rarely use the sequence function as seen above, as sequencing is implicit in many + functions. For example in the following, the note function constructs a pattern of notes, sequencing + its arguments in the same manner as the previous example. +

+
+
note(c3, [e3, g3])
+
+

+ Perhaps more often, they will use the mini-notation for even terser notation of rhythmic sequences: [^This last + example is also valid Tidal code, albeit the parenthesis is not required in its Haskell syntax in this case. Tidal + does not support passing sequences as lists directly to the note function, however.]. +

+
+
note("c3 [e3 g3]")
+
+

+ Such sequences are often treated only a starting point for manipulation, where they then undergo pattern + transformations such as repetition, symmetry, interference/combination or randomisation, potentially at multiple + timescales. Because Strudel patterns are represented as pure functions of time rather than as data structures, + very long and complex generative results can be represented and manipulated without having to store the resulting + sequences in memory. +

+

5 Pattern Example

+

+ The following example showcases how patterns can be utilized to create musical complexity from simple parts, using + repetition and interference: +

+
+
"<0 2 [4 6](3,4,1) 3>"
 .off(1/4, add(2))
 .off(1/2, add(6))
 .scale('D minor')
 .legato(.25)
 .note().s("sawtooth square")
-.delay(.8).delaytime(.125)
-

The pattern starts with a rhythm of numbers in mini notation, which -are later interpreted inside the scale of D minor. The first line could -also be expressed without mini notation:

-
cat(0, 2, [4, 6].euclid(3, 4, 1), 3)
-

These numbers then undergo various pattern transformations. Here is a -short description of all the functions used:

-
    -
  • cat: play elements sequentially, where each lasts one -cycle
  • -
  • brackets: elements inside brackets are divided equally -over the time of their parent
  • -
  • .euclid(p, s, o): place p pulses evenly over s steps, -with offset o (Toussaint -2005)
  • -
  • .off(n, f): layers a pattern on top of itself, with the -new layer offset by n cycles, and with function f applied
  • -
  • .legato(n): multiply the duration of all events in a -pattern by a factor of n
  • -
  • .echo(t, n, v): copy each event t times, with n cycles -in between each copy, decreasing velocity by v
  • -
  • .note(): interpretes values as notes
  • -
  • .s(name): play back each event with the given -sound
  • -
  • .delay(wet): add delay
  • -
  • .delaytime(t): set delay time
  • -
-

Much of the above will be familiar to Tidal users.

- -

6 Ways to make Sound (and other -events)

-

To generate sound, Strudel supports bindings for different -outputs:

-
    -
  • Tone.js (deprecated)
  • -
  • Web Audio API
  • -
  • WebDirt, a js recreation of Tidal’s Dirt sample engine -(deprecated)
  • -
  • OSC via osc-js, compatible with superdirt
  • -
  • Csound via the Csound WebAssembly build
  • -
  • MIDI via WebMIDI
  • -
  • Serial via WebSerial
  • -
-

At first, we used Tone.js as sound output, but it proved to be -limited for the use case of Strudel, where each individual event could -potentially have a completely different audio graph. While the Web Audio -API takes a fire-and-forget approach, creating a lot of Tone.js -instruments and effects causes performance issues quickly. For that -reason, we chose to search for alternatives.

-

Strudel’s new default output uses the Web Audio API to create a new -audio graph for each event. It currently supports basic oscillators, -sample playback, various effects and an experimental support for -soundfonts.

-

WebDirt (Ogborn [2016] 2022) was -created as part of the Estuary Live Coding System (Ogborn et al. -2017), and proved to be a solid choice for handling samples in -Strudel as well. We are however focused on working more directly with -the Web Audio API to be able to integrate new features more tightly.

-

Using the OSC protocol via Strudel’s provided Node.js-based OSC proxy -server, it is possible to send network messages to trigger events. This -is mainly used to render sound using SuperDirt (SuperDirt [2015] 2022), -which is the well-developed Supercollider-based synthesis framework that -Tidal live coders generally use as standard.

-

Recently, the experimental integration of Csound proved to bring a -new dimension of sound design capabilities to Strudel. Thanks to the -WebAssembly distribution of this classic system (Yi, Lazzarini, and Costello -2018), Csound ‘orchestra’ synthesisers can be embedded in and -then patterned with Strudel code.

-

MIDI output can also be used to send MIDI messages to either external -instruments or to other programs on the same device. Unlike OSC, Strudel -is able to send MIDI directly without requiring additional proxy -software, but only from web browsers that support it (at the time of -writing, this means Chromium-based browsers).

-

Finally, Strudel supports Serial output, for example to trigger -events via microcontrollers. This has already been explored for robot -choreography by Kate Sicchio and Alex McLean, via a performance -presented at the International Conference on Live Interfaces 2022.

-

7 The Strudel REPL

-

While Strudel can be used as a library in any JavaScript codebase, -its main, reference user interface is the Strudel REPL[^REPL stands for -read, evaluate, print/play, loop. It is friendly jargon for an -interactive programming interface from computing heritage, usually for a -commandline interface but also applied to live coding editors.], which -is a browser-based live coding environment. This live code editor is -dedicated to manipulating Strudel patterns while they play. The REPL -features built-in visual feedback, which highlights which elements in -the patterned (mini-notation) sequences are influencing the event that -is currently being played. This feedback is designed to support both -learning and live use of Strudel.

-

Besides a UI for playback control and meta information, the main part -of the REPL interface is the code editor powered by CodeMirror. In it, -the user can edit and evaluate pattern code live, using one of the -available synthesis outputs to create music and/or sound art. The -control flow of the REPL follows 3 basic steps:

-
    -
  1. The user writes and updates code. Each update transpiles and -evaluates it to create a Pattern instance
  2. -
  3. While the REPL is running, the Scheduler queries the -active Pattern by a regular interval, generating -Events (also known as Haps in Strudel) for the -next time span.
  4. -
  5. For each scheduling tick, all generated Events are -triggered by calling their onTrigger method, which is set -by the output.
  6. -
-
-REPL control flow - -
-

7.1 User Code

-

To create a Pattern from the user code, two steps are -needed:

-
    -
  1. Transpile the JS input code to make it functional
  2. -
  3. Evaluate the transpiled code
  4. -
-

7.1.1 Transpilation & -Evaluation

-

In the JavaScript world, using transpilation is a common practise to -be able to use language features that are not supported by the base -language. Tools like babel will transpile code that -contains unsupported language features into a version of the code -without those features.

-

In the same tradition, Strudel can add a transpilation step to -simplify the user code in the context of live coding. For example, the -Strudel REPL lets the user create mini notation patterns using just -double quoted strings, while single quoted strings remain what they -are:

-
"c3 [e3 g3]*2"
-

is transpiled to:

-
mini("c3 [e3 g3]*2").withMiniLocation([1,0,0],[1,14,14])
-

Here, the string is wrapped in mini, which will create a -pattern from a mini notation string. Additionally, the -withMiniLocation method passes the original source code -location of the string to the pattern, which enables highlighting active -events.

-

Other convenient features like pseudo variables, operator overloading -and top level await are possible with transpilation.

-

After the transpilation, the code is ready to be evaluated into a -Pattern.

-

Behind the scenes, the user code string is parsed with -acorn, turning it into an Abstract Syntax Tree (AST). The -AST allows changing the structure of the code before generating the -transpiled version using escodegen.

-

7.1.2 Mini Notation

-

While the transpilation allows JavaScript to express Patterns in a -less verbose way, it is still preferable to use the Mini Notation as a -more compact way to express rhythm. Strudel aims to provide the same -Mini Notation features and syntax as used in Tidal.

-

The Mini Notation parser is implemented using peggy, -which allows generating performant parsers for Domain Specific Languages -(DSLs) using a concise grammar notation. The generated parser turns the -Mini Notation string into an AST which is used to call the respective -Strudel functions with the given structure. For example, -"c3 [e3 g3]*2" will result in the following calls:

-
seq(
+.delay(.8).delaytime(.125)
+
+

+ The pattern starts with a rhythm of numbers in mini notation, which are later interpreted inside the scale of D + minor. The first line could also be expressed without mini notation: +

+
+
cat(0, 2, [4, 6].euclid(3, 4, 1), 3)
+
+

+ These numbers then undergo various pattern transformations. Here is a short description of all the functions used: +

+
    +
  • cat: play elements sequentially, where each lasts one cycle
  • +
  • brackets: elements inside brackets are divided equally over the time of their parent
  • +
  • + .euclid(p, s, o): place p pulses evenly over s steps, with offset o + (Toussaint 2005) +
  • +
  • + .off(n, f): layers a pattern on top of itself, with the new layer offset by n cycles, and with + function f applied +
  • +
  • .legato(n): multiply the duration of all events in a pattern by a factor of n
  • +
  • + .echo(t, n, v): copy each event t times, with n cycles in between each copy, decreasing velocity by + v +
  • +
  • .note(): interpretes values as notes
  • +
  • .s(name): play back each event with the given sound
  • +
  • .delay(wet): add delay
  • +
  • .delaytime(t): set delay time
  • +
+

Much of the above will be familiar to Tidal users.

+ +

+ 6 Ways to make Sound (and other events) +

+

To generate sound, Strudel supports bindings for different outputs:

+
    +
  • Tone.js (deprecated)
  • +
  • Web Audio API
  • +
  • WebDirt, a js recreation of Tidal’s Dirt sample engine (deprecated)
  • +
  • OSC via osc-js, compatible with superdirt
  • +
  • Csound via the Csound WebAssembly build
  • +
  • MIDI via WebMIDI
  • +
  • Serial via WebSerial
  • +
+

+ At first, we used Tone.js as sound output, but it proved to be limited for the use case of Strudel, where each + individual event could potentially have a completely different audio graph. While the Web Audio API takes a + fire-and-forget approach, creating a lot of Tone.js instruments and effects causes performance issues + quickly. For that reason, we chose to search for alternatives. +

+

+ Strudel’s new default output uses the Web Audio API to create a new audio graph for each event. It currently + supports basic oscillators, sample playback, various effects and an experimental support for soundfonts. +

+

+ WebDirt (Ogborn [2016] 2022) was created as part + of the Estuary Live Coding System + (Ogborn et al. 2017), and + proved to be a solid choice for handling samples in Strudel as well. We are however focused on working more + directly with the Web Audio API to be able to integrate new features more tightly. +

+

+ Using the OSC protocol via Strudel’s provided Node.js-based OSC proxy server, it is possible to send network + messages to trigger events. This is mainly used to render sound using SuperDirt + (SuperDirt [2015] 2022), which is the + well-developed Supercollider-based synthesis framework that Tidal live coders generally use as standard. +

+

+ Recently, the experimental integration of Csound proved to bring a new dimension of sound design capabilities to + Strudel. Thanks to the WebAssembly distribution of this classic system + (Yi, Lazzarini, and Costello 2018), Csound + ‘orchestra’ synthesisers can be embedded in and then patterned with Strudel code. +

+

+ MIDI output can also be used to send MIDI messages to either external instruments or to other programs on the same + device. Unlike OSC, Strudel is able to send MIDI directly without requiring additional proxy software, but only + from web browsers that support it (at the time of writing, this means Chromium-based browsers). +

+

+ Finally, Strudel supports Serial output, for example to trigger events via microcontrollers. This has already been + explored for robot choreography by Kate Sicchio and Alex McLean, via a performance presented at the International + Conference on Live Interfaces 2022. +

+

7 The Strudel REPL

+

+ While Strudel can be used as a library in any JavaScript codebase, its main, reference user interface is the + Strudel REPL[^REPL stands for read, evaluate, print/play, loop. It is friendly jargon for an interactive + programming interface from computing heritage, usually for a commandline interface but also applied to live coding + editors.], which is a browser-based live coding environment. This live code editor is dedicated to manipulating + Strudel patterns while they play. The REPL features built-in visual feedback, which highlights which elements in + the patterned (mini-notation) sequences are influencing the event that is currently being played. This feedback is + designed to support both learning and live use of Strudel. +

+

+ Besides a UI for playback control and meta information, the main part of the REPL interface is the code editor + powered by CodeMirror. In it, the user can edit and evaluate pattern code live, using one of the available + synthesis outputs to create music and/or sound art. The control flow of the REPL follows 3 basic steps: +

+
    +
  1. + The user writes and updates code. Each update transpiles and evaluates it to create a + Pattern instance +
  2. +
  3. + While the REPL is running, the Scheduler queries the active Pattern by a regular + interval, generating Events (also known as Haps in Strudel) for the next time span. +
  4. +
  5. + For each scheduling tick, all generated Events are triggered by calling their + onTrigger method, which is set by the output. +
  6. +
+
+ REPL control flow + +
+

7.1 User Code

+

To create a Pattern from the user code, two steps are needed:

+
    +
  1. Transpile the JS input code to make it functional
  2. +
  3. Evaluate the transpiled code
  4. +
+

+ 7.1.1 Transpilation & Evaluation +

+

+ In the JavaScript world, using transpilation is a common practise to be able to use language features that are not + supported by the base language. Tools like babel will transpile code that contains unsupported + language features into a version of the code without those features. +

+

+ In the same tradition, Strudel can add a transpilation step to simplify the user code in the context of live + coding. For example, the Strudel REPL lets the user create mini notation patterns using just double quoted + strings, while single quoted strings remain what they are: +

+
+
"c3 [e3 g3]*2"
+
+

is transpiled to:

+
+
mini("c3 [e3 g3]*2").withMiniLocation([1,0,0],[1,14,14])
+
+

+ Here, the string is wrapped in mini, which will create a pattern from a mini notation string. + Additionally, the withMiniLocation method passes the original source code location of the string to + the pattern, which enables highlighting active events. +

+

+ Other convenient features like pseudo variables, operator overloading and top level await are possible with + transpilation. +

+

After the transpilation, the code is ready to be evaluated into a Pattern.

+

+ Behind the scenes, the user code string is parsed with acorn, turning it into an Abstract Syntax Tree + (AST). The AST allows changing the structure of the code before generating the transpiled version using + escodegen. +

+

7.1.2 Mini Notation

+

+ While the transpilation allows JavaScript to express Patterns in a less verbose way, it is still preferable to use + the Mini Notation as a more compact way to express rhythm. Strudel aims to provide the same Mini Notation features + and syntax as used in Tidal. +

+

+ The Mini Notation parser is implemented using peggy, which allows generating performant parsers for + Domain Specific Languages (DSLs) using a concise grammar notation. The generated parser turns the Mini Notation + string into an AST which is used to call the respective Strudel functions with the given structure. For example, + "c3 [e3 g3]*2" will result in the following calls: +

+
+
seq(
   reify('c3').withLocation([1,1,1], [1,4,4]),
   seq(
     reify('e3').withLocation([1,5,5], [1,8,8]),
     reify('g3').withLocation([1,8,8], [1,10,10]),
   ).fast(2)
-)
-

7.1.3 Highlighting Locations

-

As seen in the examples above, both the JS and the Mini Notation -parser add source code locations using withMiniLocation and -withLocation methods. While the JS parser adds locations -relative to the user code as a whole, the Mini Notation adds locations -relative to the position of the mini notation string. The absolute -location of elements within Mini Notation can be calculated by simply -adding both locations together. This absolute location can be used to -highlight active events in real time.

-

7.2 Scheduling Events

-

After an instance of Pattern is obtained from the user -code, it is used by the scheduler to get queried for events. Once -started, the scheduler runs at a fixed interval to query active pattern -for events withing the current interval’s time span. A simplified -implementation looks like this:

-
let pattern = seq('c3', ['e3', 'g3']); // pattern from user
+)
+
+

+ 7.1.3 Highlighting Locations +

+

+ As seen in the examples above, both the JS and the Mini Notation parser add source code locations using + withMiniLocation and withLocation methods. While the JS parser adds locations relative + to the user code as a whole, the Mini Notation adds locations relative to the position of the mini notation + string. The absolute location of elements within Mini Notation can be calculated by simply adding both locations + together. This absolute location can be used to highlight active events in real time. +

+

7.2 Scheduling Events

+

+ After an instance of Pattern is obtained from the user code, it is used by the scheduler to get + queried for events. Once started, the scheduler runs at a fixed interval to query active pattern for events + withing the current interval’s time span. A simplified implementation looks like this: +

+
+
let pattern = seq('c3', ['e3', 'g3']); // pattern from user
 let interval = 0.5; // query interval in seconds
 let time = 0; // beginning of current time span
 let minLatency = .1; // min time before a hap should trigger
@@ -463,70 +603,80 @@ class="sourceCode js">    const deadline = hap.whole.begin - time + minLatency;
     onTrigger(hap, deadline, duration);
   });
-}, interval * 1000); // query each "interval" seconds
-

Note that the above code is simplified for illustrative purposes. The -actual implementation has to work around imprecise callbacks of -setInterval. More about the implementation details can be -read in this -blog post.

-

The fact that Pattern.queryArc is a pure function that -maps a time span to a set of events allows us to choose any interval we -like without changing the resulting output. It also means that when the -pattern is changed from outside, the next scheduling callback will work -with the new pattern, keeping its clock running.

-

The latency between the time the pattern is evaluated and the change -is heard is between minLatency and -interval + minLatency, in our example between 100ms and -600ms. In Strudel, the current query interval is 50ms with a minLatency -of 100ms, meaning the latency is between 50ms and 150ms.

-

7.3 Output

-

The last step is to trigger each event in the chosen output. This is -where the given time and value of each event is used to generate audio -or any other form of time based output. The default output of the -Strudel REPL is the WebAudio output. To understand what an output does, -we first have to understand what control parameters are.

-

7.3.1 Control Parameters

-

To be able to manipulate multiple aspects of sound in parallel, so -called control parameters are used to shape the value of each event. -Example:

-
note("c3 e3").cutoff(1000).s('sawtooth')
+}, interval * 1000); // query each "interval" seconds
+
+

+ Note that the above code is simplified for illustrative purposes. The actual implementation has to work around + imprecise callbacks of setInterval. More about the implementation details can be read in + this blog post. +

+

+ The fact that Pattern.queryArc is a pure function that maps a time span to a set of events allows us + to choose any interval we like without changing the resulting output. It also means that when the pattern is + changed from outside, the next scheduling callback will work with the new pattern, keeping its clock running. +

+

+ The latency between the time the pattern is evaluated and the change is heard is between + minLatency and interval + minLatency, in our example between 100ms and 600ms. In + Strudel, the current query interval is 50ms with a minLatency of 100ms, meaning the latency is between 50ms and + 150ms. +

+

7.3 Output

+

+ The last step is to trigger each event in the chosen output. This is where the given time and value of each event + is used to generate audio or any other form of time based output. The default output of the Strudel REPL is the + WebAudio output. To understand what an output does, we first have to understand what control parameters are. +

+

+ 7.3.1 Control Parameters +

+

+ To be able to manipulate multiple aspects of sound in parallel, so called control parameters are used to shape the + value of each event. Example: +

+
+
note("c3 e3").cutoff(1000).s('sawtooth')
   .queryArc(0, 1).map(hap => hap.value)
 /* [
   { note: 'c3', cutoff: 1000, s: 'sawtooth' }
   { note: 'e3', cutoff: 1000, s: 'sawtooth' }
-] */
-

Here, the control parameter functions note, -cutoff and s are used, where each controls a -different property in the value object. Each control parameter function -accepts a primitive value, a list of values to be sequenced into a -Pattern, or a Pattern. In the example, -note gets a Pattern from a Mini Notation -expression (double quoted), while cutoff and s -are given a Number and a (single quoted) -String respectively.

-

Strudel comes with a large default set of control parameter functions -that are based on the ones used by Tidal and SuperDirt, focusing on -music and audio terminology. It is however possible to create custom -control paramters for any purpose:

-
const { x, y } = createParams('x', 'y')
-x(sine.range(0, 200)).y(cosine.range(0,200))
-

This example creates the custom control parameters x and -y which are then used to form a pattern that descibes the -coordinates of a circle.

-

7.3.2 Outputs

-

Now that we know how the value of an event is manipulated using -control parameters, we can look at how outputs can use that value to -generate anything. The scheduler above was calling the -onTrigger function which is used to implement the output. A -very simple version of the web audio output could look like this:

-
function onTrigger(hap, deadline, duration) {
+] */
+
+

+ Here, the control parameter functions note, cutoff and s are used, where + each controls a different property in the value object. Each control parameter function accepts a primitive value, + a list of values to be sequenced into a Pattern, or a Pattern. In the example, + note gets a Pattern from a Mini Notation expression (double quoted), while + cutoff and s are given a Number and a (single quoted) + String respectively. +

+

+ Strudel comes with a large default set of control parameter functions that are based on the ones used by Tidal and + SuperDirt, focusing on music and audio terminology. It is however possible to create custom control paramters for + any purpose: +

+
+
const { x, y } = createParams('x', 'y')
+x(sine.range(0, 200)).y(cosine.range(0,200))
+
+

+ This example creates the custom control parameters x and y which are then used to form a + pattern that descibes the coordinates of a circle. +

+

7.3.2 Outputs

+

+ Now that we know how the value of an event is manipulated using control parameters, we can look at how outputs can + use that value to generate anything. The scheduler above was calling the onTrigger function which is + used to implement the output. A very simple version of the web audio output could look like this: +

+
+
function onTrigger(hap, deadline, duration) {
   const { note } = hap.value;
   const time = getAudioContext().currentTime + deadline;
   const o = getAudioContext().createOscillator();
@@ -534,285 +684,271 @@ class="sourceCode js">  o.start(time);
   o.stop(time + event.duration);
   o.connect(getAudioContext().destination);
-}
-

The above example will create an OscillatorNode for each -event, where the frequency is controlled by the note param. -In essence, this is how the WebAudio API output of Strudel works, only -with many more parameters to control synths, samples and effects.

-

8 Pattern alignment and -combination

-

One core aspect of Strudel, inherited from Tidal, is the flexible way -that patterns can be combined, irrespective of their structure. Its -declarative approach means a live coder does not have to think about the -details of how this is done, only what is to be -done.

-

As a simple example, consider two number patterns -"0 [1 2] 3", and "10 20". The first has three -contiguous steps of equal lengths, with the second step broken down into -two substeps, giving four events in total. There are a very large number -of ways in which the structure of these two patterns could be combined, -but the default method in both Strudel and Tidal is to line up the -cycles of the two patterns, and then take events from the first pattern -and match them with those in the second pattern. Therefore, the -following two lines are equivalent:

-
"0 [1 2] 3".add("10 20")
-"10 [11 22] 23"
-

Where the events only partially overlap, they are treated as -fragments of the event in the first pattern. This is a little difficult -to conceptualise, but lets start by comparing the two patterns in the -following example:

-
"0 1 2".add("10 20")
-"10 [11 21] 20"
-

They are similar to the previous example in that the number -1 is split in two, with its two halves added to -10 and 20 respectively. However, the -11 ‘remembers’ that it is a fragment of that original -1 event, and so is treated as having a duration of a third -of a cycle, despite only being active for a sixth of a cycle. Likewise, -the 21 is also a fragment of that original 1 -event, but a fragment of its second half. Because the start of its event -is missing, it wouldn’t actually trigger a sound (unless it underwent -further pattern transformations/combinations).

-

In practice, the effect of this default, implicit method for -combining two patterns is that the second pattern is added in -to the first one, and indeed this can be made explicit:

-
"0 1 2".add.in("10 20")
-

This makes way for other ways to align the pattern, and several are -already defined, in particular:

-
    -
  • in - as explained above, aligns cycles, and applies -values from the pattern on the right in to the pattern on the -left.
  • -
  • out - as with in, but values are applied -out of the pattern on the left (i.e. in to the one on -the right).
  • -
  • mix - structures from both patterns are combined, so -that the new events are not fragments but are created at intersections -of events from both sides.
  • -
  • squeeze - cycles from the pattern on the right are -squeezed into events on the left. So that -e.g. "0 1 2".add.squeeze("10 20") is equivalent to -"[10 20] [11 21] [12 22]".
  • -
  • squeezeout - as with squeeze, but cycles -from the left are squeezed into events on the right. So, -"0 1 2".add.squeezeout("10 20") is equivalent to -[10 11 12] [20 21 22].
  • -
  • trig is similar to squeezeout in that -cycles from the right are aligned with events on the left. However those -cycles are not ‘squeezed’, rather they are truncated to fit the event. -So "0 1 2 3 4 5 6 7".add.trig("10 [20 30]") would be -equivalent to 10 11 12 13 20 21 30 31. In effect, events on -the right ‘trigger’ cycles on the left.
  • -
  • trigzero is similar to trig, but the -pattern is ‘triggered’ from its very first cycle, rather than from the -current cycle. trig and trigzero therefore -only give different results where the leftmost pattern differs from one -cycle to the next.
  • -
-

We will save going deeper into the background, design and -practicalities of these alignment functions for future publications. -However in the next section, we take them as a case study for looking at -the different design affordances offered by Haskell to Tidal, and -JavaScript to Strudel.

-

9 Comparing Strudel and Haskell in -use

-

Unlike Haskell, JavaScript lacks the ability to define custom infix -operators, or change the meaning of existing ones. So the above Strudel -example of "0 1 2".add.out("10 20") is equivalent to the -Tidal expression "0 1 2" +| "10 20", where the vertical bar -in the operator +| stands for out (where -a |+ b would be equivalent of -a.add.in(b)).

-

From this we can already see that Tidal tends towards brevity through -mixing infix operators with functions, and Strudel tends towards -spelling out operations which are joined together with the -. operator. This then is the design trade-off of Tidal’s -tersity, versus Strudel’s simplicity.

-

To demonstrate this, consider the following Tidal pattern:

-
iter 4 $ every 3 (||+ n "10 20") $ (n "0 1 3") # s "triangle" # crush 4
-

This can be directly translated to the Strudel equivalent:

-
iter(4, every(3, add.squeeze("10 20"), n("0 1 3").s("triangle").crush(4)))
-

Although for a more canonical Strudel expression, we would reorder it -as:

-
n("0 1 3").every(3, add.squeeze("10 20")).iter(4).s("triangle").crush(4)
-

The Strudel example uses the . method call operator for -all operations and combinations, whereas the Tidal example has -# for the default method for combining patterns and uses -infix operators for other methods. The lack of parenthesis in the Tidal -example is partly due to the way that arguments are applied to Haskell’s -functions, and partly due to the use of the $ operator as -an alternative way to establish precedence and control the order of -evaluation.

-

Considering the above, we argue that the Haskell syntax is a little -cleaner, but that the Strudel syntax is easier to learn. Our informal -observation is that while Haskell’s dollar $ operator is -very useful in making code easier to work with, it is one of the most -difficult aspects of Tidal use for beginners to learn. On the other -hand, the deeper levels of parenthesis in Strudel code can be difficult -to keep track of, especially while coding under pressure of live musical -performance. However this difficulty can be largely be mitigated by -reordering expressions, and further mitigated by supporting editor -features.

-

With Strudel, we have little choice but to embrace the affordances -and constraints offered by JavaScript, and while designing a -domain-specific language entirely based on method calls is a challenge, -through creative adoption of functional programming techniques like -partial application, we are so far very happy with the results. Tidal’s -functional reactive approach to pattern-making has in general translated -well to JavaScript, and opportunities and constraints have overall -traded off to create a very approachable and useable live coding -environment.

-

9.1 The trade-off of flexible -typing

-

We have identified one problem with porting Tidal to JavaScript where -we have missed Haskell’s strict typing and type inference. In both Tidal -and Strudel, time is rational, where any point in time is represented as -the ratio of two integers. This allows representation of musical ratios -such that are impossible to represent accurately using the more common -floating point numbers. However while libraries are available that -support rational numbers in JavaScript, the lack of strict typing means -that it is easy to implement pattern methods where computationally -expensive conversion from floating point to rational numbers are -performed late, and therefore often enough to overload the CPUs, due to -the large number of iterative calculations required to estimate a ratio -for a given floating point number. To mitigate this problem, we might -consider moving to TypeScript in the future.

-

10 Future Outlook

-

The project is still young, with many features on the horizon. As -general guiding principles, Strudel aims to be

-
    -
  1. accessible
  2. -
  3. consistent with Tidal’s approach to pattern
  4. -
  5. modular and extensible
  6. -
-

While Haskell’s type system makes it a great language for the ongoing -development of Tidal’s inner representation of pattern, JavaScript’s -vibrant ecosystem, flexibility and accessibility makes it a great host -for more ad-hoc experiments, including interface design. For the future, -it is planned to integrate additional alternative sound engines such as -Glicol (Lan -[2020] 2022) and Faust (Faust - Programming -Language for Audio Applications and Plugins [2016] 2022). -Strudel is already approaching feature parity with Tidal, but there are -more Tidal functions to be ported, and work to be done to improve -compatibility with Tidal’s mininotation. Tidal version 2.0 is under -development, which brings a new representation for sequences to its -patterns, which will then be brought to Strudel. Besides sound, other -ways to render events are being explored, such as graphical, and -choreographic output. We are also looking into alternative ways of -editing patterns, including multi-user editing for network music, -parsing a novel syntax to escape the constraints of javascript, and -developing hardware/e-textile interfaces. In summary, there is a lot of -fun ahead.

-

11 Links

-

The Strudel REPL is available at https://strudel.cc, including an -interactive tutorial. The repository is at https://github.com/tidalcycles/strudel, all the code is -open source under the AGPL-3.0 License.

-

12 Acknowledgments

-

Thanks to the Strudel and wider Tidal, live coding, WebAudio and -free/open source software communities for inspiration and support. Alex -McLean’s work on this project is supported by a UKRI Future Leaders -Fellowship [grant number MR/V025260/1].

-

References

-
-
-Faust - Programming Language for Audio Applications and -Plugins. (2016) 2022. C++. GRAME. https://github.com/grame-cncm/faust. -
-
-Jack, Olivia. (2022) 2022. Hydra. https://github.com/ojack/hydra. -
-
-Lan, Qichao. (2020) 2022. Chaosprint/Glicol. Rust. https://github.com/chaosprint/glicol. -
-
-Mclean, Alex. 2020. “Algorithmic Pattern.” In -Proceedings of the International Conference on New Interfaces for -Musical Expression, 265--270. Birmingham, UK. https://zenodo.org/record/4813352. -
-
-McLean, Alex. 2020. “Feedforward.” In Proceedings of -New Interfaces for Musical Expression. Birmingham. https://zenodo.org/record/6353969. -
-
-McLean, Alex, Raphaël Forment, Sylvain Le Beux, and Damián Silvani. -2022. “TidalVortex Zero.” In Proceedings of the 7th -International Conference on Live Coding. Limerick, Ireland: Zenodo. -https://doi.org/10.5281/zenodo.6456380. -
-
-Ogborn, David. (2016) 2022. Dktr0/WebDirt. JavaScript. https://github.com/dktr0/WebDirt. -
-
-Ogborn, David, Jamie Beverley, Luis Navarro del Angel, Eldad Tsabary, -and Alex McLean. 2017. “Estuary: Browser-Based Collaborative -Projectional Live Coding of Musical Patterns.” In Proceedings -of the International Conference on Live Coding, 11. Morelia. -
-
-Roberts, Charles, and Joann Kuchera-morin. 2012. “Gibber: Live -Coding Audio in the Browser.” In In Proceedings of the 2012 -International Computer Music Conference. -
-
-Roos, Felix, and Alex McLean. 2022. “Strudel: Algorithmic Patterns -for the Web.” In. Zenodo. https://doi.org/10.5281/zenodo.6768844. -
-
-Solomon, Mike. (2021) 2022. Purescript-Ocarina. PureScript. https://github.com/mikesol/purescript-ocarina. -
-
-SuperDirt. (2015) 2022. SuperCollider. musikinformatik. https://github.com/musikinformatik/SuperDirt. -
-
-Toussaint, Godfried. 2005. “The Euclidean Algorithm Generates -Traditional Musical Rhythms.” In In Proceedings of BRIDGES: -Mathematical Connections in Art, Music and Science, 47–56. http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.62.231. -
-
-Yi, Steven, Victor Lazzarini, and Edward Costello. 2018. -“WebAssembly AudioWorklet Csound.” In. Berlin, Germany. https://mural.maynoothuniversity.ie/16018/. -
-
- +}
+
+

+ The above example will create an OscillatorNode for each event, where the frequency is controlled by + the note param. In essence, this is how the WebAudio API output of Strudel works, only with many more + parameters to control synths, samples and effects. +

+

+ 8 Pattern alignment and combination +

+

+ One core aspect of Strudel, inherited from Tidal, is the flexible way that patterns can be combined, irrespective + of their structure. Its declarative approach means a live coder does not have to think about the details of + how this is done, only what is to be done. +

+

+ As a simple example, consider two number patterns "0 [1 2] 3", and "10 20". The first + has three contiguous steps of equal lengths, with the second step broken down into two substeps, giving four + events in total. There are a very large number of ways in which the structure of these two patterns could be + combined, but the default method in both Strudel and Tidal is to line up the cycles of the two patterns, and then + take events from the first pattern and match them with those in the second pattern. Therefore, the following two + lines are equivalent: +

+
+
"0 [1 2] 3".add("10 20")
+"10 [11 22] 23"
+
+

+ Where the events only partially overlap, they are treated as fragments of the event in the first pattern. This is + a little difficult to conceptualise, but lets start by comparing the two patterns in the following example: +

+
+
"0 1 2".add("10 20")
+"10 [11 21] 20"
+
+

+ They are similar to the previous example in that the number 1 is split in two, with its two halves + added to 10 and 20 respectively. However, the 11 ‘remembers’ that it is a + fragment of that original 1 event, and so is treated as having a duration of a third of a cycle, + despite only being active for a sixth of a cycle. Likewise, the 21 is also a fragment of that + original 1 event, but a fragment of its second half. Because the start of its event is missing, it + wouldn’t actually trigger a sound (unless it underwent further pattern transformations/combinations). +

+

+ In practice, the effect of this default, implicit method for combining two patterns is that the second pattern is + added in to the first one, and indeed this can be made explicit: +

+
+
"0 1 2".add.in("10 20")
+
+

This makes way for other ways to align the pattern, and several are already defined, in particular:

+
    +
  • + in - as explained above, aligns cycles, and applies values from the pattern on the right + in to the pattern on the left. +
  • +
  • + out - as with in, but values are applied out of the pattern on the left + (i.e. in to the one on the right). +
  • +
  • + mix - structures from both patterns are combined, so that the new events are not fragments but are + created at intersections of events from both sides. +
  • +
  • + squeeze - cycles from the pattern on the right are squeezed into events on the left. So that + e.g. "0 1 2".add.squeeze("10 20") is equivalent to "[10 20] [11 21] [12 22]". +
  • +
  • + squeezeout - as with squeeze, but cycles from the left are squeezed into events on the + right. So, "0 1 2".add.squeezeout("10 20") is equivalent to [10 11 12] [20 21 22]. +
  • +
  • + trig is similar to squeezeout in that cycles from the right are aligned with events on + the left. However those cycles are not ‘squeezed’, rather they are truncated to fit the event. So + "0 1 2 3 4 5 6 7".add.trig("10 [20 30]") would be equivalent to + 10 11 12 13 20 21 30 31. In effect, events on the right ‘trigger’ cycles on the left. +
  • +
  • + trigzero is similar to trig, but the pattern is ‘triggered’ from its very first cycle, + rather than from the current cycle. trig and trigzero therefore only give different + results where the leftmost pattern differs from one cycle to the next. +
  • +
+

+ We will save going deeper into the background, design and practicalities of these alignment functions for future + publications. However in the next section, we take them as a case study for looking at the different design + affordances offered by Haskell to Tidal, and JavaScript to Strudel. +

+

+ 9 Comparing Strudel and Haskell in use +

+

+ Unlike Haskell, JavaScript lacks the ability to define custom infix operators, or change the meaning of existing + ones. So the above Strudel example of "0 1 2".add.out("10 20") is equivalent to the Tidal expression + "0 1 2" +| "10 20", where the vertical bar in the operator +| stands for + out (where a |+ b would be equivalent of a.add.in(b)). +

+

+ From this we can already see that Tidal tends towards brevity through mixing infix operators with functions, and + Strudel tends towards spelling out operations which are joined together with the . operator. This + then is the design trade-off of Tidal’s tersity, versus Strudel’s simplicity. +

+

To demonstrate this, consider the following Tidal pattern:

+
iter 4 $ every 3 (||+ n "10 20") $ (n "0 1 3") # s "triangle" # crush 4
+

This can be directly translated to the Strudel equivalent:

+
+
iter(4, every(3, add.squeeze("10 20"), n("0 1 3").s("triangle").crush(4)))
+
+

Although for a more canonical Strudel expression, we would reorder it as:

+
+
n("0 1 3").every(3, add.squeeze("10 20")).iter(4).s("triangle").crush(4)
+
+

+ The Strudel example uses the . method call operator for all operations and combinations, whereas the + Tidal example has # for the default method for combining patterns and uses infix operators for other + methods. The lack of parenthesis in the Tidal example is partly due to the way that arguments are applied to + Haskell’s functions, and partly due to the use of the $ operator as an alternative way to establish + precedence and control the order of evaluation. +

+

+ Considering the above, we argue that the Haskell syntax is a little cleaner, but that the Strudel syntax is easier + to learn. Our informal observation is that while Haskell’s dollar $ operator is very useful in making + code easier to work with, it is one of the most difficult aspects of Tidal use for beginners to learn. On the + other hand, the deeper levels of parenthesis in Strudel code can be difficult to keep track of, especially while + coding under pressure of live musical performance. However this difficulty can be largely be mitigated by + reordering expressions, and further mitigated by supporting editor features. +

+

+ With Strudel, we have little choice but to embrace the affordances and constraints offered by JavaScript, and + while designing a domain-specific language entirely based on method calls is a challenge, through creative + adoption of functional programming techniques like partial application, we are so far very happy with the results. + Tidal’s functional reactive approach to pattern-making has in general translated well to JavaScript, and + opportunities and constraints have overall traded off to create a very approachable and useable live coding + environment. +

+

+ 9.1 The trade-off of flexible typing +

+

+ We have identified one problem with porting Tidal to JavaScript where we have missed Haskell’s strict typing and + type inference. In both Tidal and Strudel, time is rational, where any point in time is represented as the ratio + of two integers. This allows representation of musical ratios such that are impossible to represent accurately + using the more common floating point numbers. However while libraries are available that support rational numbers + in JavaScript, the lack of strict typing means that it is easy to implement pattern methods where computationally + expensive conversion from floating point to rational numbers are performed late, and therefore often enough to + overload the CPUs, due to the large number of iterative calculations required to estimate a ratio for a given + floating point number. To mitigate this problem, we might consider moving to TypeScript in the future. +

+

10 Future Outlook

+

+ The project is still young, with many features on the horizon. As general guiding principles, Strudel aims to be +

+
    +
  1. accessible
  2. +
  3. consistent with Tidal’s approach to pattern
  4. +
  5. modular and extensible
  6. +
+

+ While Haskell’s type system makes it a great language for the ongoing development of Tidal’s inner representation + of pattern, JavaScript’s vibrant ecosystem, flexibility and accessibility makes it a great host for more ad-hoc + experiments, including interface design. For the future, it is planned to integrate additional alternative sound + engines such as Glicol (Lan [2020] 2022) and + Faust + (Faust - Programming Language for Audio Applications and Plugins [2016] 2022). Strudel is already approaching feature parity with Tidal, but there are more Tidal functions to be ported, and + work to be done to improve compatibility with Tidal’s mininotation. Tidal version 2.0 is under development, which + brings a new representation for sequences to its patterns, which will then be brought to Strudel. Besides sound, + other ways to render events are being explored, such as graphical, and choreographic output. We are also looking + into alternative ways of editing patterns, including multi-user editing for network music, parsing a novel syntax + to escape the constraints of javascript, and developing hardware/e-textile interfaces. In summary, there is a lot + of fun ahead. +

+

11 Links

+

+ The Strudel REPL is available at https://strudel.cc, including an + interactive tutorial. The repository is at + https://github.com/tidalcycles/strudel, all the + code is open source under the AGPL-3.0 License. +

+

12 Acknowledgments

+

+ Thanks to the Strudel and wider Tidal, live coding, WebAudio and free/open source software communities for + inspiration and support. Alex McLean’s work on this project is supported by a UKRI Future Leaders Fellowship + [grant number MR/V025260/1]. +

+

References

+
+
+ Faust - Programming Language for Audio Applications and Plugins. (2016) 2022. C++. GRAME. + https://github.com/grame-cncm/faust. +
+
+ Jack, Olivia. (2022) 2022. Hydra. + https://github.com/ojack/hydra. +
+
+ Lan, Qichao. (2020) 2022. Chaosprint/Glicol. Rust. + https://github.com/chaosprint/glicol. +
+
+ Mclean, Alex. 2020. “Algorithmic Pattern.” In + Proceedings of the International Conference on New Interfaces for Musical Expression, 265--270. + Birmingham, UK. https://zenodo.org/record/4813352. +
+
+ McLean, Alex. 2020. “Feedforward.” In + Proceedings of New Interfaces for Musical Expression. Birmingham. + https://zenodo.org/record/6353969. +
+
+ McLean, Alex, Raphaël Forment, Sylvain Le Beux, and Damián Silvani. 2022. “TidalVortex Zero.” In + Proceedings of the 7th International Conference on Live Coding. Limerick, Ireland: Zenodo. + https://doi.org/10.5281/zenodo.6456380. +
+
+ Ogborn, David. (2016) 2022. Dktr0/WebDirt. JavaScript. + https://github.com/dktr0/WebDirt. +
+
+ Ogborn, David, Jamie Beverley, Luis Navarro del Angel, Eldad Tsabary, and Alex McLean. 2017. + “Estuary: Browser-Based Collaborative Projectional Live Coding of Musical Patterns.” In + Proceedings of the International Conference on Live Coding, 11. Morelia. +
+
+ Roberts, Charles, and Joann Kuchera-morin. 2012. “Gibber: Live Coding Audio in the Browser.” In + In Proceedings of the 2012 International Computer Music Conference. +
+
+ Roos, Felix, and Alex McLean. 2022. “Strudel: Algorithmic Patterns for the Web.” In. Zenodo. + https://doi.org/10.5281/zenodo.6768844. +
+
+ Solomon, Mike. (2021) 2022. Purescript-Ocarina. PureScript. + https://github.com/mikesol/purescript-ocarina. +
+
+ SuperDirt. (2015) 2022. SuperCollider. musikinformatik. + https://github.com/musikinformatik/SuperDirt. +
+
+ Toussaint, Godfried. 2005. “The Euclidean Algorithm Generates Traditional Musical Rhythms.” In + In Proceedings of BRIDGES: Mathematical Connections in Art, Music and Science, 47–56. + http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.62.231. +
+
+ Yi, Steven, Victor Lazzarini, and Edward Costello. 2018. + “WebAssembly AudioWorklet Csound.” In. Berlin, Germany. + https://mural.maynoothuniversity.ie/16018/. +
+
+ diff --git a/docs/iclc2023-paper/pandoc/iclc.html b/docs/iclc2023-paper/pandoc/iclc.html index 90406b16e..c47d6595c 100755 --- a/docs/iclc2023-paper/pandoc/iclc.html +++ b/docs/iclc2023-paper/pandoc/iclc.html @@ -1,80 +1,63 @@ -$if(false)$ - -This is a pandoc template and should not be edited. - -$endif$ +$if(false)$ This is a pandoc template and should not be edited. $endif$ - - - - - -$for(author-meta)$ - -$endfor$ -$if(date-meta)$ - -$endif$ - $if(title-prefix)$$title-prefix$ - $endif$$pagetitle$ - -$if(quotes)$ - -$endif$ -$if(highlighting-css)$ - -$endif$ - -$for(css)$ - -$endfor$ -$if(math)$ - $math$ -$endif$ -$for(header-includes)$ - $header-includes$ -$endfor$ - - -$for(include-before)$ -$include-before$ -$endfor$ -$if(title)$ -
-

$title$

-$if(subtitle)$ -

$subtitle$

-$endif$ -
    -$for(author)$ -
  • $author$
  • -$endfor$ -
-$if(date)$ -

$date$

-$endif$ -
-$endif$ -$if(toc)$ -
-$toc$ -
-$endif$ + + + + + + $for(author-meta)$ + + $endfor$ $if(date-meta)$ + + $endif$ + $if(title-prefix)$$title-prefix$ - $endif$$pagetitle$ + + $if(quotes)$ + + $endif$ $if(highlighting-css)$ + + $endif$ + + $for(css)$ + + $endfor$ $if(math)$ $math$ $endif$ $for(header-includes)$ $header-includes$ $endfor$ + + + $for(include-before)$ $include-before$ $endfor$ $if(title)$ +
+

$title$

+ $if(subtitle)$ +

$subtitle$

+ $endif$ +
    + $for(author)$ +
  • $author$
  • + $endfor$ +
+ $if(date)$ +

$date$

+ $endif$ +
+ $endif$ $if(toc)$ +
$toc$
+ $endif$ -

Abstract

-
-$if(abstract)$ -$abstract$ -$else$ -Please provide an abstract in the metadata block at the top of your -markdown document. Refer to template.txt for details. -$endif$ -
+

Abstract

+
+ $if(abstract)$ $abstract$ $else$ Please provide an abstract in the metadata block at the top of your markdown + document. Refer to template.txt for details. $endif$ +
-$body$ -$for(include-after)$ -$include-after$ -$endfor$ - + $body$ $for(include-after)$ $include-after$ $endfor$ + From e52017c0d8605a82c4475de82a9ce9e169ee0319 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 15 Jun 2025 10:12:17 +0200 Subject: [PATCH 73/74] fix: reference tab critical fail --- website/src/repl/components/panel/Reference.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/repl/components/panel/Reference.jsx b/website/src/repl/components/panel/Reference.jsx index 18721eb43..ab925eb91 100644 --- a/website/src/repl/components/panel/Reference.jsx +++ b/website/src/repl/components/panel/Reference.jsx @@ -70,7 +70,7 @@ export function Reference() {
    {entry.params?.map(({ name, type, description }, i) => (
  • - {name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)} : ''} + {name} : {type?.names?.join(' | ')} {description ? <> - {getInnerText(description)} : ''}
  • ))}
From 351314ccdf8a52831393724b107c26d1f9a76d13 Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 15 Jun 2025 09:23:29 +0100 Subject: [PATCH 74/74] rename .github to .forgejo --- {.github => .forgejo}/FUNDING.yml | 0 {.github => .forgejo}/workflows/deploy.yml | 0 {.github => .forgejo}/workflows/test.yml | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {.github => .forgejo}/FUNDING.yml (100%) rename {.github => .forgejo}/workflows/deploy.yml (100%) rename {.github => .forgejo}/workflows/test.yml (100%) diff --git a/.github/FUNDING.yml b/.forgejo/FUNDING.yml similarity index 100% rename from .github/FUNDING.yml rename to .forgejo/FUNDING.yml diff --git a/.github/workflows/deploy.yml b/.forgejo/workflows/deploy.yml similarity index 100% rename from .github/workflows/deploy.yml rename to .forgejo/workflows/deploy.yml diff --git a/.github/workflows/test.yml b/.forgejo/workflows/test.yml similarity index 100% rename from .github/workflows/test.yml rename to .forgejo/workflows/test.yml