import { Slides, Slide } from './Slides.jsx'; import { MaxiRepl } from './MaxiRepl'; import ImgTile from './ImgTile';
# Strudel ## Algorithmic Patterns for the Web
- How to represent and control music with code? - How to leverage unique features of computing? - How to break loose from old conventions? - How to improvise electronic music? - Answer: Live Coding?
## What is Live Coding? "In live coding performance, performers create time-based works by programming them while these same works are being executed."
Roberts & Wakefield, 2016

Screenshot: "Olivia Jack -{' '} Hydra , Live Coding Visuals in the Browser".{' '}

## What is Tidal Cycles?
- A very popular software for live coding music - a powerful pattern language, inspired by konnakol and weaving - find out more at tidalcycles.org

Screenshot:{' '} "Yaxu & hellocatfood - LIVE code for Noise Quest"

## What is Strudel?
- a port of Tidal Cycles to JavaScript - a browser based editor for zero-install live coding: strudel.tidalcycles.org - many ways to make sound: Web Audio, OSC, MIDI, Speech ...
- "Strudel" = internationally known as delicious pastry, but also "swirl" / "whirlpool" in german - (a tidal can cause a whirlpool!)

Screenshot:{' '} "Algorave 10th Birthday March 2022 - froos"

## A Taste of Strudel " .scale('D minor') .color('salmon') .off(1/4, x=>x.scaleTranspose(2).color('green')) .off(1/2, x=>x.scaleTranspose(6).color('steelblue')) .legato(.5) .echo(2, 1/8, .5) .pianoroll()`, // `// froos - xylophone on the floor const t = x => x.scaleTranspose("<0 2 4 3>/4").transpose(-2) const s = x => x.scale( cat('C3 minor pentatonic','G3 minor pentatonic') .slow(4)) const delay = new FeedbackDelay(1/8, .6).chain(vol(0.1), out()); const chorus = new Chorus(1,2.5,0.5).start(); stack( // melody "<<10 7> <8 3>>/4".struct("x*3").apply(s) .scaleTranspose("<0 3 2> <1 4 3>") .superimpose(scaleTranspose(2).early(1/8)) .apply(t).tone(polysynth().set({ ...osc('triangle4'), ...adsr(0,.08,0) }) .chain(vol(0.2).connect(delay),chorus,out())) .mask("<~@3 x>/16".early(1/8)), // pad "[1,3]/4".scale('G3 minor pentatonic').apply(t) .tone(polysynth().set({ ...osc('square2'), ...adsr(0.1,.4,0.8) }).chain(vol(0.2),chorus,out())).mask("<~ x>/32"), // xylophone "c3,g3,c4".struct("").fast("<1 <2!3 [4 8]>>") .apply(s) .scaleTranspose("<0 <1 [2 [3 <4 5>]]>>").apply(t) .tone(polysynth().set({ ...osc('sawtooth4'), ...adsr(0,.1,0) }).chain(vol(0.4).connect(delay),out())) .mask("/16".early(1/8)), // bass "c2 [c2 ~]*2".scale('C hirajoshi').apply(t) .tone(synth({ ...osc('sawtooth6'), ...adsr(0,.03,.4,.1) }).chain(vol(0.4),out())), // kick "*2".tone(membrane().chain(vol(0.8),out())), // snare "~ ".tone(noise().chain(vol(0.8),out())), // hihat "c3*4".transpose("[-24 0]*2").tone(metal(adsr(0,.02)) .chain(vol(0.5).connect(delay),out())) ).slow(1)`, `// froos - strudel schneckno samples({ clubkick: 'clubkick/2.wav', sd: ['808sd/SD0010.WAV','808sd/SD0050.WAV'], hh: 'hh/000_hh3closedhh.wav', clak: 'clak/000_clak1.wav', jvbass: ['jvbass/000_01.wav','jvbass/001_02.wav','jvbass/003_04.wav','jvbass/004_05.wav','jvbass/005_06.wav'] }, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/'); stack( s(",[~ ],hh(3,4,2)") .n("<0 1 2>").speed(.8), n("<0 1*2 4(3,8) 2>").speed(.6) .off(1/8, x => x.speed(1.2).degradeBy(.5)) .s("jvbass") ).reset(">") .cutoff(perlin.slow(7).range(100,20000)) .webdirt().slow(3/2)`, `stack( // drum pattern by yaxu "24*2 [~ 25]".chunk(4, x => x.sub("12").fast(2)) .tone(membrane().toDestination()), "~ x ~ x".every(2, x => x.fast(2)).iter(4) .tone(noise().toDestination()), "c4*8".add(saw.mul(12).slow(1.5)).degrade(0.05) .tone(metal().set(adsr(0,.06,0,0)).chain(vol(0.5),out())) )`, ]} /> ## How it started
"Any application that can be written in JavaScript, will eventually be written in JavaScript."
Jeff Atwood, 2009{' '}
![strudel port quote](https://loophole-letters.vercel.app/img/strudel-port.png)
- Started by Alex McLean, based on a port of a port of a rewrite of Tidal - I implemented the REPL + additional strudel packages - Help from Tidal Community Members - Started with Tone.js, then added Web MIDI, OSC and Web Audio API outputs - Inspired by: Tidal Vortex, Gibber, Estuary, Hydra, WAGS and Feedforward.
## REPL = Read Eval Play Loop
1. Read: User code is parsed, syntax sugar is dissolved 2. Eval: Transformed code is evaluated, creating a **Pattern** 3. Play: The **Pattern** is queried for events, which trigger sounds and effects 4. Loop: The new output is fed back into the ears of the human, informing the next move
## Mini Notation \* \[\] ~ , / <\> ! @ \(\)
- Embedded Domain Specific Language - PEG grammar based on [krill](https://github.com/Mdashdotdashn/krill/) by Marc Resibois
\` .legato(.95), // sub melody \`< [~ g4]!2 [~ ab4]!2 [~ a4]!2 [~ bb4]!2 [~ a4]!2 [~ g4]!2 [d4 e4] [f4 gb4] ~!2 [~ g4]!2 [~ ab4]!2 [~ a4]!2 [~ bb4]!2 [~ a4]!2 [~ g4]!2 [d4 e4] [f4 gb4] ~!2 [~ c5]!4 [~ a4]!2 [[c4 ~] [d4 ~]] [[eb4 ~] [e4 ~]] [~ c5]!4 [~ eb5]!2 [g4*2 [f4 ~]] [[e4 ~] [d4 ~]] >\`, // bass \`< c3!7 a3 f3!2 e3!2 ~!4 c3!7 a3 f3!2 e3!2 ~!4 f3!2 e3!2 d3!2 ~!2 f3!2 e3!2 ab3!2 ~!2 >\` .legato(.5) ).fast(2).pianoroll({fold:1,cycles:8,overscan:8})`, `stack( "c2 ", "<[e3,[g3 a3 bb3 a3]]!11 [e3,g3]>".slow(2) .struct("<[[x@2 x]*4]!11 [[~@2 x] [[x@2 ~]*9]@3]>".slow(2)) ) .velocity("[.7@2 .9]*2".add(rand.range(-.1,.1))) .transpose("<1 6 1@2 6@2 1@2 8 6 1 8>/2") .pianoroll({ fold:1, cycles:16 })`, ]} />
## Parsing Expression Grammars
1. write [peg](https://github.com/tidalcycles/strudel/blob/main/packages/mini/krill.pegjs), generate [parser](https://github.com/tidalcycles/strudel/blob/main/packages/mini/krill-parser.js) with [parser generator](https://peggyjs.org/) 2. pass string (e.g. `c3 [e3 g3]`) to generated parser 3. obtain Abstract Syntax Tree (AST) 4. [transform](https://github.com/tidalcycles/strudel/blob/main/packages/mini/mini.mjs#L76) AST into Pattern
[OhmJS Editor](https://ohmjs.org/editor/)
## AST of `c3 [e3 g3]`
```js { "type_": "pattern", "arguments_": { "alignment": "h" }, "source_": [ { "type_": "element", "source_": "c3", "location_": { "start": { "offset": 1, "line": 1, "column": 2 }, "end": { "offset": 4, "line": 1, "column": 5 } } }, { "type_": "element", "location_": { "start": { "offset": 4, "line": 1, "column": 5 }, "end": { "offset": 11, "line": 1, "column": 12 } } "source_": { "type_": "pattern", "arguments_": { "alignment": "h" }, "source_": [ { "type_": "element", "source_": "e3", "location_": { "start": { "offset": 5, "line": 1, "column": 6 }, "end": { "offset": 8, "line": 1, "column": 9 } } }, { "type_": "element", "source_": "g3", "location_": { "start": { "offset": 8, "line": 1, "column": 9 }, "end": { "offset": 10, "line": 1, "column": 11 } } } ] }, } ] } ```
= `seq(c3, seq(e3, g3))`
## Pattern Factories seq, cat, stack " .pianoroll()`, `stack(c3,e3,g3) // "c3,e3,g3" .pianoroll()`, `stack(c3, seq(e3, cat(g3, c4))) // "c3,[e3,]" .pianoroll()`, ]} /> ## Time Control fast / slow, early / late, rev, palindrome ".early(.16) ).late(.16) .pianoroll()`, `"c3 e3 g3 bb3".rev() .pianoroll()`, ]} /> ## JavaScript Transpilation [converting](https://github.com/tidalcycles/strudel/blob/main/packages/eval/shapeshifter.mjs#L40) valid JavaScript to valid JavaScript
Before (User Code) After (Ready to Eval)
{`"c3 e3"
  .fast(2)`}
{`mini('c3 e3')
  .withMiniLocation([1,0,0],[1,7,7])
  .fast(2)`}
[Blog Post](https://loophole-letters.vercel.app/shift-ast), [AST Explorer](https://astexplorer.net/)
## Arithmetic & Patternified Params add, sub, mul, div ").slow(2) .pianoroll({fold:1,cycles:8})`, ]} /> ## Scales generating notes from numbers ") .scale('F#2 major') .pianoroll({autorange:1})`, `"-7, 0 2 4 7".add("<0 1 2 1>") .scale(cat('F#2 major','A2 major').slow(8)) .pianoroll({autorange:1,cycles:16,fold:1,inactive:'orange'})`, ]} /> ## Articulation velocity, legato ") .scale(cat('F#2 major','A2 major').slow(8)) .legato(.25) .velocity(.5) .pianoroll({autorange:1,cycles:16,fold:1,inactive:'orange'})`, `"-7, 0 2 4 7".add("<0 1 2 1>") .scale(cat('F#2 major','A2 major').slow(8)) .legato("<0.25 2>/8") .velocity("<1 .8>/8") .pianoroll({autorange:1,cycles:16,fold:1,inactive:'orange'})`, ]} /> ## Continuous Signals / Randomness sine / saw / tri / square / rand / perlin, range, segment x.fast(3/2).color('#ff00ff50')) .fast(1) .tone(synth().toDestination()) .segment(64) .pianoroll({fold:0,vertical:0,cycles:2}) // strudel disable-highlighting`, ]} /> ## Humanization "articulate randomness" ") .scale(cat('F#2 major','A2 major').slow(8)) .legato(perlin.range(.5,2)) .velocity("<0.4 .3 .7>*3") .late(perlin.range(0,.05)) .pianoroll({autorange:1,cycles:16,fold:1,inactive:'orange'})`, ]} /> ## Superposition superimpose, off, echo, echoWith x.add(5).color('green')) .scale('C major').slow(2) .pianoroll({autorange:1,cycles:8,fold:0,inactive:'indigo'})`, `"0 2 4 7" //.off(1/6, x=>x.add(9).color('green')) //.off(1/3, x=>x.add(7).color('brown')) .scale('C major').slow(2) .pianoroll({autorange:1,cycles:8,fold:0,inactive:'indigo'})`, `"0 2 4 7".add("<0 1>/8") //.echo(6,.125,.8) .scale('C major').slow(2) .color("indigo steelblue orange") .pianoroll({autorange:1,cycles:8,fold:1,inactive:'indigo'})`, ]} /> ## Phasing Oscillator Detune in slow motion x.fast(1.01).color('#ff00ff50')) .slow(2) .pianoroll({fold:1,vertical:0,cycles:8})`, `// Steve Reich - Clapping Music s("cp").struct("x!3 ~ x!2 ~ x ~ x!2 ~").speed(.8) .jux(x=>x.speed(1.2) .late("<0 1 2 3 4 5 6 7 8 9 10 11>/8".div(12)) ).slow(2).webdirt()`, ]} /> ## Chord Voicings chord dictionaries + smooth voice leading (blog post) E2") .slow(4) .pianoroll({cycles:8,fold:1,inactive:'orange'})`, `// froos - giant steps, stride version stack( // melody seq( "[F#5 D5] [B4 G4] Bb4 [B4 A4]", "[D5 Bb4] [G4 Eb4] F#4 [G4 F4]", "Bb4 [B4 A4] D5 [D#5 C#5]", "F#5 [G5 F5] Bb5 [F#5 [F#5 ~@3]]", ), // chords seq( "[B^7 D7] [G^7 Bb7] Eb^7 [Am7 D7]", "[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]", "Eb^7 [Am7 D7] G^7 [C#m7 F#7]", "B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]" ) .struct("~ [x ~]".fast(4*8)) .voicings(['E3', 'G4']), // bass seq( "[B2 D2] [G2 D2] [Eb2 Bb2] [A2 D2]", "[G2 Bb2] [Eb2 F#2] [B2 F#2] [F2 Bb2]", "[Eb2 Bb2] [A2 D2] [G2 D2] [C#2 F#2]", "[B2 F#2] [F2 Bb2] [Eb2 Bb2] [C#2 F#2]" ) .struct("x ~".fast(4*8)) ).slow(25) .pianoroll({cycles:8,fold:1,inactive:'orange'})`]}/> ## Speech ".speak()']} /> ## Strudel Packages `npm i @strudel.cycles/*`, where \* is one of:
- core: tidal pattern engine - mini: mini notation parser + core binding - eval: user code evaluator. syntax sugar + highlighting - tone: bindings for Tone.js instruments and effects - osc: bindings to communicate via OSC - midi: webmidi bindings - serial: webserial bindings - tonal: tonal functions - xen: microtonal / xenharmonic functions - ... there are [more](https://github.com/tidalcycles/strudel/tree/main/packages)
## Workflow
- Everything is in one [monorepo](https://github.com/tidalcycles/strudel) - Development via npm workspaces - Packages are published with lerna - REPL is Deployed to github pages - Features are added with Pull Requests - Automated tests run for every PR - Snapshot tests
## Tech Stack
- REPL: Codemirror 6 + react + tailwind - Everything is built with vite - In source documentation with jsdoc - Tutorial written with MDX - jsdoc is inserted into MDX with nunjucks