mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-19 09:38:38 +00:00
commit
db6efec417
@ -70,12 +70,10 @@ export const SIDEBAR: Sidebar = {
|
|||||||
{ text: 'MIDI & OSC', link: 'learn/input-output' },
|
{ text: 'MIDI & OSC', link: 'learn/input-output' },
|
||||||
],
|
],
|
||||||
More: [
|
More: [
|
||||||
|
{ text: 'Recipes', link: 'recipes/recipes' },
|
||||||
{ text: 'Mini-Notation', link: 'learn/mini-notation' },
|
{ text: 'Mini-Notation', link: 'learn/mini-notation' },
|
||||||
{ text: 'Coding syntax', link: 'learn/code' },
|
|
||||||
{ text: 'Offline', link: 'learn/pwa' },
|
{ text: 'Offline', link: 'learn/pwa' },
|
||||||
{ text: 'Patterns', link: 'technical-manual/patterns' },
|
{ text: 'Patterns', link: 'technical-manual/patterns' },
|
||||||
{ text: 'Pattern Alignment', link: 'technical-manual/alignment' },
|
|
||||||
{ text: 'Strudel vs Tidal', link: 'learn/strudel-vs-tidal' },
|
|
||||||
{ text: 'Music metadata', link: 'learn/metadata' },
|
{ text: 'Music metadata', link: 'learn/metadata' },
|
||||||
{ text: 'CSound', link: 'learn/csound' },
|
{ text: 'CSound', link: 'learn/csound' },
|
||||||
],
|
],
|
||||||
@ -89,7 +87,13 @@ export const SIDEBAR: Sidebar = {
|
|||||||
{ text: 'Accumulation', link: 'learn/accumulation' },
|
{ text: 'Accumulation', link: 'learn/accumulation' },
|
||||||
{ text: 'Tonal Functions', link: 'learn/tonal' },
|
{ text: 'Tonal Functions', link: 'learn/tonal' },
|
||||||
],
|
],
|
||||||
Understand: [{ text: 'Pitch', link: 'understand/pitch' }],
|
Understand: [
|
||||||
|
{ text: 'Coding syntax', link: 'learn/code' },
|
||||||
|
{ text: 'Pitch', link: 'understand/pitch' },
|
||||||
|
{ text: 'Cycles', link: 'understand/cycles' },
|
||||||
|
{ text: 'Pattern Alignment', link: 'technical-manual/alignment' },
|
||||||
|
{ text: 'Strudel vs Tidal', link: 'learn/strudel-vs-tidal' },
|
||||||
|
],
|
||||||
Development: [
|
Development: [
|
||||||
{ text: 'REPL', link: 'technical-manual/repl' },
|
{ text: 'REPL', link: 'technical-manual/repl' },
|
||||||
{ text: 'Sounds', link: 'technical-manual/sounds' },
|
{ text: 'Sounds', link: 'technical-manual/sounds' },
|
||||||
|
|||||||
312
website/src/pages/recipes/recipes.mdx
Normal file
312
website/src/pages/recipes/recipes.mdx
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
---
|
||||||
|
title: Recipes
|
||||||
|
layout: ../../layouts/MainLayout.astro
|
||||||
|
---
|
||||||
|
|
||||||
|
import { MiniRepl } from '../../docs/MiniRepl';
|
||||||
|
|
||||||
|
# Recipes
|
||||||
|
|
||||||
|
This page shows possible ways to achieve common (or not so common) musical goals.
|
||||||
|
There are often many ways to do a thing and there is no right or wrong.
|
||||||
|
The fun part is that each representation will give you different impulses when improvising.
|
||||||
|
|
||||||
|
## Arpeggios
|
||||||
|
|
||||||
|
An arpeggio is when the notes of a chord are played in sequence.
|
||||||
|
We can either write the notes by hand:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`note("c eb g c4")
|
||||||
|
.clip(2).s("gm_electric_guitar_clean")`}
|
||||||
|
punchcard
|
||||||
|
/>
|
||||||
|
|
||||||
|
...or use scales:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`n("0 2 4 7").scale("C:minor")
|
||||||
|
.clip(2).s("gm_electric_guitar_clean")`}
|
||||||
|
punchcard
|
||||||
|
/>
|
||||||
|
|
||||||
|
...or chord symbols:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`n("0 1 2 3").chord("Cm").mode("above:c3").voicing()
|
||||||
|
.clip(2).s("gm_electric_guitar_clean")`}
|
||||||
|
punchcard
|
||||||
|
/>
|
||||||
|
|
||||||
|
...using off:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`"0"
|
||||||
|
.off(1/3, add(2))
|
||||||
|
.off(1/2, add(4))
|
||||||
|
.n()
|
||||||
|
.scale("C:minor")
|
||||||
|
.s("gm_electric_guitar_clean")`}
|
||||||
|
punchcard
|
||||||
|
/>
|
||||||
|
|
||||||
|
## Chopping Breaks
|
||||||
|
|
||||||
|
A sample can be looped and chopped like this:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`await samples('github:yaxu/clean-breaks/main')
|
||||||
|
s("amen/8").fit().chop(16)`}
|
||||||
|
punchcard
|
||||||
|
/>
|
||||||
|
|
||||||
|
This fits the break into 8 cycles + chops it in 16 pieces.
|
||||||
|
The chops are not audible yet, because we're not doing any manipulation.
|
||||||
|
Let's add randmized doubling + reversing:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`await samples('github:yaxu/clean-breaks/main')
|
||||||
|
s("amen/8").fit().chop(16).cut(1)
|
||||||
|
.sometimesBy(.5, ply(2))
|
||||||
|
.sometimesBy(.25, mul(speed(-1)))`}
|
||||||
|
punchcard
|
||||||
|
/>
|
||||||
|
|
||||||
|
If we want to specify the order of samples, we can replace `chop` with `slice`:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`await samples('github:yaxu/clean-breaks/main')
|
||||||
|
s("amen/8").fit()
|
||||||
|
.slice(8, "<0 1 2 3 4*2 5 6 [6 7]>")
|
||||||
|
.cut(1).rarely(ply(2))`}
|
||||||
|
punchcard
|
||||||
|
/>
|
||||||
|
|
||||||
|
If we use `splice` instead of `slice`, the speed adjusts to the duration of the event:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`await samples('github:yaxu/clean-breaks/main')
|
||||||
|
s("amen")
|
||||||
|
.splice(8, "<0 1 2 3 4*2 5 6 [6 7]>")
|
||||||
|
.cut(1).rarely(ply(2))`}
|
||||||
|
punchcard
|
||||||
|
/>
|
||||||
|
|
||||||
|
Note that we don't need `fit`, because `splice` will do that by itself.
|
||||||
|
|
||||||
|
## Filter Envelopes
|
||||||
|
|
||||||
|
A minimal filter envelope looks like this:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`note("g1 bb1 <c2 eb2> d2")
|
||||||
|
.s("sawtooth")
|
||||||
|
.lpf(400).lpa(.2).lpenv(4)
|
||||||
|
.scope()`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
We can flip the envelope by setting `lpenv` negative + add some resonance `lpq`:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`note("g1 bb1 <c2 eb2> d2")
|
||||||
|
.s("sawtooth").lpq(8)
|
||||||
|
.lpf(400).lpa(.2).lpenv(-4)
|
||||||
|
.scope()`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
## Layering Sounds
|
||||||
|
|
||||||
|
We can layer sounds by separating them with ",":
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`note("<g1 bb1 d2 f1>")
|
||||||
|
.s("sawtooth, square") // <------
|
||||||
|
.scope()`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
We can control the gain of individual sounds like this:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`note("<g1 bb1 d2 f1>")
|
||||||
|
.s("sawtooth, square:0:.5") // <--- "name:number:gain"
|
||||||
|
.scope()`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
For more control over each voice, we can use `layer`:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`note("<g1 bb1 d2 f1>").layer(
|
||||||
|
x=>x.s("sawtooth").vib(4),
|
||||||
|
x=>x.s("square").add(note(12))
|
||||||
|
).scope()`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
Here, we give the sawtooth a vibrato and the square is moved an octave up.
|
||||||
|
With `layer`, you can use any pattern method available on each voice, so sky is the limit..
|
||||||
|
|
||||||
|
## Oscillator Detune
|
||||||
|
|
||||||
|
We can fatten a sound by adding a detuned version to itself:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`note("<g1 bb1 d2 f1>")
|
||||||
|
.add(note("0,.1")) // <------ chorus
|
||||||
|
.s("sawtooth").scope()`}
|
||||||
|
punchcard
|
||||||
|
/>
|
||||||
|
|
||||||
|
Try out different values, or add another voice!
|
||||||
|
|
||||||
|
## Polyrhythms
|
||||||
|
|
||||||
|
Here is a simple example of a polyrhythm:
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`s("bd*2,hh*3")`} punchcard />
|
||||||
|
|
||||||
|
A polyrhythm is when 2 different tempos happen at the same time.
|
||||||
|
|
||||||
|
## Polymeter
|
||||||
|
|
||||||
|
This is a polymeter:
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`s("<bd rim>,<hh hh oh>").fast(2)`} punchcard />
|
||||||
|
|
||||||
|
A polymeter is when 2 different bar lengths play at the same tempo.
|
||||||
|
|
||||||
|
## Phasing
|
||||||
|
|
||||||
|
This is a phasing:
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`note("<C D G A Bb D C A G D Bb A>*[6,6.1]").piano()`} punchcard />
|
||||||
|
|
||||||
|
Phasing happens when the same sequence plays at slightly different tempos.
|
||||||
|
|
||||||
|
## Running through samples
|
||||||
|
|
||||||
|
Using `run` with `n`, we can rush through a sample bank:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`await samples('github:Bubobubobubobubo/Dough-Fox/main')
|
||||||
|
n(run(8)).s("ftabla")`}
|
||||||
|
punchcard
|
||||||
|
/>
|
||||||
|
|
||||||
|
This works great with sample banks that contain similar sounds, like in this case different recordings of a tabla.
|
||||||
|
Often times, you'll hear the beginning of the phrase not where the pattern begins.
|
||||||
|
In this case, I hear the beginning at the third sample, which can be accounted for with `early`.
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`await samples('github:Bubobubobubobubo/Dough-Fox/main')
|
||||||
|
n(run(8)).s("ftabla").early(2/8)`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
Let's add some randomness:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`await samples('github:Bubobubobubobubo/Dough-Fox/main')
|
||||||
|
n(run(8)).s("ftabla").early(2/8)
|
||||||
|
.sometimes(mul(speed(1.5)))`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
## Tape Warble
|
||||||
|
|
||||||
|
We can emulate a pitch warbling effect like this:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`note("c4 bb f eb")
|
||||||
|
.add(note(perlin.range(0,.5))) // <------ warble
|
||||||
|
.clip(2).s("gm_electric_guitar_clean")`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
## Sound Duration
|
||||||
|
|
||||||
|
There are a number of ways to change the sound duration. Using clip:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`note("f ab bb c")
|
||||||
|
.clip("<2 1 .5 .25>/2")`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
The value of clip is relative to the duration of each event.
|
||||||
|
We can also create overlaps using release:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`note("f ab bb c")
|
||||||
|
.release("<2 1 .5 .002>/2")`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
This will smoothly fade out each sound for the given number of seconds.
|
||||||
|
We could also make the notes shorter with decay / sustain:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`note("f ab bb c")
|
||||||
|
.decay("<.2 .1 .02>/2").sustain(0)`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
For now, there is a limitation where decay values that exceed the event duration may cause little cracks, so use higher numbers with caution..
|
||||||
|
|
||||||
|
When using samples, we also have `.end` to cut relative to the sample length:
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`s("oh*4").end("<1 .5 .25 .1>")`} />
|
||||||
|
|
||||||
|
Compare that to clip:
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`s("oh*4").clip("<1 .5 .25 .1>")`} />
|
||||||
|
|
||||||
|
or decay / sustain
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`s("oh*4").decay("<.2 .12 .06 .01>").sustain(0)`} />
|
||||||
|
|
||||||
|
## Wavetable Synthesis
|
||||||
|
|
||||||
|
You can loop a sample with `loop` / `loopEnd`:
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`note("<c eb g f>").s("bd").loop(1).loopEnd(.05).gain(.2)`} />
|
||||||
|
|
||||||
|
This allows us to play the first 5% of the bass drum as a synth!
|
||||||
|
To simplify loading wavetables, any sample that starts with `wt_` will be looped automatically:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`await samples('github:bubobubobubobubo/dough-waveforms/main')
|
||||||
|
note("c eb g bb").s("wt_dbass").clip(2)`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
Running through different wavetables can also give interesting variations:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`await samples('github:bubobubobubobubo/dough-waveforms/main')
|
||||||
|
note("c2*8").s("wt_dbass").n(run(8))`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
...adding a filter envelope + reverb:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`await samples('github:bubobubobubobubo/dough-waveforms/main')
|
||||||
|
note("c2*8").s("wt_dbass").n(run(8))
|
||||||
|
.lpf(perlin.range(200,2000).slow(8))
|
||||||
|
.lpenv(-3).lpa(.1).room(.5)`}
|
||||||
|
/>
|
||||||
130
website/src/pages/understand/cycles.mdx
Normal file
130
website/src/pages/understand/cycles.mdx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
---
|
||||||
|
title: Understanding Cycles
|
||||||
|
layout: ../../layouts/MainLayout.astro
|
||||||
|
---
|
||||||
|
|
||||||
|
import { MiniRepl } from '../../docs/MiniRepl';
|
||||||
|
import { PitchSlider } from '../../components/PitchSlider';
|
||||||
|
import Box from '@components/Box.astro';
|
||||||
|
|
||||||
|
# Understanding Cycles
|
||||||
|
|
||||||
|
The concept of cycles is very central to be able to understand how Strudel works.
|
||||||
|
Strudel's mother language, TidalCycles, even has it in its name.
|
||||||
|
|
||||||
|
## Cycles and BPM
|
||||||
|
|
||||||
|
In most music software, the unit BPM (beats per minute) is used to set the tempo.
|
||||||
|
Strudel expresses tempo as CPS (cycles per second), with a default of 1CPS:
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`s("bd")`} />
|
||||||
|
|
||||||
|
Here we can hear the 1CPS in action: The kick repeats once per second like a clock.
|
||||||
|
We could say 1CPS = 1BPS (beats per second) = 60BPM. Let's add another kick:
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`s("bd bd")`} />
|
||||||
|
|
||||||
|
Now we have 2 kicks per second, but the whole pattern still plays at 1CPS.
|
||||||
|
In terms of BPM, most musicians would tell you this is playing at 120bpm.
|
||||||
|
What about this one:
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`s("bd hh")`} />
|
||||||
|
|
||||||
|
Because the second sound is now a hihat, the tempo feels slower again.
|
||||||
|
This brings us to an important realization:
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
|
||||||
|
Tempo is based on perception.
|
||||||
|
The choice of sounds also has an impact on the tempo feel.
|
||||||
|
This is why the same CPS can produce different perceived tempos.
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
## Setting CPM
|
||||||
|
|
||||||
|
If you're familiar with BPM, you can use the `cpm` method to set the tempo in cycles per minute:
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`s("bd hh").cpm(110)`} />
|
||||||
|
|
||||||
|
If you want to add more beats per cycle, you might want to divide the cpm:
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`s("bd sd bd rim, hh*8").cpm(110/4)`} />
|
||||||
|
|
||||||
|
Or using 2 beats per cycle:
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`s("bd sd, hh*4").cpm(110/2)`} />
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
|
||||||
|
To set a specific bpm, use `.cpm(bpm/bpc)`
|
||||||
|
|
||||||
|
- bpm: the target beats per minute
|
||||||
|
- bpc: the number of perceived beats per cycle
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
## Cycles and Bars
|
||||||
|
|
||||||
|
Also in most music software, multiple beats form a bar (or measure).
|
||||||
|
The so called time signature specifies how many beats are in each bar.
|
||||||
|
In many types of music, it is common to use 4 beats per bar, also known as 4/4 time.
|
||||||
|
Many music programs use it as a default.
|
||||||
|
|
||||||
|
Strudel does not a have concept of bars or measures, there are only cycles.
|
||||||
|
How you use them is up to you. Above, we've had this example:
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`s("bd sd bd rim, hh*8").cpm(110/4)`} />
|
||||||
|
|
||||||
|
This could be interpreted as 4/4 time with a tempo of 110bpm.
|
||||||
|
We could write out multiple bars like this:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`s(\`<
|
||||||
|
[bd sd bd rim, hh*8]
|
||||||
|
[bd sd bd rim*2, hh*8]
|
||||||
|
>\`).cpm(110/4)`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
Instead of writing out each bar separately, we could express this much shorter:
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`s("bd <sd rim*<1 2>>,hh*4").cpm(110/2)`} />
|
||||||
|
|
||||||
|
Here we can see that thinking in cycles rather than bars simplifies things a lot!
|
||||||
|
These types of simplifications work because of the repetitive nature of rhythm.
|
||||||
|
In computational terms, you could say the former notation has a lot of redundancy.
|
||||||
|
|
||||||
|
## Time Signatures
|
||||||
|
|
||||||
|
To get a time signature, just change the number of elements per bar. Here is a rhythm with 7 beats:
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`s("bd ~ rim bd bd rim ~")`} />
|
||||||
|
|
||||||
|
or with 5:
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`s("<bd hh hh bd hh hh bd rim bd hh>*5")`} />
|
||||||
|
|
||||||
|
We could also write multiple bars with different time signatures:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`s(\`<
|
||||||
|
[bd hh rim]@3
|
||||||
|
[bd hh rim sd]@4
|
||||||
|
>\`).cpm(110*2)`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
Here we switch between 3/4 and 4/4, keeping the same tempo.
|
||||||
|
|
||||||
|
If we don't specify the length, we get what's called a metric modulation:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`s(\`<
|
||||||
|
[bd hh rim]
|
||||||
|
[bd hh rim sd]
|
||||||
|
>\`).cpm(110/2)`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
Now the 3 elements get the same time as the 4 elements, which is why the tempo changes.
|
||||||
Loading…
x
Reference in New Issue
Block a user