Merge pull request #207 from tidalcycles/webaudio-guide

Webaudio guide
This commit is contained in:
Felix Roos 2022-09-17 15:36:53 +02:00 committed by GitHub
commit d90cb23f22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 239 additions and 51 deletions

View File

@ -11,7 +11,7 @@ const generic_params = [
/**
* Select a sound / sample by name.
*
* <details>
* <details style={{display:'none'}}>
* <summary>show all sounds</summary>
*
* 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("<bd sd>,hh*3,jvbass*2").fast(2).crush("<16 8 7 6 5 4 3 2>").osc()
* s("<bd sd>,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 <a target="_blank" href="https://tidalcycles.org/docs/patternlib/tutorials/synthesizers">tidal doc</a>
* 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 <eb2 <g2 g1>>").s('sawtooth')
* .vowel("<a e i <o u>>").out()
*
*/
[

View File

@ -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 <sd!7 sd(3,4,2)>").n(2).webdirt()
* s("[bd ~]*2, [~ hh]*2, ~ sd").out()
*
*/

View File

@ -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);
}

View File

@ -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,

View File

@ -23,8 +23,6 @@ ${item.description.replaceAll(/\{\@link ([a-zA-Z]+)?\#?([a-zA-Z]*)\}/g, (_, a, b
return `<a href="#${a}${b ? `-${b}` : ''}">${a}${b ? `#${b}` : ''}</a>`;
})}
${!!item.params?.length ? '**Parameters**' : ''}
${
item.params
?.map(
@ -36,8 +34,7 @@ ${
${
item.examples?.length
? `**Examples**
? `
<div className="space-y-2">
${item.examples?.map((example, k) => `<MiniRepl tune={\`${example}\`} />`).join('\n\n')}
</div>`

View File

@ -186,14 +186,210 @@ resulting in a rhythmical structure of "x ~ ~ x ~ ~ x ~" (3 beats over 8 segment
<MiniRepl tune={`"e5(2,8) b4(3,8) d5(2,8) c5(3,8)".slow(4)`} />
## Mini Notation TODO
<br />
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:
<MiniRepl
tune={`samples({
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
}, 'github:tidalcycles/Dirt-Samples/master/');
stack(
s("bd,[~ <sd!3 sd(3,4,2)>],hh(3,4)") // drums
.speed(perlin.range(.7,.9)) // random sample speed variation
,"<a1 b1\*2 a1(3,8) e2>" // 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
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".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:
<MiniRepl tune={`note("c2 <eb2 <g2 g1>>").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:
<MiniRepl tune={`note("c2 <eb2 <g2 g1>>").s("<sawtooth square triangle>").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:
<MiniRepl
tune={`note("c2 <eb2 <g2 g1>>").s('sawtooth')
.attack(.1).decay(.1).sustain(.2).release(.1).out()`}
/>
## Samples
Besides Synths, `s` can also play back samples:
<MiniRepl tune={`s("bd sd,hh*8,misc/2").out()`} />
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:
<MiniRepl
tune={`samples({
bd: 'bd/BT0AADA.wav',
sd: 'sd/rytm-01-classic.wav',
hh: 'hh27/000_hh27closedhh.wav',
}, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/');
s("bd sd,hh*8").out()`}
/>
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:
<MiniRepl
tune={`samples({
bd: 'bd/BT0AADA.wav',
sd: 'sd/rytm-01-classic.wav',
hh: 'hh27/000_hh27closedhh.wav',
}, 'github:tidalcycles/Dirt-Samples/master/');
s("bd sd,hh*8").out()`}
/>
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:
<MiniRepl
tune={`samples({
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav'],
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
}, 'github:tidalcycles/Dirt-Samples/master/');
s("<bd:0 bd:1>,~ <sd:0 sd:1>,[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`:
<MiniRepl
tune={`samples({
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav'],
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
}, 'github:tidalcycles/Dirt-Samples/master/');
s("bd,~ sd,hh*4").n("<0 1>").out()`}
/>
### Pitched Sounds
For pitched sounds, you can use `note`, just like with synths:
<MiniRepl
tune={`samples({
"gtr": 'gtr/0001_cleanC.wav',
}, 'github:tidalcycles/Dirt-Samples/master/');
note("g3 [bb3 c4] <g4 f4 eb4 f3>@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)`:
<MiniRepl
tune={`samples({
"gtr": 'gtr/0001_cleanC.wav',
}, 'github:tidalcycles/Dirt-Samples/master/');
note("g3 [bb3 c4] <g4 f4 eb4 f3>@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:
<MiniRepl
tune={`samples({
"gtr": 'gtr/0001_cleanC.wav',
"moog": { 'g3': 'moog/005_Mighty%20Moog%20G3.wav' },
}, 'github:tidalcycles/Dirt-Samples/master/');
note("g3 [bb3 c4] <g4 f4 eb4 f3>@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:
<MiniRepl
tune={`samples({
"moog": {
'g2': 'moog/004_Mighty%20Moog%20G2.wav',
'g3': 'moog/005_Mighty%20Moog%20G3.wav',
'g4': 'moog/006_Mighty%20Moog%20G4.wav',
}}, 'github:tidalcycles/Dirt-Samples/master/');
note("g2!2 <bb2 c3>!2, <c4@3 [<eb4 bb3> 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 }}
<br />