From 782e8522c57611be3dd84a45c7e6ff901a557180 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 16 Sep 2022 23:24:47 +0200 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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); }