From b9173ebe50a3c8259a11783db731fe5973e27d39 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 16 Sep 2022 00:14:28 +0200 Subject: [PATCH 01/45] add coarse, crush and shape --- packages/webaudio/webaudio.mjs | 32 +++++++++++- packages/webaudio/worklets.mjs | 94 ++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 packages/webaudio/worklets.mjs diff --git a/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs index 18186118..2111c10e 100644 --- a/packages/webaudio/webaudio.mjs +++ b/packages/webaudio/webaudio.mjs @@ -10,6 +10,7 @@ import { fromMidi, toMidi } from '@strudel.cycles/core'; import { loadBuffer } from './sampler.mjs'; const { Pattern } = strudel; import './vowel.mjs'; +import workletsUrl from './worklets.mjs?url'; // export const getAudioContext = () => Tone.getContext().rawContext; @@ -129,7 +130,29 @@ const splitSN = (s, n) => { return [s2, n2]; }; -Pattern.prototype.out = function () { +let workletsLoading; +function loadWorklets() { + if (workletsLoading) { + return workletsLoading; + } + workletsLoading = getAudioContext().audioWorklet.addModule(workletsUrl); + return workletsLoading; +} + +function getWorklet(ac, processor, params) { + const node = new AudioWorkletNode(ac, processor); + Object.entries(params).forEach(([key, value]) => { + node.parameters.get(key).value = value; + }); + return node; +} + +Pattern.prototype.out = async function () { + try { + await loadWorklets(); + } catch (err) { + console.warn('could not load AudioWorklet effects coarse, crush and shape', err); + } return this.onTrigger(async (t, hap, ct, cps) => { const hapDuration = hap.duration / cps; try { @@ -151,6 +174,9 @@ Pattern.prototype.out = function () { hresonance = 1, bandf, bandq = 1, + coarse, + crush, + shape, pan, attack = 0.001, decay = 0.05, @@ -261,7 +287,9 @@ Pattern.prototype.out = function () { hcutoff !== undefined && chain.push(getFilter('highpass', hcutoff, hresonance)); bandf !== undefined && chain.push(getFilter('bandpass', bandf, bandq)); vowel !== undefined && chain.push(ac.createVowelFilter(vowel)); - // TODO vowel + coarse !== undefined && chain.push(getWorklet(ac, 'coarse-processor', { coarse })); + crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush })); + shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape })); // TODO delay / delaytime / delayfeedback // panning if (pan !== undefined) { diff --git a/packages/webaudio/worklets.mjs b/packages/webaudio/worklets.mjs new file mode 100644 index 00000000..2c4ab540 --- /dev/null +++ b/packages/webaudio/worklets.mjs @@ -0,0 +1,94 @@ +// all the credit goes to webdirt: https://github.com/dktr0/WebDirt/blob/5ce3d698362c54d6e1b68acc47eb2955ac62c793/dist/AudioWorklets.js + +class CoarseProcessor extends AudioWorkletProcessor { + static get parameterDescriptors() { + return [{ name: 'coarse', defaultValue: 1 }]; + } + + constructor() { + super(); + this.notStarted = true; + } + + process(inputs, outputs, parameters) { + const input = inputs[0]; + const output = outputs[0]; + const coarse = parameters.coarse; + const blockSize = 128; + const hasInput = !(input[0] === undefined); + if (hasInput) { + this.notStarted = false; + output[0][0] = input[0][0]; + for (let n = 1; n < blockSize; n++) { + if (n % coarse == 0) output[0][n] = input[0][n]; + else output[0][n] = output[0][n - 1]; + } + } + return this.notStarted || hasInput; + } +} + +registerProcessor('coarse-processor', CoarseProcessor); + +class CrushProcessor extends AudioWorkletProcessor { + static get parameterDescriptors() { + return [{ name: 'crush', defaultValue: 0 }]; + } + + constructor() { + super(); + this.notStarted = true; + } + + process(inputs, outputs, parameters) { + const input = inputs[0]; + const output = outputs[0]; + const crush = parameters.crush; + const blockSize = 128; + const hasInput = !(input[0] === undefined); + if (hasInput) { + this.notStarted = false; + if (crush.length === 1) { + const x = Math.pow(2, crush[0] - 1); + for (let n = 0; n < blockSize; n++) output[0][n] = Math.round(input[0][n] * x) / x; + } else { + for (let n = 0; n < blockSize; n++) { + let x = Math.pow(2, crush[n] - 1); + output[0][n] = Math.round(input[0][n] * x) / x; + } + } + } + return this.notStarted || hasInput; + } +} +registerProcessor('crush-processor', CrushProcessor); + +class ShapeProcessor extends AudioWorkletProcessor { + static get parameterDescriptors() { + return [{ name: 'shape', defaultValue: 0 }]; + } + + constructor() { + super(); + this.notStarted = true; + } + + process(inputs, outputs, parameters) { + const input = inputs[0]; + const output = outputs[0]; + const shape0 = parameters.shape[0]; + const shape1 = shape0 < 1 ? shape0 : 1.0 - 4e-10; + const shape = (2.0 * shape1) / (1.0 - shape1); + const blockSize = 128; + const hasInput = !(input[0] === undefined); + if (hasInput) { + this.notStarted = false; + for (let n = 0; n < blockSize; n++) { + output[0][n] = ((1 + shape) * input[0][n]) / (1 + shape * Math.abs(input[0][n])); + } + } + return this.notStarted || hasInput; + } +} + +registerProcessor('shape-processor', ShapeProcessor); From 5d28c5c825d64fa92392c6ef9b22c190d2d95f92 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 16 Sep 2022 00:17:42 +0200 Subject: [PATCH 02/45] add license info --- packages/webaudio/worklets.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/webaudio/worklets.mjs b/packages/webaudio/worklets.mjs index 2c4ab540..c5306c45 100644 --- a/packages/webaudio/worklets.mjs +++ b/packages/webaudio/worklets.mjs @@ -1,4 +1,6 @@ -// all the credit goes to webdirt: https://github.com/dktr0/WebDirt/blob/5ce3d698362c54d6e1b68acc47eb2955ac62c793/dist/AudioWorklets.js +// LICENSE GNU General Public License v3.0 see https://github.com/dktr0/WebDirt/blob/main/LICENSE +// all the credit goes to dktr0's webdirt: https://github.com/dktr0/WebDirt/blob/5ce3d698362c54d6e1b68acc47eb2955ac62c793/dist/AudioWorklets.js +// <3 class CoarseProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { From a11d23ba62cefbd4426cae73357ea27cf80686f6 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 16 Sep 2022 00:30:37 +0200 Subject: [PATCH 03/45] hotfix: out should not be async --- packages/webaudio/webaudio.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs index 2111c10e..0a9057e8 100644 --- a/packages/webaudio/webaudio.mjs +++ b/packages/webaudio/webaudio.mjs @@ -147,9 +147,9 @@ function getWorklet(ac, processor, params) { return node; } -Pattern.prototype.out = async function () { +Pattern.prototype.out = function () { try { - await loadWorklets(); + loadWorklets(); } catch (err) { console.warn('could not load AudioWorklet effects coarse, crush and shape', err); } From 3fdfd12448d65a65db354b954e68c6c533eab500 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 16 Sep 2022 00:53:43 +0200 Subject: [PATCH 04/45] hotfix: disconnect chain after use --- packages/webaudio/webaudio.mjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs index 0a9057e8..e2936f77 100644 --- a/packages/webaudio/webaudio.mjs +++ b/packages/webaudio/webaudio.mjs @@ -304,6 +304,10 @@ Pattern.prototype.out = function () { chain.push(ac.destination); // connect chain elements together chain.slice(1).reduce((last, current) => last.connect(current), chain[0]); + // disconnect all nodes when hap is over to make sure they are garbage collected + setTimeout(() => { + chain.forEach((n) => n.disconnect()); + }, (hapDuration + release + 0.1) * 1000); } catch (e) { console.warn('.out error:', e); } From dd4464cb0884bda32746e31b4e7e0ef169e39eaa Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 16 Sep 2022 11:05:38 +0200 Subject: [PATCH 05/45] fix build: dont add sourcemaps --- package.json | 2 +- repl/vite.config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index bf045288..dc23a45c 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "osc": "cd packages/osc && npm run server", "build": "rm -rf out && cd repl && npm run build && cd ../tutorial && npm run build", "preview": "npx serve ./out", - "deploy": "gh-pages -d out", + "deploy": "NODE_DEBUG=gh-pages gh-pages -d out", "jsdoc": "jsdoc packages/ -c jsdoc.config.json", "jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json" }, diff --git a/repl/vite.config.js b/repl/vite.config.js index 1d3f3e86..728a6e22 100644 --- a/repl/vite.config.js +++ b/repl/vite.config.js @@ -7,7 +7,7 @@ export default defineConfig({ plugins: [react()], build: { outDir: '../out', - sourcemap: true, + sourcemap: false, rollupOptions: { plugins: [visualizer({ template: 'treemap' })], }, From 782e8522c57611be3dd84a45c7e6ff901a557180 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 16 Sep 2022 23:24:47 +0200 Subject: [PATCH 06/45] add all default samples to mini repl --- tutorial/MiniRepl.jsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tutorial/MiniRepl.jsx b/tutorial/MiniRepl.jsx index 0f18c2b6..56387190 100644 --- a/tutorial/MiniRepl.jsx +++ b/tutorial/MiniRepl.jsx @@ -12,14 +12,10 @@ export const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone. }, }); -samples( - { - bd: '808bd/BD0000.WAV', - sd: ['808sd/SD0010.WAV', '808sd/SD0050.WAV', '808sd/SD0000.WAV'], - hh: ['hh27/000_hh27closedhh.wav', 'hh/000_hh3closedhh.wav'], - }, - 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/', -); +await fetch('https://strudel.tidalcycles.org/EmuSP12.json') + .then((res) => res.json()) + .then((json) => samples(json, 'https://strudel.tidalcycles.org/EmuSP12/')); + evalScope( Tone, From 3c8acf9e0e9dbc08d9d5b91035b5c9c60fc4b854 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 16 Sep 2022 23:25:33 +0200 Subject: [PATCH 07/45] comment out timeout: it will cut of samples that are longer than hap.. --- packages/webaudio/webaudio.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs index e2936f77..40d0f73f 100644 --- a/packages/webaudio/webaudio.mjs +++ b/packages/webaudio/webaudio.mjs @@ -199,7 +199,7 @@ Pattern.prototype.out = function () { } if (!s || ['sine', 'square', 'triangle', 'sawtooth'].includes(s)) { // with synths, n and note are the same thing - n = note || n; + n = note || n || 36; if (typeof n === 'string') { n = toMidi(n); // e.g. c3 => 48 } @@ -305,9 +305,9 @@ Pattern.prototype.out = function () { // connect chain elements together chain.slice(1).reduce((last, current) => last.connect(current), chain[0]); // disconnect all nodes when hap is over to make sure they are garbage collected - setTimeout(() => { +/* setTimeout(() => { chain.forEach((n) => n.disconnect()); - }, (hapDuration + release + 0.1) * 1000); + }, (hapDuration + release + 0.1) * 1000); */ } catch (e) { console.warn('.out error:', e); } From e58aff41f92b2b9deedffd1b57480f83b5d25c50 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 16 Sep 2022 23:25:42 +0200 Subject: [PATCH 08/45] begin webaudio tutorial --- packages/core/controls.mjs | 4 +- packages/webaudio/sampler.mjs | 10 +-- tutorial/tutorial.mdx | 151 ++++++++++++++++++++++++++++++++-- 3 files changed, 152 insertions(+), 13 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index b9d8a257..40ed4427 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -11,7 +11,7 @@ const generic_params = [ /** * Select a sound / sample by name. * - *
+ *
* show all sounds * * 808 (6) 808bd (25) 808cy (25) 808hc (5) 808ht (5) 808lc (5) 808lt (5) 808mc (5) 808mt (5) 808oh (5) 808sd (25) 909 (1) ab (12) ade (10) ades2 (9) ades3 (7) ades4 (6) alex (2) alphabet (26) amencutup (32) armora (7) arp (2) arpy (11) auto (11) baa (7) baa2 (7) bass (4) bass0 (3) bass1 (30) bass2 (5) bass3 (11) bassdm (24) bassfoo (3) battles (2) bd (24) bend (4) bev (2) bin (2) birds (10) birds3 (19) bleep (13) blip (2) blue (2) bottle (13) breaks125 (2) breaks152 (1) breaks157 (1) breaks165 (1) breath (1) bubble (8) can (14) casio (3) cb (1) cc (6) chin (4) circus (3) clak (2) click (4) clubkick (5) co (4) coins (1) control (2) cosmicg (15) cp (2) cr (6) crow (4) d (4) db (13) diphone (38) diphone2 (12) dist (16) dork2 (4) dorkbot (2) dr (42) dr2 (6) dr55 (4) dr_few (8) drum (6) drumtraks (13) e (8) east (9) electro1 (13) em2 (6) erk (1) f (1) feel (7) feelfx (8) fest (1) fire (1) flick (17) fm (17) foo (27) future (17) gab (10) gabba (4) gabbaloud (4) gabbalouder (4) glasstap (3) glitch (8) glitch2 (8) gretsch (24) gtr (3) h (7) hand (17) hardcore (12) hardkick (6) haw (6) hc (6) hh (13) hh27 (13) hit (6) hmm (1) ho (6) hoover (6) house (8) ht (16) if (5) ifdrums (3) incoming (8) industrial (32) insect (3) invaders (18) jazz (8) jungbass (20) jungle (13) juno (12) jvbass (13) kicklinn (1) koy (2) kurt (7) latibro (8) led (1) less (4) lighter (33) linnhats (6) lt (16) made (7) made2 (1) mash (2) mash2 (4) metal (10) miniyeah (4) monsterb (6) moog (7) mouth (15) mp3 (4) msg (9) mt (16) mute (28) newnotes (15) noise (1) noise2 (8) notes (15) numbers (9) oc (4) odx (15) off (1) outdoor (6) pad (3) padlong (1) pebbles (1) perc (6) peri (15) pluck (17) popkick (10) print (11) proc (2) procshort (8) psr (30) rave (8) rave2 (4) ravemono (2) realclaps (4) reverbkick (1) rm (2) rs (1) sax (22) sd (2) seawolf (3) sequential (8) sf (18) sheffield (1) short (5) sid (12) sine (6) sitar (8) sn (52) space (18) speakspell (12) speech (7) speechless (10) speedupdown (9) stab (23) stomp (10) subroc3d (11) sugar (2) sundance (6) tabla (26) tabla2 (46) tablex (3) tacscan (22) tech (13) techno (7) tink (5) tok (4) toys (13) trump (11) ul (10) ulgab (5) uxay (3) v (6) voodoo (5) wind (10) wobble (1) world (3) xmas (1) yeah (31) @@ -23,7 +23,7 @@ const generic_params = [ * @name s * @param {string | Pattern} sound The sound / pattern of sounds to pick * @example - * s("bd hh").osc() + * s("bd hh").out() * */ ['s', 's', 'sound'], diff --git a/packages/webaudio/sampler.mjs b/packages/webaudio/sampler.mjs index 10c6d6be..a86d26ce 100644 --- a/packages/webaudio/sampler.mjs +++ b/packages/webaudio/sampler.mjs @@ -82,14 +82,14 @@ export const loadGithubSamples = async (path, nameFn) => { }; /** - * load the given sample map for webdirt + * Loads a collection of samples to use with `s` * * @example - * loadSamples({ - * bd: '808bd/BD0000.WAV', - * sd: ['808sd/SD0000.WAV','808sd/SD0010.WAV','808sd/SD0050.WAV'] + * samples({ + * bd: '808bd/BD0000.WAV', + * sd: '808sd/SD0010.WAV' * }, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/'); - * s("bd ").n(2).webdirt() + * s("[bd ~]*2, [~ hh]*2, ~ sd").out() * */ diff --git a/tutorial/tutorial.mdx b/tutorial/tutorial.mdx index 8e1fc75e..f8ca1571 100644 --- a/tutorial/tutorial.mdx +++ b/tutorial/tutorial.mdx @@ -186,14 +186,153 @@ resulting in a rhythmical structure of "x ~ ~ x ~ ~ x ~" (3 beats over 8 segment -## Mini Notation TODO +
-Compared to [tidal mini notation](https://tidalcycles.org/docs/patternlib/tutorials/mini_notation/), the following mini notation features are missing from Strudel: +# Web Audio Output + +The default way to output sound is by using the Web Audio Output. +Here is a little beat to show some of the possibilities: + +],hh(3,4)") // drums +.speed(perlin.range(.7,.9)) // random sample speed variation +,"" // bassline +.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps +.add(perlin.range(0,.5)) // random pitch variation +.superimpose(add(.05)) // add second, slightly detuned voice +.n() // wrap in "n" +.decay(.15).sustain(0) // make each note of equal length +.s('sawtooth') // waveform +.gain(.4) // turn down +.cutoff(sine.slow(7).range(300,5000)) // automate cutoff +,">".voicings() // chords +.superimpose(x=>x.add(.04)) // add second, slightly detuned voice +.add(perlin.range(0,.5)) // random pitch variation +.n() // wrap in "n" +.s('sawtooth') // waveform +.gain(.16) // turn down +.cutoff(500) // fixed cutoff +.attack(1) // slowly fade in +) +.slow(3/2) +.out()`} +/> + +## Synths + +So far, all the mini notation examples all used the same sound, which is kind of boring. +We can change the sound, using the `s` function: + +>").s('sawtooth').out()`} /> + +Here, we are wrapping our notes inside `note` and set the sound using `s`, connected by a dot. + +Those functions are only 2 of many ways to alter the properties, or _params_ of a sound. +The power of patterns allows us to sequence any _param_ independently: + +>").s("").out()`} /> + +Now we not only pattern the notes, but the sound as well! +`sawtooth` `square` and `triangle` are the basic waveforms available in `s`. + +## Samples + +Besides Synths, `s` can also play back samples: + + + +To know which sounds are available, open the [default sample map](https://strudel.tidalcycles.org/EmuSP12.json) + +### Custom Samples + +You can load your own sample map like this: + + + +The `samples` function takes an object where the properties are the sound names and the values are urls to audio files. +As most sample packs will be loaded from the same location, you can set the base url using the second argument: + + + +Because github is a popular choice to dump samples, there is a shortcut for that: + + + +The format is `github:user/repo/branch/`. + +It is also possible, to declare multiple files for one sound, using the array notation: + +,~ ,[hh:0 hh:1]*2").out()`} +/> + +The `:0` `:1` etc. are the indices of the array. + +For pitched samples, you can use `note`, just like with synths: + +@2").s('gtr').gain(.5).out()`} +/> + +Here, the guitar samples will overlap, because they always play till the end. +If we want them to behave more like a synth, we can add `clip(1)`: + +@2").s('gtr').clip(1) + .gain(.5).out()`} +/> + +If we have 2 samples with different base pitches, we can make them in tune by specifying the pitch like this: + +@2").s("gtr,moog").clip(1) + .gain(.5).out()`} +/> + +If a sample has no pitch set, `c3` is the default. -- [ ] Tie symbols "\_" -- [ ] feet marking "." -- [ ] Polymetric sequences "{ ... }" -- [ ] Fixed steps using "%"
From de4726ec8d437282c3689384f61d3d379ad07818 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 16 Sep 2022 23:59:46 +0200 Subject: [PATCH 09/45] improve sampler section --- tutorial/tutorial.mdx | 52 ++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/tutorial/tutorial.mdx b/tutorial/tutorial.mdx index f8ca1571..3ef79a0b 100644 --- a/tutorial/tutorial.mdx +++ b/tutorial/tutorial.mdx @@ -249,22 +249,10 @@ Besides Synths, `s` can also play back samples: To know which sounds are available, open the [default sample map](https://strudel.tidalcycles.org/EmuSP12.json) -### Custom Samples +### Custom Sample Maps You can load your own sample map like this: - - -The `samples` function takes an object where the properties are the sound names and the values are urls to audio files. -As most sample packs will be loaded from the same location, you can set the base url using the second argument: - +The `samples` function takes an object that maps sound names to audio file paths. +The second argument is the base URL that comes before each path. Make sure your base URL ends with a slash, while your sample paths do **not** begin with one. + Because github is a popular choice to dump samples, there is a shortcut for that: ,~ ,[hh:0 hh:1]*2").out()`} +s(",~ ,[hh:0 hh:1]*2").out()`} /> The `:0` `:1` etc. are the indices of the array. +The sample number can also be set using `n`: -For pitched samples, you can use `note`, just like with synths: +").out()`} +/> + +### Pitched Sounds + +For pitched sounds, you can use `note`, just like with synths: @2").s('gtr').clip(1) .gain(.5).out()`} /> +### Base Pitch + If we have 2 samples with different base pitches, we can make them in tune by specifying the pitch like this: @2").s("gtr,moog").clip(1) If a sample has no pitch set, `c3` is the default. +We can also declare different samples for different regions of the keyboard: + +!2, g4 f4]>") + .s('moog').clip(1) + .gain(.5).out()`} +/> + +The sampler will always pick the closest matching sample for the current note!
From acf339ad1364e206d43c81c7643bd42e1ba0c732 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 17 Sep 2022 15:18:16 +0200 Subject: [PATCH 10/45] load worklets on startup --- packages/webaudio/webaudio.mjs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs index 40d0f73f..bcba7ef0 100644 --- a/packages/webaudio/webaudio.mjs +++ b/packages/webaudio/webaudio.mjs @@ -147,12 +147,13 @@ function getWorklet(ac, processor, params) { return node; } +try { + loadWorklets(); +} catch (err) { + console.warn('could not load AudioWorklet effects coarse, crush and shape', err); +} + Pattern.prototype.out = function () { - try { - loadWorklets(); - } catch (err) { - console.warn('could not load AudioWorklet effects coarse, crush and shape', err); - } return this.onTrigger(async (t, hap, ct, cps) => { const hapDuration = hap.duration / cps; try { @@ -305,7 +306,7 @@ Pattern.prototype.out = function () { // connect chain elements together chain.slice(1).reduce((last, current) => last.connect(current), chain[0]); // disconnect all nodes when hap is over to make sure they are garbage collected -/* setTimeout(() => { + /* setTimeout(() => { chain.forEach((n) => n.disconnect()); }, (hapDuration + release + 0.1) * 1000); */ } catch (e) { From 71ca46b21bd2eaf42bbb7a4d0e620fa36686222a Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 17 Sep 2022 15:18:38 +0200 Subject: [PATCH 11/45] remove headings for generated doc --- tutorial/render.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tutorial/render.js b/tutorial/render.js index 58c6c51a..30dbab3f 100644 --- a/tutorial/render.js +++ b/tutorial/render.js @@ -23,8 +23,6 @@ ${item.description.replaceAll(/\{\@link ([a-zA-Z]+)?\#?([a-zA-Z]*)\}/g, (_, a, b return `${a}${b ? `#${b}` : ''}`; })} -${!!item.params?.length ? '**Parameters**' : ''} - ${ item.params ?.map( @@ -36,8 +34,7 @@ ${ ${ item.examples?.length - ? `**Examples** - + ? `
${item.examples?.map((example, k) => ``).join('\n\n')}
` From cafcead62ef153edbb156238b9a882961ef2b9f8 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 17 Sep 2022 15:18:57 +0200 Subject: [PATCH 12/45] doc: web audio effects --- packages/core/controls.mjs | 32 ++++++++++++++++---------------- tutorial/tutorial.mdx | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 40ed4427..c5c19644 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -129,7 +129,7 @@ const generic_params = [ * @name bandf * @param {number | Pattern} frequency center frequency * @example - * s("bd sd").bandf("<1000 2000 4000 8000>").osc() + * s("bd sd,hh*3").bandf("<1000 2000 4000 8000>").out() * */ ['f', 'bandf', 'A pattern of numbers from 0 to 1. Sets the center frequency of the band-pass filter.'], @@ -140,7 +140,7 @@ const generic_params = [ * @name bandq * @param {number | Pattern} q q factor * @example - * s("bd sd").bandf(2000).bandq("<.2 .9>").osc() + * s("bd sd").bandf(500).bandq("<0 1 2 3>").out() * */ ['f', 'bandq', 'a pattern of anumbers from 0 to 1. Sets the q-factor of the band-pass filter.'], @@ -202,7 +202,7 @@ const generic_params = [ * @name crush * @param {number | Pattern} depth between 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction). * @example - * s(",hh*3,jvbass*2").fast(2).crush("<16 8 7 6 5 4 3 2>").osc() + * s(",hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>").out() * */ [ @@ -216,7 +216,7 @@ const generic_params = [ * @name coarse * @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on. * @example - * s("xmas").coarse("<1 4 8 16 32>").osc() + * s("bd sd,hh*4").coarse("<1 4 8 16 32>").out() * */ [ @@ -253,7 +253,7 @@ const generic_params = [ * @name cutoff * @param {number | Pattern} frequency audible between 0 and 20000 * @example - * s("bd,hh*2,<~ sd>").fast(2).cutoff("<4000 2000 1000 500 200 100>").osc() + * s("bd sd,hh*3").cutoff("<4000 2000 1000 500 200 100>").out() * */ // TODO: add lpf synonym @@ -264,7 +264,7 @@ const generic_params = [ * @name hcutoff * @param {number | Pattern} frequency audible between 0 and 20000 * @example - * s("bd,hh*2,<~ sd>").fast(2).hcutoff("<4000 2000 1000 500 200 100>").osc() + * s("bd sd,hh*4").hcutoff("<4000 2000 1000 500 200 100>").out() * */ // TODO: add hpf synonym @@ -274,12 +274,12 @@ const generic_params = [ 'a pattern of numbers from 0 to 1. Applies the cutoff frequency of the high-pass filter. Also has alias @hpf@', ], /** - * Applies the cutoff frequency of the high-pass filter. + * Applies the resonance of the high-pass filter. * * @name hresonance * @param {number | Pattern} q resonance factor between 0 and 1 * @example - * s("bd,hh*2,<~ sd>").fast(2).hcutoff(2000).hresonance("<0 .2 .4 .6>").osc() + * s("bd sd,hh*4").hcutoff(2000).hresonance("<0 10 20 30>").out() * */ [ @@ -294,13 +294,13 @@ const generic_params = [ * @name resonance * @param {number | Pattern} q resonance factor between 0 and 1 * @example - * s("bd,hh*2,<~ sd>").fast(2).cutoff(2000).resonance("<0 .2 .4 .6>").osc() + * s("bd sd,hh*4").cutoff(2000).resonance("<0 10 20 30>").out() * */ ['f', 'resonance', 'a pattern of numbers from 0 to 1. Specifies the resonance of the low-pass filter.'], // TODO: add lpq synonym? /** - * Set detune of oscillators. Works only with some synths, see tidal doc + * DJ filter, below 0.5 is low pass filter, above is high pass filter. * * @name djf * @param {number | Pattern} cutoff below 0.5 is low pass filter, above is high pass filter @@ -493,7 +493,7 @@ const generic_params = [ * @name pan * @param {number | Pattern} pan between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel) * @example - * s("[bd hh]*2").pan("<.5 1 .5 0>").osc() + * s("[bd hh]*2").pan("<.5 1 .5 0>").out() * */ [ @@ -591,7 +591,7 @@ const generic_params = [ * @name shape * @param {number | Pattern} distortion between 0 and 1 * @example - * s("bd sd").shape("<0 .2 .4 .6 .8 1>").osc() + * s("bd sd,hh*4").shape("<0 .2 .4 .6 .8>").out() * */ [ @@ -648,16 +648,16 @@ const generic_params = [ // ['f', 'tomdecay', ''], // ['f', 'vcfegint', ''], // ['f', 'vcoegint', ''], + // TODO: Use a rest (~) to override the effect <- vowel /** * * Formant filter to make things sound like vowels. * * @name vowel - * @param {string | Pattern} vowel You can use a e i o u. Use a rest (~) to override the effect + * @param {string | Pattern} vowel You can use a e i o u. * @example - * vowel("a e i [o u]").slow(2) - * .n("<[0,7]!4 [2,7]!4>") - * .s('supersquare').osc() + * note("c2 >").s('sawtooth') + * .vowel(">").out() * */ [ diff --git a/tutorial/tutorial.mdx b/tutorial/tutorial.mdx index 3ef79a0b..23fbbc2d 100644 --- a/tutorial/tutorial.mdx +++ b/tutorial/tutorial.mdx @@ -241,6 +241,15 @@ The power of patterns allows us to sequence any _param_ independently: Now we not only pattern the notes, but the sound as well! `sawtooth` `square` and `triangle` are the basic waveforms available in `s`. +### Envelope + +You can control the envelope of a synth using the `attack`, `decay`, `sustain` and `release` functions: + +>").s('sawtooth') + .attack(.1).decay(.1).sustain(.2).release(.1).out()`} +/> + ## Samples Besides Synths, `s` can also play back samples: @@ -356,6 +365,32 @@ note("g2!2 !2, g4 f4]>") The sampler will always pick the closest matching sample for the current note! +## Effects + +Wether you're using a synth or a sample, you can apply these effects: + +{{ 'cutoff' | jsdoc }} + +{{ 'resonance' | jsdoc }} + +{{ 'hcutoff' | jsdoc }} + +{{ 'hresonance' | jsdoc }} + +{{ 'bandf' | jsdoc }} + +{{ 'bandq' | jsdoc }} + +{{ 'vowel' | jsdoc }} + +{{ 'pan' | jsdoc }} + +{{ 'coarse' | jsdoc }} + +{{ 'shape' | jsdoc }} + +{{ 'crush' | jsdoc }} +
# Core API From 1ccc391c8e4e6ed2d8e82118be8e5bde94832489 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 17 Sep 2022 15:32:36 +0200 Subject: [PATCH 13/45] proper disconnect onend --- packages/webaudio/webaudio.mjs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs index bcba7ef0..b1568ea7 100644 --- a/packages/webaudio/webaudio.mjs +++ b/packages/webaudio/webaudio.mjs @@ -305,10 +305,8 @@ Pattern.prototype.out = function () { chain.push(ac.destination); // connect chain elements together chain.slice(1).reduce((last, current) => last.connect(current), chain[0]); - // disconnect all nodes when hap is over to make sure they are garbage collected - /* setTimeout(() => { - chain.forEach((n) => n.disconnect()); - }, (hapDuration + release + 0.1) * 1000); */ + // disconnect all nodes when source node has ended: + chain[0].onended = () => chain.forEach((n) => n.disconnect()); } catch (e) { console.warn('.out error:', e); } From 762a17d949c3f30365a3d8e4b0bd80a7b8daec0f Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 17 Sep 2022 15:42:34 +0200 Subject: [PATCH 14/45] fix: remove TLA --- tutorial/MiniRepl.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial/MiniRepl.jsx b/tutorial/MiniRepl.jsx index 56387190..e46e35bc 100644 --- a/tutorial/MiniRepl.jsx +++ b/tutorial/MiniRepl.jsx @@ -12,7 +12,7 @@ export const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone. }, }); -await fetch('https://strudel.tidalcycles.org/EmuSP12.json') +fetch('https://strudel.tidalcycles.org/EmuSP12.json') .then((res) => res.json()) .then((json) => samples(json, 'https://strudel.tidalcycles.org/EmuSP12/')); From dd69e4caff4dc4a8d4a3fbce06ed9fb40a9d3588 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 17 Sep 2022 21:03:58 +0200 Subject: [PATCH 15/45] doc: rand irand perlin --- packages/core/signal.mjs | 32 +++++++++++++++++++++++++++++++- tutorial/tutorial.mdx | 6 ++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 9a4b5fe5..f7c33152 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -111,7 +111,17 @@ const timeToRandsPrime = (seed, n) => { const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n); /** - * A continuous pattern of random numbers, between 0 and 1 + * + */ + +/** + * A continuous pattern of random numbers, between 0 and 1. + * + * @name rand + * @example + * // randomly change the cutoff + * s("bd sd,hh*4").cutoff(rand.range(500,2000)).out() + * */ export const rand = signal(timeToRand); /** @@ -124,6 +134,17 @@ export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin(); export const brand = _brandBy(0.5); export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i)); + +/** + * A continuous pattern of random integers, between 0 and n-1. + * + * @name irand + * @param {number} n max value (exclusive) + * @example + * // randomly select scale notes from 0 - 7 (= C to C) + * irand(8).struct("x(3,8)").scale('C minor').note().out() + * + */ export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin(); export const __chooseWith = (pat, xs) => { @@ -225,6 +246,15 @@ export const perlinWith = (pat) => { return pat.sub(pata).fmap(interp).appBoth(pata.fmap(timeToRand)).appBoth(patb.fmap(timeToRand)); }; +/** + * Generates a continuous pattern of [perlin noise](https://en.wikipedia.org/wiki/Perlin_noise), in the range 0..1. + * + * @name perlin + * @example + * // randomly change the cutoff + * s("bd sd,hh*4").cutoff(perlin.range(500,2000)).out() + * + */ export const perlin = perlinWith(time); Pattern.prototype._degradeByWith = function (withPat, x) { diff --git a/tutorial/tutorial.mdx b/tutorial/tutorial.mdx index 23fbbc2d..4b4eecd8 100644 --- a/tutorial/tutorial.mdx +++ b/tutorial/tutorial.mdx @@ -678,6 +678,12 @@ These methods add random behavior to your Patterns. {{ 'Pattern.always' | jsdoc }} +{{ 'rand' | jsdoc }} + +{{ 'perlin' | jsdoc }} + +{{ 'irand' | jsdoc }} + ## Tone API To make the sounds more interesting, we can use Tone.js instruments ands effects. From b38b16f70eb0ad0e2e9d8b5b8aab19f6ad7e2644 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 17 Sep 2022 21:45:39 +0200 Subject: [PATCH 16/45] doc: early, late, each, every, rev + doc structure --- packages/core/pattern.mjs | 83 +++++++++++++++++++++++++--- tutorial/tutorial.mdx | 110 ++++++++++++++------------------------ 2 files changed, 117 insertions(+), 76 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 48e52643..d9b63d14 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -734,14 +734,14 @@ export class Pattern { } /** - * Speed up a pattern by the given factor. + * Speed up a pattern by the given factor. Used by "*" in mini notation. * * @name fast * @memberof Pattern * @param {number | Pattern} factor speed up factor * @returns Pattern * @example - * seq(e5, b4, d5, c5).fast(2) + * seq(e5, b4, d5, c5).fast(2) // "[e5 b4 d5 c5]*2" */ _fast(factor) { const fastQuery = this.withQueryTime((t) => t.mul(factor)); @@ -749,14 +749,14 @@ export class Pattern { } /** - * Slow down a pattern over the given number of cycles. + * Slow down a pattern over the given number of cycles. Like the "/" operator in mini notation. * * @name slow * @memberof Pattern * @param {number | Pattern} factor slow down factor * @returns Pattern * @example - * seq(e5, b4, d5, c5).slow(2) + * seq(e5, b4, d5, c5).slow(2) // "[e5 b4 d5 c5]/2" */ _slow(factor) { return this._fast(Fraction(1).div(factor)); @@ -795,14 +795,32 @@ export class Pattern { return this._fast(cpm / 60); } + /** + * Nudge a pattern to start earlier in time. Equivalent of Tidal's <~ operator + * + * @name early + * @memberof Pattern + * @param {number | Pattern} cycles number of cycles to nudge left + * @returns Pattern + * @example + * "bd ~".stack("hh ~".early(.1)).s().out() + */ _early(offset) { - // Equivalent of Tidal's <~ operator offset = Fraction(offset); return this.withQueryTime((t) => t.add(offset)).withHapTime((t) => t.sub(offset)); } + /** + * Nudge a pattern to start later in time. Equivalent of Tidal's ~> operator + * + * @name late + * @memberof Pattern + * @param {number | Pattern} cycles number of cycles to nudge right + * @returns Pattern + * @example + * "bd ~".stack("hh ~".late(.1)).s().out() + */ _late(offset) { - // Equivalent of Tidal's ~> operator offset = Fraction(offset); return this._early(Fraction(0).sub(offset)); } @@ -889,12 +907,56 @@ export class Pattern { return stack(this, func(this.late(time_pat))); } + /** + * Applies the given function every n cycles. + * @name every + * @memberof Pattern + * @param {number} n how many cycles + * @param {function} func function to apply + * @returns Pattern + * @example + * note("c3 d3 e3 g3").every(4, x=>x.rev()).out() + */ + every(n, func) { + const pat = this; + const pats = Array(n - 1).fill(pat); + // pats.unshift(func(pat)); + pats.push(func(pat)); + return slowcatPrime(...pats); + } + /** + * Applies the given function every n cycles, starting from the first cycle. + * @name every + * @memberof Pattern + * @param {number} n how many cycles + * @param {function} func function to apply + * @returns Pattern + * @example + * note("c3 d3 e3 g3").every(4, x=>x.rev()).out() + */ every(n, func) { const pat = this; const pats = Array(n - 1).fill(pat); pats.unshift(func(pat)); return slowcatPrime(...pats); } + + /** + * Applies the given function every n cycles, starting from the last cycle. + * @name each + * @memberof Pattern + * @param {number} n how many cycles + * @param {function} func function to apply + * @returns Pattern + * @example + * note("c3 d3 e3 g3").every(4, x=>x.rev()).out() + */ + each(n, func) { + const pat = this; + const pats = Array(n - 1).fill(pat); + pats.push(func(pat)); + return slowcatPrime(...pats); + } /** * Returns a new pattern where every other cycle is played once, twice as @@ -906,6 +968,15 @@ export class Pattern { return this.when(slowcat(false, true), (x) => fastcat(x, silence)._late(0.25)); } + /** + * Reverse all haps in a pattern + * + * @name rev + * @memberof Pattern + * @returns Pattern + * @example + * "c3 d3 e3 g3".rev() + */ rev() { const pat = this; const query = function (state) { diff --git a/tutorial/tutorial.mdx b/tutorial/tutorial.mdx index 4b4eecd8..dc963734 100644 --- a/tutorial/tutorial.mdx +++ b/tutorial/tutorial.mdx @@ -491,59 +491,72 @@ We can write the same with **stack** and **cat**: You can also use the shorthand **pr** instead of **polyrhythm**. -## Pattern modifier functions +## Time Modifiers -The following functions modify a pattern. +The following functions modify a pattern temporal structure in some way. -### slow(factor) +{{ 'Pattern.slow' | jsdoc }} -Like "/" in mini notation, **slow** will slow down a pattern over the given number of cycles: +{{ 'Pattern.fast' | jsdoc }} - +{{ 'Pattern.early' | jsdoc }} -The same in mini notation: +{{ 'Pattern.late' | jsdoc }} - +{{ 'Pattern.rev' | jsdoc }} -### fast(factor) +### struct(binary_pat) -Like "\*" in mini notation, **fast** will play a pattern times the given number in one cycle: +Applies the given structure to the pattern: - + -### early(cycles) +This is also useful to sample signals: -With early, you can nudge a pattern to start earlier in time: + - +## Conditional Modifiers -### late(cycles) +{{ 'Pattern.every' | jsdoc }} -Like early, but in the other direction: +{{ 'Pattern.each' | jsdoc }} - +### when(binary_pat, func) - +Applies the given function whenever the given pattern is in a true state. -### rev() +/2", sub(5))`} /> -Will reverse the pattern: +## Accumulation Modifiers - +### stack(pat) -### every(n, func) +Stacks the given pattern to the current pattern: -Will apply the given function every n cycles: + - +### superimpose(...func) - +Superimposes the result of the given function(s) on top of the original pattern: -Note that late is called directly. This is a shortcut for: +".scale('C minor').superimpose(scaleTranspose("2,4"))`} /> - x.late(0.5)))`} /> +### layer(...func) - +Layers the result of the given function(s) on top of each other. Like superimpose, but the original pattern is not part of the result. + +".scale('C minor').layer(scaleTranspose("0,2,4"))`} /> + +### off(time, func) + +Applies the given function by the given time offset: + + + +## Value Modifiers ### add(n) @@ -597,55 +610,12 @@ Rounds all values to the nearest integer: -### struct(binary_pat) - -Applies the given structure to the pattern: - - - -This is also useful to sample signals: - - - -### when(binary_pat, func) - -Applies the given function whenever the given pattern is in a true state. - -/2", sub(5))`} /> - -### superimpose(...func) - -Superimposes the result of the given function(s) on top of the original pattern: - -".scale('C minor').superimpose(scaleTranspose("2,4"))`} /> - -### layer(...func) - -Layers the result of the given function(s) on top of each other. Like superimpose, but the original pattern is not part of the result. - -".scale('C minor').layer(scaleTranspose("0,2,4"))`} /> - ### apply(func) Like layer, but with a single function: ".scale('C minor').apply(scaleTranspose("0,2,4"))`} /> -### off(time, func) - -Applies the given function by the given time offset: - - - -### stack(pat) - -Stacks the given pattern to the current pattern: - - - ## Randomness These methods add random behavior to your Patterns. From 19b8f073b11202c912ce00892c74f2cefb0600eb Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 17 Sep 2022 22:46:30 +0200 Subject: [PATCH 17/45] dedupe most of core api doc --- packages/core/pattern.mjs | 71 +++++++++---- packages/core/signal.mjs | 2 +- tutorial/tutorial.mdx | 206 ++++++++++++++------------------------ 3 files changed, 127 insertions(+), 152 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index d9b63d14..7f5ad154 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -513,11 +513,14 @@ export class Pattern { } /** - * Assumes a numerical pattern, containing unipolar values in the range 0 .. - * 1. Returns a new pattern with values scaled to the given min/max range. - * @param {Number} min - * @param {Number} max + * Assumes a numerical pattern, containing unipolar values in the range 0 .. 1. + * Returns a new pattern with values scaled to the given min/max range. + * Most useful in combination with continuous patterns. + * @name range + * @memberof Pattern * @returns Pattern + * @example + * s("bd sd,hh*4").cutoff(sine.range(500,2000).slow(4)).out() */ range(min, max) { return this.mul(max - min).add(min); @@ -741,7 +744,7 @@ export class Pattern { * @param {number | Pattern} factor speed up factor * @returns Pattern * @example - * seq(e5, b4, d5, c5).fast(2) // "[e5 b4 d5 c5]*2" + * s(" hh").fast(2).out() // s("[ hh]*2").out() */ _fast(factor) { const fastQuery = this.withQueryTime((t) => t.mul(factor)); @@ -756,7 +759,7 @@ export class Pattern { * @param {number | Pattern} factor slow down factor * @returns Pattern * @example - * seq(e5, b4, d5, c5).slow(2) // "[e5 b4 d5 c5]/2" + * s(" hh").slow(2).out() // s("[ hh]/2").out() */ _slow(factor) { return this._fast(Fraction(1).div(factor)); @@ -847,6 +850,17 @@ export class Pattern { return this._zoom(0, t)._slow(t); } + /** + * Applies the given structure to the pattern: + * + * @name struct + * @memberof Pattern + * @returns Pattern + * @example + * "c3,eb3,g3" + * .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~") + * .slow(4).note().out() + */ // struct(...binary_pats) { // // Re structure the pattern according to a binary pattern (false values are dropped) // const binary_pat = sequence(binary_pats); @@ -917,7 +931,7 @@ export class Pattern { * @example * note("c3 d3 e3 g3").every(4, x=>x.rev()).out() */ - every(n, func) { + every(n, func) { const pat = this; const pats = Array(n - 1).fill(pat); // pats.unshift(func(pat)); @@ -940,7 +954,7 @@ export class Pattern { pats.unshift(func(pat)); return slowcatPrime(...pats); } - + /** * Applies the given function every n cycles, starting from the last cycle. * @name each @@ -951,7 +965,7 @@ export class Pattern { * @example * note("c3 d3 e3 g3").every(4, x=>x.rev()).out() */ - each(n, func) { + each(n, func) { const pat = this; const pats = Array(n - 1).fill(pat); pats.push(func(pat)); @@ -1277,12 +1291,11 @@ Pattern.prototype.factories = { // Nothing export const silence = new Pattern((_) => []); -/** A discrete value that repeats once per cycle: +/** A discrete value that repeats once per cycle. * - * @param {any} value - The value to repeat * @returns {Pattern} * @example - * pure('e4') + * pure('e4') // "e4" */ export function pure(value) { function query(state) { @@ -1312,12 +1325,11 @@ export function reify(thing) { return pure(thing); } -/** The given items are played at the same time at the same length: +/** The given items are played at the same time at the same length. * - * @param {...any} items - The items to stack * @return {Pattern} * @example - * stack(g3, b3, [e4, d4]) + * stack(g3, b3, [e4, d4]) // "g3,b3,[e4,d4]" */ export function stack(...pats) { // Array test here is to avoid infinite recursions.. @@ -1330,7 +1342,6 @@ export function stack(...pats) { * * synonyms: {@link cat} * - * @param {...any} items - The items to concatenate * @return {Pattern} * @example * slowcat(e5, b4, [d5, c5]) @@ -1386,16 +1397,22 @@ export function fastcat(...pats) { return slowcat(...pats)._fast(pats.length); } -/** See {@link slowcat} */ +/** The given items are con**cat**enated, where each one takes one cycle. Synonym: slowcat + * + * @param {...any} items - The items to concatenate + * @return {Pattern} + * @example + * cat(e5, b4, [d5, c5]) // "" + * + */ export function cat(...pats) { return slowcat(...pats); } -/** Like {@link fastcat}, but where each step has a temporal weight: - * @param {...Array} items - The items to concatenate +/** Like {@link seq}, but each step has a length, relative to the whole. * @return {Pattern} * @example - * timeCat([3,e3],[1, g3]) + * timeCat([3,e3],[1, g3]) // "e3@3 g3" */ export function timeCat(...timepats) { const total = timepats.map((a) => a[0]).reduce((a, b) => a.add(b), Fraction(0)); @@ -1414,7 +1431,11 @@ export function sequence(...pats) { return fastcat(...pats); } -/** See {@link fastcat} */ +/** Like **cat**, but the items are crammed into one cycle. Synonyms: fastcat, sequence + * @example + * seq(e5, b4, [d5, c5]) // "e5 b4 [d5 c5]" + * + */ export function seq(...pats) { return fastcat(...pats); } @@ -1463,6 +1484,14 @@ export function pm(...args) { polymeter(...args); } +/* + * Plays the given items at the same time, within the same length: + * @param {...any} items - The items to play + * @return {Pattern} + * @example + * + * + */ export function polyrhythm(...xs) { const seqs = xs.map((a) => sequence(a)); diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index f7c33152..8637c30e 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -74,7 +74,7 @@ export const square2 = square._toBipolar(); * * @return {Pattern} * @example - * triangle.segment(2).range(0,7).scale('C minor') + * tri.segment(8).range(0,7).scale('C minor') * */ export const tri = fastcat(isaw, saw); diff --git a/tutorial/tutorial.mdx b/tutorial/tutorial.mdx index dc963734..46da410f 100644 --- a/tutorial/tutorial.mdx +++ b/tutorial/tutorial.mdx @@ -413,65 +413,21 @@ The above is the same as: Using strings, you can also use "#". -## Functions that create Patterns +## Pattern Factories -The following functions will return a pattern. We will see later what that means. +The following functions will return a pattern. -## pure(value) - -To create a pattern from a value, you can wrap the value in pure: - - +{{ 'pure' | jsdoc }} Most of the time, you won't need that function as input values of pattern creating functions are purified by default. -### cat(...values) +{{ 'cat' | jsdoc }} -The given items are con**cat**enated, where each one takes one cycle: +{{ 'seq' | jsdoc }} - +{{ 'stack' | jsdoc }} -- Square brackets will create a subsequence -- The function **slowcat** does the same as **cat**. - -### seq(...values) - -Like **cat**, but the items are crammed into one cycle: - - - -- Synonyms: **fastcat**, **sequence** - -### stack(...values) - -The given items are played at the same time at the same length: - - - -- Square Brackets will create a subsequence - -### Nesting functions - -You can nest functions inside one another: - - - -The above is equivalent to - -"`} /> - -### timeCat(...[weight,value]) - -Like with "@" in mini notation, we can specify weights to the items in a sequence: - - +{{ 'timeCat' | jsdoc }} + + +## Combining Patterns + +You can freely mix JS patterns, mini patterns and values! For example, this pattern: + + + +...is equivalent to: + + + +... as well as: + +"`} /> + +While mini notation is almost always shorter, it only has a handful of modifiers: \* / ! @. +When using JS patterns, there is a lot more you can do. + ## Time Modifiers The following functions modify a pattern temporal structure in some way. @@ -505,18 +498,9 @@ The following functions modify a pattern temporal structure in some way. {{ 'Pattern.rev' | jsdoc }} -### struct(binary_pat) +{{ 'Pattern.struct' | jsdoc }} -Applies the given structure to the pattern: - - - -This is also useful to sample signals: - - +{{ 'Pattern.legato' | jsdoc }} ## Conditional Modifiers @@ -616,7 +600,36 @@ Like layer, but with a single function: ".scale('C minor').apply(scaleTranspose("0,2,4"))`} /> -## Randomness +{{ 'Pattern.range' | jsdoc }} + +## Continuous Signals + +Signals are patterns with continuous values, meaning they have theoretically infinite steps. +They can provide streams of numbers that can be sampled at discrete points in time. + +## + +{{ 'saw' | jsdoc }} + +{{ 'sine' | jsdoc }} + +{{ 'cosine' | jsdoc }} + +{{ 'tri' | jsdoc }} + +{{ 'square' | jsdoc }} + +### Ranges from -1 to 1 + +There is also `saw2`, `sine2`, `cosine2`, `tri2` and `square2` which have a range from -1 to 1! + +{{ 'rand' | jsdoc }} + +{{ 'perlin' | jsdoc }} + +{{ 'irand' | jsdoc }} + +## Random Modifiers These methods add random behavior to your Patterns. @@ -648,12 +661,6 @@ These methods add random behavior to your Patterns. {{ 'Pattern.always' | jsdoc }} -{{ 'rand' | jsdoc }} - -{{ 'perlin' | jsdoc }} - -{{ 'irand' | jsdoc }} - ## Tone API To make the sounds more interesting, we can use Tone.js instruments ands effects. @@ -903,67 +910,6 @@ If you want to contribute in another way, either

-# API Docs - -The following is generated from the source documentation. - -## Pattern Factories - -The following functions will return a pattern. We will see later what that means. - -{{ 'pure' | jsdoc }} - -{{ 'slowcat' | jsdoc }} - -{{ 'fastcat' | jsdoc }} - -{{ 'stack' | jsdoc }} - -{{ 'timeCat' | jsdoc }} - -{{ 'polyrhythm' | jsdoc }} - -## Pattern Modifiers - -{{ 'Pattern.slow' | jsdoc }} - -{{ 'Pattern.fast' | jsdoc }} - -{{ 'Pattern.early' | jsdoc }} - -{{ 'Pattern.late' | jsdoc }} - -{{ 'Pattern.rev' | jsdoc }} - -{{ 'Pattern.legato' | jsdoc }} - -## Continuous Signals - -Signals are patterns with continuous values, meaning they have theoretically infinite steps. -They can provide streams of numbers that can be sampled at discrete points in time. - -{{ 'Pattern.range' | jsdoc }} - -{{ 'saw' | jsdoc }} - -{{ 'saw2' | jsdoc }} - -{{ 'sine' | jsdoc }} - -{{ 'sine2' | jsdoc }} - -{{ 'cosine' | jsdoc }} - -{{ 'cosine2' | jsdoc }} - -{{ 'tri' | jsdoc }} - -{{ 'tri2' | jsdoc }} - -{{ 'square' | jsdoc }} - -{{ 'square2' | jsdoc }} - ## Using Samples with Webdirt You can use the powerful sampling engine [Webdirt](https://github.com/dktr0/WebDirt) with Strudel. From dfd33bab8e2191858b333644158359769f222ff4 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 17 Sep 2022 23:16:13 +0200 Subject: [PATCH 18/45] dedupe accumulation modifiers --- packages/core/pattern.mjs | 68 ++++++++++++++++++++++++++++++++++++++- tutorial/tutorial.mdx | 30 +++++------------ 2 files changed, 75 insertions(+), 23 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 7f5ad154..d4d928ba 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -686,6 +686,16 @@ export class Pattern { return func(this); } + /** + * Layers the result of the given function(s). Like {@link superimpose}, but without the original pattern: + * @name layer + * @memberof Pattern + * @returns Pattern + * @example + * "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*4" + * .layer(x=>x.add("0,2")) + * .scale('C minor').note().out() + */ layer(...funcs) { return stack(...funcs.map((func) => func(this))); } @@ -908,6 +918,16 @@ export class Pattern { return this.invert(); } + /** + * Applies the given function whenever the given pattern is in a true state. + * @name when + * @memberof Pattern + * @param {Pattern} binary_pat + * @param {function} func + * @returns Pattern + * @example + * "c3 eb3 g3".when("<0 1>/2", x=>x.sub(5)) + */ when(binary_pat, func) { //binary_pat = sequence(binary_pat) const true_pat = binary_pat._filterValues(id); @@ -917,6 +937,16 @@ export class Pattern { return stack(with_pat, without_pat); } + /** + * Superimposes the function result on top of the original pattern, delayed by the given time. + * @name off + * @memberof Pattern + * @param {Pattern | number} time offset time + * @param {function} func function to apply + * @returns Pattern + * @example + * "c3 eb3 g3".off(1/8, x=>x.add(7)) + */ off(time_pat, func) { return stack(this, func(this.late(time_pat))); } @@ -1033,6 +1063,15 @@ export class Pattern { return this.juxBy(1, func); } + /** + * Stacks the given pattern(s) to the current pattern. + * @name stack + * @memberof Pattern + * @example + * s("hh*2").stack( + * n("c2(3,8)") + * ).out() + */ stack(...pats) { return stack(this, ...pats); } @@ -1041,11 +1080,28 @@ export class Pattern { return sequence(this, ...pats); } - // shorthand for sequence + /** + * Appends the given pattern(s) to the current pattern. Synonyms: .sequence .fastcat + * @name seq + * @memberof Pattern + * @example + * s("hh*2").seq( + * n("c2(3,8)") + * ).out() + */ seq(...pats) { return sequence(this, ...pats); } + /** + * Appends the given pattern(s) to the next cycle. Synonym: .slowcat + * @name cat + * @memberof Pattern + * @example + * s("hh*2").cat( + * n("c2(3,8)") + * ).out() + */ cat(...pats) { return cat(this, ...pats); } @@ -1058,6 +1114,16 @@ export class Pattern { return slowcat(this, ...pats); } + /** + * Superimposes the result of the given function(s) on top of the original pattern: + * @name superimpose + * @memberof Pattern + * @returns Pattern + * @example + * "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*4" + * .superimpose(x=>x.add(2)) + * .scale('C minor').note().out() + */ superimpose(...funcs) { return this.stack(...funcs.map((func) => func(this))); } diff --git a/tutorial/tutorial.mdx b/tutorial/tutorial.mdx index 46da410f..134ea978 100644 --- a/tutorial/tutorial.mdx +++ b/tutorial/tutorial.mdx @@ -508,37 +508,23 @@ The following functions modify a pattern temporal structure in some way. {{ 'Pattern.each' | jsdoc }} -### when(binary_pat, func) - -Applies the given function whenever the given pattern is in a true state. - -/2", sub(5))`} /> +{{ 'Pattern.when' | jsdoc }} ## Accumulation Modifiers -### stack(pat) +{{ 'Pattern.stack' | jsdoc }} -Stacks the given pattern to the current pattern: +{{ 'Pattern.superimpose' | jsdoc }} - +{{ 'Pattern.layer' | jsdoc }} -### superimpose(...func) +{{ 'Pattern.off' | jsdoc }} -Superimposes the result of the given function(s) on top of the original pattern: +## Concat Modifiers -".scale('C minor').superimpose(scaleTranspose("2,4"))`} /> +{{ 'Pattern.seq' | jsdoc }} -### layer(...func) - -Layers the result of the given function(s) on top of each other. Like superimpose, but the original pattern is not part of the result. - -".scale('C minor').layer(scaleTranspose("0,2,4"))`} /> - -### off(time, func) - -Applies the given function by the given time offset: - - +{{ 'Pattern.cat' | jsdoc }} ## Value Modifiers From e4913dfb737b604803e43f07e027f726b37b6e3f Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 17 Sep 2022 23:35:35 +0200 Subject: [PATCH 19/45] mark tone + webdirt deprecated + add superdirt param list --- tutorial/tutorial.mdx | 426 ++++++++++++++++++------------------------ 1 file changed, 178 insertions(+), 248 deletions(-) diff --git a/tutorial/tutorial.mdx b/tutorial/tutorial.mdx index 134ea978..09990cc2 100644 --- a/tutorial/tutorial.mdx +++ b/tutorial/tutorial.mdx @@ -647,11 +647,186 @@ These methods add random behavior to your Patterns. {{ 'Pattern.always' | jsdoc }} -## Tone API +# Tonal API -To make the sounds more interesting, we can use Tone.js instruments ands effects. +The Tonal API, uses [tonaljs](https://github.com/tonaljs/tonal) to provide helpers for musical operations. -[Show Source on Github](https://github.com/tidalcycles/strudel/blob/main/repl/src/tone.ts) +### transpose(semitones) + +Transposes all notes to the given number of semitones: + +".slow(2)).transpose(0)`} /> + +This method gets really exciting when we use it with a pattern as above. + +Instead of numbers, scientific interval notation can be used as well: + +".slow(2)).transpose(1)`} /> + +### scale(name) + +Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like scaleTranpose. + + + +Note that the scale root is octaved here. You can also omit the octave, then index zero will default to octave 3. + +All the available scale names can be found [here](https://github.com/tonaljs/tonal/blob/main/packages/scale-type/data.ts). + +### scaleTranspose(steps) + +Transposes notes inside the scale by the number of steps: + +")`} +/> + +### voicings(range?) + +Turns chord symbols into voicings, using the smoothest voice leading possible: + +".voicings(), "")`} /> + + + +### rootNotes(octave = 2) + +Turns chord symbols into root notes of chords in given octave. + +".rootNotes(3)`} /> + +Together with layer, struct and voicings, this can be used to create a basic backing track: + +".layer( + x => x.voicings(['d3','g4']).struct("~ x"), + x => x.rootNotes(2).tone(synth(osc('sawtooth4')).chain(out())) +)`} +/> + + + + +## Microtonal API + +TODO + +## MIDI API + +Strudel also supports midi via [webmidi](https://npmjs.com/package/webmidi). + +### midi(outputName?) + +Make sure to have a midi device connected or to use an IAC Driver. +If no outputName is given, it uses the first midi output it finds. + +Midi is currently not supported by the mini repl used here, but you can [open the midi example in the repl](https://strudel.tidalcycles.org/#c3RhY2soIjxDXjcgQTcgRG03IEc3PiIubS52b2ljaW5ncygpLCAnPEMzIEEyIEQzIEcyPicubSkKICAubWlkaSgp). + +In the REPL, you will se a log of the available MIDI devices. + + + +# Superdirt API + +In mainline tidal, the actual sound is generated via Superdirt, which runs inside Supercollider. +Strudel also supports using Superdirt as a backend, although it requires some developer tooling to run. + +## Prequisites + +Getting Superdirt to work with Strudel, you need to + +1. install SuperCollider + sc3 plugins, see [Tidal Docs](https://tidalcycles.org/docs/) (Install Tidal) for more info. +2. install [node.js](https://nodejs.org/en/) +3. download [Strudel Repo](https://github.com/tidalcycles/strudel/) (or git clone, if you have git installed) +4. run `npm i` in the strudel directory +5. run `npm run osc` to start the osc server, which forwards OSC messages from Strudel REPL to SuperCollider + +Now you're all set! + +## Usage + +1. Start SuperCollider, either using SuperCollider IDE or by running `sclang` in a terminal +2. Open the [Strudel REPL](https://strudel.tidalcycles.org/#cygiYmQgc2QiKS5vc2MoKQ%3D%3D) + +...or test it here: + + + +If you now hear sound, congratulations! If not, you can get help on the [#strudel channel in the TidalCycles discord](https://discord.com/invite/HGEdXmRkzT). + +{{ 'Pattern.osc' | jsdoc }} + +## Superdirt Params + +The following functions can be used with superdirt: + +- s +- n +- freq +- channel +- orbit +- cutoff +- resonance +- hcutoff +- hresonance +- bandf +- bandq +- djf +- vowelSa +- cut +- begin +- end +- loop +- fadeTime +- speed +- unitA +- gain +- amp +- accelerate +- crush +- coarse +- delay +- lock +- leslie +- lrate +- lsize +- pan +- panspan +- pansplay +- room +- size +- dry +- shape +- squiz +- waveloss +- attack +- decayS +- octave +- detune +- tremolodepth + +Please refer to [Tidal Docs](https://tidalcycles.org/) for more info. + +# Webdirt API (deprecated) + +You can use the powerful sampling engine [Webdirt](https://github.com/dktr0/WebDirt) with Strudel. + +{{ 'Pattern.webdirt' | jsdoc }} + +
+
+ +# Tone API (deprecated) + +The Tone API uses Tone.js instruments ands effects to create sounds. - -## Tonal API - -The Tonal API, uses [tonaljs](https://github.com/tonaljs/tonal) to provide helpers for musical operations. - -### transpose(semitones) - -Transposes all notes to the given number of semitones: - -".slow(2)).transpose(0)`} /> - -This method gets really exciting when we use it with a pattern as above. - -Instead of numbers, scientific interval notation can be used as well: - -".slow(2)).transpose(1)`} /> - -### scale(name) - -Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like scaleTranpose. - - - -Note that the scale root is octaved here. You can also omit the octave, then index zero will default to octave 3. - -All the available scale names can be found [here](https://github.com/tonaljs/tonal/blob/main/packages/scale-type/data.ts). - -### scaleTranspose(steps) - -Transposes notes inside the scale by the number of steps: - -")`} -/> - -### voicings(range?) - -Turns chord symbols into voicings, using the smoothest voice leading possible: - -".voicings(), "")`} /> - - - -### rootNotes(octave = 2) - -Turns chord symbols into root notes of chords in given octave. - -".rootNotes(3)`} /> - -Together with layer, struct and voicings, this can be used to create a basic backing track: - -".layer( - x => x.voicings(['d3','g4']).struct("~ x"), - x => x.rootNotes(2).tone(synth(osc('sawtooth4')).chain(out())) -)`} -/> - - - - -## Microtonal API - -TODO - -## MIDI API - -Strudel also supports midi via [webmidi](https://npmjs.com/package/webmidi). - -### midi(outputName?) - -Make sure to have a midi device connected or to use an IAC Driver. -If no outputName is given, it uses the first midi output it finds. - -Midi is currently not supported by the mini repl used here, but you can [open the midi example in the repl](https://strudel.tidalcycles.org/#c3RhY2soIjxDXjcgQTcgRG03IEc3PiIubS52b2ljaW5ncygpLCAnPEMzIEEyIEQzIEcyPicubSkKICAubWlkaSgp). - -In the REPL, you will se a log of the available MIDI devices. - - - -# Contributing - -Contributions of any sort are very welcome! You can contribute by editing [this file](https://github.com/tidalcycles/strudel/blob/main/repl/src/tutorial/tutorial.mdx). -All you need is a github account. - -If you want to run the tutorial locally, you can clone the and run: - -```sh -cd repl && npm i && npm run tutorial -``` - -If you want to contribute in another way, either - -- [fork strudel repo on GitHub](https://github.com/tidalcycles/strudel) -- [Join the Discord Channel](https://discord.gg/remJ6gQA) -- [play with the Strudel REPL](https://strudel.tidalcycles.org/) - -
-
- -## Using Samples with Webdirt - -You can use the powerful sampling engine [Webdirt](https://github.com/dktr0/WebDirt) with Strudel. - -{{ 'Pattern.webdirt' | jsdoc }} - -## Using Superdirt via OSC - -In mainline tidal, the actual sound is generated via Superdirt, which runs inside Supercollider. -Strudel also supports using Superdirt as a backend, although it requires some developer tooling to run. - -### Getting Started - -Getting Superdirt to work with Strudel, you need to - -1. install SuperCollider + sc3 plugins, see [Tidal Docs](https://tidalcycles.org/docs/) (Install Tidal) for more info. -2. install [node.js](https://nodejs.org/en/) -3. download [Strudel Repo](https://github.com/tidalcycles/strudel/) (or git clone, if you have git installed) -4. run `npm i` in the strudel directory -5. run `npm run osc` to start the osc server, which forwards OSC messages from Strudel REPL to SuperCollider - -Now you're all set! - -### Usage - -1. Start SuperCollider, either using SuperCollider IDE or by running `sclang` in a terminal -2. Open the [Strudel REPL](https://strudel.tidalcycles.org/#cygiYmQgc2QiKS5vc2MoKQ%3D%3D) - -...or test it here: - - - -If you now hear sound, congratulations! If not, you can get help on the [#strudel channel in the TidalCycles discord](https://discord.com/invite/HGEdXmRkzT). - -{{ 'Pattern.osc' | jsdoc }} - -# Superdirt Params - -The following functions are specific to SuperDirt and won't work with other Strudel outputs. - -## Basic Types - -{{ 's' | jsdoc }} - -{{ 'n' | jsdoc }} - -{{ 'freq' | jsdoc }} - -{{ 'channel' | jsdoc }} - -{{ 'orbit' | jsdoc }} - -## Filters - -{{ 'cutoff' | jsdoc }} - -{{ 'resonance' | jsdoc }} - -{{ 'hcutoff' | jsdoc }} - -{{ 'hresonance' | jsdoc }} - -{{ 'bandf' | jsdoc }} - -{{ 'bandq' | jsdoc }} - -{{ 'djf' | jsdoc }} - -{{ 'vowel' | jsdoc }} - -## Sample Editing - -{{ 'cut' | jsdoc }} - -{{ 'begin' | jsdoc }} - -{{ 'end' | jsdoc }} - -{{ 'loop' | jsdoc }} - -{{ 'fadeTime' | jsdoc }} - -{{ 'speed' | jsdoc }} - -{{ 'unit' | jsdoc }} - -## Audio Effects - -{{ 'gain' | jsdoc }} - -{{ 'amp' | jsdoc }} - -{{ 'accelerate' | jsdoc }} - -{{ 'crush' | jsdoc }} - -{{ 'coarse' | jsdoc }} - -{{ 'delay' | jsdoc }} - -{{ 'lock' | jsdoc }} - -{{ 'leslie' | jsdoc }} - -{{ 'lrate' | jsdoc }} - -{{ 'lsize' | jsdoc }} - -{{ 'pan' | jsdoc }} - -{{ 'panspan' | jsdoc }} - -{{ 'pansplay' | jsdoc }} - -{{ 'room' | jsdoc }} - -{{ 'size' | jsdoc }} - -{{ 'dry' | jsdoc }} - -{{ 'shape' | jsdoc }} - -{{ 'squiz' | jsdoc }} - -{{ 'waveloss' | jsdoc }} - -{{ 'attack' | jsdoc }} - -{{ 'decay' | jsdoc }} - -## Synth Effects - -{{ 'octave' | jsdoc }} - -{{ 'detune' | jsdoc }} - -{{ 'tremolodepth' | jsdoc }} From c786d77b07aaea0d09c97ee79e37e40597324ae3 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 17 Sep 2022 23:38:42 +0200 Subject: [PATCH 20/45] make polyrhythm alias of stack #211 --- packages/core/pattern.mjs | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index d4d928ba..29ec0695 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -1404,6 +1404,10 @@ export function stack(...pats) { return new Pattern(query); } +// aliases +const polyrhythm = stack; +const pr = stack; + /** Concatenation: combines a list of patterns, switching between them successively, one per cycle: * * synonyms: {@link cat} @@ -1550,28 +1554,6 @@ export function pm(...args) { polymeter(...args); } -/* - * Plays the given items at the same time, within the same length: - * @param {...any} items - The items to play - * @return {Pattern} - * @example - * - * - */ -export function polyrhythm(...xs) { - const seqs = xs.map((a) => sequence(a)); - - if (seqs.length == 0) { - return silence; - } - return stack(...seqs); -} - -// alias -export function pr(args) { - polyrhythm(args); -} - export const add = curry((a, pat) => pat.add(a)); export const chop = curry((a, pat) => pat.chop(a)); export const chunk = curry((a, pat) => pat.chunk(a)); From a14c7233d45e7287017bc628e72cbfa2c143980c Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 17 Sep 2022 23:40:56 +0200 Subject: [PATCH 21/45] fix: tests --- packages/core/pattern.mjs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 29ec0695..9ff616cf 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -1335,6 +1335,11 @@ Pattern.prototype.patternified = [ 'slow', 'velocity', ]; + +// aliases +export const polyrhythm = stack; +export const pr = stack; + // methods that create patterns, which are added to patternified Pattern methods Pattern.prototype.factories = { pure, @@ -1404,10 +1409,6 @@ export function stack(...pats) { return new Pattern(query); } -// aliases -const polyrhythm = stack; -const pr = stack; - /** Concatenation: combines a list of patterns, switching between them successively, one per cycle: * * synonyms: {@link cat} From fdc4bb455fa59165e3b295322caa637a596efa9e Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 17 Sep 2022 23:54:28 +0200 Subject: [PATCH 22/45] fix: formatting --- tutorial/tutorial.mdx | 55 ++++++------------------------------------- 1 file changed, 7 insertions(+), 48 deletions(-) diff --git a/tutorial/tutorial.mdx b/tutorial/tutorial.mdx index 09990cc2..48db137a 100644 --- a/tutorial/tutorial.mdx +++ b/tutorial/tutorial.mdx @@ -647,6 +647,9 @@ These methods add random behavior to your Patterns. {{ 'Pattern.always' | jsdoc }} +
+
+ # Tonal API The Tonal API, uses [tonaljs](https://github.com/tonaljs/tonal) to provide helpers for musical operations. @@ -712,11 +715,10 @@ Together with layer, struct and voicings, this can be used to create a basic bac -## Microtonal API +
+
-TODO - -## MIDI API +# MIDI API Strudel also supports midi via [webmidi](https://npmjs.com/package/webmidi). @@ -768,50 +770,7 @@ If you now hear sound, congratulations! If not, you can get help on the [#strude The following functions can be used with superdirt: -- s -- n -- freq -- channel -- orbit -- cutoff -- resonance -- hcutoff -- hresonance -- bandf -- bandq -- djf -- vowelSa -- cut -- begin -- end -- loop -- fadeTime -- speed -- unitA -- gain -- amp -- accelerate -- crush -- coarse -- delay -- lock -- leslie -- lrate -- lsize -- pan -- panspan -- pansplay -- room -- size -- dry -- shape -- squiz -- waveloss -- attack -- decayS -- octave -- detune -- tremolodepth +`s n note freq channel orbit cutoff resonance hcutoff hresonance bandf bandq djf vowel cut begin end loop fadeTime speed unitA gain amp accelerate crush coarse delay lock leslie lrate lsize pan panspan pansplay room size dry shape squiz waveloss attack decay octave detune tremolodepth` Please refer to [Tidal Docs](https://tidalcycles.org/) for more info. From 505cc01a18a48d295656c8e13575143a11256ea5 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 17 Sep 2022 23:59:16 +0200 Subject: [PATCH 23/45] remove empty heading --- tutorial/tutorial.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/tutorial/tutorial.mdx b/tutorial/tutorial.mdx index 48db137a..6eaf1d58 100644 --- a/tutorial/tutorial.mdx +++ b/tutorial/tutorial.mdx @@ -593,8 +593,6 @@ Like layer, but with a single function: Signals are patterns with continuous values, meaning they have theoretically infinite steps. They can provide streams of numbers that can be sampled at discrete points in time. -## - {{ 'saw' | jsdoc }} {{ 'sine' | jsdoc }} From 6d3d9659e2f19d8f26a9de4e66f9270aa9651978 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 19 Sep 2022 21:48:47 +0200 Subject: [PATCH 24/45] try build workflow --- .github/workflows/build.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..1eb9ed15 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,15 @@ +name: Strudel Build + +on: [workflow_dispatch] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v3 + with: + node-version: 16 + cache: "npm" + - run: npm install + - run: npm run build From 6c363d9c4b0dfe1b96352dcd2b57029a47d65fed Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 19 Sep 2022 21:50:38 +0200 Subject: [PATCH 25/45] add rollup-plugin-visualizer in root pkg --- package-lock.json | 267 ++++++++++++++++++++++++++++++++++++++++++---- package.json | 1 + 2 files changed, 248 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 04b7cca7..494751aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "jsdoc-json": "^2.0.2", "jsdoc-to-markdown": "^7.1.1", "lerna": "^4.0.0", + "rollup-plugin-visualizer": "^5.8.1", "vitest": "^0.21.1" } }, @@ -4256,6 +4257,15 @@ "clone": "^1.0.2" } }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -6506,6 +6516,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6722,6 +6747,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -7998,6 +8035,18 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -8575,6 +8624,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -9178,18 +9244,6 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, - "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -9941,6 +9995,91 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-visualizer": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.8.1.tgz", + "integrity": "sha512-NBT/xN/LWCwDM2/j5vYmjzpEAKHyclo/8Cv8AfTCwgADAG+tLJDy1vzxMw6NO0dSDjmTeRELD9UU3FwknLv0GQ==", + "dev": true, + "dependencies": { + "nanoid": "^3.3.4", + "open": "^8.4.0", + "source-map": "^0.7.3", + "yargs": "^17.5.1" + }, + "bin": { + "rollup-plugin-visualizer": "dist/bin/cli.js" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "rollup": "^2.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/rollup-plugin-visualizer/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup-plugin-visualizer/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/rollup-plugin-visualizer/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup-plugin-visualizer/node_modules/yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/rollup-plugin-visualizer/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -15950,6 +16089,12 @@ "clone": "^1.0.2" } }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -17575,6 +17720,12 @@ "has-tostringtag": "^1.0.0" } }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -17725,6 +17876,15 @@ "call-bind": "^1.0.2" } }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -18733,6 +18893,12 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -19176,6 +19342,17 @@ "mimic-fn": "^2.1.0" } }, + "open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -19551,14 +19728,6 @@ "nanoid": "^3.3.4", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" - }, - "dependencies": { - "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true - } } }, "postcss-js": { @@ -20263,6 +20432,64 @@ "fsevents": "~2.3.2" } }, + "rollup-plugin-visualizer": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.8.1.tgz", + "integrity": "sha512-NBT/xN/LWCwDM2/j5vYmjzpEAKHyclo/8Cv8AfTCwgADAG+tLJDy1vzxMw6NO0dSDjmTeRELD9UU3FwknLv0GQ==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "open": "^8.4.0", + "source-map": "^0.7.3", + "yargs": "^17.5.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + } + } + }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", diff --git a/package.json b/package.json index dc23a45c..97f26247 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "jsdoc-json": "^2.0.2", "jsdoc-to-markdown": "^7.1.1", "lerna": "^4.0.0", + "rollup-plugin-visualizer": "^5.8.1", "vitest": "^0.21.1" } } From 88d12223a52f3b7c3d1451dc72b96c5ba687150d Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 19 Sep 2022 21:52:31 +0200 Subject: [PATCH 26/45] build: add npm i in repl folder --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1eb9ed15..7c8f8b8b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,5 +11,5 @@ jobs: with: node-version: 16 cache: "npm" - - run: npm install + - run: npm install && cd repl && npm install - run: npm run build From 8ab912429bbf4d2c82846f8286b71ec82d1d7527 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 19 Sep 2022 21:54:34 +0200 Subject: [PATCH 27/45] build: npm i inside tutorial --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7c8f8b8b..7df3b881 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,5 +11,5 @@ jobs: with: node-version: 16 cache: "npm" - - run: npm install && cd repl && npm install + - run: npm ci && cd repl && npm ci && cd ../tutorial && npm ci - run: npm run build From 1da005e811a1079dd17fba55982d58b6a088873e Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 19 Sep 2022 21:58:12 +0200 Subject: [PATCH 28/45] deploy after build --- .github/workflows/{build.yml => delpoy.yml} | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename .github/workflows/{build.yml => delpoy.yml} (86%) diff --git a/.github/workflows/build.yml b/.github/workflows/delpoy.yml similarity index 86% rename from .github/workflows/build.yml rename to .github/workflows/delpoy.yml index 7df3b881..d13d0d77 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/delpoy.yml @@ -1,4 +1,4 @@ -name: Strudel Build +name: Build & Deploy on: [workflow_dispatch] @@ -13,3 +13,4 @@ jobs: cache: "npm" - run: npm ci && cd repl && npm ci && cd ../tutorial && npm ci - run: npm run build + - run: npm run deploy From 0134a50ffafe50164d8dccf77ca4e5f4cfef13a5 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 19 Sep 2022 22:09:23 +0200 Subject: [PATCH 29/45] add update button --- repl/src/App.jsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/repl/src/App.jsx b/repl/src/App.jsx index fd464d2b..ae55fc78 100644 --- a/repl/src/App.jsx +++ b/repl/src/App.jsx @@ -214,6 +214,19 @@ function App() { <>loading... )} + {!isEmbedded && (