From fdc201a799b841d4e2be20466fdeb56ae3c8c7d6 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 4 Sep 2023 06:03:05 +0200 Subject: [PATCH 01/74] Adding vibrato to synth oscillator --- packages/superdough/synth.mjs | 43 ++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index a409d990f..b216a4274 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -40,6 +40,8 @@ export function registerSynthSounds() { fmrelease: fmRelease, fmvelocity: fmVelocity, fmwave: fmWaveform = 'sine', + vibrato = 0, + vdepth = 100, } = value; let { n, note, freq } = value; // with synths, n and note are the same thing @@ -53,7 +55,7 @@ export function registerSynthSounds() { } // maybe pull out the above frequency resolution?? (there is also getFrequency but it has no default) // make oscillator - const { node: o, stop } = getOscillator({ t, s: wave, freq, partials: n }); + const { node: o, stop } = getOscillator({ t, s: wave, freq, vibrato, vdepth, partials: n }); // FM + FM envelope let stopFm, fmEnvelope; @@ -137,8 +139,14 @@ export function waveformN(partials, type) { return osc; } -export function getOscillator({ s, freq, t, partials }) { - // make oscillator +export function getOscillator({ s, freq, t, vibrato, vdepth, partials }) { + // Additional oscillator for vibrato effect + if (vibrato > 0) { + var vibrato_oscillator = getAudioContext().createOscillator(); + vibrato_oscillator.frequency.value = vibrato; + } + + // Make oscillator with partial count let o; if (!partials || s === 'sine') { o = getAudioContext().createOscillator(); @@ -146,9 +154,28 @@ export function getOscillator({ s, freq, t, partials }) { } else { o = waveformN(partials, s); } - o.frequency.value = Number(freq); - o.start(t); - //o.stop(t + duration + release); - const stop = (time) => o.stop(time); - return { node: o, stop }; + + if (vibrato > 0) { + // Vibrato by creating a gain node + o.frequency.value = Number(freq); + var gain = getAudioContext().createGain(); + gain.gain.value = vdepth * 100; + vibrato_oscillator.connect(gain); + gain.connect(o.detune); + vibrato_oscillator.start(t); + o.start(t); + return { + node: o, + stop: (time) => { + vibrato_oscillator.stop(time); + o.stop(time); + }, + }; + } else { + // Normal operation, without vibrato + o.frequency.value = Number(freq); + o.start(t); + const stop = (time) => o.stop(time); + return { node: o, stop }; + } } From 60f5032b1270084efa5185f4560e8c70da24e3a9 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 4 Sep 2023 06:25:49 +0200 Subject: [PATCH 02/74] Implement pitch slide --- packages/superdough/synth.mjs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index b216a4274..e1d7905c7 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -42,6 +42,8 @@ export function registerSynthSounds() { fmwave: fmWaveform = 'sine', vibrato = 0, vdepth = 100, + slide, + slide_speed = 1, } = value; let { n, note, freq } = value; // with synths, n and note are the same thing @@ -55,7 +57,16 @@ export function registerSynthSounds() { } // maybe pull out the above frequency resolution?? (there is also getFrequency but it has no default) // make oscillator - const { node: o, stop } = getOscillator({ t, s: wave, freq, vibrato, vdepth, partials: n }); + const { node: o, stop } = getOscillator({ + t, + s: wave, + freq, + vibrato, + vdepth, + slide: slide * freq, + slide_speed: sustain / slide_speed, + partials: n, + }); // FM + FM envelope let stopFm, fmEnvelope; @@ -139,7 +150,7 @@ export function waveformN(partials, type) { return osc; } -export function getOscillator({ s, freq, t, vibrato, vdepth, partials }) { +export function getOscillator({ s, freq, t, vibrato, vdepth, slide, slide_speed, partials }) { // Additional oscillator for vibrato effect if (vibrato > 0) { var vibrato_oscillator = getAudioContext().createOscillator(); @@ -156,8 +167,8 @@ export function getOscillator({ s, freq, t, vibrato, vdepth, partials }) { } if (vibrato > 0) { - // Vibrato by creating a gain node o.frequency.value = Number(freq); + slide > 0 && o.frequency.linearRampToValueAtTime(freq + slide, t + slide_speed); var gain = getAudioContext().createGain(); gain.gain.value = vdepth * 100; vibrato_oscillator.connect(gain); @@ -174,6 +185,7 @@ export function getOscillator({ s, freq, t, vibrato, vdepth, partials }) { } else { // Normal operation, without vibrato o.frequency.value = Number(freq); + slide > 0 && o.frequency.linearRampToValueAtTime(freq + slide, t + slide_speed); o.start(t); const stop = (time) => o.stop(time); return { node: o, stop }; From 3ba195c2d91af5cfe5675e0e8b00f3f242af688c Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 4 Sep 2023 18:38:05 +0200 Subject: [PATCH 03/74] add new controls + rename slide_speed > slidespeed --- packages/core/controls.mjs | 3 +++ packages/superdough/synth.mjs | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 71f584275..36a5728bd 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -664,7 +664,10 @@ const generic_params = [ // TODO: LFO rate see https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare ['rate'], // TODO: slide param for certain synths + ['vibrato'], + ['vdepth'], ['slide'], + ['slidespeed'], // TODO: detune? https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare ['semitone'], // TODO: dedup with synth param, see https://tidalcycles.org/docs/reference/synthesizers/#superpiano diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index e1d7905c7..4ebfb3acf 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -43,7 +43,7 @@ export function registerSynthSounds() { vibrato = 0, vdepth = 100, slide, - slide_speed = 1, + slidespeed = 1, } = value; let { n, note, freq } = value; // with synths, n and note are the same thing @@ -64,7 +64,7 @@ export function registerSynthSounds() { vibrato, vdepth, slide: slide * freq, - slide_speed: sustain / slide_speed, + slidespeed: sustain / slidespeed, partials: n, }); @@ -150,7 +150,7 @@ export function waveformN(partials, type) { return osc; } -export function getOscillator({ s, freq, t, vibrato, vdepth, slide, slide_speed, partials }) { +export function getOscillator({ s, freq, t, vibrato, vdepth, slide, slidespeed, partials }) { // Additional oscillator for vibrato effect if (vibrato > 0) { var vibrato_oscillator = getAudioContext().createOscillator(); @@ -168,7 +168,7 @@ export function getOscillator({ s, freq, t, vibrato, vdepth, slide, slide_speed, if (vibrato > 0) { o.frequency.value = Number(freq); - slide > 0 && o.frequency.linearRampToValueAtTime(freq + slide, t + slide_speed); + slide > 0 && o.frequency.linearRampToValueAtTime(freq + slide, t + slidespeed); var gain = getAudioContext().createGain(); gain.gain.value = vdepth * 100; vibrato_oscillator.connect(gain); @@ -185,7 +185,7 @@ export function getOscillator({ s, freq, t, vibrato, vdepth, slide, slide_speed, } else { // Normal operation, without vibrato o.frequency.value = Number(freq); - slide > 0 && o.frequency.linearRampToValueAtTime(freq + slide, t + slide_speed); + slide > 0 && o.frequency.linearRampToValueAtTime(freq + slide, t + slidespeed); o.start(t); const stop = (time) => o.stop(time); return { node: o, stop }; From fc9525e7d80f1a23332c7271c043dcd08be58ea9 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Tue, 5 Sep 2023 11:24:21 +0200 Subject: [PATCH 04/74] saner vibrato default --- packages/core/controls.mjs | 2 ++ packages/superdough/synth.mjs | 27 ++++++++++++++------------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 36a5728bd..c2cf917e5 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -393,6 +393,8 @@ const generic_params = [ */ // currently an alias of 'hcutoff' https://github.com/tidalcycles/strudel/issues/496 // ['hpf'], + [['vib'], 'vib'], + [['vibmod'], 'vibmod'], [['hcutoff', 'hresonance'], 'hpf', 'hp'], /** * Controls the **h**igh-**p**ass **q**-value. diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 4ebfb3acf..f02a1909e 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -40,10 +40,10 @@ export function registerSynthSounds() { fmrelease: fmRelease, fmvelocity: fmVelocity, fmwave: fmWaveform = 'sine', - vibrato = 0, - vdepth = 100, + vib = 0, + vibmod = 1, slide, - slidespeed = 1, + slide_speed = 1, } = value; let { n, note, freq } = value; // with synths, n and note are the same thing @@ -61,10 +61,10 @@ export function registerSynthSounds() { t, s: wave, freq, - vibrato, - vdepth, + vib, + vibmod, slide: slide * freq, - slidespeed: sustain / slidespeed, + slide_speed: sustain / slide_speed, partials: n, }); @@ -150,11 +150,11 @@ export function waveformN(partials, type) { return osc; } -export function getOscillator({ s, freq, t, vibrato, vdepth, slide, slidespeed, partials }) { +export function getOscillator({ s, freq, t, vib, vibmod, slide, slide_speed, partials }) { // Additional oscillator for vibrato effect - if (vibrato > 0) { + if (vib > 0) { var vibrato_oscillator = getAudioContext().createOscillator(); - vibrato_oscillator.frequency.value = vibrato; + vibrato_oscillator.frequency.value = vib; } // Make oscillator with partial count @@ -166,11 +166,12 @@ export function getOscillator({ s, freq, t, vibrato, vdepth, slide, slidespeed, o = waveformN(partials, s); } - if (vibrato > 0) { + if (vib > 0) { o.frequency.value = Number(freq); - slide > 0 && o.frequency.linearRampToValueAtTime(freq + slide, t + slidespeed); + slide > 0 && o.frequency.linearRampToValueAtTime(freq + slide, t + slide_speed); var gain = getAudioContext().createGain(); - gain.gain.value = vdepth * 100; + // Vibmod is the amount of vibrato, in semitones + gain.gain.value = vibmod * freq; vibrato_oscillator.connect(gain); gain.connect(o.detune); vibrato_oscillator.start(t); @@ -185,7 +186,7 @@ export function getOscillator({ s, freq, t, vibrato, vdepth, slide, slidespeed, } else { // Normal operation, without vibrato o.frequency.value = Number(freq); - slide > 0 && o.frequency.linearRampToValueAtTime(freq + slide, t + slidespeed); + slide > 0 && o.frequency.linearRampToValueAtTime(freq + slide, t + slide_speed); o.start(t); const stop = (time) => o.stop(time); return { node: o, stop }; From 76051987742f2703c78fab3378fb9e6490cc65d1 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Tue, 5 Sep 2023 11:27:42 +0200 Subject: [PATCH 05/74] parameter renaming --- packages/superdough/synth.mjs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index f02a1909e..4101aaee1 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -42,8 +42,8 @@ export function registerSynthSounds() { fmwave: fmWaveform = 'sine', vib = 0, vibmod = 1, - slide, - slide_speed = 1, + pitchJump, + pitchJumpSpeed = 1, } = value; let { n, note, freq } = value; // with synths, n and note are the same thing @@ -63,8 +63,8 @@ export function registerSynthSounds() { freq, vib, vibmod, - slide: slide * freq, - slide_speed: sustain / slide_speed, + pitchJump: pitchJump * freq, + pitchJumpSpeed: sustain / pitchJumpSpeed, partials: n, }); @@ -150,7 +150,7 @@ export function waveformN(partials, type) { return osc; } -export function getOscillator({ s, freq, t, vib, vibmod, slide, slide_speed, partials }) { +export function getOscillator({ s, freq, t, vib, vibmod, pitchJump, pitchJumpSpeed, partials }) { // Additional oscillator for vibrato effect if (vib > 0) { var vibrato_oscillator = getAudioContext().createOscillator(); @@ -168,7 +168,7 @@ export function getOscillator({ s, freq, t, vib, vibmod, slide, slide_speed, par if (vib > 0) { o.frequency.value = Number(freq); - slide > 0 && o.frequency.linearRampToValueAtTime(freq + slide, t + slide_speed); + slide > 0 && o.frequency.linearRampToValueAtTime(freq + pitchJump, t + pitchJumpSpeed); var gain = getAudioContext().createGain(); // Vibmod is the amount of vibrato, in semitones gain.gain.value = vibmod * freq; From 34318039807c8d0faa83b3bd032b609704a93ba7 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Fri, 8 Sep 2023 13:08:00 +0200 Subject: [PATCH 06/74] documenting vib and vibmod --- packages/core/controls.mjs | 23 ++++++++++++++++++++--- website/src/pages/learn/synths.mdx | 10 ++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index c2cf917e5..f7f029e05 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -393,9 +393,28 @@ const generic_params = [ */ // currently an alias of 'hcutoff' https://github.com/tidalcycles/strudel/issues/496 // ['hpf'], - [['vib'], 'vib'], + /** + * Applies a vibrato to the frequency of the oscillator. + * + * @name vib + * @param {number | Pattern} frequency of the vibrato in hertz + * @synonyms vibrato + * @example + * sound("triangle").freq(300).vib("<1 2 4 8 16>") + */ + [['vibrato'], 'vib'], + /** + * Sets the vibrato depth as a multiple of base frequency (not vibrato speed!). + * + * @name vibmod + * @param {number | Pattern} depth of vibrato (multiple of base frequency) + * @example + * sound("triangle").freq(300).vib("<8 16>").vibmod("<0.25 0.5 0.75 1 2 4>") + */ [['vibmod'], 'vibmod'], [['hcutoff', 'hresonance'], 'hpf', 'hp'], + ['vib'], + ['vibmod'], /** * Controls the **h**igh-**p**ass **q**-value. * @@ -666,8 +685,6 @@ const generic_params = [ // TODO: LFO rate see https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare ['rate'], // TODO: slide param for certain synths - ['vibrato'], - ['vdepth'], ['slide'], ['slidespeed'], // TODO: detune? https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare diff --git a/website/src/pages/learn/synths.mdx b/website/src/pages/learn/synths.mdx index b24726cfb..68bbee343 100644 --- a/website/src/pages/learn/synths.mdx +++ b/website/src/pages/learn/synths.mdx @@ -48,6 +48,16 @@ You can also set `n` directly in mini notation with `sound`: Note for tidal users: `n` in tidal is synonymous to `note` for synths only. In strudel, this is not the case, where `n` will always change timbre, be it though different samples or different waveforms. +### Vibrato + +#### vib + + + +#### vibmod + + + ## FM Synthesis FM Synthesis is a technique that changes the frequency of a basic waveform rapidly to alter the timbre. From 74442b0d76eb12b3a44e824c9422aaba6b8e9eb1 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Fri, 8 Sep 2023 13:43:43 +0200 Subject: [PATCH 07/74] documenting vibrato and removing broken slide/pitchJump mechanism --- packages/core/controls.mjs | 8 ++++++-- packages/superdough/synth.mjs | 8 +------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index f7f029e05..f6fcc89f3 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -686,6 +686,7 @@ const generic_params = [ ['rate'], // TODO: slide param for certain synths ['slide'], + ['slidespeed'], // TODO: detune? https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare ['semitone'], @@ -893,10 +894,13 @@ const generic_params = [ // ZZFX ['zrand'], ['curve'], - ['slide'], // superdirt duplicate - ['deltaSlide'], ['pitchJump'], ['pitchJumpTime'], + ['slide'], // superdirt duplicate + ['deltaSlide'], + /** + * + */ ['lfo', 'repeatTime'], ['noise'], ['zmod'], diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 4101aaee1..8a2403406 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -42,8 +42,6 @@ export function registerSynthSounds() { fmwave: fmWaveform = 'sine', vib = 0, vibmod = 1, - pitchJump, - pitchJumpSpeed = 1, } = value; let { n, note, freq } = value; // with synths, n and note are the same thing @@ -63,8 +61,6 @@ export function registerSynthSounds() { freq, vib, vibmod, - pitchJump: pitchJump * freq, - pitchJumpSpeed: sustain / pitchJumpSpeed, partials: n, }); @@ -150,7 +146,7 @@ export function waveformN(partials, type) { return osc; } -export function getOscillator({ s, freq, t, vib, vibmod, pitchJump, pitchJumpSpeed, partials }) { +export function getOscillator({ s, freq, t, vib, vibmod, partials }) { // Additional oscillator for vibrato effect if (vib > 0) { var vibrato_oscillator = getAudioContext().createOscillator(); @@ -168,7 +164,6 @@ export function getOscillator({ s, freq, t, vib, vibmod, pitchJump, pitchJumpSpe if (vib > 0) { o.frequency.value = Number(freq); - slide > 0 && o.frequency.linearRampToValueAtTime(freq + pitchJump, t + pitchJumpSpeed); var gain = getAudioContext().createGain(); // Vibmod is the amount of vibrato, in semitones gain.gain.value = vibmod * freq; @@ -186,7 +181,6 @@ export function getOscillator({ s, freq, t, vib, vibmod, pitchJump, pitchJumpSpe } else { // Normal operation, without vibrato o.frequency.value = Number(freq); - slide > 0 && o.frequency.linearRampToValueAtTime(freq + slide, t + slide_speed); o.start(t); const stop = (time) => o.stop(time); return { node: o, stop }; From 78ccec5d7b4922cc211fa5244601d9d68197da18 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Sun, 10 Sep 2023 09:14:41 +0200 Subject: [PATCH 08/74] adding test run --- test/__snapshots__/examples.test.mjs.snap | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 60b39c53d..76a7cf119 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -4557,6 +4557,24 @@ exports[`runs examples > example "velocity" example index 0 1`] = ` ] `; +exports[`runs examples > example "vib" example index 0 1`] = ` +[ + "[ 0/1 → 1/1 | s:triangle freq:300 vib:1 ]", + "[ 1/1 → 2/1 | s:triangle freq:300 vib:2 ]", + "[ 2/1 → 3/1 | s:triangle freq:300 vib:4 ]", + "[ 3/1 → 4/1 | s:triangle freq:300 vib:8 ]", +] +`; + +exports[`runs examples > example "vibmod" example index 0 1`] = ` +[ + "[ 0/1 → 1/1 | s:triangle freq:300 vib:8 vibmod:0.25 ]", + "[ 1/1 → 2/1 | s:triangle freq:300 vib:16 vibmod:0.5 ]", + "[ 2/1 → 3/1 | s:triangle freq:300 vib:8 vibmod:0.75 ]", + "[ 3/1 → 4/1 | s:triangle freq:300 vib:16 vibmod:1 ]", +] +`; + exports[`runs examples > example "voicing" example index 0 1`] = ` [ "[ 0/1 → 1/1 | note:E4 ]", From 3582ae9163e2c95bb7babe6fe4df4ebba44a65f7 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Sun, 10 Sep 2023 18:34:21 +0200 Subject: [PATCH 09/74] adding loopBegin and loopEnd --- packages/core/controls.mjs | 2 ++ packages/superdough/sampler.mjs | 17 +++++++---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 71f584275..2b99bdb3b 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -882,6 +882,8 @@ const generic_params = [ ['zdelay'], ['tremolo'], ['zzfx'], + ['loopBegin'], + ['loopEnd'], ]; // TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13 diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index 02e5eada7..60e615349 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -207,7 +207,9 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { n = 0, note, speed = 1, // sample playback speed + loopBegin = 0, begin = 0, + loopEnd = 1, end = 1, } = value; // load sample @@ -242,19 +244,14 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { // rather than the current playback rate, so even if the sound is playing at twice its normal speed, // the midway point through a 10-second audio buffer is still 5." const offset = begin * bufferSource.buffer.duration; - bufferSource.start(time, offset); const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value; - /*if (loop) { - // TODO: idea for loopBegin / loopEnd - // if one of [loopBegin,loopEnd] is <= 1, interpret it as normlized - // if [loopBegin,loopEnd] is bigger >= 1, interpret it as sample number - // this will simplify perfectly looping things, while still keeping the normalized option - // the only drawback is that looping between samples 0 and 1 is not possible (which is not real use case) + if (loop) { bufferSource.loop = true; - bufferSource.loopStart = offset; - bufferSource.loopEnd = offset + duration; + bufferSource.loopStart = loopBegin * bufferDuration - offset; + bufferSource.loopEnd = loopEnd * bufferDuration - offset; duration = loop * duration; - }*/ + } + bufferSource.start(time, offset); const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t); bufferSource.connect(envelope); const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox... From bac8594c5f1db32d618696c1dacf41bb88a33fb4 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Sun, 10 Sep 2023 20:36:09 +0200 Subject: [PATCH 10/74] beginning documentation work (breaks) --- packages/core/controls.mjs | 39 ++++++++++++++++------- test/__snapshots__/examples.test.mjs.snap | 18 +++++++++++ website/src/pages/learn/synths.mdx | 11 +++++++ 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 2b99bdb3b..4d427da36 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -309,6 +309,33 @@ const generic_params = [ * */ ['loop'], + /** + * @name loopBegin + * @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample + * @synonyms loopb + * @example + * s("numbers").loopBegin("<0 .25 .5 .75>").loop(1) + * + */ + ['loopBegin', 'loopb'], + /** + * @name loopEnd + * @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample + * @synonyms loope + * @example + * s("numbers").loopEnd("<0 .25 .5 .75>").loop(1) + * + */ + ['loopEnd', 'loope'], + /** + * bit crusher effect. + * + * @name crush + * @param {number | Pattern} depth between 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction). + * @example + * s(",hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>") + * + */ // TODO: currently duplicated with "native" legato // TODO: superdirt legato will do more: https://youtu.be/dQPmE1WaD1k?t=419 /** @@ -323,15 +350,6 @@ const generic_params = [ */ // ['legato'], // ['clhatdecay'], - /** - * bit crusher effect. - * - * @name crush - * @param {number | Pattern} depth between 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction). - * @example - * s(",hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>") - * - */ ['crush'], /** * fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers @@ -343,7 +361,6 @@ const generic_params = [ * */ ['coarse'], - /** * choose the channel the pattern is sent to in superdirt * @@ -882,8 +899,6 @@ const generic_params = [ ['zdelay'], ['tremolo'], ['zzfx'], - ['loopBegin'], - ['loopEnd'], ]; // TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13 diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 60b39c53d..3e2d81bd1 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -2566,6 +2566,24 @@ exports[`runs examples > example "loopAtCps" example index 0 1`] = ` ] `; +exports[`runs examples > example "loopBegin" example index 0 1`] = ` +[ + "[ 0/1 → 1/1 | s:numbers loopBegin:0 loop:1 ]", + "[ 1/1 → 2/1 | s:numbers loopBegin:0.25 loop:1 ]", + "[ 2/1 → 3/1 | s:numbers loopBegin:0.5 loop:1 ]", + "[ 3/1 → 4/1 | s:numbers loopBegin:0.75 loop:1 ]", +] +`; + +exports[`runs examples > example "loopEnd" example index 0 1`] = ` +[ + "[ 0/1 → 1/1 | s:numbers loopEnd:0 loop:1 ]", + "[ 1/1 → 2/1 | s:numbers loopEnd:0.25 loop:1 ]", + "[ 2/1 → 3/1 | s:numbers loopEnd:0.5 loop:1 ]", + "[ 3/1 → 4/1 | s:numbers loopEnd:0.75 loop:1 ]", +] +`; + exports[`runs examples > example "lpf" example index 0 1`] = ` [ "[ 0/1 → 1/3 | s:hh cutoff:4000 ]", diff --git a/website/src/pages/learn/synths.mdx b/website/src/pages/learn/synths.mdx index b24726cfb..6a2d73950 100644 --- a/website/src/pages/learn/synths.mdx +++ b/website/src/pages/learn/synths.mdx @@ -78,6 +78,17 @@ You can use fm with any of the above waveforms, although the below examples all +## Wavetable Synthesis + +Strudel can also use wavetables to create custom waveforms instead of the default ones used by WebAudio. There is a default set of wavetables accessible by default (approximatively 1000 coming from the [AKWF](https://www.adventurekid.se/akrt/waveforms/adventure-kid-waveforms/) set) but you can also import/use your own. A wavetable is a one-cycle waveform, which is then repeated to create a sound at the desired frequency. It is a classic but very effective synthesis technique. Wavetable synthesis is based on sample looping, using the `loop`, `loopBegin` and `loopEnd` properties of the sampler: + +- `loop`: either `0` (no loop) or `1` (loop) +- `loopBegin`: start of the loop (between 0 and 1) +- `loopEnd`: end of the loop (between 0 and 1) + + + + ## ZZFX The "Zuper Zmall Zound Zynth" [ZZFX](https://github.com/KilledByAPixel/ZzFX) is also integrated in strudel. From f09b89ed6320c3364b44aa60c9ec00a9efae4c72 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 10 Sep 2023 20:55:59 +0200 Subject: [PATCH 11/74] fix: duration for loops --- packages/superdough/sampler.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index 60e615349..139442995 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -262,7 +262,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { out.disconnect(); onended(); }; - const stop = (endTime, playWholeBuffer = clip === undefined) => { + const stop = (endTime, playWholeBuffer = clip === undefined && loop === undefined) => { let releaseTime = endTime; if (playWholeBuffer) { releaseTime = t + (end - begin) * bufferDuration; From ba27f2ba28a61fa4914ad394cc1e4c22946e000c Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 10 Sep 2023 21:05:57 +0200 Subject: [PATCH 12/74] fix: looped samples pitch --- packages/superdough/sampler.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index 139442995..26611c0a9 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -244,11 +244,10 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { // rather than the current playback rate, so even if the sound is playing at twice its normal speed, // the midway point through a 10-second audio buffer is still 5." const offset = begin * bufferSource.buffer.duration; - const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value; if (loop) { bufferSource.loop = true; - bufferSource.loopStart = loopBegin * bufferDuration - offset; - bufferSource.loopEnd = loopEnd * bufferDuration - offset; + bufferSource.loopStart = loopBegin * bufferSource.buffer.duration - offset; + bufferSource.loopEnd = loopEnd * bufferSource.buffer.duration - offset; duration = loop * duration; } bufferSource.start(time, offset); @@ -265,6 +264,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { const stop = (endTime, playWholeBuffer = clip === undefined && loop === undefined) => { let releaseTime = endTime; if (playWholeBuffer) { + const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value; releaseTime = t + (end - begin) * bufferDuration; } bufferSource.stop(releaseTime + release); From 6de04f77817c4ed739ec580ef3b1d638a790490e Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Sun, 10 Sep 2023 23:29:09 +0200 Subject: [PATCH 13/74] turn loop on for samples starting with wt_ --- packages/superdough/sampler.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index 26611c0a9..28f090829 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -196,7 +196,7 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option const cutGroups = []; export async function onTriggerSample(t, value, onended, bank, resolveUrl) { - const { + let { s, freq, unit, @@ -217,6 +217,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { // no playback return; } + loop = s.startsWith('wt_') ? 1 : value.loop; const ac = getAudioContext(); // destructure adsr here, because the default should be different for synths and samples const { attack = 0.001, decay = 0.001, sustain = 1, release = 0.001 } = value; From 7a74189771f2d14ab584275d9423f158ebb4d1d9 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 11 Sep 2023 00:41:55 +0200 Subject: [PATCH 14/74] document wavetable synthesis --- website/src/pages/learn/synths.mdx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/website/src/pages/learn/synths.mdx b/website/src/pages/learn/synths.mdx index 6a2d73950..26e944673 100644 --- a/website/src/pages/learn/synths.mdx +++ b/website/src/pages/learn/synths.mdx @@ -80,14 +80,20 @@ You can use fm with any of the above waveforms, although the below examples all ## Wavetable Synthesis -Strudel can also use wavetables to create custom waveforms instead of the default ones used by WebAudio. There is a default set of wavetables accessible by default (approximatively 1000 coming from the [AKWF](https://www.adventurekid.se/akrt/waveforms/adventure-kid-waveforms/) set) but you can also import/use your own. A wavetable is a one-cycle waveform, which is then repeated to create a sound at the desired frequency. It is a classic but very effective synthesis technique. Wavetable synthesis is based on sample looping, using the `loop`, `loopBegin` and `loopEnd` properties of the sampler: +Strudel can also use the sampler to load custom waveforms as a replacement of the default waveforms used by WebAudio for the base synth. A default set of more than 1000 wavetables is accessible by default (coming from the [AKWF](https://www.adventurekid.se/akrt/waveforms/adventure-kid-waveforms/) set). You can also import/use your own. A wavetable is a one-cycle waveform, which is then repeated to create a sound at the desired frequency. It is a classic but very effective synthesis technique. -- `loop`: either `0` (no loop) or `1` (loop) -- `loopBegin`: start of the loop (between 0 and 1) -- `loopEnd`: end of the loop (between 0 and 1) +Any sample preceded by the `wt_` prefix will be loaded as a wavetable. This means that the `loop` argument will be set to `1` by defalt. You can scan over the wavetable by using `loopBegin` and `loopEnd` as well. - - +") +.n("<1 2 3 4 5 6 7 8 9 10>/2").room(0.5).size(0.9) +.s('wt_flute').velocity(0.25).often(n => n.ply(2)) +.release(0.125).decay("<0.1 0.25 0.3 0.4>").sustain(0) +.cutoff(2000).scope({}).cutoff("<1000 2000 4000>").fast(2)`} +/> ## ZZFX From 843d9d52c33b33b6d4b3f556ee325e629b28c9ba Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 11 Sep 2023 00:46:31 +0200 Subject: [PATCH 15/74] add documentation but it crashes --- packages/core/controls.mjs | 13 +++++++++---- website/src/pages/learn/samples.mdx | 12 ++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 4d427da36..a490463de 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -314,8 +314,10 @@ const generic_params = [ * @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample * @synonyms loopb * @example - * s("numbers").loopBegin("<0 .25 .5 .75>").loop(1) - * + * s("numbers") + * .loop(1) + * .begin(0).end(1) + * .loopBegin("<0 .25 .5 .75>") */ ['loopBegin', 'loopb'], /** @@ -323,8 +325,11 @@ const generic_params = [ * @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample * @synonyms loope * @example - * s("numbers").loopEnd("<0 .25 .5 .75>").loop(1) - * + * s("numbers") + * .loop(1) + * .begin(0).end(1) + * .loopBegin("<0 .25 .5 .75>") + * .loopEnd("<0.1 .35 .6 .85>") */ ['loopEnd', 'loope'], /** diff --git a/website/src/pages/learn/samples.mdx b/website/src/pages/learn/samples.mdx index 9f79e54a5..b1ef1cd30 100644 --- a/website/src/pages/learn/samples.mdx +++ b/website/src/pages/learn/samples.mdx @@ -303,6 +303,18 @@ Sampler effects are functions that can be used to change the behaviour of sample +### loop + + + +### loopBegin + + + +### loopEnd + + + ### cut From 4a65113bff85ee36dba1511c2f17e9015496aaea Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 11 Sep 2023 00:52:49 +0200 Subject: [PATCH 16/74] do not panic when no description --- website/src/docs/JsDoc.jsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/website/src/docs/JsDoc.jsx b/website/src/docs/JsDoc.jsx index 88a775a5f..1ba80e0a9 100644 --- a/website/src/docs/JsDoc.jsx +++ b/website/src/docs/JsDoc.jsx @@ -12,10 +12,11 @@ export function JsDoc({ name, h = 3, hideDescription, punchcard, canvasHeight }) } const synonyms = getTag('synonyms', item)?.split(', ') || []; const CustomHeading = `h${h}`; - const description = item.description.replaceAll(/\{@link ([a-zA-Z\.]+)?#?([a-zA-Z]*)\}/g, (_, a, b) => { - // console.log(_, 'a', a, 'b', b); - return `${a}${b ? `#${b}` : ''}`; - }); + const description = + item.description?.replaceAll(/\{@link ([a-zA-Z\.]+)?#?([a-zA-Z]*)\}/g, (_, a, b) => { + // console.log(_, 'a', a, 'b', b); + return `${a}${b ? `#${b}` : ''}`; + }) || ''; return ( <> {!!h && {item.longname}} From 78adaaedef56b45095cdce922292989980838cb2 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 11 Sep 2023 00:56:43 +0200 Subject: [PATCH 17/74] fixing minor bugs and adding description --- packages/core/controls.mjs | 7 +++++++ packages/superdough/sampler.mjs | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index a490463de..42f98a179 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -310,6 +310,9 @@ const generic_params = [ */ ['loop'], /** + * Begin to loop at a specific point in the sample (inbetween `begin` and `end`). + * Note that the loop point must be inbetween `begin` and `end`, and before `loopEnd`! + * * @name loopBegin * @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample * @synonyms loopb @@ -321,6 +324,10 @@ const generic_params = [ */ ['loopBegin', 'loopb'], /** + * + * End the looping section at a specific point in the sample (inbetween `begin` and `end`). + * Note that the loop point must be inbetween `begin` and `end`, and after `loopBegin`! + * * @name loopEnd * @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample * @synonyms loope diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index 28f090829..76b6a542c 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -249,7 +249,6 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { bufferSource.loop = true; bufferSource.loopStart = loopBegin * bufferSource.buffer.duration - offset; bufferSource.loopEnd = loopEnd * bufferSource.buffer.duration - offset; - duration = loop * duration; } bufferSource.start(time, offset); const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t); From 4cb93ddcded1d14794787de057c1a59d1efbb1c1 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 13 Sep 2023 12:59:51 +0200 Subject: [PATCH 18/74] fix: eval scope --- website/src/repl/Repl.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 173bb4556..eb61a216c 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -33,7 +33,7 @@ const supabase = createClient( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', ); -const modules = [ +let modules = [ import('@strudel.cycles/core'), import('@strudel.cycles/tonal'), import('@strudel.cycles/mini'), @@ -45,13 +45,13 @@ const modules = [ import('@strudel.cycles/csound'), ]; if (isTauri()) { - modules.concat([ + modules = modules.concat([ import('@strudel/desktopbridge/loggerbridge.mjs'), import('@strudel/desktopbridge/midibridge.mjs'), import('@strudel/desktopbridge/oscbridge.mjs'), ]); } else { - modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); + modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); } const modulesLoading = evalScope( From 0a8874180cf5b22c85d8a844c7035623e7230573 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 13 Sep 2023 13:48:33 +0200 Subject: [PATCH 19/74] generalize getDevice + begin midiIn implementation --- packages/midi/midi.mjs | 49 ++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 98509b461..831c3afde 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -8,6 +8,7 @@ import * as _WebMidi from 'webmidi'; import { Pattern, isPattern, logger } from '@strudel.cycles/core'; import { noteToMidi } from '@strudel.cycles/core'; import { Note } from 'webmidi'; +import EventEmitter from 'events'; // if you use WebMidi from outside of this package, make sure to import that instance: export const { WebMidi } = _WebMidi; @@ -15,8 +16,8 @@ function supportsMidi() { return typeof navigator.requestMIDIAccess === 'function'; } -function getMidiDeviceNamesString(outputs) { - return outputs.map((o) => `'${o.name}'`).join(' | '); +function getMidiDeviceNamesString(devices) { + return devices.map((o) => `'${o.name}'`).join(' | '); } export function enableWebMidi(options = {}) { @@ -52,26 +53,24 @@ export function enableWebMidi(options = {}) { }); }); } -// const outputByName = (name: string) => WebMidi.getOutputByName(name); -const outputByName = (name) => WebMidi.getOutputByName(name); -// output?: string | number, outputs: typeof WebMidi.outputs -function getDevice(output, outputs) { - if (!outputs.length) { +function getDevice(indexOrName, devices) { + if (!devices.length) { throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`); } - if (typeof output === 'number') { - return outputs[output]; + if (typeof indexOrName === 'number') { + return devices[indexOrName]; } - if (typeof output === 'string') { - return outputByName(output); + const byName = (name) => devices.find((output) => output.name.includes(name)); + if (typeof indexOrName === 'string') { + return byName(indexOrName); } // attempt to default to first IAC device if none is specified - const IACOutput = outputs.find((output) => output.name.includes('IAC')); - const device = IACOutput ?? outputs[0]; + const IACOutput = byName('IAC'); + const device = IACOutput ?? devices[0]; if (!device) { throw new Error( - `🔌 MIDI device '${output ? output : ''}' not found. Use one of ${getMidiDeviceNamesString(WebMidi.outputs)}`, + `🔌 MIDI device '${device ? device : ''}' not found. Use one of ${getMidiDeviceNamesString(devices)}`, ); } @@ -137,3 +136,25 @@ Pattern.prototype.midi = function (output) { } }); }; + +export async function midiIn(input) { + console.log('midi in...'); + const initial = await enableWebMidi(); // only returns on first init + const device = getDevice(input, WebMidi.inputs); + + if (initial) { + const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name); + logger( + `Midi enabled! Using "${device.name}". ${ + otherInputs?.length ? `Also available: ${getMidiDeviceNamesString(otherInputs)}` : '' + }`, + ); + } + return (fn) => { + device.addListener(EventEmitter.ANY_EVENT, (...args) => { + console.log('event!', args); + fn(args); + }); + return; + }; +} From 0f72729f0de9fedd7e4bdccf8c717746351942c8 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Sep 2023 09:36:06 +0200 Subject: [PATCH 20/74] midi cc input poc --- packages/core/pattern.mjs | 6 ++++++ packages/midi/midi.mjs | 24 ++++++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 7e694f498..d29fde3f3 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -2336,3 +2336,9 @@ export const fit = register('fit', (pat) => export const { loopAtCps, loopatcps } = register(['loopAtCps', 'loopatcps'], function (factor, cps, pat) { return _loopAt(factor, pat, cps); }); + +/** exposes a custom value at query time. basically allows mutating state without evaluation */ +export const ref = (accessor) => + pure(1) + .withValue(() => reify(accessor())) + .innerJoin(); diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 831c3afde..95af837ec 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -8,7 +8,6 @@ import * as _WebMidi from 'webmidi'; import { Pattern, isPattern, logger } from '@strudel.cycles/core'; import { noteToMidi } from '@strudel.cycles/core'; import { Note } from 'webmidi'; -import EventEmitter from 'events'; // if you use WebMidi from outside of this package, make sure to import that instance: export const { WebMidi } = _WebMidi; @@ -137,7 +136,10 @@ Pattern.prototype.midi = function (output) { }); }; -export async function midiIn(input) { +let listeners = {}; +const refs = {}; + +export async function midin(input) { console.log('midi in...'); const initial = await enableWebMidi(); // only returns on first init const device = getDevice(input, WebMidi.inputs); @@ -149,12 +151,18 @@ export async function midiIn(input) { otherInputs?.length ? `Also available: ${getMidiDeviceNamesString(otherInputs)}` : '' }`, ); + refs[input] = {}; } - return (fn) => { - device.addListener(EventEmitter.ANY_EVENT, (...args) => { - console.log('event!', args); - fn(args); - }); - return; + const cc = (cc) => ref(() => refs[input][cc] || 0); + + listeners[input] && device.removeListener('midimessage', listeners[input]); + listeners[input] = (e) => { + const cc = e.dataBytes[0]; + const v = e.dataBytes[1]; + console.log(cc, v); + refs[input][cc] = v / 127; }; + device.addListener('midimessage', listeners[input]); + //return { cc }; + return cc; } From 3a69fd50bb7d0c314dae7f961e7beae1f6c674a1 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Sep 2023 09:39:31 +0200 Subject: [PATCH 21/74] fix: linting errors --- packages/midi/midi.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 95af837ec..bc65497a4 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th */ import * as _WebMidi from 'webmidi'; -import { Pattern, isPattern, logger } from '@strudel.cycles/core'; +import { Pattern, isPattern, logger, ref } from '@strudel.cycles/core'; import { noteToMidi } from '@strudel.cycles/core'; import { Note } from 'webmidi'; // if you use WebMidi from outside of this package, make sure to import that instance: @@ -73,7 +73,7 @@ function getDevice(indexOrName, devices) { ); } - return IACOutput ?? outputs[0]; + return IACOutput ?? devices[0]; } Pattern.prototype.midi = function (output) { From 30d96dcb65fd176ea6c515e9145d4181358de04a Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Sep 2023 16:28:47 +0200 Subject: [PATCH 22/74] remove log --- packages/midi/midi.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index bc65497a4..688b7a9b9 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -159,7 +159,6 @@ export async function midin(input) { listeners[input] = (e) => { const cc = e.dataBytes[0]; const v = e.dataBytes[1]; - console.log(cc, v); refs[input][cc] = v / 127; }; device.addListener('midimessage', listeners[input]); From 2deafe214a6c060decdeee0e36830b2f3e8972b5 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 07:59:55 +0200 Subject: [PATCH 23/74] update loop examples --- packages/core/controls.mjs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index b54170c19..0c166e111 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -299,13 +299,14 @@ const generic_params = [ */ ['end'], /** - * Loops the sample (from `begin` to `end`) the specified number of times. + * Loops the sample. * Note that the tempo of the loop is not synced with the cycle tempo. + * To change the loop region, use loopBegin / loopEnd. * * @name loop - * @param {number | Pattern} times How often the sample is looped + * @param {number | Pattern} on If 1, the sample is looped * @example - * s("bd").loop("<1 2 3 4>").osc() + * s("casio").loop(1) * */ ['loop'], @@ -314,13 +315,11 @@ const generic_params = [ * Note that the loop point must be inbetween `begin` and `end`, and before `loopEnd`! * * @name loopBegin - * @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample + * @param {number | Pattern} time between 0 and 1, where 1 is the length of the sample * @synonyms loopb * @example - * s("numbers") - * .loop(1) - * .begin(0).end(1) - * .loopBegin("<0 .25 .5 .75>") + * s("space").loop(1) + * .loopBegin("<0 .125 .25>").scope() */ ['loopBegin', 'loopb'], /** @@ -329,14 +328,11 @@ const generic_params = [ * Note that the loop point must be inbetween `begin` and `end`, and after `loopBegin`! * * @name loopEnd - * @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample + * @param {number | Pattern} time between 0 and 1, where 1 is the length of the sample * @synonyms loope * @example - * s("numbers") - * .loop(1) - * .begin(0).end(1) - * .loopBegin("<0 .25 .5 .75>") - * .loopEnd("<0.1 .35 .6 .85>") + * s("space").loop(1) + * .loopEnd("<1 .75 .5 .25>").scope() */ ['loopEnd', 'loope'], /** From b98e24fabf77b8acaa1567485224f53fff6b1c8d Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 08:02:02 +0200 Subject: [PATCH 24/74] docs: fit + splice --- packages/core/pattern.mjs | 19 +++++++++++++------ website/src/pages/learn/samples.mdx | 8 ++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 7e694f498..e3efb427b 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -2273,14 +2273,14 @@ export const slice = register( false, // turns off auto-patternification ); -/* +/** * Works the same as slice, but changes the playback speed of each slice to match the duration of its step. * @name splice - * @memberof Pattern - * @returns Pattern * @example * await samples('github:tidalcycles/Dirt-Samples/master') - * s("breaks165").splice(8, "0 1 [2 3 0]@2 3 0@2 7").hurry(0.65) + * s("breaks165") + * .splice(8, "0 1 [2 3 0]@2 3 0@2 7") + * .hurry(0.65) */ export const splice = register( @@ -2307,9 +2307,16 @@ export const { loopAt, loopat } = register(['loopAt', 'loopat'], function (facto return _loopAt(factor, pat, 1); }); -// this function will be redefined in repl.mjs to use the correct cps value. +// the fit function will be redefined in repl.mjs to use the correct cps value. // It is still here to work in cases where repl.mjs is not used - +/** + * Makes the sample fit its event duration. Good for rhythmical loops like drum breaks. + * Similar to loopAt. + * @name fit + * @example + * samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' }) + * s("rhodes/4").fit() + */ export const fit = register('fit', (pat) => pat.withHap((hap) => hap.withValue((v) => ({ diff --git a/website/src/pages/learn/samples.mdx b/website/src/pages/learn/samples.mdx index b1ef1cd30..10d8c7306 100644 --- a/website/src/pages/learn/samples.mdx +++ b/website/src/pages/learn/samples.mdx @@ -327,6 +327,10 @@ Sampler effects are functions that can be used to change the behaviour of sample +### fit + + + ### chop @@ -335,6 +339,10 @@ Sampler effects are functions that can be used to change the behaviour of sample +### splice + + + ### speed From 4490681fa375fb692d27043e302560bcfbd985cc Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 08:03:09 +0200 Subject: [PATCH 25/74] add note about wt_ --- packages/core/controls.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 0c166e111..f988f0a73 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -313,6 +313,7 @@ const generic_params = [ /** * Begin to loop at a specific point in the sample (inbetween `begin` and `end`). * Note that the loop point must be inbetween `begin` and `end`, and before `loopEnd`! + * Note: Samples starting with wt_ will automatically loop! (wt = wavetable) * * @name loopBegin * @param {number | Pattern} time between 0 and 1, where 1 is the length of the sample From 3879b01ddd55f3674d0c068478b9829f8ce918a7 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 08:04:02 +0200 Subject: [PATCH 26/74] snapshots --- test/__snapshots__/examples.test.mjs.snap | 63 ++++++++++++++++++----- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index ad0ce7b96..48835a481 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -1828,6 +1828,15 @@ exports[`runs examples > example "firstOf" example index 0 1`] = ` ] `; +exports[`runs examples > example "fit" example index 0 1`] = ` +[ + "[ (0/1 → 1/1) ⇝ 4/1 | s:rhodes speed:0.25 unit:c ]", + "[ 0/1 ⇜ (1/1 → 2/1) ⇝ 4/1 | s:rhodes speed:0.25 unit:c ]", + "[ 0/1 ⇜ (2/1 → 3/1) ⇝ 4/1 | s:rhodes speed:0.25 unit:c ]", + "[ 0/1 ⇜ (3/1 → 4/1) | s:rhodes speed:0.25 unit:c ]", +] +`; + exports[`runs examples > example "floor" example index 0 1`] = ` [ "[ 0/1 → 1/4 | note:42 ]", @@ -2640,10 +2649,10 @@ exports[`runs examples > example "linger" example index 0 1`] = ` exports[`runs examples > example "loop" example index 0 1`] = ` [ - "[ 0/1 → 1/1 | s:bd loop:1 ]", - "[ 1/1 → 2/1 | s:bd loop:2 ]", - "[ 2/1 → 3/1 | s:bd loop:3 ]", - "[ 3/1 → 4/1 | s:bd loop:4 ]", + "[ 0/1 → 1/1 | s:casio loop:1 ]", + "[ 1/1 → 2/1 | s:casio loop:1 ]", + "[ 2/1 → 3/1 | s:casio loop:1 ]", + "[ 3/1 → 4/1 | s:casio loop:1 ]", ] `; @@ -2667,19 +2676,19 @@ exports[`runs examples > example "loopAtCps" example index 0 1`] = ` exports[`runs examples > example "loopBegin" example index 0 1`] = ` [ - "[ 0/1 → 1/1 | s:numbers loop:1 begin:0 end:1 loopBegin:0 ]", - "[ 1/1 → 2/1 | s:numbers loop:1 begin:0 end:1 loopBegin:0.25 ]", - "[ 2/1 → 3/1 | s:numbers loop:1 begin:0 end:1 loopBegin:0.5 ]", - "[ 3/1 → 4/1 | s:numbers loop:1 begin:0 end:1 loopBegin:0.75 ]", + "[ 0/1 → 1/1 | s:space loop:1 loopBegin:0 analyze:1 ]", + "[ 1/1 → 2/1 | s:space loop:1 loopBegin:0.125 analyze:1 ]", + "[ 2/1 → 3/1 | s:space loop:1 loopBegin:0.25 analyze:1 ]", + "[ 3/1 → 4/1 | s:space loop:1 loopBegin:0 analyze:1 ]", ] `; exports[`runs examples > example "loopEnd" example index 0 1`] = ` [ - "[ 0/1 → 1/1 | s:numbers loop:1 begin:0 end:1 loopBegin:0 loopEnd:0.1 ]", - "[ 1/1 → 2/1 | s:numbers loop:1 begin:0 end:1 loopBegin:0.25 loopEnd:0.35 ]", - "[ 2/1 → 3/1 | s:numbers loop:1 begin:0 end:1 loopBegin:0.5 loopEnd:0.6 ]", - "[ 3/1 → 4/1 | s:numbers loop:1 begin:0 end:1 loopBegin:0.75 loopEnd:0.85 ]", + "[ 0/1 → 1/1 | s:space loop:1 loopEnd:1 analyze:1 ]", + "[ 1/1 → 2/1 | s:space loop:1 loopEnd:0.75 analyze:1 ]", + "[ 2/1 → 3/1 | s:space loop:1 loopEnd:0.5 analyze:1 ]", + "[ 3/1 → 4/1 | s:space loop:1 loopEnd:0.25 analyze:1 ]", ] `; @@ -4341,6 +4350,36 @@ exports[`runs examples > example "speed" example index 1 1`] = ` ] `; +exports[`runs examples > example "splice" example index 0 1`] = ` +[ + "[ 0/1 → 5/26 | speed:0.65 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 5/26 → 5/13 | speed:0.65 unit:c begin:0.125 end:0.25 _slices:8 s:breaks165 ]", + "[ 5/13 → 20/39 | speed:0.9750000000000001 unit:c begin:0.25 end:0.375 _slices:8 s:breaks165 ]", + "[ 20/39 → 25/39 | speed:0.9750000000000001 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]", + "[ 25/39 → 10/13 | speed:0.9750000000000001 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 10/13 → 25/26 | speed:0.65 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]", + "[ (25/26 → 1/1) ⇝ 35/26 | speed:0.325 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 25/26 ⇜ (1/1 → 35/26) | speed:0.325 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 35/26 → 20/13 | speed:0.65 unit:c begin:0.875 end:1 _slices:8 s:breaks165 ]", + "[ 20/13 → 45/26 | speed:0.65 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 45/26 → 25/13 | speed:0.65 unit:c begin:0.125 end:0.25 _slices:8 s:breaks165 ]", + "[ (25/13 → 2/1) ⇝ 80/39 | speed:0.9750000000000001 unit:c begin:0.25 end:0.375 _slices:8 s:breaks165 ]", + "[ 25/13 ⇜ (2/1 → 80/39) | speed:0.9750000000000001 unit:c begin:0.25 end:0.375 _slices:8 s:breaks165 ]", + "[ 80/39 → 85/39 | speed:0.9750000000000001 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]", + "[ 85/39 → 30/13 | speed:0.9750000000000001 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 30/13 → 5/2 | speed:0.65 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]", + "[ 5/2 → 75/26 | speed:0.325 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ (75/26 → 3/1) ⇝ 40/13 | speed:0.65 unit:c begin:0.875 end:1 _slices:8 s:breaks165 ]", + "[ 75/26 ⇜ (3/1 → 40/13) | speed:0.65 unit:c begin:0.875 end:1 _slices:8 s:breaks165 ]", + "[ 40/13 → 85/26 | speed:0.65 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 85/26 → 45/13 | speed:0.65 unit:c begin:0.125 end:0.25 _slices:8 s:breaks165 ]", + "[ 45/13 → 140/39 | speed:0.9750000000000001 unit:c begin:0.25 end:0.375 _slices:8 s:breaks165 ]", + "[ 140/39 → 145/39 | speed:0.9750000000000001 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]", + "[ 145/39 → 50/13 | speed:0.9750000000000001 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ (50/13 → 4/1) ⇝ 105/26 | speed:0.65 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]", +] +`; + exports[`runs examples > example "square" example index 0 1`] = ` [ "[ 0/1 → 1/2 | note:C3 ]", From 5c5df748600891860811bac1805bb0edbacf22e8 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 10:15:02 +0200 Subject: [PATCH 27/74] fix: control naming --- packages/core/controls.mjs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 69c43bfa3..57ed4b910 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -649,7 +649,7 @@ const generic_params = [ * @example * sound("triangle").freq(300).vib("<1 2 4 8 16>") */ - [['vibrato'], 'vib'], + ['vib', 'vibrato'], /** * Sets the vibrato depth as a multiple of base frequency (not vibrato speed!). * @@ -658,10 +658,8 @@ const generic_params = [ * @example * sound("triangle").freq(300).vib("<8 16>").vibmod("<0.25 0.5 0.75 1 2 4>") */ - [['vibmod'], 'vibmod'], - [['hcutoff', 'hresonance'], 'hpf', 'hp'], - ['vib'], ['vibmod'], + [['hcutoff', 'hresonance'], 'hpf', 'hp'], /** * Controls the **h**igh-**p**ass **q**-value. * @@ -933,8 +931,6 @@ const generic_params = [ ['rate'], // TODO: slide param for certain synths ['slide'], - - ['slidespeed'], // TODO: detune? https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare ['semitone'], // TODO: dedup with synth param, see https://tidalcycles.org/docs/reference/synthesizers/#superpiano From c354ee32e3d391f53c4819f8f4373e1b200c8793 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 10:15:33 +0200 Subject: [PATCH 28/74] simplify vibrato logic --- packages/superdough/synth.mjs | 39 ++++++++++++++--------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 8a2403406..2e2b7cb29 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -147,12 +147,6 @@ export function waveformN(partials, type) { } export function getOscillator({ s, freq, t, vib, vibmod, partials }) { - // Additional oscillator for vibrato effect - if (vib > 0) { - var vibrato_oscillator = getAudioContext().createOscillator(); - vibrato_oscillator.frequency.value = vib; - } - // Make oscillator with partial count let o; if (!partials || s === 'sine') { @@ -161,28 +155,27 @@ export function getOscillator({ s, freq, t, vib, vibmod, partials }) { } else { o = waveformN(partials, s); } + o.frequency.value = Number(freq); + o.start(t); + // Additional oscillator for vibrato effect + let vibrato_oscillator; if (vib > 0) { - o.frequency.value = Number(freq); - var gain = getAudioContext().createGain(); + vibrato_oscillator = getAudioContext().createOscillator(); + vibrato_oscillator.frequency.value = vib; + const gain = getAudioContext().createGain(); // Vibmod is the amount of vibrato, in semitones - gain.gain.value = vibmod * freq; + gain.gain.value = vibmod * 100; vibrato_oscillator.connect(gain); gain.connect(o.detune); vibrato_oscillator.start(t); - o.start(t); - return { - node: o, - stop: (time) => { - vibrato_oscillator.stop(time); - o.stop(time); - }, - }; - } else { - // Normal operation, without vibrato - o.frequency.value = Number(freq); - o.start(t); - const stop = (time) => o.stop(time); - return { node: o, stop }; } + + return { + node: o, + stop: (time) => { + vibrato_oscillator?.stop(time); + o.stop(time); + }, + }; } From cd6d1fb2d21f05d70605d3af5db1c6bfc8ae67d5 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 10:41:02 +0200 Subject: [PATCH 29/74] fix: headings --- website/src/pages/learn/synths.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/src/pages/learn/synths.mdx b/website/src/pages/learn/synths.mdx index a9cf094cd..9f21204fa 100644 --- a/website/src/pages/learn/synths.mdx +++ b/website/src/pages/learn/synths.mdx @@ -48,13 +48,13 @@ You can also set `n` directly in mini notation with `sound`: Note for tidal users: `n` in tidal is synonymous to `note` for synths only. In strudel, this is not the case, where `n` will always change timbre, be it though different samples or different waveforms. -### Vibrato +## Vibrato -#### vib +### vib -#### vibmod +### vibmod From c2560e0cf8b521443a12c846e0e39c3d72fb9b5b Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 10:41:13 +0200 Subject: [PATCH 30/74] set vib default to .5 --- 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 2e2b7cb29..5ca621052 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -41,7 +41,7 @@ export function registerSynthSounds() { fmvelocity: fmVelocity, fmwave: fmWaveform = 'sine', vib = 0, - vibmod = 1, + vibmod = .5, } = value; let { n, note, freq } = value; // with synths, n and note are the same thing From f052adb93a7ded3c3161031b6779842bb5d3988a Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 10:41:22 +0200 Subject: [PATCH 31/74] simplify examples --- packages/core/controls.mjs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 57ed4b910..072dc83ff 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -644,21 +644,24 @@ const generic_params = [ * Applies a vibrato to the frequency of the oscillator. * * @name vib + * @synonyms vibrato, v * @param {number | Pattern} frequency of the vibrato in hertz - * @synonyms vibrato * @example - * sound("triangle").freq(300).vib("<1 2 4 8 16>") + * note("a") + * .vib("<.5 1 2 4 8 16>") */ - ['vib', 'vibrato'], + [['vib', 'vibmod'], 'vibrato', 'v'], /** - * Sets the vibrato depth as a multiple of base frequency (not vibrato speed!). + * Sets the vibrato depth in semitones. * * @name vibmod - * @param {number | Pattern} depth of vibrato (multiple of base frequency) + * @synonyms vmod + * @param {number | Pattern} depth of vibrato (in semitones) * @example - * sound("triangle").freq(300).vib("<8 16>").vibmod("<0.25 0.5 0.75 1 2 4>") + * note("a").vib(4) + * .vibmod("<.25 .5 1 2 12>") */ - ['vibmod'], + [['vibmod', 'vib'], 'vmod'], [['hcutoff', 'hresonance'], 'hpf', 'hp'], /** * Controls the **h**igh-**p**ass **q**-value. From a97384cec1ec3a0a92637a509ea89ac9edf07dfc Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 10:42:21 +0200 Subject: [PATCH 32/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 5ca621052..633d0113d 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -41,7 +41,7 @@ export function registerSynthSounds() { fmvelocity: fmVelocity, fmwave: fmWaveform = 'sine', vib = 0, - vibmod = .5, + vibmod = 0.5, } = value; let { n, note, freq } = value; // with synths, n and note are the same thing From c6a74c040e258db1dd2684e48384422285fefd57 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 10:42:51 +0200 Subject: [PATCH 33/74] snapshot --- test/__snapshots__/examples.test.mjs.snap | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index d146a0650..858e30e74 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -4760,19 +4760,19 @@ exports[`runs examples > example "velocity" example index 0 1`] = ` exports[`runs examples > example "vib" example index 0 1`] = ` [ - "[ 0/1 → 1/1 | s:triangle freq:300 vib:1 ]", - "[ 1/1 → 2/1 | s:triangle freq:300 vib:2 ]", - "[ 2/1 → 3/1 | s:triangle freq:300 vib:4 ]", - "[ 3/1 → 4/1 | s:triangle freq:300 vib:8 ]", + "[ 0/1 → 1/1 | note:a vib:0.5 ]", + "[ 1/1 → 2/1 | note:a vib:1 ]", + "[ 2/1 → 3/1 | note:a vib:2 ]", + "[ 3/1 → 4/1 | note:a vib:4 ]", ] `; exports[`runs examples > example "vibmod" example index 0 1`] = ` [ - "[ 0/1 → 1/1 | s:triangle freq:300 vib:8 vibmod:0.25 ]", - "[ 1/1 → 2/1 | s:triangle freq:300 vib:16 vibmod:0.5 ]", - "[ 2/1 → 3/1 | s:triangle freq:300 vib:8 vibmod:0.75 ]", - "[ 3/1 → 4/1 | s:triangle freq:300 vib:16 vibmod:1 ]", + "[ 0/1 → 1/1 | note:a vib:4 vibmod:0.25 ]", + "[ 1/1 → 2/1 | note:a vib:4 vibmod:0.5 ]", + "[ 2/1 → 3/1 | note:a vib:4 vibmod:1 ]", + "[ 3/1 → 4/1 | note:a vib:4 vibmod:2 ]", ] `; From 32fee6313b4f1ac41eb4b00c58f3d998e1666eb8 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 10:44:15 +0200 Subject: [PATCH 34/74] move stuff back --- packages/core/controls.mjs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 072dc83ff..edbb77d48 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1140,13 +1140,10 @@ const generic_params = [ // ZZFX ['zrand'], ['curve'], - ['pitchJump'], - ['pitchJumpTime'], ['slide'], // superdirt duplicate ['deltaSlide'], - /** - * - */ + ['pitchJump'], + ['pitchJumpTime'], ['lfo', 'repeatTime'], ['noise'], ['zmod'], From 0e7c9a4001c3acf50c5f56f0c5555d95668fbbce Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 11:01:12 +0200 Subject: [PATCH 35/74] add vib examples for : notation --- packages/core/controls.mjs | 10 +++++++++- test/__snapshots__/examples.test.mjs.snap | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index edbb77d48..6cac6e540 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -649,10 +649,14 @@ const generic_params = [ * @example * note("a") * .vib("<.5 1 2 4 8 16>") + * @example + * // change the modulation depth with ":" + * note("a") + * .vib("<.5 1 2 4 8 16>:12") */ [['vib', 'vibmod'], 'vibrato', 'v'], /** - * Sets the vibrato depth in semitones. + * Sets the vibrato depth in semitones. Only has an effect if `vibrato` | `vib` | `v` is is also set * * @name vibmod * @synonyms vmod @@ -660,6 +664,10 @@ const generic_params = [ * @example * note("a").vib(4) * .vibmod("<.25 .5 1 2 12>") + * @example + * // change the vibrato frequency with ":" + * note("a") + * .vibmod("<.25 .5 1 2 12>:8") */ [['vibmod', 'vib'], 'vmod'], [['hcutoff', 'hresonance'], 'hpf', 'hp'], diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 858e30e74..e026f9c43 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -4767,6 +4767,15 @@ exports[`runs examples > example "vib" example index 0 1`] = ` ] `; +exports[`runs examples > example "vib" example index 1 1`] = ` +[ + "[ 0/1 → 1/1 | note:a vib:0.5 vibmod:12 ]", + "[ 1/1 → 2/1 | note:a vib:1 vibmod:12 ]", + "[ 2/1 → 3/1 | note:a vib:2 vibmod:12 ]", + "[ 3/1 → 4/1 | note:a vib:4 vibmod:12 ]", +] +`; + exports[`runs examples > example "vibmod" example index 0 1`] = ` [ "[ 0/1 → 1/1 | note:a vib:4 vibmod:0.25 ]", @@ -4776,6 +4785,15 @@ exports[`runs examples > example "vibmod" example index 0 1`] = ` ] `; +exports[`runs examples > example "vibmod" example index 1 1`] = ` +[ + "[ 0/1 → 1/1 | note:a vibmod:0.25 vib:8 ]", + "[ 1/1 → 2/1 | note:a vibmod:0.5 vib:8 ]", + "[ 2/1 → 3/1 | note:a vibmod:1 vib:8 ]", + "[ 3/1 → 4/1 | note:a vibmod:2 vib:8 ]", +] +`; + exports[`runs examples > example "voicing" example index 0 1`] = ` [ "[ 0/1 → 1/1 | note:E4 ]", From 578bede46b4310d5b5e49b7a88b895b38062c102 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 11:17:37 +0200 Subject: [PATCH 36/74] fix: tune n -> note --- website/src/repl/tunes.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/repl/tunes.mjs b/website/src/repl/tunes.mjs index e33482cf5..fdfc47d8b 100644 --- a/website/src/repl/tunes.mjs +++ b/website/src/repl/tunes.mjs @@ -432,7 +432,7 @@ export const waa2 = `// "Waa2" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos -n( +note( "a4 [a3 c3] a3 c3" .sub("<7 12 5 12>".slow(2)) .off(1/4,x=>x.add(7)) From 4fb364195cf43021e26810ab6061cdd1b358181b Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 11:18:20 +0200 Subject: [PATCH 37/74] snapshot --- test/__snapshots__/tunes.test.mjs.snap | 32 +++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/test/__snapshots__/tunes.test.mjs.snap b/test/__snapshots__/tunes.test.mjs.snap index aca92b556..4485f4caa 100644 --- a/test/__snapshots__/tunes.test.mjs.snap +++ b/test/__snapshots__/tunes.test.mjs.snap @@ -10180,22 +10180,22 @@ exports[`renders tunes > tune: undergroundPlumber 1`] = ` exports[`renders tunes > tune: waa2 1`] = ` [ - "[ -1/4 ⇜ (0/1 → 1/4) | n:48 clip:1.1738393178344886 s:sawtooth cutoff:3997.892048359052 gain:0.5 room:0.5 ]", - "[ -1/4 ⇜ (0/1 → 1/4) | n:64 clip:1.1738393178344886 s:sawtooth cutoff:3997.892048359052 gain:0.5 room:0.5 ]", - "[ (0/1 → 1/4) ⇝ 1/2 | n:62 clip:1.197659880151613 s:sawtooth cutoff:3991.5732716763446 gain:0.5 room:0.5 ]", - "[ (0/1 → 1/4) ⇝ 1/2 | n:43 clip:1.197659880151613 s:sawtooth cutoff:3991.5732716763446 gain:0.5 room:0.5 ]", - "[ 0/1 ⇜ (1/4 → 1/2) | n:62 clip:1.197659880151613 s:square cutoff:3991.5732716763446 gain:0.5 room:0.5 ]", - "[ 0/1 ⇜ (1/4 → 1/2) | n:43 clip:1.197659880151613 s:square cutoff:3991.5732716763446 gain:0.5 room:0.5 ]", - "[ (1/4 → 1/2) ⇝ 3/4 | n:74 clip:1.2451698046878117 s:square cutoff:3966.3742407056534 gain:0.5 room:0.5 ]", - "[ (1/4 → 1/2) ⇝ 3/4 | n:55 clip:1.2451698046878117 s:square cutoff:3966.3742407056534 gain:0.5 room:0.5 ]", - "[ 1/4 ⇜ (1/2 → 3/4) | n:74 clip:1.2451698046878117 s:sawtooth cutoff:3966.3742407056534 gain:0.5 room:0.5 ]", - "[ 1/4 ⇜ (1/2 → 3/4) | n:55 clip:1.2451698046878117 s:sawtooth cutoff:3966.3742407056534 gain:0.5 room:0.5 ]", - "[ 1/2 → 3/4 | n:50 clip:1.2688217886051745 s:sawtooth cutoff:3947.554693090452 gain:0.5 room:0.5 ]", - "[ (1/2 → 3/4) ⇝ 1/1 | n:69 clip:1.292380289809026 s:sawtooth cutoff:3924.645587531366 gain:0.5 room:0.5 ]", - "[ 1/2 ⇜ (3/4 → 1/1) | n:69 clip:1.292380289809026 s:square cutoff:3924.645587531366 gain:0.5 room:0.5 ]", - "[ 3/4 → 1/1 | n:41 clip:1.315826773713709 s:square cutoff:3897.7021140702864 gain:0.5 room:0.5 ]", - "[ 3/4 → 1/1 | n:62 clip:1.315826773713709 s:square cutoff:3897.7021140702864 gain:0.5 room:0.5 ]", - "[ (3/4 → 1/1) ⇝ 5/4 | n:81 clip:1.315826773713709 s:square cutoff:3897.7021140702864 gain:0.5 room:0.5 ]", + "[ -1/4 ⇜ (0/1 → 1/4) | note:48 clip:1.1738393178344886 s:sawtooth cutoff:3997.892048359052 gain:0.5 room:0.5 ]", + "[ -1/4 ⇜ (0/1 → 1/4) | note:64 clip:1.1738393178344886 s:sawtooth cutoff:3997.892048359052 gain:0.5 room:0.5 ]", + "[ (0/1 → 1/4) ⇝ 1/2 | note:62 clip:1.197659880151613 s:sawtooth cutoff:3991.5732716763446 gain:0.5 room:0.5 ]", + "[ (0/1 → 1/4) ⇝ 1/2 | note:43 clip:1.197659880151613 s:sawtooth cutoff:3991.5732716763446 gain:0.5 room:0.5 ]", + "[ 0/1 ⇜ (1/4 → 1/2) | note:62 clip:1.197659880151613 s:square cutoff:3991.5732716763446 gain:0.5 room:0.5 ]", + "[ 0/1 ⇜ (1/4 → 1/2) | note:43 clip:1.197659880151613 s:square cutoff:3991.5732716763446 gain:0.5 room:0.5 ]", + "[ (1/4 → 1/2) ⇝ 3/4 | note:74 clip:1.2451698046878117 s:square cutoff:3966.3742407056534 gain:0.5 room:0.5 ]", + "[ (1/4 → 1/2) ⇝ 3/4 | note:55 clip:1.2451698046878117 s:square cutoff:3966.3742407056534 gain:0.5 room:0.5 ]", + "[ 1/4 ⇜ (1/2 → 3/4) | note:74 clip:1.2451698046878117 s:sawtooth cutoff:3966.3742407056534 gain:0.5 room:0.5 ]", + "[ 1/4 ⇜ (1/2 → 3/4) | note:55 clip:1.2451698046878117 s:sawtooth cutoff:3966.3742407056534 gain:0.5 room:0.5 ]", + "[ 1/2 → 3/4 | note:50 clip:1.2688217886051745 s:sawtooth cutoff:3947.554693090452 gain:0.5 room:0.5 ]", + "[ (1/2 → 3/4) ⇝ 1/1 | note:69 clip:1.292380289809026 s:sawtooth cutoff:3924.645587531366 gain:0.5 room:0.5 ]", + "[ 1/2 ⇜ (3/4 → 1/1) | note:69 clip:1.292380289809026 s:square cutoff:3924.645587531366 gain:0.5 room:0.5 ]", + "[ 3/4 → 1/1 | note:41 clip:1.315826773713709 s:square cutoff:3897.7021140702864 gain:0.5 room:0.5 ]", + "[ 3/4 → 1/1 | note:62 clip:1.315826773713709 s:square cutoff:3897.7021140702864 gain:0.5 room:0.5 ]", + "[ (3/4 → 1/1) ⇝ 5/4 | note:81 clip:1.315826773713709 s:square cutoff:3897.7021140702864 gain:0.5 room:0.5 ]", ] `; From b7ef2d97e23651a7cc9fd99993fc8db1e0bba05c Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 11:50:18 +0200 Subject: [PATCH 38/74] make desktopbridge private for now --- packages/desktopbridge/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/desktopbridge/package.json b/packages/desktopbridge/package.json index c2acba9ed..d750a82ba 100644 --- a/packages/desktopbridge/package.json +++ b/packages/desktopbridge/package.json @@ -1,6 +1,7 @@ { "name": "@strudel/desktopbridge", "version": "0.1.0", + "private": true, "description": "tools/shims for communicating between the JS and Tauri (Rust) sides of the Studel desktop app", "main": "index.mjs", "type": "module", From 57265b9e33cd832b39309ec82eddb29dbd866253 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 12:34:01 +0200 Subject: [PATCH 39/74] add filter envelopes here and there + comment out outroMusic (lame) --- website/src/repl/tunes.mjs | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/website/src/repl/tunes.mjs b/website/src/repl/tunes.mjs index fdfc47d8b..5f68ee93f 100644 --- a/website/src/repl/tunes.mjs +++ b/website/src/repl/tunes.mjs @@ -444,7 +444,7 @@ note( .cutoff(cosine.range(500,4000).slow(16)) .gain(.5) .room(.5) - `; + .lpa(.125).lpenv(-2).v("8:.125").fanchor(.25)`; export const hyperpop = `// "Hyperpop" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ @@ -541,6 +541,7 @@ stack( .s('sawtooth') // waveform .gain(.4) // turn down .cutoff(sine.slow(7).range(300,5000)) // automate cutoff + .lpa(.1).lpenv(-2) //.hush() ,chord(">") .dict('lefthand').voicing() // chords @@ -563,6 +564,7 @@ stack( ) .slow(3/2)`; +/* export const outroMusic = `// "Outro music" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos @@ -593,7 +595,8 @@ chord("*2").dict('lefthand').anchor("G4").voicing() .n(3).color('gray') ).slow(3/2) //.pianoroll({autorange:1,vertical:1,fold:0}) - `; + `; +*/ export const bassFuge = `// "Bass fuge" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ @@ -776,7 +779,8 @@ stack( .gain("0.4,0.4(5,8,-1)"), note("<0 2 5 3>".scale('G1 minor')).struct("x(5,8,-1)") - .s('sawtooth').decay(.1).sustain(0), + .s('sawtooth').decay(.1).sustain(0) + .lpa(.1).lpenv(-4).lpf(800).lpq(8), note(",Bb3,D3").struct("~ x*2").s('square').clip(1) .cutoff(sine.range(500,4000).slow(16)).resonance(10) @@ -807,8 +811,10 @@ stack( sine.add(saw.slow(4)).range(0,7).segment(8) .superimpose(x=>x.add(.1)) .scale('G0 minor').note() - .s("sawtooth").decay(.1).sustain(0).lpa(.1).lpenv(4) - .gain(.4).cutoff(perlin.range(300,3000).slow(8)).resonance(10) + .s("sawtooth") + .gain(.4).decay(.1).sustain(0) + .lpa(.1).lpenv(-4).lpq(10) + .cutoff(perlin.range(300,3000).slow(8)) .degradeBy("0 0.1 .5 .1") .rarely(add(note("12"))) , @@ -831,8 +837,8 @@ note("c3 eb3 g3 bb3").palindrome() .s('sawtooth') .jux(x=>x.rev().color('green').s('sawtooth')) .off(1/4, x=>x.add(note("<7 12>/2")).slow(2).late(.005).s('triangle')) -//.delay(.5) -.fast(1).cutoff(sine.range(200,2000).slow(8)) +.lpf(sine.range(200,2000).slow(8)) +.lpa(.2).lpenv(-2) .decay(.05).sustain(0) .room(.6) .delay(.5).delaytime(.1).delayfeedback(.4) @@ -911,7 +917,13 @@ n("[0,3] 2 [1,3] 2".fast(3).lastOf(4, fast(2))).clip(2) .delay(.2) .room(.5).pan(sine.range(.3,.6)) .s('piano') - .stack("<!2 F2 [F2 E2]>".add.out("0 -5".fast(2)).add("0,.12").note().s('sawtooth').clip(1).cutoff(300)) + .stack( + "<!2 F2 F2>" + .add.out("0 -5".fast(2)) + .add("0,.12").note() + .s('sawtooth').cutoff(180) + .lpa(.1).lpenv(2) + ) .slow(4) .stack(s("bd*4, [~ [hh hh? hh?]]*2,~ [sd ~ [sd:2? bd?]]").bank('RolandTR909').gain(.5).slow(2)) `; From de9a52366f6658a4f241d5fe054a854b159e7642 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 12:35:03 +0200 Subject: [PATCH 40/74] Publish - @strudel/codemirror@0.9.0 - @strudel.cycles/core@0.9.0 - @strudel.cycles/csound@0.9.0 - @strudel.cycles/midi@0.9.0 - @strudel.cycles/mini@0.9.0 - @strudel.cycles/osc@0.9.0 - @strudel.cycles/react@0.9.0 - @strudel.cycles/serial@0.9.0 - @strudel.cycles/soundfonts@0.9.0 - superdough@0.9.8 - @strudel.cycles/tonal@0.9.0 - @strudel.cycles/transpiler@0.9.0 - @strudel/web@0.9.0 - @strudel.cycles/webaudio@0.9.0 - @strudel.cycles/xen@0.9.0 --- packages/codemirror/package.json | 2 +- packages/core/package.json | 2 +- packages/csound/package.json | 2 +- packages/midi/package.json | 2 +- packages/mini/package.json | 2 +- packages/osc/package.json | 2 +- packages/react/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 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index 0e32fef6f..4e443648e 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/codemirror", - "version": "0.8.4", + "version": "0.9.0", "description": "Codemirror Extensions for Strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/core/package.json b/packages/core/package.json index 16581904c..8fd572425 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@strudel.cycles/core", - "version": "0.8.2", + "version": "0.9.0", "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 e15cfeaf0..a0d21b9fc 100644 --- a/packages/csound/package.json +++ b/packages/csound/package.json @@ -1,6 +1,6 @@ { "name": "@strudel.cycles/csound", - "version": "0.8.0", + "version": "0.9.0", "description": "csound bindings for strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/midi/package.json b/packages/midi/package.json index 42679804d..e13887e23 100644 --- a/packages/midi/package.json +++ b/packages/midi/package.json @@ -1,6 +1,6 @@ { "name": "@strudel.cycles/midi", - "version": "0.8.0", + "version": "0.9.0", "description": "Midi API for strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/mini/package.json b/packages/mini/package.json index 16bd724cd..29ba3e832 100644 --- a/packages/mini/package.json +++ b/packages/mini/package.json @@ -1,6 +1,6 @@ { "name": "@strudel.cycles/mini", - "version": "0.8.2", + "version": "0.9.0", "description": "Mini notation for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/osc/package.json b/packages/osc/package.json index af2c9954d..dd61c3a7c 100644 --- a/packages/osc/package.json +++ b/packages/osc/package.json @@ -1,6 +1,6 @@ { "name": "@strudel.cycles/osc", - "version": "0.8.0", + "version": "0.9.0", "description": "OSC messaging for strudel", "main": "osc.mjs", "publishConfig": { diff --git a/packages/react/package.json b/packages/react/package.json index 519160494..deef95d88 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@strudel.cycles/react", - "version": "0.8.0", + "version": "0.9.0", "description": "React components for strudel", "main": "src/index.js", "publishConfig": { diff --git a/packages/serial/package.json b/packages/serial/package.json index 09a3267a8..c25c806ad 100644 --- a/packages/serial/package.json +++ b/packages/serial/package.json @@ -1,6 +1,6 @@ { "name": "@strudel.cycles/serial", - "version": "0.8.0", + "version": "0.9.0", "description": "Webserial API for strudel", "main": "serial.mjs", "publishConfig": { diff --git a/packages/soundfonts/package.json b/packages/soundfonts/package.json index 1f7b70e64..c4bf1032e 100644 --- a/packages/soundfonts/package.json +++ b/packages/soundfonts/package.json @@ -1,6 +1,6 @@ { "name": "@strudel.cycles/soundfonts", - "version": "0.8.2", + "version": "0.9.0", "description": "Soundsfont support for strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/superdough/package.json b/packages/superdough/package.json index 22b58cc5b..69ba6f8c7 100644 --- a/packages/superdough/package.json +++ b/packages/superdough/package.json @@ -1,6 +1,6 @@ { "name": "superdough", - "version": "0.9.7", + "version": "0.9.8", "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 d98d565ff..bef3c2f04 100644 --- a/packages/tonal/package.json +++ b/packages/tonal/package.json @@ -1,6 +1,6 @@ { "name": "@strudel.cycles/tonal", - "version": "0.8.2", + "version": "0.9.0", "description": "Tonal functions for strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/transpiler/package.json b/packages/transpiler/package.json index 95fbfee73..b546bce2d 100644 --- a/packages/transpiler/package.json +++ b/packages/transpiler/package.json @@ -1,6 +1,6 @@ { "name": "@strudel.cycles/transpiler", - "version": "0.8.2", + "version": "0.9.0", "description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.", "main": "index.mjs", "publishConfig": { diff --git a/packages/web/package.json b/packages/web/package.json index 9e866bf17..7e182f40c 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/web", - "version": "0.8.3", + "version": "0.9.0", "description": "Easy to setup, opiniated bundle of Strudel for the browser.", "main": "web.mjs", "publishConfig": { diff --git a/packages/webaudio/package.json b/packages/webaudio/package.json index edd53cbd6..cebb30f3d 100644 --- a/packages/webaudio/package.json +++ b/packages/webaudio/package.json @@ -1,6 +1,6 @@ { "name": "@strudel.cycles/webaudio", - "version": "0.8.2", + "version": "0.9.0", "description": "Web Audio helpers for Strudel", "main": "index.mjs", "type": "module", diff --git a/packages/xen/package.json b/packages/xen/package.json index 42ce4805d..ef2e04d29 100644 --- a/packages/xen/package.json +++ b/packages/xen/package.json @@ -1,6 +1,6 @@ { "name": "@strudel.cycles/xen", - "version": "0.8.0", + "version": "0.9.0", "description": "Xenharmonic API for strudel", "main": "index.mjs", "publishConfig": { From 9c73ef770fd1395a306b35d14dc8c0be81d30dc4 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Sep 2023 13:37:16 +0200 Subject: [PATCH 41/74] snapshot --- test/__snapshots__/tunes.test.mjs.snap | 109 +++++++++++-------------- 1 file changed, 47 insertions(+), 62 deletions(-) diff --git a/test/__snapshots__/tunes.test.mjs.snap b/test/__snapshots__/tunes.test.mjs.snap index 4485f4caa..cd89ee3f0 100644 --- a/test/__snapshots__/tunes.test.mjs.snap +++ b/test/__snapshots__/tunes.test.mjs.snap @@ -3,23 +3,23 @@ exports[`renders tunes > tune: amensister 1`] = ` [ "[ 0/1 → 1/16 | s:breath room:1 shape:0.6 begin:0.9375 end:1 ]", - "[ 0/1 → 1/8 | note:Eb1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:4 gain:0.4 cutoff:300.0066107586751 resonance:10 ]", - "[ 0/1 → 1/8 | note:F1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:4 gain:0.4 cutoff:300.0066107586751 resonance:10 ]", + "[ 0/1 → 1/8 | note:Eb1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:300.0066107586751 ]", + "[ 0/1 → 1/8 | note:F1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:300.0066107586751 ]", "[ 0/1 → 1/4 | n:0 s:amencutup room:0.5 ]", "[ 1/16 → 1/8 | s:breath room:1 shape:0.6 begin:0.875 end:0.9375 ]", "[ 1/8 → 3/16 | s:breath room:1 shape:0.6 begin:0.8125 end:0.875 ]", - "[ 1/8 → 1/4 | note:45 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:4 gain:0.4 cutoff:300.174310575404 resonance:10 ]", - "[ 1/8 → 1/4 | note:45 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:4 gain:0.4 cutoff:300.174310575404 resonance:10 ]", + "[ 1/8 → 1/4 | note:45 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:300.174310575404 ]", + "[ 1/8 → 1/4 | note:45 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:300.174310575404 ]", "[ 3/16 → 1/4 | s:breath room:1 shape:0.6 begin:0.75 end:0.8125 ]", "[ 1/4 → 5/16 | s:breath room:1 shape:0.6 begin:0.6875 end:0.75 ]", "[ 1/4 → 3/8 | n:1 speed:2 delay:0.5 s:amencutup room:0.5 ]", - "[ 1/4 → 3/8 | note:A1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:4 gain:0.4 cutoff:300.7878869297153 resonance:10 ]", - "[ 1/4 → 3/8 | note:A1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:4 gain:0.4 cutoff:300.7878869297153 resonance:10 ]", + "[ 1/4 → 3/8 | note:A1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:300.7878869297153 ]", + "[ 1/4 → 3/8 | note:A1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:300.7878869297153 ]", "[ 5/16 → 3/8 | s:breath room:1 shape:0.6 begin:0.625 end:0.6875 ]", "[ 3/8 → 7/16 | s:breath room:1 shape:0.6 begin:0.5625 end:0.625 ]", "[ 3/8 → 1/2 | n:1 s:amencutup room:0.5 ]", - "[ 3/8 → 1/2 | note:F1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:4 gain:0.4 cutoff:302.11020572391345 resonance:10 ]", - "[ 3/8 → 1/2 | note:F1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:4 gain:0.4 cutoff:302.11020572391345 resonance:10 ]", + "[ 3/8 → 1/2 | note:F1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:302.11020572391345 ]", + "[ 3/8 → 1/2 | note:F1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:302.11020572391345 ]", "[ 7/16 → 1/2 | s:breath room:1 shape:0.6 begin:0.5 end:0.5625 ]", "[ 1/2 → 9/16 | s:breath room:1 shape:0.6 begin:0.4375 end:0.5 ]", "[ 1/2 → 3/4 | n:2 s:amencutup room:0.5 ]", @@ -28,13 +28,13 @@ exports[`renders tunes > tune: amensister 1`] = ` "[ 11/16 → 3/4 | s:breath room:1 shape:0.6 begin:0.25 end:0.3125 ]", "[ 3/4 → 13/16 | s:breath room:1 shape:0.6 begin:0.1875 end:0.25 ]", "[ 3/4 → 7/8 | n:3 s:amencutup room:0.5 ]", - "[ 3/4 → 7/8 | note:Bb0 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:4 gain:0.4 cutoff:312.54769231985796 resonance:10 ]", - "[ 3/4 → 7/8 | note:Bb0 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:4 gain:0.4 cutoff:312.54769231985796 resonance:10 ]", + "[ 3/4 → 7/8 | note:Bb0 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:312.54769231985796 ]", + "[ 3/4 → 7/8 | note:Bb0 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:312.54769231985796 ]", "[ 13/16 → 7/8 | s:breath room:1 shape:0.6 begin:0.125 end:0.1875 ]", "[ 7/8 → 15/16 | s:breath room:1 shape:0.6 begin:0.0625 end:0.125 ]", "[ 7/8 → 1/1 | n:3 s:amencutup room:0.5 ]", - "[ 7/8 → 1/1 | note:D1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:4 gain:0.4 cutoff:318.7927796831686 resonance:10 ]", - "[ 7/8 → 1/1 | note:D1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:4 gain:0.4 cutoff:318.7927796831686 resonance:10 ]", + "[ 7/8 → 1/1 | note:D1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:318.7927796831686 ]", + "[ 7/8 → 1/1 | note:D1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:318.7927796831686 ]", "[ 15/16 → 1/1 | s:breath room:1 shape:0.6 begin:0 end:0.0625 ]", ] `; @@ -44,8 +44,8 @@ exports[`renders tunes > tune: arpoon 1`] = ` "[ (0/1 → 1/4) ⇝ 1/3 | note:55.000070545713186 clip:2 cutoff:501.2345499807435 resonance:12 gain:0.5 decay:0.16 sustain:0.5 delay:0.2 room:0.5 pan:0.48882285676537807 s:piano ]", "[ (0/1 → 1/4) ⇝ 1/3 | note:64.00007054571319 clip:2 cutoff:501.2345499807435 resonance:12 gain:0.5 decay:0.16 sustain:0.5 delay:0.2 room:0.5 pan:0.48882285676537807 s:piano ]", "[ 0/1 → 1/2 | s:bd bank:RolandTR909 gain:0.5 ]", - "[ 0/1 → 1/1 | note:33 s:sawtooth clip:1 cutoff:300 ]", - "[ 0/1 → 1/1 | note:33.12 s:sawtooth clip:1 cutoff:300 ]", + "[ 0/1 → 1/1 | note:33 s:sawtooth cutoff:180 lpattack:0.1 lpenv:2 ]", + "[ 0/1 → 1/1 | note:33.12 s:sawtooth cutoff:180 lpattack:0.1 lpenv:2 ]", "[ 0/1 ⇜ (1/4 → 1/3) | note:55.000070545713186 clip:2 cutoff:501.2345499807435 resonance:12 gain:0.8 decay:0.16 sustain:0.5 delay:0.2 room:0.5 pan:0.48882285676537807 s:piano ]", "[ 0/1 ⇜ (1/4 → 1/3) | note:64.00007054571319 clip:2 cutoff:501.2345499807435 resonance:12 gain:0.8 decay:0.16 sustain:0.5 delay:0.2 room:0.5 pan:0.48882285676537807 s:piano ]", "[ (1/3 → 1/2) ⇝ 2/3 | note:60.00166796373806 clip:2 cutoff:529.1893654159594 resonance:12 gain:0.8 decay:0.16 sustain:0.5 delay:0.2 room:0.5 pan:0.5560660171779821 s:piano ]", @@ -7315,20 +7315,20 @@ exports[`renders tunes > tune: flatrave 1`] = ` "[ 0/1 ⇜ (1/8 → 1/4) | s:hh n:1 end:0.02000058072071123 bank:RolandTR909 room:0.5 gain:0.4 ]", "[ 1/8 → 1/4 | s:hh n:1 speed:0.5 delay:0.5 end:0.020001936784171157 bank:RolandTR909 room:0.5 gain:0.4 ]", "[ 1/8 → 1/4 | s:hh n:1 speed:0.5 delay:0.5 end:0.020001936784171157 bank:RolandTR909 room:0.5 gain:0.4 ]", - "[ 1/8 → 1/4 | note:G1 s:sawtooth decay:0.1 sustain:0 ]", + "[ 1/8 → 1/4 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]", "[ 1/4 → 3/8 | s:hh n:1 end:0.02000875429921906 bank:RolandTR909 room:0.5 gain:0.4 ]", "[ 1/4 → 3/8 | s:hh n:1 end:0.02000875429921906 bank:RolandTR909 room:0.5 gain:0.4 ]", - "[ 1/4 → 3/8 | note:G1 s:sawtooth decay:0.1 sustain:0 ]", + "[ 1/4 → 3/8 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]", "[ 3/8 → 1/2 | s:hh n:1 end:0.020023446730265706 bank:RolandTR909 room:0.5 gain:0.4 ]", - "[ 1/2 → 5/8 | note:G1 s:sawtooth decay:0.1 sustain:0 ]", + "[ 1/2 → 5/8 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]", "[ 1/2 → 1/1 | s:bd bank:RolandTR909 ]", "[ 1/2 → 1/1 | s:cp bank:RolandTR909 ]", "[ 1/2 → 1/1 | s:sd bank:RolandTR909 ]", "[ 5/8 → 3/4 | s:hh n:1 end:0.020086608138500644 bank:RolandTR909 room:0.5 gain:0.4 ]", "[ 5/8 → 3/4 | s:hh n:1 end:0.020086608138500644 bank:RolandTR909 room:0.5 gain:0.4 ]", - "[ 5/8 → 3/4 | note:G1 s:sawtooth decay:0.1 sustain:0 ]", + "[ 5/8 → 3/4 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]", "[ 3/4 → 7/8 | s:hh n:1 end:0.02013941880355398 bank:RolandTR909 room:0.5 gain:0.4 ]", - "[ 7/8 → 1/1 | note:G1 s:sawtooth decay:0.1 sustain:0 ]", + "[ 7/8 → 1/1 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]", ] `; @@ -8372,16 +8372,16 @@ exports[`renders tunes > tune: hyperpop 1`] = ` exports[`renders tunes > tune: juxUndTollerei 1`] = ` [ - "[ 0/1 → 1/4 | note:bb3 s:sawtooth pan:0 cutoff:1188.2154262966046 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ 0/1 → 1/4 | note:c3 s:sawtooth pan:1 cutoff:1188.2154262966046 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ 1/4 → 1/2 | note:g3 s:sawtooth pan:0 cutoff:1361.2562095290161 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ 1/4 → 1/2 | note:eb3 s:sawtooth pan:1 cutoff:1361.2562095290161 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ 1/2 → 3/4 | note:eb3 s:sawtooth pan:0 cutoff:1524.257063143398 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ 1/2 → 3/4 | note:g3 s:sawtooth pan:1 cutoff:1524.257063143398 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ (101/200 → 1/1) ⇝ 201/200 | note:65 s:triangle pan:0 cutoff:1601.4815730092653 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ (101/200 → 1/1) ⇝ 201/200 | note:55 s:triangle pan:1 cutoff:1601.4815730092653 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ 3/4 → 1/1 | note:c3 s:sawtooth pan:0 cutoff:1670.953955747281 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", - "[ 3/4 → 1/1 | note:bb3 s:sawtooth pan:1 cutoff:1670.953955747281 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ 0/1 → 1/4 | note:bb3 s:sawtooth pan:0 cutoff:1188.2154262966046 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ 0/1 → 1/4 | note:c3 s:sawtooth pan:1 cutoff:1188.2154262966046 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ 1/4 → 1/2 | note:g3 s:sawtooth pan:0 cutoff:1361.2562095290161 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ 1/4 → 1/2 | note:eb3 s:sawtooth pan:1 cutoff:1361.2562095290161 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ 1/2 → 3/4 | note:eb3 s:sawtooth pan:0 cutoff:1524.257063143398 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ 1/2 → 3/4 | note:g3 s:sawtooth pan:1 cutoff:1524.257063143398 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ (101/200 → 1/1) ⇝ 201/200 | note:65 s:triangle pan:0 cutoff:1601.4815730092653 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ (101/200 → 1/1) ⇝ 201/200 | note:55 s:triangle pan:1 cutoff:1601.4815730092653 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ 3/4 → 1/1 | note:c3 s:sawtooth pan:0 cutoff:1670.953955747281 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", + "[ 3/4 → 1/1 | note:bb3 s:sawtooth pan:1 cutoff:1670.953955747281 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]", ] `; @@ -8422,8 +8422,8 @@ exports[`renders tunes > tune: meltingsubmarine 1`] = ` "[ 0/1 → 3/16 | note:93.00057728554401 decay:0.1 sustain:0 s:triangle gain:0.075 ]", "[ 0/1 → 3/16 | note:93.04057728554402 decay:0.1 sustain:0 s:triangle gain:0.075 ]", "[ (0/1 → 1/1) ⇝ 3/2 | s:bd n:5 speed:0.7519542165100574 ]", - "[ (0/1 → 1/1) ⇝ 3/2 | note:33.129885541275144 decay:0.15 sustain:0 s:sawtooth gain:0.4 cutoff:3669.6267869262615 ]", - "[ (0/1 → 1/1) ⇝ 3/2 | note:33.17988554127514 decay:0.15 sustain:0 s:sawtooth gain:0.4 cutoff:3669.6267869262615 ]", + "[ (0/1 → 1/1) ⇝ 3/2 | note:33.129885541275144 decay:0.15 sustain:0 s:sawtooth gain:0.4 cutoff:3669.6267869262615 lpattack:0.1 lpenv:-2 ]", + "[ (0/1 → 1/1) ⇝ 3/2 | note:33.17988554127514 decay:0.15 sustain:0 s:sawtooth gain:0.4 cutoff:3669.6267869262615 lpattack:0.1 lpenv:-2 ]", "[ (0/1 → 1/1) ⇝ 3/2 | note:60.129885541275144 s:sawtooth gain:0.16 cutoff:500 attack:1 ]", "[ (0/1 → 1/1) ⇝ 3/2 | note:60.16988554127514 s:sawtooth gain:0.16 cutoff:500 attack:1 ]", "[ (0/1 → 1/1) ⇝ 3/2 | note:64.12988554127514 s:sawtooth gain:0.16 cutoff:500 attack:1 ]", @@ -8481,21 +8481,6 @@ exports[`renders tunes > tune: orbit 1`] = ` ] `; -exports[`renders tunes > tune: outroMusic 1`] = ` -[ - "[ 0/1 → 1/2 | s:hh speed:0.9036881079621337 n:3 ]", - "[ 0/1 → 3/4 | note:C2 s:sawtooth attack:0.05 decay:0.1 sustain:0.7 cutoff:864.536878321087 gain:0.3 ]", - "[ 0/1 → 3/4 | s:bd speed:0.9107561463868479 n:3 ]", - "[ (0/1 → 1/1) ⇝ 3/1 | note:B3 s:gm_epiano1 n:1 attack:0.05 decay:0.1 sustain:0.7 cutoff:1111.7252990603447 gain:0.3 ]", - "[ (0/1 → 1/1) ⇝ 3/1 | note:D4 s:gm_epiano1 n:1 attack:0.05 decay:0.1 sustain:0.7 cutoff:1111.7252990603447 gain:0.3 ]", - "[ (0/1 → 1/1) ⇝ 3/1 | note:E4 s:gm_epiano1 n:1 attack:0.05 decay:0.1 sustain:0.7 cutoff:1111.7252990603447 gain:0.3 ]", - "[ (0/1 → 1/1) ⇝ 3/1 | note:G4 s:gm_epiano1 n:1 attack:0.05 decay:0.1 sustain:0.7 cutoff:1111.7252990603447 gain:0.3 ]", - "[ (0/1 → 1/1) ⇝ 9/2 | n:1 note:C5 s:gm_epiano1 attack:0.05 decay:0.1 sustain:0.7 cutoff:1111.7252990603447 gain:0.3 ]", - "[ 1/2 → 1/1 | s:hh speed:0.9519542165100575 n:3 ]", - "[ (3/4 → 1/1) ⇝ 3/2 | s:sd speed:0.9931522866332672 n:3 ]", -] -`; - exports[`renders tunes > tune: randomBells 1`] = ` [ "[ -9/8 ⇜ (0/1 → 3/8) | note:G4 s:bell gain:0.6 delay:0.2 delaytime:0.3333333333333333 delayfeedback:0.8 ]", @@ -10180,22 +10165,22 @@ exports[`renders tunes > tune: undergroundPlumber 1`] = ` exports[`renders tunes > tune: waa2 1`] = ` [ - "[ -1/4 ⇜ (0/1 → 1/4) | note:48 clip:1.1738393178344886 s:sawtooth cutoff:3997.892048359052 gain:0.5 room:0.5 ]", - "[ -1/4 ⇜ (0/1 → 1/4) | note:64 clip:1.1738393178344886 s:sawtooth cutoff:3997.892048359052 gain:0.5 room:0.5 ]", - "[ (0/1 → 1/4) ⇝ 1/2 | note:62 clip:1.197659880151613 s:sawtooth cutoff:3991.5732716763446 gain:0.5 room:0.5 ]", - "[ (0/1 → 1/4) ⇝ 1/2 | note:43 clip:1.197659880151613 s:sawtooth cutoff:3991.5732716763446 gain:0.5 room:0.5 ]", - "[ 0/1 ⇜ (1/4 → 1/2) | note:62 clip:1.197659880151613 s:square cutoff:3991.5732716763446 gain:0.5 room:0.5 ]", - "[ 0/1 ⇜ (1/4 → 1/2) | note:43 clip:1.197659880151613 s:square cutoff:3991.5732716763446 gain:0.5 room:0.5 ]", - "[ (1/4 → 1/2) ⇝ 3/4 | note:74 clip:1.2451698046878117 s:square cutoff:3966.3742407056534 gain:0.5 room:0.5 ]", - "[ (1/4 → 1/2) ⇝ 3/4 | note:55 clip:1.2451698046878117 s:square cutoff:3966.3742407056534 gain:0.5 room:0.5 ]", - "[ 1/4 ⇜ (1/2 → 3/4) | note:74 clip:1.2451698046878117 s:sawtooth cutoff:3966.3742407056534 gain:0.5 room:0.5 ]", - "[ 1/4 ⇜ (1/2 → 3/4) | note:55 clip:1.2451698046878117 s:sawtooth cutoff:3966.3742407056534 gain:0.5 room:0.5 ]", - "[ 1/2 → 3/4 | note:50 clip:1.2688217886051745 s:sawtooth cutoff:3947.554693090452 gain:0.5 room:0.5 ]", - "[ (1/2 → 3/4) ⇝ 1/1 | note:69 clip:1.292380289809026 s:sawtooth cutoff:3924.645587531366 gain:0.5 room:0.5 ]", - "[ 1/2 ⇜ (3/4 → 1/1) | note:69 clip:1.292380289809026 s:square cutoff:3924.645587531366 gain:0.5 room:0.5 ]", - "[ 3/4 → 1/1 | note:41 clip:1.315826773713709 s:square cutoff:3897.7021140702864 gain:0.5 room:0.5 ]", - "[ 3/4 → 1/1 | note:62 clip:1.315826773713709 s:square cutoff:3897.7021140702864 gain:0.5 room:0.5 ]", - "[ (3/4 → 1/1) ⇝ 5/4 | note:81 clip:1.315826773713709 s:square cutoff:3897.7021140702864 gain:0.5 room:0.5 ]", + "[ -1/4 ⇜ (0/1 → 1/4) | note:48 clip:1.1738393178344886 s:sawtooth cutoff:3997.892048359052 gain:0.5 room:0.5 lpattack:0.125 lpenv:-2 vib:8 vibmod:0.125 fanchor:0.25 ]", + "[ -1/4 ⇜ (0/1 → 1/4) | note:64 clip:1.1738393178344886 s:sawtooth cutoff:3997.892048359052 gain:0.5 room:0.5 lpattack:0.125 lpenv:-2 vib:8 vibmod:0.125 fanchor:0.25 ]", + "[ (0/1 → 1/4) ⇝ 1/2 | note:62 clip:1.197659880151613 s:sawtooth cutoff:3991.5732716763446 gain:0.5 room:0.5 lpattack:0.125 lpenv:-2 vib:8 vibmod:0.125 fanchor:0.25 ]", + "[ (0/1 → 1/4) ⇝ 1/2 | note:43 clip:1.197659880151613 s:sawtooth cutoff:3991.5732716763446 gain:0.5 room:0.5 lpattack:0.125 lpenv:-2 vib:8 vibmod:0.125 fanchor:0.25 ]", + "[ 0/1 ⇜ (1/4 → 1/2) | note:62 clip:1.197659880151613 s:square cutoff:3991.5732716763446 gain:0.5 room:0.5 lpattack:0.125 lpenv:-2 vib:8 vibmod:0.125 fanchor:0.25 ]", + "[ 0/1 ⇜ (1/4 → 1/2) | note:43 clip:1.197659880151613 s:square cutoff:3991.5732716763446 gain:0.5 room:0.5 lpattack:0.125 lpenv:-2 vib:8 vibmod:0.125 fanchor:0.25 ]", + "[ (1/4 → 1/2) ⇝ 3/4 | note:74 clip:1.2451698046878117 s:square cutoff:3966.3742407056534 gain:0.5 room:0.5 lpattack:0.125 lpenv:-2 vib:8 vibmod:0.125 fanchor:0.25 ]", + "[ (1/4 → 1/2) ⇝ 3/4 | note:55 clip:1.2451698046878117 s:square cutoff:3966.3742407056534 gain:0.5 room:0.5 lpattack:0.125 lpenv:-2 vib:8 vibmod:0.125 fanchor:0.25 ]", + "[ 1/4 ⇜ (1/2 → 3/4) | note:74 clip:1.2451698046878117 s:sawtooth cutoff:3966.3742407056534 gain:0.5 room:0.5 lpattack:0.125 lpenv:-2 vib:8 vibmod:0.125 fanchor:0.25 ]", + "[ 1/4 ⇜ (1/2 → 3/4) | note:55 clip:1.2451698046878117 s:sawtooth cutoff:3966.3742407056534 gain:0.5 room:0.5 lpattack:0.125 lpenv:-2 vib:8 vibmod:0.125 fanchor:0.25 ]", + "[ 1/2 → 3/4 | note:50 clip:1.2688217886051745 s:sawtooth cutoff:3947.554693090452 gain:0.5 room:0.5 lpattack:0.125 lpenv:-2 vib:8 vibmod:0.125 fanchor:0.25 ]", + "[ (1/2 → 3/4) ⇝ 1/1 | note:69 clip:1.292380289809026 s:sawtooth cutoff:3924.645587531366 gain:0.5 room:0.5 lpattack:0.125 lpenv:-2 vib:8 vibmod:0.125 fanchor:0.25 ]", + "[ 1/2 ⇜ (3/4 → 1/1) | note:69 clip:1.292380289809026 s:square cutoff:3924.645587531366 gain:0.5 room:0.5 lpattack:0.125 lpenv:-2 vib:8 vibmod:0.125 fanchor:0.25 ]", + "[ 3/4 → 1/1 | note:41 clip:1.315826773713709 s:square cutoff:3897.7021140702864 gain:0.5 room:0.5 lpattack:0.125 lpenv:-2 vib:8 vibmod:0.125 fanchor:0.25 ]", + "[ 3/4 → 1/1 | note:62 clip:1.315826773713709 s:square cutoff:3897.7021140702864 gain:0.5 room:0.5 lpattack:0.125 lpenv:-2 vib:8 vibmod:0.125 fanchor:0.25 ]", + "[ (3/4 → 1/1) ⇝ 5/4 | note:81 clip:1.315826773713709 s:square cutoff:3897.7021140702864 gain:0.5 room:0.5 lpattack:0.125 lpenv:-2 vib:8 vibmod:0.125 fanchor:0.25 ]", ] `; From 258eb88684a136b26190616d8b7cdc8c6e7572a8 Mon Sep 17 00:00:00 2001 From: vmilovidov Date: Sun, 17 Sep 2023 13:14:13 +0000 Subject: [PATCH 42/74] Update tauri.yml workflow --- .github/workflows/tauri.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tauri.yml b/.github/workflows/tauri.yml index 39ec34ca5..dde52a258 100644 --- a/.github/workflows/tauri.yml +++ b/.github/workflows/tauri.yml @@ -42,7 +42,7 @@ jobs: if: matrix.platform == 'ubuntu-latest' run: | sudo apt-get update - sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf + sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf pkg-config alsa-tools libasound2-dev - name: Install app dependencies from lockfile and build web run: pnpm install From 05c09eff192ea66f0c64dac87693c6553907752b Mon Sep 17 00:00:00 2001 From: vmilovidov Date: Sun, 17 Sep 2023 13:26:58 +0000 Subject: [PATCH 43/74] Update tauri.yml workflow file --- .github/workflows/tauri.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tauri.yml b/.github/workflows/tauri.yml index dde52a258..07fd91be8 100644 --- a/.github/workflows/tauri.yml +++ b/.github/workflows/tauri.yml @@ -42,7 +42,7 @@ jobs: if: matrix.platform == 'ubuntu-latest' run: | sudo apt-get update - sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf pkg-config alsa-tools libasound2-dev + sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf libasound2-dev - name: Install app dependencies from lockfile and build web run: pnpm install From 6e26f3975165ff4430b2079242937be351c40317 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Sep 2023 22:34:31 +0200 Subject: [PATCH 44/74] add dough function for raw dsp --- packages/superdough/dspworklet.mjs | 80 ++++++++++++++++++++++++++++++ packages/superdough/index.mjs | 1 + website/src/repl/Repl.jsx | 7 ++- 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 packages/superdough/dspworklet.mjs diff --git a/packages/superdough/dspworklet.mjs b/packages/superdough/dspworklet.mjs new file mode 100644 index 000000000..ac51c8fc9 --- /dev/null +++ b/packages/superdough/dspworklet.mjs @@ -0,0 +1,80 @@ +import { Pattern } from '@strudel.cycles/core'; +import { getAudioContext } from './superdough.mjs'; + +let worklet; +export async function dspWorklet(ac, code) { + const name = `dsp-worklet-${Date.now()}`; + const workletCode = `${code} +let __q = []; // trigger queue +class MyProcessor extends AudioWorkletProcessor { + constructor() { + super(); + this.t = 0; + this.stopped = false; + this.port.onmessage = (e) => { + if(e.data==='stop') { + this.stopped = true; + } else if(e.data?.dough) { + const deadline = e.data.time-currentTime; + __q.push(e.data) + } else { + msg?.(e.data) + } + }; + } + process(inputs, outputs, parameters) { + const output = outputs[0]; + if(__q.length) { + __q = __q.filter((el) => { + const deadline = el.time-currentTime; + return deadline>0 ? true : trigger(el.dough) + }) + } + for (let i = 0; i < output[0].length; i++) { + const out = dsp(this.t / sampleRate); + output.forEach((channel) => { + channel[i] = out; + }); + this.t++; + } + return !this.stopped; + } +} +registerProcessor('${name}', MyProcessor); +`; + const base64String = btoa(workletCode); + const dataURL = `data:text/javascript;base64,${base64String}`; + await ac.audioWorklet.addModule(dataURL); + const node = new AudioWorkletNode(ac, name); + const stop = () => node.port.postMessage('stop'); + return { node, stop }; +} +const stop = () => { + if (worklet) { + worklet?.stop(); + worklet?.node?.disconnect(); + } +}; + +if (typeof window !== 'undefined') { + window.addEventListener('message', (e) => { + if (e.data === 'strudel-stop') { + stop(); + } else if (e.data?.dough) { + worklet?.node.port.postMessage(e.data); + } + }); +} + +export const dough = async (code) => { + const ac = getAudioContext(); + stop(); + worklet = await dspWorklet(ac, code); + worklet.node.connect(ac.destination); +}; + +Pattern.prototype.dough = function () { + return this.onTrigger((t, hap) => { + window.postMessage({ time: t, dough: hap.value }); + }, 1); +}; diff --git a/packages/superdough/index.mjs b/packages/superdough/index.mjs index e5d4498b8..3247c5b49 100644 --- a/packages/superdough/index.mjs +++ b/packages/superdough/index.mjs @@ -10,3 +10,4 @@ export * from './helpers.mjs'; export * from './synth.mjs'; export * from './zzfx.mjs'; export * from './logger.mjs'; +export * from './dspworklet.mjs'; diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 173bb4556..fe32e7412 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -149,7 +149,12 @@ export function Repl({ embedded = false }) { onEvalError: (err) => { setPending(false); }, - onToggle: (play) => !play && cleanupDraw(false), + onToggle: (play) => { + if (!play) { + cleanupDraw(false); + window.postMessage('strudel-stop'); + } + }, drawContext, // drawTime: [0, 6], paintOptions, From 52c01abbe9e62abc18820143bfea0569e2c7d31a Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Sep 2023 22:39:03 +0200 Subject: [PATCH 45/74] encapsulate .dough --- packages/superdough/dspworklet.mjs | 9 +++------ packages/webaudio/webaudio.mjs | 6 +++++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/superdough/dspworklet.mjs b/packages/superdough/dspworklet.mjs index ac51c8fc9..2a77de534 100644 --- a/packages/superdough/dspworklet.mjs +++ b/packages/superdough/dspworklet.mjs @@ -1,4 +1,3 @@ -import { Pattern } from '@strudel.cycles/core'; import { getAudioContext } from './superdough.mjs'; let worklet; @@ -73,8 +72,6 @@ export const dough = async (code) => { worklet.node.connect(ac.destination); }; -Pattern.prototype.dough = function () { - return this.onTrigger((t, hap) => { - window.postMessage({ time: t, dough: hap.value }); - }, 1); -}; +export function doughTrigger(t, hap, currentTime, duration, cps) { + window.postMessage({ time: t, dough: hap.value, currentTime, duration, cps }); +} diff --git a/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs index 8b32a90c4..fb4a3d7de 100644 --- a/packages/webaudio/webaudio.mjs +++ b/packages/webaudio/webaudio.mjs @@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th */ import * as strudel from '@strudel.cycles/core'; -import { superdough, getAudioContext, setLogger } from 'superdough'; +import { superdough, getAudioContext, setLogger, doughTrigger } from 'superdough'; const { Pattern, logger } = strudel; setLogger(logger); @@ -35,3 +35,7 @@ export function webaudioScheduler(options = {}) { onTrigger: strudel.getTrigger({ defaultOutput, getTime }), }); } + +Pattern.prototype.dough = function () { + return this.onTrigger(doughTrigger, 1); +}; From 7078e20200bcbcc98e61de795604fc174d9495e3 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Sep 2023 22:56:44 +0200 Subject: [PATCH 46/74] less garbage --- packages/superdough/dspworklet.mjs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/superdough/dspworklet.mjs b/packages/superdough/dspworklet.mjs index 2a77de534..1c1094523 100644 --- a/packages/superdough/dspworklet.mjs +++ b/packages/superdough/dspworklet.mjs @@ -24,10 +24,13 @@ class MyProcessor extends AudioWorkletProcessor { process(inputs, outputs, parameters) { const output = outputs[0]; if(__q.length) { - __q = __q.filter((el) => { - const deadline = el.time-currentTime; - return deadline>0 ? true : trigger(el.dough) - }) + for(let i=0;i<__q.length;++i) { + const deadline = __q[i].time-currentTime; + if(deadline<=0) { + trigger(__q[i].dough) + __q.splice(i,1) + } + } } for (let i = 0; i < output[0].length; i++) { const out = dsp(this.t / sampleRate); From eec3752b5a9b2ba0318029fbe4c51e03e9bf4dc7 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Sep 2023 23:57:09 +0200 Subject: [PATCH 47/74] cleanup --- packages/superdough/dspworklet.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/superdough/dspworklet.mjs b/packages/superdough/dspworklet.mjs index 1c1094523..deff485a3 100644 --- a/packages/superdough/dspworklet.mjs +++ b/packages/superdough/dspworklet.mjs @@ -14,7 +14,6 @@ class MyProcessor extends AudioWorkletProcessor { if(e.data==='stop') { this.stopped = true; } else if(e.data?.dough) { - const deadline = e.data.time-currentTime; __q.push(e.data) } else { msg?.(e.data) From 62743edf4540356e7e7aae474ff4adbf4a3bfe53 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Sep 2023 23:57:19 +0200 Subject: [PATCH 48/74] bump superdough to 0.9.9 --- packages/superdough/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/package.json b/packages/superdough/package.json index 69ba6f8c7..387749c8e 100644 --- a/packages/superdough/package.json +++ b/packages/superdough/package.json @@ -1,6 +1,6 @@ { "name": "superdough", - "version": "0.9.8", + "version": "0.9.9", "description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.", "main": "index.mjs", "type": "module", From cf72e3bba5f22a6109c381af47634ebb34c2141a Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 27 Sep 2023 21:25:30 +0200 Subject: [PATCH 49/74] fix: add conditional imports to eval scope + fire postMessage on start --- website/src/repl/Repl.jsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index fe32e7412..4002eba87 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -33,7 +33,7 @@ const supabase = createClient( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', ); -const modules = [ +let modules = [ import('@strudel.cycles/core'), import('@strudel.cycles/tonal'), import('@strudel.cycles/mini'), @@ -45,13 +45,13 @@ const modules = [ import('@strudel.cycles/csound'), ]; if (isTauri()) { - modules.concat([ + modules = modules.concat([ import('@strudel/desktopbridge/loggerbridge.mjs'), import('@strudel/desktopbridge/midibridge.mjs'), import('@strudel/desktopbridge/oscbridge.mjs'), ]); } else { - modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); + modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); } const modulesLoading = evalScope( @@ -153,6 +153,8 @@ export function Repl({ embedded = false }) { if (!play) { cleanupDraw(false); window.postMessage('strudel-stop'); + } else { + window.postMessage('strudel-start'); } }, drawContext, From 68ab43b3ab2c79691e55e3cf2ca288573bc00e72 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 27 Sep 2023 21:26:24 +0200 Subject: [PATCH 50/74] support midi clock via "clock" control (not on desktop yet) --- packages/core/controls.mjs | 1 + packages/midi/midi.mjs | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 6cac6e540..62cfdd46e 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1066,6 +1066,7 @@ const generic_params = [ */ ['waveloss'], // TODO: midi effects? + ['clock'], ['dur'], // ['modwheel'], ['expression'], diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 98509b461..32583d75d 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -90,6 +90,13 @@ Pattern.prototype.midi = function (output) { enableWebMidi({ onEnabled: ({ outputs }) => { const device = getDevice(output, outputs); + if (typeof window !== 'undefined') { + window.addEventListener('message', (e) => { + if (e.data === 'strudel-stop') { + device.sendStop(); + } + }); + } const otherOutputs = outputs.filter((o) => o.name !== device.name); logger( `Midi enabled! Using "${device.name}". ${ @@ -103,6 +110,7 @@ Pattern.prototype.midi = function (output) { return this.onTrigger((time, hap, currentTime, cps) => { if (!WebMidi.enabled) { + console.log('not enabled'); return; } const device = getDevice(output, WebMidi.outputs); @@ -113,7 +121,7 @@ Pattern.prototype.midi = function (output) { const timeOffsetString = `+${offset}`; // destructure value - const { note, nrpnn, nrpv, ccn, ccv, midichan = 1 } = hap.value; + const { note, nrpnn, nrpv, ccn, ccv, midichan = 1, clock } = hap.value; const velocity = hap.context?.velocity ?? 0.9; // TODO: refactor velocity // note off messages will often a few ms arrive late, try to prevent glitching by subtracting from the duration length @@ -125,7 +133,7 @@ Pattern.prototype.midi = function (output) { time: timeOffsetString, }); } - if (ccv && ccn) { + if (ccv !== undefined && ccn !== undefined) { if (typeof ccv !== 'number' || ccv < 0 || ccv > 1) { throw new Error('expected ccv to be a number between 0 and 1'); } @@ -135,5 +143,12 @@ Pattern.prototype.midi = function (output) { const scaled = Math.round(ccv * 127); device.sendControlChange(ccn, scaled, midichan, { time: timeOffsetString }); } + const begin = hap.whole.begin + 0; + if (begin === 0) { + device.sendStart({ time: timeOffsetString }); + } + if (clock) { + device.sendClock({ time: timeOffsetString }); + } }); }; From dea1c31701a9ad8b0bbb081c85401b088f480f88 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 27 Sep 2023 22:10:21 +0200 Subject: [PATCH 51/74] use midicmd instead of clock --- packages/core/controls.mjs | 2 +- packages/midi/midi.mjs | 21 ++++++++------------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 62cfdd46e..d1a9ce7f5 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1066,7 +1066,7 @@ const generic_params = [ */ ['waveloss'], // TODO: midi effects? - ['clock'], + ['midicmd'], ['dur'], // ['modwheel'], ['expression'], diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 32583d75d..ee111d7e1 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -90,13 +90,6 @@ Pattern.prototype.midi = function (output) { enableWebMidi({ onEnabled: ({ outputs }) => { const device = getDevice(output, outputs); - if (typeof window !== 'undefined') { - window.addEventListener('message', (e) => { - if (e.data === 'strudel-stop') { - device.sendStop(); - } - }); - } const otherOutputs = outputs.filter((o) => o.name !== device.name); logger( `Midi enabled! Using "${device.name}". ${ @@ -121,7 +114,7 @@ Pattern.prototype.midi = function (output) { const timeOffsetString = `+${offset}`; // destructure value - const { note, nrpnn, nrpv, ccn, ccv, midichan = 1, clock } = hap.value; + const { note, nrpnn, nrpv, ccn, ccv, midichan = 1, midicmd } = hap.value; const velocity = hap.context?.velocity ?? 0.9; // TODO: refactor velocity // note off messages will often a few ms arrive late, try to prevent glitching by subtracting from the duration length @@ -143,12 +136,14 @@ Pattern.prototype.midi = function (output) { const scaled = Math.round(ccv * 127); device.sendControlChange(ccn, scaled, midichan, { time: timeOffsetString }); } - const begin = hap.whole.begin + 0; - if (begin === 0) { - device.sendStart({ time: timeOffsetString }); - } - if (clock) { + if (['clock', 'midiClock'].includes(midicmd)) { device.sendClock({ time: timeOffsetString }); + } else if (['start'].includes(midicmd)) { + device.sendStart({ time: timeOffsetString }); + } else if (['stop'].includes(midicmd)) { + device.sendStop({ time: timeOffsetString }); + } else if (['continue'].includes(midicmd)) { + device.sendContinue({ time: timeOffsetString }); } }); }; From f11462bf41ee9898cac562970e6252035cdd77a4 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 27 Sep 2023 22:28:52 +0200 Subject: [PATCH 52/74] sync start / stop automatically too --- packages/midi/midi.mjs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index ee111d7e1..e3ce0b3e3 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -78,6 +78,20 @@ function getDevice(output, outputs) { return IACOutput ?? outputs[0]; } +// send start/stop messages to outputs when repl starts/stops +if (typeof window !== 'undefined') { + window.addEventListener('message', (e) => { + if (!WebMidi?.enabled) { + return; + } + if (e.data === 'strudel-stop') { + WebMidi.outputs.forEach((output) => output.sendStop()); + } else if (e.data === 'strudel-start') { + WebMidi.outputs.forEach((output) => output.sendStart()); + } + }); +} + Pattern.prototype.midi = function (output) { if (isPattern(output)) { throw new Error( From 4eb0a7b7c0606acd5469b93f5275481dff86132a Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 27 Sep 2023 22:42:35 +0200 Subject: [PATCH 53/74] send start with accurate timing --- packages/midi/midi.mjs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index e3ce0b3e3..07e6e65ce 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -86,9 +86,8 @@ if (typeof window !== 'undefined') { } if (e.data === 'strudel-stop') { WebMidi.outputs.forEach((output) => output.sendStop()); - } else if (e.data === 'strudel-start') { - WebMidi.outputs.forEach((output) => output.sendStart()); } + // cannot start here, since we have no timing info, see sendStart below }); } @@ -150,6 +149,10 @@ Pattern.prototype.midi = function (output) { const scaled = Math.round(ccv * 127); device.sendControlChange(ccn, scaled, midichan, { time: timeOffsetString }); } + if (hap.whole.begin + 0 === 0) { + // we need to start here because we have the timing info + device.sendStart({ time: timeOffsetString }); + } if (['clock', 'midiClock'].includes(midicmd)) { device.sendClock({ time: timeOffsetString }); } else if (['start'].includes(midicmd)) { From 6c3a3b9e29123286f686c00851c4ff44a28e142a Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 28 Sep 2023 10:40:44 +0200 Subject: [PATCH 54/74] dedupe --- packages/core/controls.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index d1a9ce7f5..6cac6e540 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1066,7 +1066,6 @@ const generic_params = [ */ ['waveloss'], // TODO: midi effects? - ['midicmd'], ['dur'], // ['modwheel'], ['expression'], From 0dcc55ee167a6dd26cf4a671ae73e326e62ae997 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 28 Sep 2023 10:48:31 +0200 Subject: [PATCH 55/74] prevent error --- packages/midi/midi.mjs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 1d89b6dcb..ab59c2e42 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -167,7 +167,6 @@ let listeners = {}; const refs = {}; export async function midin(input) { - console.log('midi in...'); const initial = await enableWebMidi(); // only returns on first init const device = getDevice(input, WebMidi.inputs); @@ -186,9 +185,8 @@ export async function midin(input) { listeners[input] = (e) => { const cc = e.dataBytes[0]; const v = e.dataBytes[1]; - refs[input][cc] = v / 127; + refs[input] && (refs[input][cc] = v / 127); }; device.addListener('midimessage', listeners[input]); - //return { cc }; return cc; } From aa094bf9309ccef568b1d34726aaf878437df802 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 29 Sep 2023 10:40:00 +0200 Subject: [PATCH 56/74] checkbox + number slider --- packages/codemirror/checkbox.mjs | 87 ++++++++++++ packages/codemirror/slider.mjs | 129 ++++++++++++++++++ packages/react/src/components/CodeMirror6.jsx | 4 +- 3 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 packages/codemirror/checkbox.mjs create mode 100644 packages/codemirror/slider.mjs diff --git a/packages/codemirror/checkbox.mjs b/packages/codemirror/checkbox.mjs new file mode 100644 index 000000000..279e2eb89 --- /dev/null +++ b/packages/codemirror/checkbox.mjs @@ -0,0 +1,87 @@ +import { WidgetType } from '@codemirror/view'; +import { ViewPlugin, Decoration } from '@codemirror/view'; +import { syntaxTree } from '@codemirror/language'; + +export class CheckboxWidget extends WidgetType { + constructor(checked) { + super(); + this.checked = checked; + } + + eq(other) { + return other.checked == this.checked; + } + + toDOM() { + let wrap = document.createElement('span'); + wrap.setAttribute('aria-hidden', 'true'); + wrap.className = 'cm-boolean-toggle'; + let box = wrap.appendChild(document.createElement('input')); + box.type = 'checkbox'; + box.checked = this.checked; + return wrap; + } + + ignoreEvent() { + return false; + } +} + +// EditorView +export function checkboxes(view) { + let widgets = []; + for (let { from, to } of view.visibleRanges) { + syntaxTree(view.state).iterate({ + from, + to, + enter: (node) => { + if (node.name == 'BooleanLiteral') { + let isTrue = view.state.doc.sliceString(node.from, node.to) == 'true'; + let deco = Decoration.widget({ + widget: new CheckboxWidget(isTrue), + side: 1, + }); + widgets.push(deco.range(node.from)); + } + }, + }); + } + return Decoration.set(widgets); +} + +export const checkboxPlugin = ViewPlugin.fromClass( + class { + decorations; //: DecorationSet + + constructor(view /* : EditorView */) { + this.decorations = checkboxes(view); + } + + update(update /* : ViewUpdate */) { + if (update.docChanged || update.viewportChanged) this.decorations = checkboxes(update.view); + } + }, + { + decorations: (v) => v.decorations, + + eventHandlers: { + mousedown: (e, view) => { + let target = e.target; /* as HTMLElement */ + if (target.nodeName == 'INPUT' && target.parentElement.classList.contains('cm-boolean-toggle')) + return toggleBoolean(view, view.posAtDOM(target)); + }, + }, + }, +); + +function toggleBoolean(view /* : EditorView */, pos /* : number */) { + let before = view.state.doc.sliceString(Math.max(0, pos), pos + 5).trim(); + let change; + if (!['true', 'false'].includes(before)) { + return false; + } + let insert = before === 'true' ? 'false' : 'true'; + change = { from: pos, to: pos + before.length, insert }; + view.dispatch({ changes: change }); + return true; +} diff --git a/packages/codemirror/slider.mjs b/packages/codemirror/slider.mjs new file mode 100644 index 000000000..f03dba182 --- /dev/null +++ b/packages/codemirror/slider.mjs @@ -0,0 +1,129 @@ +import { WidgetType } from '@codemirror/view'; +import { ViewPlugin, Decoration } from '@codemirror/view'; +import { syntaxTree } from '@codemirror/language'; + +export class SliderWidget extends WidgetType { + constructor(value, min, max, from, to) { + super(); + this.value = value; + this.min = min; + this.max = max; + this.from = from; + this.to = to; + } + + eq(other) { + const isSame = other.value.toFixed(4) == this.value.toFixed(4); + return isSame; + } + + toDOM() { + let wrap = document.createElement('span'); + wrap.setAttribute('aria-hidden', 'true'); + wrap.className = 'cm-slider'; // inline-flex items-center + let slider = wrap.appendChild(document.createElement('input')); + slider.type = 'range'; + slider.min = this.min; + slider.max = this.max; + slider.step = (this.max - this.min) / 1000; + slider.value = this.value; + slider.from = this.from; + slider.to = this.to; + slider.className = 'w-16'; + return wrap; + } + + ignoreEvent() { + return false; + } +} + +// EditorView +export function sliders(view) { + let widgets = []; + for (let { from, to } of view.visibleRanges) { + syntaxTree(view.state).iterate({ + from, + to, + enter: (node) => { + if (node.name == 'Number') { + let value = view.state.doc.sliceString(node.from, node.to); + value = Number(value); + /* let min = Math.min(0, value); + let max = Math.max(value, 1); */ + let min = 0; + let max = 10; + //console.log('from', node.from, 'to', node.to); + let deco = Decoration.widget({ + widget: new SliderWidget(value, min, max, node.from, node.to), + side: 1, + }); + widgets.push(deco.range(node.from)); + } + }, + }); + } + return Decoration.set(widgets); +} + +let draggedSlider, init; +export const sliderPlugin = ViewPlugin.fromClass( + class { + decorations; //: DecorationSet + + constructor(view /* : EditorView */) { + this.decorations = sliders(view); + } + + update(update /* : ViewUpdate */) { + if (update.docChanged || update.viewportChanged) { + !init && (this.decorations = sliders(update.view)); + //init = true; + } + } + }, + { + decorations: (v) => v.decorations, + + eventHandlers: { + mousedown: (e, view) => { + let target = e.target; /* as HTMLElement */ + if (target.nodeName == 'INPUT' && target.parentElement.classList.contains('cm-slider')) { + draggedSlider = target; + // remember offsetLeft / clientWidth, as they will vanish inside mousemove events for some reason + draggedSlider._offsetLeft = draggedSlider.offsetLeft; + draggedSlider._clientWidth = draggedSlider.clientWidth; + return updateSliderValue(view, e); + } + }, + mouseup: () => { + draggedSlider = undefined; + }, + mousemove: (e, view) => { + draggedSlider && updateSliderValue(view, e); + }, + }, + }, +); + +function updateSliderValue(view, e) { + const mouseX = e.clientX; + let progress = (mouseX - draggedSlider._offsetLeft) / draggedSlider._clientWidth; + progress = Math.max(Math.min(1, progress), 0); + let min = Number(draggedSlider.min); + let max = Number(draggedSlider.max); + const next = Number(progress * (max - min) + min); + let insert = next.toFixed(2); + let before = view.state.doc.sliceString(draggedSlider.from, draggedSlider.to).trim(); + before = Number(before).toFixed(4); + if (before === next) { + return false; + } + //console.log('before', before, '->', insert); + let change = { from: draggedSlider.from, to: draggedSlider.to, insert }; + draggedSlider.to = draggedSlider.from + insert.length; + //console.log('change', change); + view.dispatch({ changes: change }); + + return true; +} diff --git a/packages/react/src/components/CodeMirror6.jsx b/packages/react/src/components/CodeMirror6.jsx index 0f1b2274b..f3c764e09 100644 --- a/packages/react/src/components/CodeMirror6.jsx +++ b/packages/react/src/components/CodeMirror6.jsx @@ -15,10 +15,12 @@ import { updateMiniLocations, } from '@strudel/codemirror'; import './style.css'; +import { checkboxPlugin } from '@strudel/codemirror/checkbox.mjs'; +import { sliderPlugin } from '@strudel/codemirror/slider.mjs'; export { flash, highlightMiniLocations, updateMiniLocations }; -const staticExtensions = [javascript(), flashField, highlightExtension]; +const staticExtensions = [javascript(), flashField, highlightExtension, checkboxPlugin, sliderPlugin]; export default function CodeMirror({ value, From c93e4a951a003a21431e8cc9388968bc55dc68e7 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 29 Sep 2023 12:58:16 +0200 Subject: [PATCH 57/74] match number.slider --- packages/codemirror/slider.mjs | 57 ++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/packages/codemirror/slider.mjs b/packages/codemirror/slider.mjs index f03dba182..423d36512 100644 --- a/packages/codemirror/slider.mjs +++ b/packages/codemirror/slider.mjs @@ -38,6 +38,43 @@ export class SliderWidget extends WidgetType { } } +let nodeValue = (node, view) => view.state.doc.sliceString(node.from, node.to); + +// matches a number and returns slider widget +/* let matchNumber = (node, view) => { + if (node.name == 'Number') { + const value = view.state.doc.sliceString(node.from, node.to); + let min = 0; + let max = 10; + return Decoration.widget({ + widget: new SliderWidget(Number(value), min, max, node.from, node.to), + side: 0, + }); + } +}; */ +// matches something like 123.xxx and returns slider widget +let matchNumberSlider = (node, view) => { + if ( + node.name === 'MemberExpression' && + node.node.firstChild.name === 'Number' && + node.node.lastChild.name === 'PropertyName' + ) { + // node is sth like 123.xxx + let prop = nodeValue(node.node.lastChild, view); // get prop name (e.g. xxx) + if (prop === 'slider') { + let value = nodeValue(node.node.firstChild, view); // get number (e.g. 123) + // console.log('slider value', value); + let { from, to } = node.node.firstChild; + let min = 0; + let max = 10; + return Decoration.widget({ + widget: new SliderWidget(Number(value), min, max, from, to), + side: 0, + }); + } + } +}; + // EditorView export function sliders(view) { let widgets = []; @@ -46,20 +83,14 @@ export function sliders(view) { from, to, enter: (node) => { - if (node.name == 'Number') { - let value = view.state.doc.sliceString(node.from, node.to); - value = Number(value); - /* let min = Math.min(0, value); - let max = Math.max(value, 1); */ - let min = 0; - let max = 10; - //console.log('from', node.from, 'to', node.to); - let deco = Decoration.widget({ - widget: new SliderWidget(value, min, max, node.from, node.to), - side: 1, - }); - widgets.push(deco.range(node.from)); + let numberSlider = matchNumberSlider(node, view); + if (numberSlider) { + widgets.push(numberSlider.range(node.from)); } + /* let number = matchNumber(node, view); + if (number) { + widgets.push(number.range(node.from)); + } */ }, }); } From 276cf858fcf851bbe92a7b3f4a31dac405714c64 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 30 Sep 2023 20:39:03 +0200 Subject: [PATCH 58/74] match slider as function --- packages/codemirror/slider.mjs | 53 ++++++++++++++++------------------ website/tailwind.config.cjs | 1 + 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/packages/codemirror/slider.mjs b/packages/codemirror/slider.mjs index 423d36512..630150514 100644 --- a/packages/codemirror/slider.mjs +++ b/packages/codemirror/slider.mjs @@ -13,7 +13,7 @@ export class SliderWidget extends WidgetType { } eq(other) { - const isSame = other.value.toFixed(4) == this.value.toFixed(4); + const isSame = other.value.toFixed(4) == this.value.toFixed(4) && other.min == this.min && other.max == this.max; return isSame; } @@ -29,7 +29,7 @@ export class SliderWidget extends WidgetType { slider.value = this.value; slider.from = this.from; slider.to = this.to; - slider.className = 'w-16'; + slider.className = 'w-16 translate-y-1.5'; return wrap; } @@ -52,26 +52,28 @@ let nodeValue = (node, view) => view.state.doc.sliceString(node.from, node.to); }); } }; */ -// matches something like 123.xxx and returns slider widget -let matchNumberSlider = (node, view) => { - if ( - node.name === 'MemberExpression' && - node.node.firstChild.name === 'Number' && - node.node.lastChild.name === 'PropertyName' - ) { - // node is sth like 123.xxx - let prop = nodeValue(node.node.lastChild, view); // get prop name (e.g. xxx) - if (prop === 'slider') { - let value = nodeValue(node.node.firstChild, view); // get number (e.g. 123) - // console.log('slider value', value); - let { from, to } = node.node.firstChild; - let min = 0; - let max = 10; - return Decoration.widget({ + +// matches something like slider(123) and returns slider widget +let matchSliderFunction = (node, view) => { + if (node.name === 'CallExpression' /* && node.node.firstChild.name === 'ArgList' */) { + let name = nodeValue(node.node.firstChild, view); // slider ? + if (name === 'slider') { + const args = node.node.lastChild.getChildren('Number'); + if (!args.length) { + return; + } + const [value, min = 0, max = 1] = args.map((node) => nodeValue(node, view)); + //console.log('slider value', value, min, max); + let { from, to } = args[0]; + let widget = Decoration.widget({ widget: new SliderWidget(Number(value), min, max, from, to), side: 0, }); + //widget._range = widget.range(from); + widget._range = widget.range(node.from); + return widget; } + // node is sth like 123.xxx } }; @@ -83,14 +85,11 @@ export function sliders(view) { from, to, enter: (node) => { - let numberSlider = matchNumberSlider(node, view); - if (numberSlider) { - widgets.push(numberSlider.range(node.from)); + let widget = matchSliderFunction(node, view); + // let widget = matchNumber(node, view); + if (widget) { + widgets.push(widget._range || widget.range(node.from)); } - /* let number = matchNumber(node, view); - if (number) { - widgets.push(number.range(node.from)); - } */ }, }); } @@ -150,11 +149,9 @@ function updateSliderValue(view, e) { if (before === next) { return false; } - //console.log('before', before, '->', insert); let change = { from: draggedSlider.from, to: draggedSlider.to, insert }; draggedSlider.to = draggedSlider.from + insert.length; - //console.log('change', change); view.dispatch({ changes: change }); - + window.postMessage({ type: 'slider-change', value: next }); return true; } diff --git a/website/tailwind.config.cjs b/website/tailwind.config.cjs index d92d5949c..2c682d7d8 100644 --- a/website/tailwind.config.cjs +++ b/website/tailwind.config.cjs @@ -7,6 +7,7 @@ module.exports = { content: [ './src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}', '../packages/react/src/**/*.{html,js,jsx,md,mdx,ts,tsx}', + '../packages/codemirror/slider.mjs', ], theme: { extend: { From 2731e70fb7c41b654d01cb2cd21a65afcfcbf758 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 30 Sep 2023 21:03:28 +0200 Subject: [PATCH 59/74] use loc as slider id --- packages/codemirror/slider.mjs | 2 +- packages/transpiler/transpiler.mjs | 25 ++++++++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/packages/codemirror/slider.mjs b/packages/codemirror/slider.mjs index 630150514..2e75959b2 100644 --- a/packages/codemirror/slider.mjs +++ b/packages/codemirror/slider.mjs @@ -152,6 +152,6 @@ function updateSliderValue(view, e) { let change = { from: draggedSlider.from, to: draggedSlider.to, insert }; draggedSlider.to = draggedSlider.from + insert.length; view.dispatch({ changes: change }); - window.postMessage({ type: 'slider-change', value: next }); + window.postMessage({ type: 'cm-slider', value: next, loc: draggedSlider.from }); return true; } diff --git a/packages/transpiler/transpiler.mjs b/packages/transpiler/transpiler.mjs index 78aae9f7c..9ff558cbd 100644 --- a/packages/transpiler/transpiler.mjs +++ b/packages/transpiler/transpiler.mjs @@ -35,6 +35,10 @@ export function transpiler(input, options = {}) { emitMiniLocations && collectMiniLocations(value, node); return this.replace(miniWithLocation(value, node)); } + if (isWidgetFunction(node)) { + // collectSliderLocations? + return this.replace(widgetWithLocation(node)); + } // TODO: remove pseudo note variables? if (node.type === 'Identifier' && isNoteWithOctave(node.name)) { this.skip(); @@ -68,11 +72,10 @@ export function transpiler(input, options = {}) { } function isStringWithDoubleQuotes(node, locations, code) { - const { raw, type } = node; - if (type !== 'Literal') { + if (node.type !== 'Literal') { return false; } - return raw[0] === '"'; + return node.raw[0] === '"'; } function isBackTickString(node, parent) { @@ -94,3 +97,19 @@ function miniWithLocation(value, node) { optional: false, }; } + +function isWidgetFunction(node) { + return node.type === 'CallExpression' && node.callee.name === 'slider'; +} + +function widgetWithLocation(node) { + const loc = node.arguments[0].start; + // add loc as identifier to first argument + // the slider function is assumed to be slider(loc, value, min?, max?) + node.arguments.unshift({ + type: 'Literal', + value: loc, + raw: loc + '', + }); + return node; +} From b36cee93f7e86b003e65b438caee14bdd24b976c Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 30 Sep 2023 21:22:49 +0200 Subject: [PATCH 60/74] add slider function to scope --- packages/codemirror/index.mjs | 1 + packages/codemirror/slider.mjs | 25 +++++++++++++++++++++++-- packages/transpiler/transpiler.mjs | 10 ++++++---- website/src/repl/Repl.jsx | 1 + 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/packages/codemirror/index.mjs b/packages/codemirror/index.mjs index bf7ce9714..c847c32c2 100644 --- a/packages/codemirror/index.mjs +++ b/packages/codemirror/index.mjs @@ -1,3 +1,4 @@ export * from './codemirror.mjs'; export * from './highlight.mjs'; export * from './flash.mjs'; +export * from './slider.mjs'; diff --git a/packages/codemirror/slider.mjs b/packages/codemirror/slider.mjs index 2e75959b2..7695cee62 100644 --- a/packages/codemirror/slider.mjs +++ b/packages/codemirror/slider.mjs @@ -13,7 +13,12 @@ export class SliderWidget extends WidgetType { } eq(other) { - const isSame = other.value.toFixed(4) == this.value.toFixed(4) && other.min == this.min && other.max == this.max; + const isSame = + other.value.toFixed(4) == this.value.toFixed(4) && + other.min == this.min && + other.max == this.max && + other.from === this.from && + other.to === this.to; return isSame; } @@ -152,6 +157,22 @@ function updateSliderValue(view, e) { let change = { from: draggedSlider.from, to: draggedSlider.to, insert }; draggedSlider.to = draggedSlider.from + insert.length; view.dispatch({ changes: change }); - window.postMessage({ type: 'cm-slider', value: next, loc: draggedSlider.from }); + const id = 'slider_' + draggedSlider.from; // matches id generated in transpiler + window.postMessage({ type: 'cm-slider', value: next, id }); return true; } + +export let sliderValues = {}; + +export let slider = (id, value, min, max) => { + sliderValues[id] = value; // sync state at eval time (code -> state) + return ref(() => sliderValues[id]); // use state at query time +}; +if (typeof window !== 'undefined') { + window.addEventListener('message', (e) => { + if (e.data.type === 'cm-slider') { + // update state when slider is moved + sliderValues[e.data.id] = e.data.value; + } + }); +} diff --git a/packages/transpiler/transpiler.mjs b/packages/transpiler/transpiler.mjs index 9ff558cbd..28f7fdfaa 100644 --- a/packages/transpiler/transpiler.mjs +++ b/packages/transpiler/transpiler.mjs @@ -98,18 +98,20 @@ function miniWithLocation(value, node) { }; } +// these functions are connected to @strudel/codemirror -> slider.mjs +// maybe someday there will be pluggable transpiler functions, then move this there function isWidgetFunction(node) { return node.type === 'CallExpression' && node.callee.name === 'slider'; } function widgetWithLocation(node) { - const loc = node.arguments[0].start; + const id = 'slider_' + node.arguments[0].start; // use loc of first arg for id // add loc as identifier to first argument - // the slider function is assumed to be slider(loc, value, min?, max?) + // the slider function is assumed to be slider(id, value, min?, max?) node.arguments.unshift({ type: 'Literal', - value: loc, - raw: loc + '', + value: id, + raw: id, }); return node; } diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 4002eba87..9d80cc533 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -39,6 +39,7 @@ let modules = [ import('@strudel.cycles/mini'), import('@strudel.cycles/xen'), import('@strudel.cycles/webaudio'), + import('@strudel/codemirror'), import('@strudel.cycles/serial'), import('@strudel.cycles/soundfonts'), From 062d92690036394418717821499ebc71225e1114 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 1 Oct 2023 00:24:31 +0200 Subject: [PATCH 61/74] make sliders work! --- packages/codemirror/slider.mjs | 149 +++++++++++------------- packages/react/src/hooks/useWidgets.mjs | 13 +++ packages/transpiler/transpiler.mjs | 14 ++- website/src/repl/Repl.jsx | 6 +- 4 files changed, 95 insertions(+), 87 deletions(-) create mode 100644 packages/react/src/hooks/useWidgets.mjs diff --git a/packages/codemirror/slider.mjs b/packages/codemirror/slider.mjs index 7695cee62..6510f5114 100644 --- a/packages/codemirror/slider.mjs +++ b/packages/codemirror/slider.mjs @@ -1,6 +1,8 @@ -import { WidgetType } from '@codemirror/view'; -import { ViewPlugin, Decoration } from '@codemirror/view'; -import { syntaxTree } from '@codemirror/language'; +import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view'; +import { StateEffect, StateField } from '@codemirror/state'; + +export let sliderValues = {}; +const getSliderID = (from) => `slider_${from}`; export class SliderWidget extends WidgetType { constructor(value, min, max, from, to) { @@ -9,17 +11,12 @@ export class SliderWidget extends WidgetType { this.min = min; this.max = max; this.from = from; + this.originalFrom = from; this.to = to; } - eq(other) { - const isSame = - other.value.toFixed(4) == this.value.toFixed(4) && - other.min == this.min && - other.max == this.max && - other.from === this.from && - other.to === this.to; - return isSame; + eq() { + return false; } toDOM() { @@ -31,10 +28,15 @@ export class SliderWidget extends WidgetType { slider.min = this.min; slider.max = this.max; slider.step = (this.max - this.min) / 1000; - slider.value = this.value; + slider.originalValue = this.value.toFixed(2); + // to make sure the code stays in sync, let's save the original value + // becuase .value automatically clamps values so it'll desync with the code + slider.value = slider.originalValue; slider.from = this.from; + slider.originalFrom = this.originalFrom; slider.to = this.to; - slider.className = 'w-16 translate-y-1.5'; + slider.className = 'w-16 translate-y-1.5 mx-2'; + this.slider = slider; return wrap; } @@ -43,78 +45,50 @@ export class SliderWidget extends WidgetType { } } -let nodeValue = (node, view) => view.state.doc.sliceString(node.from, node.to); +export const setWidgets = StateEffect.define(); -// matches a number and returns slider widget -/* let matchNumber = (node, view) => { - if (node.name == 'Number') { - const value = view.state.doc.sliceString(node.from, node.to); - let min = 0; - let max = 10; - return Decoration.widget({ - widget: new SliderWidget(Number(value), min, max, node.from, node.to), - side: 0, - }); - } -}; */ - -// matches something like slider(123) and returns slider widget -let matchSliderFunction = (node, view) => { - if (node.name === 'CallExpression' /* && node.node.firstChild.name === 'ArgList' */) { - let name = nodeValue(node.node.firstChild, view); // slider ? - if (name === 'slider') { - const args = node.node.lastChild.getChildren('Number'); - if (!args.length) { - return; - } - const [value, min = 0, max = 1] = args.map((node) => nodeValue(node, view)); - //console.log('slider value', value, min, max); - let { from, to } = args[0]; - let widget = Decoration.widget({ - widget: new SliderWidget(Number(value), min, max, from, to), - side: 0, - }); - //widget._range = widget.range(from); - widget._range = widget.range(node.from); - return widget; - } - // node is sth like 123.xxx - } +export const updateWidgets = (view, widgets) => { + view.dispatch({ effects: setWidgets.of(widgets) }); }; -// EditorView -export function sliders(view) { - let widgets = []; - for (let { from, to } of view.visibleRanges) { - syntaxTree(view.state).iterate({ - from, - to, - enter: (node) => { - let widget = matchSliderFunction(node, view); - // let widget = matchNumber(node, view); - if (widget) { - widgets.push(widget._range || widget.range(node.from)); - } - }, - }); - } - return Decoration.set(widgets); +let draggedSlider; + +function getWidgets(widgetConfigs) { + return widgetConfigs.map(({ from, to, value, min, max }) => { + return Decoration.widget({ + widget: new SliderWidget(Number(value), min, max, from, to), + side: 0, + }).range(from /* , to */); + }); } -let draggedSlider, init; export const sliderPlugin = ViewPlugin.fromClass( class { decorations; //: DecorationSet constructor(view /* : EditorView */) { - this.decorations = sliders(view); + this.decorations = Decoration.set([]); } update(update /* : ViewUpdate */) { - if (update.docChanged || update.viewportChanged) { - !init && (this.decorations = sliders(update.view)); - //init = true; - } + update.transactions.forEach((tr) => { + if (tr.docChanged) { + this.decorations = this.decorations.map(tr.changes); + const iterator = this.decorations.iter(); + while (iterator.value) { + // when the widgets are moved, we need to tell the dom node the current position + // this is important because the updateSliderValue function has to work with the dom node + iterator.value.widget.slider.from = iterator.from; + iterator.value.widget.slider.to = iterator.to; + iterator.next(); + } + } + for (let e of tr.effects) { + if (e.is(setWidgets)) { + this.decorations = Decoration.set(getWidgets(e.value)); + } + } + }); } }, { @@ -124,6 +98,8 @@ export const sliderPlugin = ViewPlugin.fromClass( mousedown: (e, view) => { let target = e.target; /* as HTMLElement */ if (target.nodeName == 'INPUT' && target.parentElement.classList.contains('cm-slider')) { + e.preventDefault(); + e.stopPropagation(); draggedSlider = target; // remember offsetLeft / clientWidth, as they will vanish inside mousemove events for some reason draggedSlider._offsetLeft = draggedSlider.offsetLeft; @@ -141,6 +117,7 @@ export const sliderPlugin = ViewPlugin.fromClass( }, ); +// moves slider on mouse event function updateSliderValue(view, e) { const mouseX = e.clientX; let progress = (mouseX - draggedSlider._offsetLeft) / draggedSlider._clientWidth; @@ -149,30 +126,38 @@ function updateSliderValue(view, e) { let max = Number(draggedSlider.max); const next = Number(progress * (max - min) + min); let insert = next.toFixed(2); - let before = view.state.doc.sliceString(draggedSlider.from, draggedSlider.to).trim(); - before = Number(before).toFixed(4); - if (before === next) { + //let before = view.state.doc.sliceString(draggedSlider.from, draggedSlider.to).trim(); + let before = draggedSlider.originalValue; + before = Number(before).toFixed(2); + // console.log('before', before, 'insert', insert, 'v'); + if (before === insert) { return false; } - let change = { from: draggedSlider.from, to: draggedSlider.to, insert }; - draggedSlider.to = draggedSlider.from + insert.length; + const to = draggedSlider.from + before.length; + let change = { from: draggedSlider.from, to, insert }; + draggedSlider.originalValue = insert; + draggedSlider.value = insert; view.dispatch({ changes: change }); - const id = 'slider_' + draggedSlider.from; // matches id generated in transpiler + const id = getSliderID(draggedSlider.originalFrom); // matches id generated in transpiler window.postMessage({ type: 'cm-slider', value: next, id }); return true; } -export let sliderValues = {}; - +// user api export let slider = (id, value, min, max) => { sliderValues[id] = value; // sync state at eval time (code -> state) return ref(() => sliderValues[id]); // use state at query time }; +// update state when sliders are moved if (typeof window !== 'undefined') { window.addEventListener('message', (e) => { if (e.data.type === 'cm-slider') { - // update state when slider is moved - sliderValues[e.data.id] = e.data.value; + if (sliderValues[e.data.id] !== undefined) { + // update state when slider is moved + sliderValues[e.data.id] = e.data.value; + } else { + console.warn(`slider with id "${e.data.id}" is not registered. Only ${Object.keys(sliderValues)}`); + } } }); } diff --git a/packages/react/src/hooks/useWidgets.mjs b/packages/react/src/hooks/useWidgets.mjs new file mode 100644 index 000000000..e7ca136a1 --- /dev/null +++ b/packages/react/src/hooks/useWidgets.mjs @@ -0,0 +1,13 @@ +import { useEffect, useState } from 'react'; +import { updateWidgets } from '@strudel/codemirror'; + +// i know this is ugly.. in the future, repl needs to run without react +export function useWidgets(view) { + const [widgets, setWidgets] = useState([]); + useEffect(() => { + if (view) { + updateWidgets(view, widgets); + } + }, [view, widgets]); + return { widgets, setWidgets }; +} diff --git a/packages/transpiler/transpiler.mjs b/packages/transpiler/transpiler.mjs index 28f7fdfaa..48b223d46 100644 --- a/packages/transpiler/transpiler.mjs +++ b/packages/transpiler/transpiler.mjs @@ -5,7 +5,7 @@ import { isNoteWithOctave } from '@strudel.cycles/core'; import { getLeafLocations } from '@strudel.cycles/mini'; export function transpiler(input, options = {}) { - const { wrapAsync = false, addReturn = true, emitMiniLocations = true } = options; + const { wrapAsync = false, addReturn = true, emitMiniLocations = true, emitWidgets = true } = options; let ast = parse(input, { ecmaVersion: 2022, @@ -16,9 +16,9 @@ export function transpiler(input, options = {}) { let miniLocations = []; const collectMiniLocations = (value, node) => { const leafLocs = getLeafLocations(`"${value}"`, node.start); // stimmt! - //const withOffset = leafLocs.map((offsets) => offsets.map((o) => o + node.start)); miniLocations = miniLocations.concat(leafLocs); }; + let widgets = []; walk(ast, { enter(node, parent /* , prop, index */) { @@ -37,6 +37,14 @@ export function transpiler(input, options = {}) { } if (isWidgetFunction(node)) { // collectSliderLocations? + emitWidgets && + widgets.push({ + from: node.arguments[0].start, + to: node.arguments[0].end, + value: node.arguments[0].value, + min: node.arguments[1]?.value ?? 0, + max: node.arguments[2]?.value ?? 1, + }); return this.replace(widgetWithLocation(node)); } // TODO: remove pseudo note variables? @@ -68,7 +76,7 @@ export function transpiler(input, options = {}) { if (!emitMiniLocations) { return { output }; } - return { output, miniLocations }; + return { output, miniLocations, widgets }; } function isStringWithDoubleQuotes(node, locations, code) { diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 9d80cc533..8fe43c6d2 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -22,6 +22,7 @@ import Loader from './Loader'; import { settingPatterns } from '../settings.mjs'; import { code2hash, hash2code } from './helpers.mjs'; import { isTauri } from '../tauri.mjs'; +import { useWidgets } from '@strudel.cycles/react/src/hooks/useWidgets.mjs'; const { latestCode } = settingsMap.get(); @@ -129,7 +130,7 @@ export function Repl({ embedded = false }) { } = useSettings(); const paintOptions = useMemo(() => ({ fontFamily }), [fontFamily]); - + const { setWidgets } = useWidgets(view); const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } = useStrudel({ initialCode: '// LOADING...', @@ -143,6 +144,7 @@ export function Repl({ embedded = false }) { }, afterEval: ({ code, meta }) => { setMiniLocations(meta.miniLocations); + setWidgets(meta.widgets); setPending(false); setLatestCode(code); window.location.hash = '#' + code2hash(code); @@ -220,7 +222,7 @@ export function Repl({ embedded = false }) { const handleChangeCode = useCallback( (c) => { setCode(c); - started && logger('[edit] code changed. hit ctrl+enter to update'); + //started && logger('[edit] code changed. hit ctrl+enter to update'); }, [started], ); From 33c40e5ef84af31a1f395d1cf30416c9602aa9e8 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 1 Oct 2023 00:33:10 +0200 Subject: [PATCH 62/74] fix some odd number / string problems --- packages/codemirror/slider.mjs | 6 +++--- packages/transpiler/transpiler.mjs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/codemirror/slider.mjs b/packages/codemirror/slider.mjs index 6510f5114..5c2dcdab1 100644 --- a/packages/codemirror/slider.mjs +++ b/packages/codemirror/slider.mjs @@ -28,7 +28,7 @@ export class SliderWidget extends WidgetType { slider.min = this.min; slider.max = this.max; slider.step = (this.max - this.min) / 1000; - slider.originalValue = this.value.toFixed(2); + slider.originalValue = this.value; // to make sure the code stays in sync, let's save the original value // becuase .value automatically clamps values so it'll desync with the code slider.value = slider.originalValue; @@ -56,7 +56,7 @@ let draggedSlider; function getWidgets(widgetConfigs) { return widgetConfigs.map(({ from, to, value, min, max }) => { return Decoration.widget({ - widget: new SliderWidget(Number(value), min, max, from, to), + widget: new SliderWidget(value, min, max, from, to), side: 0, }).range(from /* , to */); }); @@ -133,7 +133,7 @@ function updateSliderValue(view, e) { if (before === insert) { return false; } - const to = draggedSlider.from + before.length; + const to = draggedSlider.from + draggedSlider.originalValue.length; let change = { from: draggedSlider.from, to, insert }; draggedSlider.originalValue = insert; draggedSlider.value = insert; diff --git a/packages/transpiler/transpiler.mjs b/packages/transpiler/transpiler.mjs index 48b223d46..5fa9dc49c 100644 --- a/packages/transpiler/transpiler.mjs +++ b/packages/transpiler/transpiler.mjs @@ -41,7 +41,7 @@ export function transpiler(input, options = {}) { widgets.push({ from: node.arguments[0].start, to: node.arguments[0].end, - value: node.arguments[0].value, + value: node.arguments[0].raw, // don't use value! min: node.arguments[1]?.value ?? 0, max: node.arguments[2]?.value ?? 1, }); From d7bc309eeb06bbe5f2f0e8aa12f9c9af2015452f Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 1 Oct 2023 00:36:34 +0200 Subject: [PATCH 63/74] fix: import --- packages/codemirror/slider.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/codemirror/slider.mjs b/packages/codemirror/slider.mjs index 5c2dcdab1..44875e25b 100644 --- a/packages/codemirror/slider.mjs +++ b/packages/codemirror/slider.mjs @@ -1,3 +1,4 @@ +import { ref } from '@strudel.cycles/core'; import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view'; import { StateEffect, StateField } from '@codemirror/state'; From c6ecd31ea11baef947f4f261f7edcea31290ebec Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 1 Oct 2023 13:27:44 +0200 Subject: [PATCH 64/74] improve mouse tracking --- packages/codemirror/slider.mjs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/codemirror/slider.mjs b/packages/codemirror/slider.mjs index 44875e25b..d4fbcefa4 100644 --- a/packages/codemirror/slider.mjs +++ b/packages/codemirror/slider.mjs @@ -36,7 +36,7 @@ export class SliderWidget extends WidgetType { slider.from = this.from; slider.originalFrom = this.originalFrom; slider.to = this.to; - slider.className = 'w-16 translate-y-1.5 mx-2'; + slider.className = 'w-16 translate-y-1'; this.slider = slider; return wrap; } @@ -97,7 +97,7 @@ export const sliderPlugin = ViewPlugin.fromClass( eventHandlers: { mousedown: (e, view) => { - let target = e.target; /* as HTMLElement */ + let target = e.target; if (target.nodeName == 'INPUT' && target.parentElement.classList.contains('cm-slider')) { e.preventDefault(); e.stopPropagation(); @@ -121,7 +121,8 @@ export const sliderPlugin = ViewPlugin.fromClass( // moves slider on mouse event function updateSliderValue(view, e) { const mouseX = e.clientX; - let progress = (mouseX - draggedSlider._offsetLeft) / draggedSlider._clientWidth; + let mx = 10; + let progress = (mouseX - draggedSlider._offsetLeft - mx) / (draggedSlider._clientWidth - mx * 2); progress = Math.max(Math.min(1, progress), 0); let min = Number(draggedSlider.min); let max = Number(draggedSlider.max); From b550ff038c33b1ecabff5f52e8916f0092740e31 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 1 Oct 2023 13:42:29 +0200 Subject: [PATCH 65/74] simplify: use native events --- packages/codemirror/slider.mjs | 75 +++++++++------------------------- 1 file changed, 19 insertions(+), 56 deletions(-) diff --git a/packages/codemirror/slider.mjs b/packages/codemirror/slider.mjs index d4fbcefa4..a4ee6a93f 100644 --- a/packages/codemirror/slider.mjs +++ b/packages/codemirror/slider.mjs @@ -6,7 +6,7 @@ export let sliderValues = {}; const getSliderID = (from) => `slider_${from}`; export class SliderWidget extends WidgetType { - constructor(value, min, max, from, to) { + constructor(value, min, max, from, to, view) { super(); this.value = value; this.min = min; @@ -14,6 +14,7 @@ export class SliderWidget extends WidgetType { this.from = from; this.originalFrom = from; this.to = to; + this.view = view; } eq() { @@ -38,11 +39,23 @@ export class SliderWidget extends WidgetType { slider.to = this.to; slider.className = 'w-16 translate-y-1'; this.slider = slider; + slider.addEventListener('input', (e) => { + const next = e.target.value; + let insert = next; + //let insert = next.toFixed(2); + const to = slider.from + slider.originalValue.length; + let change = { from: slider.from, to, insert }; + slider.originalValue = insert; + slider.value = insert; + this.view.dispatch({ changes: change }); + const id = getSliderID(slider.originalFrom); // matches id generated in transpiler + window.postMessage({ type: 'cm-slider', value: Number(next), id }); + }); return wrap; } - ignoreEvent() { - return false; + ignoreEvent(e) { + return true; } } @@ -52,12 +65,10 @@ export const updateWidgets = (view, widgets) => { view.dispatch({ effects: setWidgets.of(widgets) }); }; -let draggedSlider; - -function getWidgets(widgetConfigs) { +function getWidgets(widgetConfigs, view) { return widgetConfigs.map(({ from, to, value, min, max }) => { return Decoration.widget({ - widget: new SliderWidget(value, min, max, from, to), + widget: new SliderWidget(value, min, max, from, to, view), side: 0, }).range(from /* , to */); }); @@ -86,7 +97,7 @@ export const sliderPlugin = ViewPlugin.fromClass( } for (let e of tr.effects) { if (e.is(setWidgets)) { - this.decorations = Decoration.set(getWidgets(e.value)); + this.decorations = Decoration.set(getWidgets(e.value, update.view)); } } }); @@ -94,57 +105,9 @@ export const sliderPlugin = ViewPlugin.fromClass( }, { decorations: (v) => v.decorations, - - eventHandlers: { - mousedown: (e, view) => { - let target = e.target; - if (target.nodeName == 'INPUT' && target.parentElement.classList.contains('cm-slider')) { - e.preventDefault(); - e.stopPropagation(); - draggedSlider = target; - // remember offsetLeft / clientWidth, as they will vanish inside mousemove events for some reason - draggedSlider._offsetLeft = draggedSlider.offsetLeft; - draggedSlider._clientWidth = draggedSlider.clientWidth; - return updateSliderValue(view, e); - } - }, - mouseup: () => { - draggedSlider = undefined; - }, - mousemove: (e, view) => { - draggedSlider && updateSliderValue(view, e); - }, - }, }, ); -// moves slider on mouse event -function updateSliderValue(view, e) { - const mouseX = e.clientX; - let mx = 10; - let progress = (mouseX - draggedSlider._offsetLeft - mx) / (draggedSlider._clientWidth - mx * 2); - progress = Math.max(Math.min(1, progress), 0); - let min = Number(draggedSlider.min); - let max = Number(draggedSlider.max); - const next = Number(progress * (max - min) + min); - let insert = next.toFixed(2); - //let before = view.state.doc.sliceString(draggedSlider.from, draggedSlider.to).trim(); - let before = draggedSlider.originalValue; - before = Number(before).toFixed(2); - // console.log('before', before, 'insert', insert, 'v'); - if (before === insert) { - return false; - } - const to = draggedSlider.from + draggedSlider.originalValue.length; - let change = { from: draggedSlider.from, to, insert }; - draggedSlider.originalValue = insert; - draggedSlider.value = insert; - view.dispatch({ changes: change }); - const id = getSliderID(draggedSlider.originalFrom); // matches id generated in transpiler - window.postMessage({ type: 'cm-slider', value: next, id }); - return true; -} - // user api export let slider = (id, value, min, max) => { sliderValues[id] = value; // sync state at eval time (code -> state) From f84d5ba3a02d7d498cef44df7baaee654ab378b0 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 1 Oct 2023 13:44:41 +0200 Subject: [PATCH 66/74] add back some margin --- packages/codemirror/slider.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/codemirror/slider.mjs b/packages/codemirror/slider.mjs index a4ee6a93f..47bb7509f 100644 --- a/packages/codemirror/slider.mjs +++ b/packages/codemirror/slider.mjs @@ -37,7 +37,7 @@ export class SliderWidget extends WidgetType { slider.from = this.from; slider.originalFrom = this.originalFrom; slider.to = this.to; - slider.className = 'w-16 translate-y-1'; + slider.className = 'w-16 translate-y-1 mr-1'; this.slider = slider; slider.addEventListener('input', (e) => { const next = e.target.value; From d4bf358eae42ccbd78e1c6b9df81f9f1bba44d8c Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 1 Oct 2023 13:56:57 +0200 Subject: [PATCH 67/74] use raw css instead of tailwind --- packages/codemirror/slider.mjs | 2 +- website/tailwind.config.cjs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/codemirror/slider.mjs b/packages/codemirror/slider.mjs index 47bb7509f..e7be78f7c 100644 --- a/packages/codemirror/slider.mjs +++ b/packages/codemirror/slider.mjs @@ -37,7 +37,7 @@ export class SliderWidget extends WidgetType { slider.from = this.from; slider.originalFrom = this.originalFrom; slider.to = this.to; - slider.className = 'w-16 translate-y-1 mr-1'; + slider.style = 'width:64px;margin-right:4px;transform:translateY(4px)'; this.slider = slider; slider.addEventListener('input', (e) => { const next = e.target.value; diff --git a/website/tailwind.config.cjs b/website/tailwind.config.cjs index 2c682d7d8..d92d5949c 100644 --- a/website/tailwind.config.cjs +++ b/website/tailwind.config.cjs @@ -7,7 +7,6 @@ module.exports = { content: [ './src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}', '../packages/react/src/**/*.{html,js,jsx,md,mdx,ts,tsx}', - '../packages/codemirror/slider.mjs', ], theme: { extend: { From 564697e175e99ffea7430db1bb6d48d1757bf284 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 1 Oct 2023 14:05:16 +0200 Subject: [PATCH 68/74] add extra sliderWithID function + add warning to slider function --- packages/codemirror/slider.mjs | 8 ++++++-- packages/transpiler/transpiler.mjs | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/codemirror/slider.mjs b/packages/codemirror/slider.mjs index e7be78f7c..159e4fd18 100644 --- a/packages/codemirror/slider.mjs +++ b/packages/codemirror/slider.mjs @@ -108,8 +108,12 @@ export const sliderPlugin = ViewPlugin.fromClass( }, ); -// user api -export let slider = (id, value, min, max) => { +export let slider = (value) => { + console.warn('slider will only work when the transpiler is used... passing value as is'); + return pure(value); +}; +// function transpiled from slider = (value, min, max) +export let sliderWithID = (id, value, min, max) => { sliderValues[id] = value; // sync state at eval time (code -> state) return ref(() => sliderValues[id]); // use state at query time }; diff --git a/packages/transpiler/transpiler.mjs b/packages/transpiler/transpiler.mjs index 5fa9dc49c..37a937bc5 100644 --- a/packages/transpiler/transpiler.mjs +++ b/packages/transpiler/transpiler.mjs @@ -121,5 +121,6 @@ function widgetWithLocation(node) { value: id, raw: id, }); + node.callee.name = 'sliderWithID'; return node; } From c051a1249d7a6adede3817f85cb3fa0992e33ba5 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 1 Oct 2023 14:06:32 +0200 Subject: [PATCH 69/74] remove checkbox --- packages/codemirror/checkbox.mjs | 87 ------------------- packages/react/src/components/CodeMirror6.jsx | 3 +- 2 files changed, 1 insertion(+), 89 deletions(-) delete mode 100644 packages/codemirror/checkbox.mjs diff --git a/packages/codemirror/checkbox.mjs b/packages/codemirror/checkbox.mjs deleted file mode 100644 index 279e2eb89..000000000 --- a/packages/codemirror/checkbox.mjs +++ /dev/null @@ -1,87 +0,0 @@ -import { WidgetType } from '@codemirror/view'; -import { ViewPlugin, Decoration } from '@codemirror/view'; -import { syntaxTree } from '@codemirror/language'; - -export class CheckboxWidget extends WidgetType { - constructor(checked) { - super(); - this.checked = checked; - } - - eq(other) { - return other.checked == this.checked; - } - - toDOM() { - let wrap = document.createElement('span'); - wrap.setAttribute('aria-hidden', 'true'); - wrap.className = 'cm-boolean-toggle'; - let box = wrap.appendChild(document.createElement('input')); - box.type = 'checkbox'; - box.checked = this.checked; - return wrap; - } - - ignoreEvent() { - return false; - } -} - -// EditorView -export function checkboxes(view) { - let widgets = []; - for (let { from, to } of view.visibleRanges) { - syntaxTree(view.state).iterate({ - from, - to, - enter: (node) => { - if (node.name == 'BooleanLiteral') { - let isTrue = view.state.doc.sliceString(node.from, node.to) == 'true'; - let deco = Decoration.widget({ - widget: new CheckboxWidget(isTrue), - side: 1, - }); - widgets.push(deco.range(node.from)); - } - }, - }); - } - return Decoration.set(widgets); -} - -export const checkboxPlugin = ViewPlugin.fromClass( - class { - decorations; //: DecorationSet - - constructor(view /* : EditorView */) { - this.decorations = checkboxes(view); - } - - update(update /* : ViewUpdate */) { - if (update.docChanged || update.viewportChanged) this.decorations = checkboxes(update.view); - } - }, - { - decorations: (v) => v.decorations, - - eventHandlers: { - mousedown: (e, view) => { - let target = e.target; /* as HTMLElement */ - if (target.nodeName == 'INPUT' && target.parentElement.classList.contains('cm-boolean-toggle')) - return toggleBoolean(view, view.posAtDOM(target)); - }, - }, - }, -); - -function toggleBoolean(view /* : EditorView */, pos /* : number */) { - let before = view.state.doc.sliceString(Math.max(0, pos), pos + 5).trim(); - let change; - if (!['true', 'false'].includes(before)) { - return false; - } - let insert = before === 'true' ? 'false' : 'true'; - change = { from: pos, to: pos + before.length, insert }; - view.dispatch({ changes: change }); - return true; -} diff --git a/packages/react/src/components/CodeMirror6.jsx b/packages/react/src/components/CodeMirror6.jsx index f3c764e09..a5af53126 100644 --- a/packages/react/src/components/CodeMirror6.jsx +++ b/packages/react/src/components/CodeMirror6.jsx @@ -15,12 +15,11 @@ import { updateMiniLocations, } from '@strudel/codemirror'; import './style.css'; -import { checkboxPlugin } from '@strudel/codemirror/checkbox.mjs'; import { sliderPlugin } from '@strudel/codemirror/slider.mjs'; export { flash, highlightMiniLocations, updateMiniLocations }; -const staticExtensions = [javascript(), flashField, highlightExtension, checkboxPlugin, sliderPlugin]; +const staticExtensions = [javascript(), flashField, highlightExtension, sliderPlugin]; export default function CodeMirror({ value, From 21b99b3810eeb9b114d8a39f83faab8c1bd97f40 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 1 Oct 2023 14:07:48 +0200 Subject: [PATCH 70/74] remove comment --- packages/transpiler/transpiler.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/transpiler/transpiler.mjs b/packages/transpiler/transpiler.mjs index 37a937bc5..dced458fd 100644 --- a/packages/transpiler/transpiler.mjs +++ b/packages/transpiler/transpiler.mjs @@ -36,7 +36,6 @@ export function transpiler(input, options = {}) { return this.replace(miniWithLocation(value, node)); } if (isWidgetFunction(node)) { - // collectSliderLocations? emitWidgets && widgets.push({ from: node.arguments[0].start, From 935d8e8aea53362c247d9e3541ece5105769fa0d Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 1 Oct 2023 14:08:41 +0200 Subject: [PATCH 71/74] fix: comment --- packages/transpiler/transpiler.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/transpiler/transpiler.mjs b/packages/transpiler/transpiler.mjs index dced458fd..6eac171fe 100644 --- a/packages/transpiler/transpiler.mjs +++ b/packages/transpiler/transpiler.mjs @@ -114,7 +114,7 @@ function isWidgetFunction(node) { function widgetWithLocation(node) { const id = 'slider_' + node.arguments[0].start; // use loc of first arg for id // add loc as identifier to first argument - // the slider function is assumed to be slider(id, value, min?, max?) + // the sliderWithID function is assumed to be sliderWithID(id, value, min?, max?) node.arguments.unshift({ type: 'Literal', value: id, From 96f06d802644c4ec1175328ec893d0df10f345c4 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 1 Oct 2023 14:09:07 +0200 Subject: [PATCH 72/74] fix: import --- packages/codemirror/slider.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/codemirror/slider.mjs b/packages/codemirror/slider.mjs index 159e4fd18..47b686c8e 100644 --- a/packages/codemirror/slider.mjs +++ b/packages/codemirror/slider.mjs @@ -1,4 +1,4 @@ -import { ref } from '@strudel.cycles/core'; +import { ref, pure } from '@strudel.cycles/core'; import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view'; import { StateEffect, StateField } from '@codemirror/state'; From edbd437d7b8076a9ce3b020a6900b9e6a043d0ed Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 1 Oct 2023 14:20:30 +0200 Subject: [PATCH 73/74] hotfix: add missing dependency --- pnpm-lock.yaml | 18 ++++++++++++++++++ website/package.json | 1 + 2 files changed, 19 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b48a93fe..9f222a209 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -588,6 +588,9 @@ importers: '@strudel.cycles/xen': specifier: workspace:* version: link:../packages/xen + '@strudel/codemirror': + specifier: workspace:* + version: link:../packages/codemirror '@strudel/desktopbridge': specifier: workspace:* version: link:../packages/desktopbridge @@ -1426,6 +1429,7 @@ packages: /@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.21.5): resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1441,6 +1445,7 @@ packages: /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.21.5): resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1454,6 +1459,7 @@ packages: /@babel/plugin-proposal-class-static-block@7.20.7(@babel/core@7.21.5): resolution: {integrity: sha512-AveGOoi9DAjUYYuUAG//Ig69GlazLnoyzMw68VCDux+c1tsnnH/OkYcpz/5xzMkEFC6UxjR5Gw1c+iY2wOGVeQ==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-static-block instead. peerDependencies: '@babel/core': ^7.12.0 dependencies: @@ -1468,6 +1474,7 @@ packages: /@babel/plugin-proposal-dynamic-import@7.18.6(@babel/core@7.21.5): resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-dynamic-import instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1479,6 +1486,7 @@ packages: /@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.21.5): resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1490,6 +1498,7 @@ packages: /@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.21.5): resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-json-strings instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1501,6 +1510,7 @@ packages: /@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.21.5): resolution: {integrity: sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1512,6 +1522,7 @@ packages: /@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.21.5): resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1523,6 +1534,7 @@ packages: /@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.21.5): resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1534,6 +1546,7 @@ packages: /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.21.5): resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1548,6 +1561,7 @@ packages: /@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.21.5): resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1559,6 +1573,7 @@ packages: /@babel/plugin-proposal-optional-chaining@7.20.7(@babel/core@7.21.5): resolution: {integrity: sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1571,6 +1586,7 @@ packages: /@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.21.5): resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1584,6 +1600,7 @@ packages: /@babel/plugin-proposal-private-property-in-object@7.20.5(@babel/core@7.21.5): resolution: {integrity: sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1599,6 +1616,7 @@ packages: /@babel/plugin-proposal-unicode-property-regex@7.18.6(@babel/core@7.21.5): resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==} engines: {node: '>=4'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: diff --git a/website/package.json b/website/package.json index 6ac799d35..c8fec20cb 100644 --- a/website/package.json +++ b/website/package.json @@ -28,6 +28,7 @@ "@strudel.cycles/mini": "workspace:*", "@strudel.cycles/osc": "workspace:*", "@strudel.cycles/react": "workspace:*", + "@strudel/codemirror": "workspace:*", "@strudel.cycles/serial": "workspace:*", "@strudel.cycles/soundfonts": "workspace:*", "@strudel.cycles/tonal": "workspace:*", From fd316c81c0b9529a25c8d08ede3f4f2c3ccce6c2 Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Sun, 1 Oct 2023 13:20:49 +0100 Subject: [PATCH 74/74] support mininotation '..' range operator, fixes #715 (#716) * support mininotation .. range operator, fixes #715 * remove logs --- packages/mini/krill-parser.js | 437 +++++++++++++++++-------------- packages/mini/krill.pegjs | 8 +- packages/mini/mini.mjs | 11 + packages/mini/test/mini.test.mjs | 6 + 4 files changed, 270 insertions(+), 192 deletions(-) diff --git a/packages/mini/krill-parser.js b/packages/mini/krill-parser.js index e85e33630..7831087b0 100644 --- a/packages/mini/krill-parser.js +++ b/packages/mini/krill-parser.js @@ -200,20 +200,21 @@ function peg$parse(input, options) { var peg$c23 = "*"; var peg$c24 = "?"; var peg$c25 = ":"; - var peg$c26 = "struct"; - var peg$c27 = "target"; - var peg$c28 = "euclid"; - var peg$c29 = "slow"; - var peg$c30 = "rotL"; - var peg$c31 = "rotR"; - var peg$c32 = "fast"; - var peg$c33 = "scale"; - var peg$c34 = "//"; - var peg$c35 = "cat"; - var peg$c36 = "$"; - var peg$c37 = "setcps"; - var peg$c38 = "setbpm"; - var peg$c39 = "hush"; + var peg$c26 = ".."; + var peg$c27 = "struct"; + var peg$c28 = "target"; + var peg$c29 = "euclid"; + var peg$c30 = "slow"; + var peg$c31 = "rotL"; + var peg$c32 = "rotR"; + var peg$c33 = "fast"; + var peg$c34 = "scale"; + var peg$c35 = "//"; + var peg$c36 = "cat"; + var peg$c37 = "$"; + var peg$c38 = "setcps"; + var peg$c39 = "setbpm"; + var peg$c40 = "hush"; var peg$r0 = /^[1-9]/; var peg$r1 = /^[eE]/; @@ -255,64 +256,67 @@ function peg$parse(input, options) { var peg$e30 = peg$literalExpectation("*", false); var peg$e31 = peg$literalExpectation("?", false); var peg$e32 = peg$literalExpectation(":", false); - var peg$e33 = peg$literalExpectation("struct", false); - var peg$e34 = peg$literalExpectation("target", false); - var peg$e35 = peg$literalExpectation("euclid", false); - var peg$e36 = peg$literalExpectation("slow", false); - var peg$e37 = peg$literalExpectation("rotL", false); - var peg$e38 = peg$literalExpectation("rotR", false); - var peg$e39 = peg$literalExpectation("fast", false); - var peg$e40 = peg$literalExpectation("scale", false); - var peg$e41 = peg$literalExpectation("//", false); - var peg$e42 = peg$classExpectation(["\n"], true, false); - var peg$e43 = peg$literalExpectation("cat", false); - var peg$e44 = peg$literalExpectation("$", false); - var peg$e45 = peg$literalExpectation("setcps", false); - var peg$e46 = peg$literalExpectation("setbpm", false); - var peg$e47 = peg$literalExpectation("hush", false); + var peg$e33 = peg$literalExpectation("..", false); + var peg$e34 = peg$literalExpectation("struct", false); + var peg$e35 = peg$literalExpectation("target", false); + var peg$e36 = peg$literalExpectation("euclid", false); + var peg$e37 = peg$literalExpectation("slow", false); + var peg$e38 = peg$literalExpectation("rotL", false); + var peg$e39 = peg$literalExpectation("rotR", false); + var peg$e40 = peg$literalExpectation("fast", false); + var peg$e41 = peg$literalExpectation("scale", false); + var peg$e42 = peg$literalExpectation("//", false); + var peg$e43 = peg$classExpectation(["\n"], true, false); + var peg$e44 = peg$literalExpectation("cat", false); + var peg$e45 = peg$literalExpectation("$", false); + var peg$e46 = peg$literalExpectation("setcps", false); + var peg$e47 = peg$literalExpectation("setbpm", false); + var peg$e48 = peg$literalExpectation("hush", false); var peg$f0 = function() { return parseFloat(text()); }; - var peg$f1 = function(chars) { return new AtomStub(chars.join("")) }; - var peg$f2 = function(s) { return s }; - var peg$f3 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; }; - var peg$f4 = function(a) { return a }; - var peg$f5 = function(s) { s.arguments_.alignment = 'slowcat'; return s; }; - var peg$f6 = function(a) { return x => x.options_['weight'] = a }; - var peg$f7 = function(a) { return x => x.options_['reps'] = a }; - var peg$f8 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) }; - var peg$f9 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'slow' }}) }; - var peg$f10 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'fast' }}) }; - var peg$f11 = function(a) { return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a, seed: seed++ } }) }; - var peg$f12 = function(s) { return x => x.options_['ops'].push({ type_: "tail", arguments_ :{ element:s } }) }; - var peg$f13 = function(s, ops) { const result = new ElementStub(s, {ops: [], weight: 1, reps: 1}); + var peg$f1 = function() { return parseInt(text()); }; + var peg$f2 = function(chars) { return new AtomStub(chars.join("")) }; + var peg$f3 = function(s) { return s }; + var peg$f4 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; }; + var peg$f5 = function(a) { return a }; + var peg$f6 = function(s) { s.arguments_.alignment = 'slowcat'; return s; }; + var peg$f7 = function(a) { return x => x.options_['weight'] = a }; + var peg$f8 = function(a) { return x => x.options_['reps'] = a }; + var peg$f9 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) }; + var peg$f10 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'slow' }}) }; + var peg$f11 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'fast' }}) }; + var peg$f12 = function(a) { return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a, seed: seed++ } }) }; + var peg$f13 = function(s) { return x => x.options_['ops'].push({ type_: "tail", arguments_ :{ element:s } }) }; + var peg$f14 = function(s) { return x => x.options_['ops'].push({ type_: "range", arguments_ :{ element:s } }) }; + var peg$f15 = function(s, ops) { const result = new ElementStub(s, {ops: [], weight: 1, reps: 1}); for (const op of ops) { op(result); } return result; }; - var peg$f14 = function(s) { return new PatternStub(s, 'fastcat'); }; - var peg$f15 = function(tail) { return { alignment: 'stack', list: tail }; }; - var peg$f16 = function(tail) { return { alignment: 'rand', list: tail, seed: seed++ }; }; - var peg$f17 = function(head, tail) { if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment, tail.seed); } else { return head; } }; - var peg$f18 = function(head, tail) { return new PatternStub(tail ? [head, ...tail.list] : [head], 'polymeter'); }; - var peg$f19 = function(sc) { return sc; }; - var peg$f20 = function(s) { return { name: "struct", args: { mini:s }}}; - var peg$f21 = function(s) { return { name: "target", args : { name:s}}}; - var peg$f22 = function(p, s, r) { return { name: "bjorklund", args :{ pulse: p, step:parseInt(s) }}}; - var peg$f23 = function(a) { return { name: "stretch", args :{ amount: a}}}; - var peg$f24 = function(a) { return { name: "shift", args :{ amount: "-"+a}}}; - var peg$f25 = function(a) { return { name: "shift", args :{ amount: a}}}; - var peg$f26 = function(a) { return { name: "stretch", args :{ amount: "1/"+a}}}; - var peg$f27 = function(s) { return { name: "scale", args :{ scale: s.join("")}}}; - var peg$f28 = function(s, v) { return v}; - var peg$f29 = function(s, ss) { ss.unshift(s); return new PatternStub(ss, 'slowcat'); }; - var peg$f30 = function(sg) {return sg}; - var peg$f31 = function(o, soc) { return new OperatorStub(o.name,o.args,soc)}; - var peg$f32 = function(sc) { return sc }; - var peg$f33 = function(c) { return c }; - var peg$f34 = function(v) { return new CommandStub("setcps", { value: v})}; - var peg$f35 = function(v) { return new CommandStub("setcps", { value: (v/120/2)})}; - var peg$f36 = function() { return new CommandStub("hush")}; + var peg$f16 = function(s) { return new PatternStub(s, 'fastcat'); }; + var peg$f17 = function(tail) { return { alignment: 'stack', list: tail }; }; + var peg$f18 = function(tail) { return { alignment: 'rand', list: tail, seed: seed++ }; }; + var peg$f19 = function(head, tail) { if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment, tail.seed); } else { return head; } }; + var peg$f20 = function(head, tail) { return new PatternStub(tail ? [head, ...tail.list] : [head], 'polymeter'); }; + var peg$f21 = function(sc) { return sc; }; + var peg$f22 = function(s) { return { name: "struct", args: { mini:s }}}; + var peg$f23 = function(s) { return { name: "target", args : { name:s}}}; + var peg$f24 = function(p, s, r) { return { name: "bjorklund", args :{ pulse: p, step:parseInt(s) }}}; + var peg$f25 = function(a) { return { name: "stretch", args :{ amount: a}}}; + var peg$f26 = function(a) { return { name: "shift", args :{ amount: "-"+a}}}; + var peg$f27 = function(a) { return { name: "shift", args :{ amount: a}}}; + var peg$f28 = function(a) { return { name: "stretch", args :{ amount: "1/"+a}}}; + var peg$f29 = function(s) { return { name: "scale", args :{ scale: s.join("")}}}; + var peg$f30 = function(s, v) { return v}; + var peg$f31 = function(s, ss) { ss.unshift(s); return new PatternStub(ss, 'slowcat'); }; + var peg$f32 = function(sg) {return sg}; + var peg$f33 = function(o, soc) { return new OperatorStub(o.name,o.args,soc)}; + var peg$f34 = function(sc) { return sc }; + var peg$f35 = function(c) { return c }; + var peg$f36 = function(v) { return new CommandStub("setcps", { value: v})}; + var peg$f37 = function(v) { return new CommandStub("setcps", { value: (v/120/2)})}; + var peg$f38 = function() { return new CommandStub("hush")}; var peg$currPos = 0; var peg$savedPos = 0; var peg$posDetailsCache = [{ line: 1, column: 1 }]; @@ -651,6 +655,26 @@ function peg$parse(input, options) { return s0; } + function peg$parseintneg() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = peg$parseminus(); + if (s1 === peg$FAILED) { + s1 = null; + } + s2 = peg$parseint(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f1(); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + function peg$parseminus() { var s0; @@ -884,7 +908,7 @@ function peg$parse(input, options) { if (s2 !== peg$FAILED) { s3 = peg$parsews(); peg$savedPos = s0; - s0 = peg$f1(s2); + s0 = peg$f2(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -920,7 +944,7 @@ function peg$parse(input, options) { if (s6 !== peg$FAILED) { s7 = peg$parsews(); peg$savedPos = s0; - s0 = peg$f2(s4); + s0 = peg$f3(s4); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -968,7 +992,7 @@ function peg$parse(input, options) { } s8 = peg$parsews(); peg$savedPos = s0; - s0 = peg$f3(s4, s7); + s0 = peg$f4(s4, s7); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1000,7 +1024,7 @@ function peg$parse(input, options) { s2 = peg$parseslice(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f4(s2); + s0 = peg$f5(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1040,7 +1064,7 @@ function peg$parse(input, options) { if (s6 !== peg$FAILED) { s7 = peg$parsews(); peg$savedPos = s0; - s0 = peg$f5(s4); + s0 = peg$f6(s4); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1090,6 +1114,9 @@ function peg$parse(input, options) { s0 = peg$parseop_degrade(); if (s0 === peg$FAILED) { s0 = peg$parseop_tail(); + if (s0 === peg$FAILED) { + s0 = peg$parseop_range(); + } } } } @@ -1115,7 +1142,7 @@ function peg$parse(input, options) { s2 = peg$parsenumber(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f6(s2); + s0 = peg$f7(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1143,7 +1170,7 @@ function peg$parse(input, options) { s2 = peg$parsenumber(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f7(s2); + s0 = peg$f8(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1197,7 +1224,7 @@ function peg$parse(input, options) { } if (s13 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f8(s3, s7, s11); + s0 = peg$f9(s3, s7, s11); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1237,7 +1264,7 @@ function peg$parse(input, options) { s2 = peg$parseslice(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f9(s2); + s0 = peg$f10(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1265,7 +1292,7 @@ function peg$parse(input, options) { s2 = peg$parseslice(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f10(s2); + s0 = peg$f11(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1295,7 +1322,7 @@ function peg$parse(input, options) { s2 = null; } peg$savedPos = s0; - s0 = peg$f11(s2); + s0 = peg$f12(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1319,7 +1346,35 @@ function peg$parse(input, options) { s2 = peg$parseslice(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f12(s2); + s0 = peg$f13(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseop_range() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 2) === peg$c26) { + s1 = peg$c26; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e33); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseslice(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f14(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1345,7 +1400,7 @@ function peg$parse(input, options) { s3 = peg$parseslice_op(); } peg$savedPos = s0; - s0 = peg$f13(s1, s2); + s0 = peg$f15(s1, s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1370,7 +1425,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f14(s1); + s1 = peg$f16(s1); } s0 = s1; @@ -1419,7 +1474,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f15(s1); + s1 = peg$f17(s1); } s0 = s1; @@ -1468,7 +1523,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f16(s1); + s1 = peg$f18(s1); } s0 = s1; @@ -1489,7 +1544,7 @@ function peg$parse(input, options) { s2 = null; } peg$savedPos = s0; - s0 = peg$f17(s1, s2); + s0 = peg$f19(s1, s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1509,7 +1564,7 @@ function peg$parse(input, options) { s2 = null; } peg$savedPos = s0; - s0 = peg$f18(s1, s2); + s0 = peg$f20(s1, s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1532,7 +1587,7 @@ function peg$parse(input, options) { s6 = peg$parsequote(); if (s6 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f19(s4); + s0 = peg$f21(s4); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1582,19 +1637,19 @@ function peg$parse(input, options) { var s0, s1, s2, s3; s0 = peg$currPos; - if (input.substr(peg$currPos, 6) === peg$c26) { - s1 = peg$c26; + if (input.substr(peg$currPos, 6) === peg$c27) { + s1 = peg$c27; peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e33); } + if (peg$silentFails === 0) { peg$fail(peg$e34); } } if (s1 !== peg$FAILED) { s2 = peg$parsews(); s3 = peg$parsemini_or_operator(); if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f20(s3); + s0 = peg$f22(s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1611,12 +1666,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 6) === peg$c27) { - s1 = peg$c27; + if (input.substr(peg$currPos, 6) === peg$c28) { + s1 = peg$c28; peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e34); } + if (peg$silentFails === 0) { peg$fail(peg$e35); } } if (s1 !== peg$FAILED) { s2 = peg$parsews(); @@ -1627,7 +1682,7 @@ function peg$parse(input, options) { s5 = peg$parsequote(); if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f21(s4); + s0 = peg$f23(s4); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1652,12 +1707,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5, s6, s7; s0 = peg$currPos; - if (input.substr(peg$currPos, 6) === peg$c28) { - s1 = peg$c28; + if (input.substr(peg$currPos, 6) === peg$c29) { + s1 = peg$c29; peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e35); } + if (peg$silentFails === 0) { peg$fail(peg$e36); } } if (s1 !== peg$FAILED) { s2 = peg$parsews(); @@ -1672,7 +1727,7 @@ function peg$parse(input, options) { s7 = null; } peg$savedPos = s0; - s0 = peg$f22(s3, s5, s7); + s0 = peg$f24(s3, s5, s7); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1692,35 +1747,6 @@ function peg$parse(input, options) { function peg$parseslow() { var s0, s1, s2, s3; - s0 = peg$currPos; - if (input.substr(peg$currPos, 4) === peg$c29) { - s1 = peg$c29; - peg$currPos += 4; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e36); } - } - if (s1 !== peg$FAILED) { - s2 = peg$parsews(); - s3 = peg$parsenumber(); - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f23(s3); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parserotL() { - var s0, s1, s2, s3; - s0 = peg$currPos; if (input.substr(peg$currPos, 4) === peg$c30) { s1 = peg$c30; @@ -1729,35 +1755,6 @@ function peg$parse(input, options) { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e37); } } - if (s1 !== peg$FAILED) { - s2 = peg$parsews(); - s3 = peg$parsenumber(); - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f24(s3); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parserotR() { - var s0, s1, s2, s3; - - s0 = peg$currPos; - if (input.substr(peg$currPos, 4) === peg$c31) { - s1 = peg$c31; - peg$currPos += 4; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e38); } - } if (s1 !== peg$FAILED) { s2 = peg$parsews(); s3 = peg$parsenumber(); @@ -1776,16 +1773,16 @@ function peg$parse(input, options) { return s0; } - function peg$parsefast() { + function peg$parserotL() { var s0, s1, s2, s3; s0 = peg$currPos; - if (input.substr(peg$currPos, 4) === peg$c32) { - s1 = peg$c32; + if (input.substr(peg$currPos, 4) === peg$c31) { + s1 = peg$c31; peg$currPos += 4; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e39); } + if (peg$silentFails === 0) { peg$fail(peg$e38); } } if (s1 !== peg$FAILED) { s2 = peg$parsews(); @@ -1805,16 +1802,74 @@ function peg$parse(input, options) { return s0; } + function peg$parserotR() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c32) { + s1 = peg$c32; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e39); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsenumber(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f27(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsefast() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c33) { + s1 = peg$c33; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e40); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsenumber(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f28(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + function peg$parsescale() { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 5) === peg$c33) { - s1 = peg$c33; + if (input.substr(peg$currPos, 5) === peg$c34) { + s1 = peg$c34; peg$currPos += 5; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e40); } + if (peg$silentFails === 0) { peg$fail(peg$e41); } } if (s1 !== peg$FAILED) { s2 = peg$parsews(); @@ -1834,7 +1889,7 @@ function peg$parse(input, options) { s5 = peg$parsequote(); if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f27(s4); + s0 = peg$f29(s4); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1859,12 +1914,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3; s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c34) { - s1 = peg$c34; + if (input.substr(peg$currPos, 2) === peg$c35) { + s1 = peg$c35; peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e41); } + if (peg$silentFails === 0) { peg$fail(peg$e42); } } if (s1 !== peg$FAILED) { s2 = []; @@ -1873,7 +1928,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e42); } + if (peg$silentFails === 0) { peg$fail(peg$e43); } } while (s3 !== peg$FAILED) { s2.push(s3); @@ -1882,7 +1937,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e42); } + if (peg$silentFails === 0) { peg$fail(peg$e43); } } } s1 = [s1, s2]; @@ -1899,12 +1954,12 @@ function peg$parse(input, options) { var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; s0 = peg$currPos; - if (input.substr(peg$currPos, 3) === peg$c35) { - s1 = peg$c35; + if (input.substr(peg$currPos, 3) === peg$c36) { + s1 = peg$c36; peg$currPos += 3; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e43); } + if (peg$silentFails === 0) { peg$fail(peg$e44); } } if (s1 !== peg$FAILED) { s2 = peg$parsews(); @@ -1926,7 +1981,7 @@ function peg$parse(input, options) { s9 = peg$parsemini_or_operator(); if (s9 !== peg$FAILED) { peg$savedPos = s7; - s7 = peg$f28(s5, s9); + s7 = peg$f30(s5, s9); } else { peg$currPos = s7; s7 = peg$FAILED; @@ -1943,7 +1998,7 @@ function peg$parse(input, options) { s9 = peg$parsemini_or_operator(); if (s9 !== peg$FAILED) { peg$savedPos = s7; - s7 = peg$f28(s5, s9); + s7 = peg$f30(s5, s9); } else { peg$currPos = s7; s7 = peg$FAILED; @@ -1963,7 +2018,7 @@ function peg$parse(input, options) { } if (s8 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f29(s5, s6); + s0 = peg$f31(s5, s6); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2009,7 +2064,7 @@ function peg$parse(input, options) { s4 = peg$parsecomment(); } peg$savedPos = s0; - s0 = peg$f30(s1); + s0 = peg$f32(s1); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2020,18 +2075,18 @@ function peg$parse(input, options) { if (s1 !== peg$FAILED) { s2 = peg$parsews(); if (input.charCodeAt(peg$currPos) === 36) { - s3 = peg$c36; + s3 = peg$c37; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e44); } + if (peg$silentFails === 0) { peg$fail(peg$e45); } } if (s3 !== peg$FAILED) { s4 = peg$parsews(); s5 = peg$parsemini_or_operator(); if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f31(s1, s5); + s0 = peg$f33(s1, s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2056,7 +2111,7 @@ function peg$parse(input, options) { s1 = peg$parsemini_or_operator(); if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f32(s1); + s1 = peg$f34(s1); } s0 = s1; if (s0 === peg$FAILED) { @@ -2089,7 +2144,7 @@ function peg$parse(input, options) { if (s2 !== peg$FAILED) { s3 = peg$parsews(); peg$savedPos = s0; - s0 = peg$f33(s2); + s0 = peg$f35(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2102,19 +2157,19 @@ function peg$parse(input, options) { var s0, s1, s2, s3; s0 = peg$currPos; - if (input.substr(peg$currPos, 6) === peg$c37) { - s1 = peg$c37; + if (input.substr(peg$currPos, 6) === peg$c38) { + s1 = peg$c38; peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e45); } + if (peg$silentFails === 0) { peg$fail(peg$e46); } } if (s1 !== peg$FAILED) { s2 = peg$parsews(); s3 = peg$parsenumber(); if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f34(s3); + s0 = peg$f36(s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2131,19 +2186,19 @@ function peg$parse(input, options) { var s0, s1, s2, s3; s0 = peg$currPos; - if (input.substr(peg$currPos, 6) === peg$c38) { - s1 = peg$c38; + if (input.substr(peg$currPos, 6) === peg$c39) { + s1 = peg$c39; peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e46); } + if (peg$silentFails === 0) { peg$fail(peg$e47); } } if (s1 !== peg$FAILED) { s2 = peg$parsews(); s3 = peg$parsenumber(); if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f35(s3); + s0 = peg$f37(s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2160,16 +2215,16 @@ function peg$parse(input, options) { var s0, s1; s0 = peg$currPos; - if (input.substr(peg$currPos, 4) === peg$c39) { - s1 = peg$c39; + if (input.substr(peg$currPos, 4) === peg$c40) { + s1 = peg$c40; peg$currPos += 4; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e47); } + if (peg$silentFails === 0) { peg$fail(peg$e48); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f36(); + s1 = peg$f38(); } s0 = s1; diff --git a/packages/mini/krill.pegjs b/packages/mini/krill.pegjs index 72b453bea..f52743bfc 100644 --- a/packages/mini/krill.pegjs +++ b/packages/mini/krill.pegjs @@ -79,6 +79,9 @@ frac int = zero / (digit1_9 DIGIT*) +intneg + = minus? int { return parseInt(text()); } + minus = "-" @@ -123,7 +126,7 @@ slice = step / sub_cycle / polymeter / slow_sequence // slice modifier affects the timing/size of a slice (e.g. [a b c]@3) // at this point, we assume we can represent them as regular sequence operators -slice_op = op_weight / op_bjorklund / op_slow / op_fast / op_replicate / op_degrade / op_tail +slice_op = op_weight / op_bjorklund / op_slow / op_fast / op_replicate / op_degrade / op_tail / op_range op_weight = "@" a:number { return x => x.options_['weight'] = a } @@ -146,6 +149,9 @@ op_degrade = "?"a:number? op_tail = ":" s:slice { return x => x.options_['ops'].push({ type_: "tail", arguments_ :{ element:s } }) } +op_range = ".." s:slice + { return x => x.options_['ops'].push({ type_: "range", arguments_ :{ element:s } }) } + // a slice with an modifier applied i.e [bd@4 sd@3]@2 hh] slice_with_ops = s:slice ops:slice_op* { const result = new ElementStub(s, {ops: [], weight: 1, reps: 1}); diff --git a/packages/mini/mini.mjs b/packages/mini/mini.mjs index 3321e7bc6..8e6b844f0 100644 --- a/packages/mini/mini.mjs +++ b/packages/mini/mini.mjs @@ -45,6 +45,17 @@ const applyOptions = (parent, enter) => (pat, i) => { pat = pat.fmap((a) => (b) => Array.isArray(a) ? [...a, b] : [a, b]).appLeft(friend); break; } + case 'range': { + const friend = enter(op.arguments_.element); + pat = strudel.reify(pat); + const arrayRange = (start, stop, step = 1) => + Array.from({ length: Math.abs(stop - start) / step + 1 }, (value, index) => + start < stop ? start + index * step : start - index * step, + ); + let range = (apat, bpat) => apat.squeezeBind((a) => bpat.bind((b) => strudel.fastcat(...arrayRange(a, b)))); + pat = range(pat, friend); + break; + } default: { console.warn(`operator "${op.type_}" not implemented`); } diff --git a/packages/mini/test/mini.test.mjs b/packages/mini/test/mini.test.mjs index 0c7f381e9..6d9ac3671 100644 --- a/packages/mini/test/mini.test.mjs +++ b/packages/mini/test/mini.test.mjs @@ -184,6 +184,12 @@ describe('mini', () => { it('supports lists', () => { expect(minV('a:b c:d:[e:f] g')).toEqual([['a', 'b'], ['c', 'd', ['e', 'f']], 'g']); }); + it('supports ranges', () => { + expect(minV('0 .. 4')).toEqual([0, 1, 2, 3, 4]); + }); + it('supports patterned ranges', () => { + expect(minS('[<0 1> .. <2 4>]*2')).toEqual(minS('[0 1 2] [1 2 3 4]')); + }); }); describe('getLeafLocation', () => {