import { Slides, Slide } from './Slides.jsx'; import { MaxiRepl } from './MaxiRepl'; import ImgTile from './ImgTile';
# Strudel ## Algorithmic Patterns for the Web ![whirlpool](./strudel_icon.png) Alex McLean, Felix Roos & Tidal Cycles Community
## Strudel is..
- zero-install live coding editor for the web - based on a port of Tidal Cycles - terse language for algorithmic patterns - dynamic music sequencing - algorithmic composition - live improvisation - probably more exciting than your regular DAW
![whirlpool](./strudel_icon.png)
## A Taste of Strudel " .color('salmon') .off(1/4, x=>x.add(2).color('green')) .off(1/2, x=>x.add(6).color('steelblue')) .scale('D minor') .legato(.5) .echo(2, 1/8, .5) .pianoroll() .note().piano().out()`, // `stack( s("bd,[~ ],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 ,"a4 c5 ".struct("x(5,8)") .superimpose(x=>x.add(.04)) // add second, slightly detuned voice .add(perlin.range(0,.5)) // random pitch variation .n() // wrap in "n" .decay(.1).sustain(0) // make notes short .s('triangle') // waveform .degradeBy(perlin.range(0,.5)) // randomly controlled random removal :) .echoWith(4,.125,(x,n)=>x.gain(.15*1/(n+1))) // echo notes ) .out() .slow(3/2)`, /* `// 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())) )`,*/ /* `stack( note("<[0 2 4 2] [0 3 5 3]>".scale('D3 dorian')) .s('jazzbass'), note("0,2,4".add("<0 1 2 1>").scale('D3 dorian')) .s('stage73') ).clip(1).out()`, */ ]} /> ## 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 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"

## Strudel: How it started
"Any application that can be written in JavaScript, will eventually be written in JavaScript."
Jeff Atwood, 2009{' '}
![strudel port quote](./strudel-port.png)
- Started by Alex McLean in January 2022 - based on a port of a rewrite of Tidal - I implemented the REPL + additional strudel packages - Help from Tidal Community Members - Inspired by: Tidal Vortex, Gibber, Estuary, Hydra, WAGS and Feedforward.
## High Level Overview
➡️
## Mini Notation \* \[\] ~ , / <\> ! @ \(\)
- Embedded Domain Specific Language - PEG grammar based on [krill](https://github.com/Mdashdotdashn/krill/) by Marc Resibois - `"c3 [e3 g3]"` === `seq(c3, [e3, g3])`
\` .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 })`, */ ]} />
## High Level Overview
➡️
## 🕵🏽 Querying Events Asking a Pattern for Events within a certain time span
```js seq('c3', ['e3', 'g3']) // <--- Pattern .queryArc(0, 2) // query events within 0 and 2 seconds .map((event) => event.showWhole()); // make readable ``` ```js [ '0/1 -> 1/2: c3', '1/2 -> 3/4: e3', '3/4 -> 1/1: g3', '1/1 -> 3/2: c3', // second time '3/2 -> 7/4: e3', '7/4 -> 2/1: g3', ]; ```
The scheduler will query the active pattern at regular intervals!
## High Level Overview
➡️
## Sound Output Patterns are wrapped with param functions to control different aspects of the sound. bb1]]") // sets frequency .s("") // sound source .gain(.5) // turn down volume .cutoff(sine.range(200,1000).slow(4)) // modulated cutoff .slow(2) .out().logValues()`, ]} />
- `.out()` will turn each event value into a Web Audio Graph! - Everything until the output are just objects in time, which can be used for any time-based output
## 🔭 Future Outlook
- Alternative Sound Backends - Collaborative Mode - Context Aware Documentation - Port more Tidal Features
## 🌀 Try it out now
- REPL: [strudel.tidalcycles.org](https://strudel.tidalcycles.org/?EeNsQ8hdNZwN) (-> [offline version](http://localhost:3000/#Y29uc3QgZGVsYXkgPSBuZXcgRmVlZGJhY2tEZWxheSgxLzMsIC41KS5jaGFpbih2b2woLjIpLCBvdXQoKSkKbGV0IGthbGltYmEgPSBhd2FpdCBzYW1wbGVyKHsKICBDNTogJy4vc2FtcGxlcy9rYWxpbWJhL2thbGltYmEtYzUubXAzJwp9KQprYWxpbWJhID0ga2FsaW1iYS5jaGFpbih2b2woMC42KS5jb25uZWN0KGRlbGF5KSxvdXQoKSk7CmNvbnN0IHNjYWxlcyA9IGNhdCgnQyBtYWpvcicsICdDIG1peG9seWRpYW4nLCAnRiBseWRpYW4nLCBbJ0YgbWlub3InLCAnRGIgbWFqb3InXSkKCnN0YWNrKAogICJbMCAyIDQgNiA5IDIgMCAtMl0qMyIKICAuYWRkKCI8MCAyPi80IikKICAuc2NhbGUoc2NhbGVzKQogIC5zdHJ1Y3QoIngqOCIpCiAgLnZlbG9jaXR5KCI8LjggLjMgLjY%2BKjgiKQogIC5zbG93KDIpCiAgLnRvbmUoa2FsaW1iYSksCiAgIjxjMiBjMiBmMiBbW0YyIEMyXSBkYjJdPiIKICAuc2NhbGUoc2NhbGVzKQogIC5zY2FsZVRyYW5zcG9zZSgiWzAgPDIgND5dKjIiKQogIC5zdHJ1Y3QoIngqNCIpCiAgLnZlbG9jaXR5KCI8LjggLjU%2BKjQiKQogIC52ZWxvY2l0eSgwLjgpCiAgLnNsb3coMikKICAudG9uZShrYWxpbWJhKQopCiAgLmxlZ2F0bygiPC40IC44IDEgMS4yIDEuNCAxLjYgMS44IDI%2BLzgiKQogIC5mYXN0KDEpCi5waWFub3JvbGwoe2F1dG9yYW5nZTowfSk%3D)) - Interactive Tutorial: [strudel.tidalcycles.org/tutorial](https://strudel.tidalcycles.org/) - News: [@tidalcycles](https://twitter.com/tidalcycles) - Github: [https://github.com/tidalcycles/strudel](https://github.com/tidalcycles/strudel)
### Happy Strudeling!
## More? ~, ~ sd, hh*3") //.cutoff(perlin.range(100,900)) //.sometimes(x=>x.speed("2")) , note( "Cm7 ".voicings() ).rhodes().slow(4) , note( "[c2 ](3,9)".slow(2) //.superimpose(add(0.02)) ) .s("").gain(.5) .cutoff(800), note( "0 2 4 1".iter(4) .off(1/12, add("6")) .degradeBy(.5) .scale('C5 minor') .legato(.05) .echo(2, 1/6, .5) ) .piano().hcutoff(2000) ).out() //.cutoff(800) //.reset("") `, ]} /> ## Strudel Workshop ![whirlpool](./strudel_icon.png) ## 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()`, ]} /> ## 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()']} />