diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index b9d8a257..c5c19644 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'], @@ -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/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/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs index e2936f77..b1568ea7 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 { @@ -199,7 +200,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 } @@ -304,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); } 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, 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')}
` diff --git a/tutorial/tutorial.mdx b/tutorial/tutorial.mdx index 8e1fc75e..23fbbc2d 100644 --- a/tutorial/tutorial.mdx +++ b/tutorial/tutorial.mdx @@ -186,14 +186,210 @@ 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 -- [ ] Tie symbols "\_" -- [ ] feet marking "." -- [ ] Polymetric sequences "{ ... }" -- [ ] Fixed steps using "%" +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`. + +### 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: + + + +To know which sounds are available, open the [default sample map](https://strudel.tidalcycles.org/EmuSP12.json) + +### Custom Sample Maps + +You can load your own sample map like this: + + + +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: + + + +The format is `github:user/repo/branch/`. + +### Multiple Samples per Sound + +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. +The sample number can also be set using `n`: + +").out()`} +/> + +### Pitched Sounds + +For pitched sounds, 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()`} +/> + +### 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) + .gain(.5).out()`} +/> + +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! + +## 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 }}