# Table of Contents - [ Getting Started 🌀 Strudel ](#-getting-started-strudel-) - [ Showcase 🌀 Strudel ](#-showcase-strudel-) - [ Getting Started 🌀 Strudel ](#-getting-started-strudel-) - [Unknown](#unknown) - [ Intro 🌀 Strudel ](#-intro-strudel-) - [ First Sounds 🌀 Strudel ](#-first-sounds-strudel-) - [ First Notes 🌀 Strudel ](#-first-notes-strudel-) - [ Recap 🌀 Strudel ](#-recap-strudel-) - [ Pattern Effects 🌀 Strudel ](#-pattern-effects-strudel-) - [ First Effects 🌀 Strudel ](#-first-effects-strudel-) - [ Synths 🌀 Strudel ](#-synths-strudel-) - [ Recipes 🌀 Strudel ](#-recipes-strudel-) - [ MIDI, OSC & MQTT 🌀 Strudel ](#-midi-osc-mqtt-strudel-) - [ Offline 🌀 Strudel ](#-offline-strudel-) - [ Patterns 🌀 Strudel ](#-patterns-strudel-) - [ Mini Notation 🌀 Strudel ](#-mini-notation-strudel-) - [ Samples 🌀 Strudel ](#-samples-strudel-) - [ Music metadata 🌀 Strudel ](#-music-metadata-strudel-) - [ Csound 🌀 Strudel ](#-csound-strudel-) - [ Mondo Notation 🌀 Strudel ](#-mondo-notation-strudel-) - [ Visual Feedback 🌀 Strudel ](#-visual-feedback-strudel-) - [ Hydra 🌀 Strudel ](#-hydra-strudel-) - [ Device Motion 🌀 Strudel ](#-device-motion-strudel-) - [ Input Devices 🌀 Strudel ](#-input-devices-strudel-) - [ JavaScript API 🌀 Strudel ](#-javascript-api-strudel-) - [ Audio effects 🌀 Strudel ](#-audio-effects-strudel-) - [ Creating Patterns 🌀 Strudel ](#-creating-patterns-strudel-) - [ Control Parameters 🌀 Strudel ](#-control-parameters-strudel-) - [ Time Modifiers 🌀 Strudel ](#-time-modifiers-strudel-) - [ Tonal Functions 🌀 Strudel ](#-tonal-functions-strudel-) - [ Signals 🌀 Strudel ](#-signals-strudel-) - [ Accumulation Modifiers 🌀 Strudel ](#-accumulation-modifiers-strudel-) - [ Coding syntax 🌀 Strudel ](#-coding-syntax-strudel-) - [ Random Modifiers 🌀 Strudel ](#-random-modifiers-strudel-) - [ Conditional Modifiers 🌀 Strudel ](#-conditional-modifiers-strudel-) - [ Stepwise patterning 🌀 Strudel ](#-stepwise-patterning-strudel-) - [ Pattern Aligment 🌀 Strudel ](#-pattern-aligment-strudel-) - [ Xen Harmonic Functions 🌀 Strudel ](#-xen-harmonic-functions-strudel-) - [ Understanding Cycles 🌀 Strudel ](#-understanding-cycles-strudel-) - [ Strudel vs Tidal 🌀 Strudel ](#-strudel-vs-tidal-strudel-) - [ Understanding Pitch 🌀 Strudel ](#-understanding-pitch-strudel-) - [ Using Strudel in your Project 🌀 Strudel ](#-using-strudel-in-your-project-strudel-) - [ Understanding Chord Voicings 🌀 Strudel ](#-understanding-chord-voicings-strudel-) - [ Docs 🌀 Strudel ](#-docs-strudel-) - [ Strudel Packages 🌀 Strudel ](#-strudel-packages-strudel-) - [ Sounds 🌀 Strudel ](#-sounds-strudel-) - [ Testing 🌀 Strudel ](#-testing-strudel-) - [ Notes 🌀 Strudel ](#-notes-strudel-) - [🌀 Strudel Blog](#-strudel-blog) - [ REPL 🌀 Strudel ](#-repl-strudel-) - [ Erste Sounds 🌀 Strudel ](#-erste-sounds-strudel-) - [ Erste Töne 🌀 Strudel ](#-erste-t-ne-strudel-) - [ Erste Effekte 🌀 Strudel ](#-erste-effekte-strudel-) - [ Pattern-Effekte 🌀 Strudel ](#-pattern-effekte-strudel-) - [ Recap 🌀 Strudel ](#-recap-strudel-) - [Strudel REPL](#strudel-repl) - [ Sounds 🌀 Strudel ](#-sounds-strudel-) - [🌀 Strudel Community Bakery](#-strudel-community-bakery) - [ Vim Shortcuts 🌀 Strudel ](#-vim-shortcuts-strudel-) --- # Getting Started 🌀 Strudel Welcome[](https://strudel.cc/learn/getting-started/#welcome) ============================================================= Welcome to the Strudel documentation pages! These pages will introduce you to [Strudel](https://strudel.cc/) , a web-based [live coding](https://github.com/toplap/awesome-livecoding/) environment that implements the [Tidal Cycles](https://tidalcycles.org/) algorithmic pattern language. What is Strudel?[](https://strudel.cc/learn/getting-started/#what-is-strudel) ============================================================================== [Strudel](https://strudel.cc/) is a version of [Tidal Cycles](https://tidalcycles.org/) written in [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) , initiated by [Alex McLean](https://slab.org/) and [Felix Roos](https://froos.cc/) in 2022. Tidal Cycles, also known as Tidal, is a language for [algorithmic pattern](https://algorithmicpattern.org/) , and though it is most commonly used for [making music](https://tidalcycles.org/docs/showcase) , it can be used for any kind of pattern making activity, including [weaving](https://www.youtube.com/watch?v=TfEmEsusXjU) . Tidal was first implemented as a library written in the [Haskell](https://www.haskell.org/) functional programming language, and by itself it does not make any sound. To make sound, it has to be connected to a sound engine, and by default this is a [SuperCollider](https://supercollider.github.io/) plugin called [SuperDirt](https://github.com/musikinformatik/SuperDirt/) . As such, it can be difficult for first-time users to install both Tidal Cycles and SuperDirt, as there are many small details to get right. Strudel however runs directly in your web browser, does not require any custom software installation, and can make sound all by itself. Strudel REPL and MiniREPL[](https://strudel.cc/learn/getting-started/#strudel-repl-and-minirepl) ================================================================================================= The main place to actually make music with Strudel is the [Strudel REPL](https://strudel.cc/) ([what is a REPL?](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) ), but in these pages you will also encounter interactive “MiniREPLs” where you can listen to and edit Strudel patterns. Try clicking the play icon below: 9 1 s("bd sd") Then edit the text so it reads `s("bd sd cp hh")` and click the refresh icon. Congratulations, you have now live coded your first Strudel pattern! With Strudel, you can expressively write dynamic music pieces. You don’t need to know JavaScript or Tidal Cycles to make music with Strudel. This interactive tutorial will guide you through the basics of Strudel. Show me some demos![](https://strudel.cc/learn/getting-started/#show-me-some-demos) ==================================================================================== To see and hear what Strudel can do, visit the [Strudel REPL](https://strudel.cc/) and click the Shuffle icon in the top menu bar. You can get a feel for Strudel by browsing and editing these examples and clicking the Refresh icon to update. You can also browse through the examples [here](https://strudel.cc/examples/) . Alternatively, you can get a taste of what Strudel can do by clicking play on this track: 99 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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'); stack( s("bd,\[~ \],hh\*8") // 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 .note() // wrap in "note" .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('lefthand') // chords .superimpose(x\=>x.add(.04)) // add second, slightly detuned voice .add(perlin.range(0,.5)) // random pitch variation .note() // wrap in "note" .s('sawtooth') // waveform .gain(.16) // turn down .cutoff(500) // fixed cutoff .attack(1) // slowly fade in ) .slow(3/2) Strudel is a work in progress 🚧[](https://strudel.cc/learn/getting-started/#strudel-is-a-work-in-progress-) ============================================================================================================= Please note that this project is still in its experimental state. In the future, parts of it might change significantly. This tutorial is also far from complete. You can contribute to it clicking ‘Edit this page’ in the top right, or by visiting the [Strudel GitHub page](https://codeberg.org/uzu/strudel/) . What’s next?[](https://strudel.cc/learn/getting-started/#whats-next) ===================================================================== Head on over to the [Notes](https://strudel.cc/learn/notes/) page. --- # Showcase 🌀 Strudel Showcase[](https://strudel.cc/intro/showcase/#showcase) ======================================================== This page contains a randomly shuffled selection of videos that show people using strudel in some way. --- # Getting Started 🌀 Strudel Welcome[](https://strudel.cc/workshop/getting-started/#welcome) ================================================================ ![Strudel Icon](https://strudel.cc/icons/strudel_icon.png) Welcome to the Strudel documentation pages! You’ve come to the right place if you want to learn how to make music with code. What is Strudel?[](https://strudel.cc/workshop/getting-started/#what-is-strudel) --------------------------------------------------------------------------------- With Strudel, you can expressively write dynamic music pieces. It is an official port of the [Tidal Cycles](https://tidalcycles.org/) pattern language to JavaScript. You don’t need to know JavaScript or Tidal Cycles to make music with Strudel. This interactive tutorial will guide you through the basics of Strudel. The best place to actually make music with Strudel is the [Strudel REPL](https://strudel.cc/) What can you do with Strudel?[](https://strudel.cc/workshop/getting-started/#what-can-you-do-with-strudel) ----------------------------------------------------------------------------------------------------------- * live code music: make music with code in real time * algorithmic composition: compose music using tidal’s unique approach to pattern manipulation * teaching: focussing on a low barrier of entry, Strudel is a good fit for teaching music and code at the same time. * integrate into your existing music setup: either via MIDI or OSC, you can use Strudel as a really flexible sequencer Examples[](https://strudel.cc/workshop/getting-started/#examples) ------------------------------------------------------------------ Here are some examples of how strudel can sound: // "coastline" @by eddyflux // @version 1.0 samples('github:eddyflux/crate') setcps(.75) let chords = chord("/4").dict('ireal') stack( stack( // DRUMS s("bd").struct("<\[x\*<1 2> \[~@3 x\]\] x>"), s("~ \[rim, sd:<2 3>\]").room("<0 .2>"), n("\[0 <1 3>\]\*<2!3 4>").s("hh"), s("rd:<1!3 2>\*2").mask("<0 0 1 1>/16").gain(.5) ).bank('crate') .mask("<\[0 1\] 1 1 1>/16".early(.5)) , // CHORDS chords.offset(-1).voicing().s("gm\_epiano1:1") .phaser(4).room(.5) , // MELODY n("<0!3 1\*2>").set(chords).mode("root:g2") .voicing().s("gm\_acoustic\_bass"), chords.n("\[0 <4 3 <2 5>>\*2\](<3 5>,8)") .anchor("D5").voicing() .segment(4).clip(rand.range(.4,.8)) .room(.75).shape(.3).delay(.25) .fm(sine.range(3,8).slow(8)) .lpf(sine.range(500,1000).slow(8)).lpq(5) .rarely(ply("2")).chunk(4, fast(2)) .gain(perlin.range(.6, .9)) .mask("<0 1 1 0>/16") ) .late("\[0 .01\]\*4").late("\[0 .01\]\*2").size(4) These examples cannot fully encompass the variety of things you can do, so [check out the showcase](https://strudel.cc/intro/showcase/) for some videos of how people use Strudel. Getting Started[](https://strudel.cc/workshop/getting-started/#getting-started) -------------------------------------------------------------------------------- The best way to start learning Strudel is the workshop. If you’re ready to dive in, let’s start with your [first sounds](https://strudel.cc/workshop/first-sounds/) --- # Unknown [Amensister](https://strudel.cc/#Ly8gIkFtZW5zaXN0ZXIiCi8vIEBsaWNlbnNlIENDIEJZLU5DLVNBIDQuMCBodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wLwovLyBAYnkgRmVsaXggUm9vcwoKc2FtcGxlcygnZ2l0aHViOnRpZGFsY3ljbGVzL2RpcnQtc2FtcGxlcycpCgpzdGFjaygKICAvLyBhbWVuCiAgbigiMCAxIDIgMyA0IDUgNiA3IikKICAuc29tZXRpbWVzKHg9PngucGx5KDIpKQogIC5yYXJlbHkoeD0+eC5zcGVlZCgiMiB8IC0yIikpCiAgLnNvbWV0aW1lc0J5KC40LCB4PT54LmRlbGF5KCIuNSIpKQogIC5zKCJhbWVuY3V0dXAiKQogIC5zbG93KDIpCiAgLnJvb20oLjUpCiAgLAogIC8vIGJhc3MKICBzaW5lLmFkZChzYXcuc2xvdyg0KSkucmFuZ2UoMCw3KS5zZWdtZW50KDgpCiAgLnN1cGVyaW1wb3NlKHg9PnguYWRkKC4xKSkKICAuc2NhbGUoJ0cwIG1pbm9yJykubm90ZSgpCiAgLnMoInNhd3Rvb3RoIikKICAuZ2FpbiguNCkuZGVjYXkoLjEpLnN1c3RhaW4oMCkKICAubHBhKC4xKS5scGVudigtNCkubHBxKDEwKQogIC5jdXRvZmYocGVybGluLnJhbmdlKDMwMCwzMDAwKS5zbG93KDgpKQogIC5kZWdyYWRlQnkoIjAgMC4xIC41IC4xIikKICAucmFyZWx5KGFkZChub3RlKCIxMiIpKSkKICAsCiAgLy8gY2hvcmQKICBub3RlKCJCYjMsRDQiLnN1cGVyaW1wb3NlKHg9PnguYWRkKC4yKSkpCiAgLnMoJ3Nhd3Rvb3RoJykubHBmKDEwMDApLnN0cnVjdCgiPH5AMyBbfiB4XT4iKQogIC5kZWNheSguMDUpLnN1c3RhaW4oLjApLmRlbGF5KC44KS5kZWxheXRpbWUoLjEyNSkucm9vbSguOCkKICAsCiAgLy8gYWxpZW4KICBzKCJicmVhdGgiKS5yb29tKDEpLnNoYXBlKC42KS5jaG9wKDE2KS5yZXYoKS5tYXNrKCI8eCB+QDc+IikKICAsCiAgbigiMCAxIikucygiZWFzdCIpLmRlbGF5KC41KS5kZWdyYWRlQnkoLjgpLnNwZWVkKHJhbmQucmFuZ2UoLjUsMS41KSkKKS5yZXNldCgiPHhANyB4KDUsOCwtMSk+Iik=) [Arpoon](https://strudel.cc/#Ly8gIkFycG9vbiIKLy8gQGxpY2Vuc2UgQ0MgQlktTkMtU0EgNC4wIGh0dHBzOi8vY3JlYXRpdmVjb21tb25zLm9yZy9saWNlbnNlcy9ieS1uYy1zYS80LjAvCi8vIEBieSBGZWxpeCBSb29zCgpzYW1wbGVzKCdnaXRodWI6dGlkYWxjeWNsZXMvZGlydC1zYW1wbGVzJykKCm4oIlswLDNdIDIgWzEsM10gMiIuZmFzdCgzKS5sYXN0T2YoNCwgZmFzdCgyKSkpLmNsaXAoMikKICAub2Zmc2V0KCI8PDEgMj4gMiAxIDE+IikKICAuY2hvcmQoIjw8QW03IENeNz4gQzcgRl43IFtGbTcgRTdiOV0+IikKICAuZGljdCgnbGVmdGhhbmQnKS52b2ljaW5nKCkKICAuYWRkKHBlcmxpbi5yYW5nZSgwLDAuMikuYWRkKCI8LTEyIDA+LzgiKS5ub3RlKCkpCiAgLmN1dG9mZihwZXJsaW4ucmFuZ2UoNTAwLDQwMDApKS5yZXNvbmFuY2UoMTIpCiAgLmdhaW4oIjwuNSAuOD4qMTYiKQogIC5kZWNheSguMTYpLnN1c3RhaW4oMC41KQogIC5kZWxheSguMikKICAucm9vbSguNSkucGFuKHNpbmUucmFuZ2UoLjMsLjYpKQogIC5zKCdwaWFubycpCiAgLnN0YWNrKAogICAgIjw8QTEgQzI+ITIgRjIgRjI+IgogICAgLmFkZC5vdXQoIjAgLTUiLmZhc3QoMikpCiAgICAuYWRkKCIwLC4xMiIpLm5vdGUoKQogICAgLnMoJ3Nhd3Rvb3RoJykuY3V0b2ZmKDE4MCkKICAgIC5scGEoLjEpLmxwZW52KDIpCiAgKQogIC5zbG93KDQpCiAgLnN0YWNrKHMoImJkKjQsIFt+IFtoaCBoaD8gaGg/XV0qMix+IFtzZCB+IFtzZDoyPyBiZD9dXSIpLmJhbmsoJ1JvbGFuZFRSOTA5JykuZ2FpbiguNSkuc2xvdygyKSkK) [barryHarris](https://strudel.cc/#Ly8gYWRhcHRlZCBmcm9tIGEgQmFycnkgSGFycmlzIGV4Y2VyY2lzZQoiMCwyLFs3IDZdIgogIC5hZGQoIjwwIDEgMiAzIDQgNSA3IDg+IikKICAuc2NhbGUoJ0MgYmVib3AgbWFqb3InKQogIC50cmFuc3Bvc2UoIjwwIDEgMiAxPi84IikKICAuc2xvdygyKQogIC5ub3RlKCkucGlhbm8oKQogIC5jb2xvcignIzAwQjhENCcpCg==) [Bass fuge](https://strudel.cc/#Ly8gIkJhc3MgZnVnZSIKLy8gQGxpY2Vuc2UgQ0MgQlktTkMtU0EgNC4wIGh0dHBzOi8vY3JlYXRpdmVjb21tb25zLm9yZy9saWNlbnNlcy9ieS1uYy1zYS80LjAvCi8vIEBieSBGZWxpeCBSb29zCgpzYW1wbGVzKHsgZmxiYXNzOiBbJzAwX2MyX2Zpbmdlcl9sb25nX25lY2sud2F2JywnMDFfYzJfZmluZ2VyX3Nob3J0X25lY2sud2F2JywnMDJfYzJfZmluZ2VyX2xvbmdfYnJpZGdlLndhdicsJzAzX2MyX2Zpbmdlcl9zaG9ydF9icmlkZ2Uud2F2JywnMDRfYzJfcGlja19sb25nLndhdicsJzA1X2MyX3BpY2tfc2hvcnQud2F2JywnMDZfYzJfcGFsbV9tdXRlLndhdiddIH0sIAogICdnaXRodWI6Y2xlYXJ5L3NhbXBsZXMtZmxiYXNzL21haW4vJykKc2FtcGxlcyh7CmJkOiBbJ2JkL0JUMEFBREEud2F2JywnYmQvQlQwQUFEMC53YXYnLCdiZC9CVDBBMERBLndhdicsJ2JkL0JUMEEwRDMud2F2JywnYmQvQlQwQTBEMC53YXYnLCdiZC9CVDBBMEE3LndhdiddLApzZDogWydzZC9yeXRtLTAxLWNsYXNzaWMud2F2Jywnc2Qvcnl0bS0wMC1oYXJkLndhdiddLApoaDogWydoaDI3LzAwMF9oaDI3Y2xvc2VkaGgud2F2JywnaGgvMDAwX2hoM2Nsb3NlZGhoLndhdiddLAp9LCAnZ2l0aHViOnRpZGFsY3ljbGVzL2RpcnQtc2FtcGxlcycpOwoKc2V0Y3BzKDEpCgoiPDgoMyw4KSA8NyA3KjI+IFs0IDVAM10gOD4iLnN1YigxKSAvLyBzdWIgMSAtPiAxLWluZGV4ZWQKLmxheWVyKAp4PT54LAp4PT54LmFkZCg3KQoub2ZmKDEvOCx4PT54LmFkZCgiMiw0Iikub2ZmKDEvOCx4PT54LmFkZCg1KS5lY2hvKDQsLjEyNSwuNSkpKQouc2xvdygyKSwKKS5uKCkuc2NhbGUoJ0ExIG1pbm9yJykKLnMoImZsYmFzcyIpLm4oMCkKLm11bChnYWluKC4zKSkKLmN1dG9mZihzaW5lLnNsb3coNykucmFuZ2UoMjAwLDQwMDApKQoucmVzb25hbmNlKDEwKQovLy5oY3V0b2ZmKDQwMCkKLmNsaXAoMSkKLnN0YWNrKHMoImJkOjEqMix+IHNkOjAsW34gaGg6MF0qMiIpKQoucGlhbm9yb2xsKHt2ZXJ0aWNhbDoxfSk=) [Belldub](https://strudel.cc/#Ly8gIkJlbGxkdWIiCi8vIEBsaWNlbnNlIENDIEJZLU5DLVNBIDQuMCBodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wLwovLyBAYnkgRmVsaXggUm9vcwoKc2FtcGxlcyh7IGJlbGw6IHtiNDonaHR0cHM6Ly9jZG4uZnJlZXNvdW5kLm9yZy9wcmV2aWV3cy8zMzkvMzM5ODA5XzUxMjEyMzYtbHEubXAzJ319KQovLyAiSGFuZCBCZWxscywgQiwgU2luZ2xlLndhdiIgYnkgSW5zcGVjdG9ySiAod3d3LmpzaGF3LmNvLnVrKSBvZiBGcmVlc291bmQub3JnCnN0YWNrKAogIC8vIGJhc3MKICBub3RlKCJbMCB+XSBbMiBbMCAyXV0gWzQgNCoyXSBbWzQgfl0gWzIgfl0gMEAyXSIuc2NhbGUoJ2cxIGRvcmlhbicpLnN1cGVyaW1wb3NlKHg9PnguYWRkKC4wMikpKQogIC5zKCdzYXd0b290aCcpLmN1dG9mZigyMDApLnJlc29uYW5jZSgyMCkuZ2FpbiguMTUpLnNoYXBlKC42KS5yZWxlYXNlKC4wNSksCiAgLy8gcGVyYwogIHMoIlt+IGhoXSo0Iikucm9vbSgiMCAwLjUiLmZhc3QoMikpLmVuZChwZXJsaW4ucmFuZ2UoMC4wMiwxKSksCiAgcygibXQgbHQgaHQiKS5zdHJ1Y3QoIngoMyw4KSIpLmZhc3QoMikuZ2FpbiguNSkucm9vbSguNSkuc29tZXRpbWVzKHg9Pnguc3BlZWQoIi41IikpLAogIHMoIm1pc2M6MiIpLnNwZWVkKDEpLmRlbGF5KC41KS5kZWxheXRpbWUoMS8zKS5nYWluKC40KSwKICAvLyBjaG9yZHMKICBjaG9yZCgiW34gR203XSB+IFt+IERtN10gfiIpCiAgLmRpY3QoJ2xlZnRoYW5kJykudm9pY2luZygpCiAgLmFkZChub3RlKCIwLC4xIikpCiAgLnMoJ3Nhd3Rvb3RoJykuZ2FpbiguOCkKICAuY3V0b2ZmKHBlcmxpbi5yYW5nZSg0MDAsMzAwMCkuc2xvdyg4KSkKICAuZGVjYXkocGVybGluLnJhbmdlKDAuMDUsLjIpKS5zdXN0YWluKDApCiAgLmRlbGF5KC45KS5yb29tKDEpLAogIC8vIGJsaXBzCiAgbm90ZSgKICAgICIwIDUgNCAyIi5pdGVyKDQpCiAgICAub2ZmKDEvMywgYWRkKDcpKQogICAgLnNjYWxlKCdnNCBkb3JpYW4nKQogICkucygnc3F1YXJlJykuY3V0b2ZmKDIwMDApLmRlY2F5KC4wMykuc3VzdGFpbigwKQogIC5kZWdyYWRlQnkoLjIpCiAgLm9yYml0KDIpLmRlbGF5KC4yKS5kZWxheXRpbWUoIi4zMyB8IC42IHwgLjE2NiB8IC4yNSIpCiAgLnJvb20oMSkuZ2FpbiguNSkubWFzaygiPDAgMT4vOCIpLAogIC8vIGJlbGwKICBub3RlKHJhbmQucmFuZ2UoMCwxMikuc3RydWN0KCJ4KDUsOCwtMSkiKS5zY2FsZSgnZzIgbWlub3IgcGVudGF0b25pYycpKS5zKCdiZWxsJykuYmVnaW4oLjA1KQogIC5kZWxheSguMikuZGVncmFkZUJ5KC40KS5nYWluKC40KQogIC5tYXNrKCI8MSAwPi84IikKKS5zbG93KDUp) [Blippy Rhodes](https://strudel.cc/#Ly8gIkJsaXBweSBSaG9kZXMiCi8vIEBsaWNlbnNlIENDIEJZLU5DLVNBIDQuMCBodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wLwovLyBAYnkgRmVsaXggUm9vcwoKc2FtcGxlcyh7CiAgYmQ6ICdzYW1wbGVzL3RpZGFsL2JkL0JUMEEwRDAud2F2JywKICBzbjogJ3NhbXBsZXMvdGlkYWwvc24vU1QwVDBTMy53YXYnLAogIGhoOiAnc2FtcGxlcy90aWRhbC9oaC8wMDBfaGgzY2xvc2VkaGgud2F2JywKICByaG9kZXM6IHsKICBFMTogJ3NhbXBsZXMvcmhvZGVzL01LMk1kMjAwMC5tcDMnLAogIEUyOiAnc2FtcGxlcy9yaG9kZXMvTUsyTWQyMDEyLm1wMycsCiAgRTM6ICdzYW1wbGVzL3Job2Rlcy9NSzJNZDIwMjQubXAzJywKICBFNDogJ3NhbXBsZXMvcmhvZGVzL01LMk1kMjAzNi5tcDMnLAogIEU1OiAnc2FtcGxlcy9yaG9kZXMvTUsyTWQyMDQ4Lm1wMycsCiAgRTY6ICdzYW1wbGVzL3Job2Rlcy9NSzJNZDIwNjAubXAzJywKICBFNzogJ3NhbXBsZXMvcmhvZGVzL01LMk1kMjA3Mi5tcDMnCiAgfQp9LCAnaHR0cHM6Ly9sb29waG9sZS1sZXR0ZXJzLnZlcmNlbC5hcHAvJykKCnN0YWNrKAogIHMoIjxiZCBzbj4gPGhoIGhoKjIgaGgqMz4iKS5jb2xvcignIzAwQjhENCcpLAogICI8ZzQgYzUgYTQgW2FiNCA8ZWI1IGY1Pl0+IgogIC5zY2FsZSgiPEM6bWFqb3IgQzptaXhvbHlkaWFuIEY6bHlkaWFuIFtGOm1pbm9yIDxEYjptYWpvciBEYjptaXhvbHlkaWFuPl0+IikKICAuc3RydWN0KCJ4KjgiKQogIC5zY2FsZVRyYW5zcG9zZSgiMCBbLTUsLTJdIC03IFstOSwtMl0iKQogIC5zbG93KDIpCiAgLm5vdGUoKQogIC5jbGlwKC4zKQogIC5zKCdyaG9kZXMnKQogIC5yb29tKC41KQogIC5kZWxheSguMykKICAuZGVsYXlmZWVkYmFjayguNCkKICAuZGVsYXl0aW1lKDEvMTIpLmdhaW4oLjUpLmNvbG9yKCcjN0VEMzIxJyksCiAgIjxjMiBjMyBmMiBbW0YyIEMyXSBkYjJdPi8yIgogIC5hZGQoIjAsLjAyIikKICAubm90ZSgpLmdhaW4oLjMpCiAgLmNsaXAoIjwxQDMgWy4zIDFdPi8yIikKICAuY3V0b2ZmKDYwMCkKICAubHBhKC4yKS5scGVudigtNCkKICAucygnc2F3dG9vdGgnKS5jb2xvcignI0Y4RTcxQycpLAopLmZhc3QoMy8yKQovLy5waWFub3JvbGwoe2ZvbGQ6MX0p) [Caverave](https://strudel.cc/#Ly8gIkNhdmVyYXZlIgovLyBAbGljZW5zZSBDQyBCWS1OQy1TQSA0LjAgaHR0cHM6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC8KLy8gQGJ5IEZlbGl4IFJvb3MKCmNvbnN0IGtleXMgPSB4ID0+IHgucygnc2F3dG9vdGgnKS5jdXRvZmYoMTIwMCkuZ2FpbiguNSkKICAuYXR0YWNrKDApLmRlY2F5KC4xNikuc3VzdGFpbiguMykucmVsZWFzZSguMSk7Cgpjb25zdCBkcnVtcyA9IHN0YWNrKAogIHMoImJkKjIiKS5tYXNrKCI8eEA3IH4+LzgiKS5nYWluKC44KSwKICBzKCJ+IDxzZCE3IFtzZEAzIH5dPiIpLm1hc2soIjx4QDcgfj4vNCIpLmdhaW4oLjUpLAogIHMoIlt+IGhoXSoyIikuZGVsYXkoLjMpLmRlbGF5ZmVlZGJhY2soLjUpLmRlbGF5dGltZSguMTI1KS5nYWluKC40KQopOwoKY29uc3Qgc3ludGhzID0gc3RhY2soCiAgCiAgIjxlYjQgZDQgYzQgYjM+LzIiCiAgLnNjYWxlKCI8QzptaW5vciEzIEM6bWVsb2RpYzptaW5vcj4vMiIpCiAgLnN0cnVjdCgiW34geF0qMiIpCiAgLmxheWVyKAogICAgeD0+eC5zY2FsZVRyYW5zcG9zZSgwKS5lYXJseSgwKSwKICAgIHg9Pnguc2NhbGVUcmFuc3Bvc2UoMikuZWFybHkoMS84KSwKICAgIHg9Pnguc2NhbGVUcmFuc3Bvc2UoNykuZWFybHkoMS80KSwKICAgIHg9Pnguc2NhbGVUcmFuc3Bvc2UoOCkuZWFybHkoMy84KQogICkubm90ZSgpLmFwcGx5KGtleXMpLm1hc2soIjx+IHg+LzE2IikKICAuY29sb3IoJ2RhcmtzZWFncmVlbicpLAogIAogIG5vdGUoIjxDMiBCYjEgQWIxIFtHMSBbRzIgRzFdXT4vMiIpCiAgLnN0cnVjdCgiW3ggW34geF0gPFt+IFt+IHhdXSEzIFt4IHhdPkAyXS8yIi5mYXN0KDIpKQogIC5zKCdzYXd0b290aCcpLmF0dGFjaygwLjAwMSkuZGVjYXkoMC4yKS5zdXN0YWluKDEpLmN1dG9mZig1MDApCiAgLmNvbG9yKCdicm93bicpLAogIGNob3JkKCI8Q203IEJiNyBGbTcgRzdiMTM+LzIiKQogIC5zdHJ1Y3QoIn4gW3hAMC4yIH5dIi5mYXN0KDIpKQogIC5kaWN0KCdsZWZ0aGFuZCcpLnZvaWNpbmcoKQogIC5ldmVyeSgyLCBlYXJseSgxLzgpKQogIC5hcHBseShrZXlzKS5zdXN0YWluKDApCiAgLmRlbGF5KC40KS5kZWxheXRpbWUoLjEyKQogIC5tYXNrKCI8eEA3IH4+LzgiLmVhcmx5KDEvNCkpCikuYWRkKG5vdGUoIjwtMSAwPi84IikpCnN0YWNrKAogIGRydW1zLmZhc3QoMikuY29sb3IoJ3RvbWF0bycpLCAKICBzeW50aHMKKS5zbG93KDIpCiAgLy8ucGlhbm9yb2xsKHtmb2xkOjF9KQ==) [Chop](https://strudel.cc/#Ly8gIkNob3AiCi8vIEBsaWNlbnNlIENDIEJZLU5DLVNBIDQuMCBodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wLwovLyBAYnkgRmVsaXggUm9vcwoKc2FtcGxlcyh7IHA6ICdodHRwczovL2Nkbi5mcmVlc291bmQub3JnL3ByZXZpZXdzLzY0OC82NDg0MzNfMTE5NDMxMjktbHEubXAzJyB9KQoKcygicCIpCiAgLmxvb3BBdCgzMikKICAuY2hvcCgxMjgpCiAgLmp1eChyZXYpCiAgLnNoYXBlKC40KQogIC5kZWNheSguMSkKICAuc3VzdGFpbiguNikKICA=) [CSound demo](https://strudel.cc/#Ly8gIkNTb3VuZCBkZW1vIgovLyBAbGljZW5zZSB3aXRoIENDIEJZLU5DLVNBIDQuMCBodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wLwovLyBAYnkgRmVsaXggUm9vcwoKYXdhaXQgbG9hZENzb3VuZGAKaW5zdHIgQ29vbFN5bnRoCiAgICBpZHVyYXRpb24gPSBwMwogICAgaWZyZXEgPSBwNAogICAgaWdhaW4gPSBwNQogICAgaW9jdCA9IG9jdGNwcyhpZnJlcSkKCiAgICBrcHdtID0gb3NjaWxpKC4wNSwgOCkKICAgIGFzaWcgPSB2Y28yKGlnYWluLCBpZnJlcSwgNCwgLjUgKyBrcHdtKQogICAgYXNpZyArPSB2Y28yKGlnYWluLCBpZnJlcSAqIDIpCgogICAgaWRlcHRoID0gMgogICAgYWN1dCA9IHRyYW5zZWdyOmEoMCwgLjAwNSwgMCwgaWRlcHRoLCAuMDYsIC00LjIsIDAuMDAxLCAuMDEsIC00LjIsIDApIDsgZmlsdGVyIGVudmVsb3BlCiAgICBhc2lnID0gemRmXzJwb2xlKGFzaWcsIGNwc29jdChpb2N0ICsgYWN1dCArIDIpLCAwLjUpCgogICAgaWF0dGFjayA9IC4wMQogICAgaXN1c3RhaW4gPSAuNQogICAgaWRlY2F5ID0gLjEKICAgIGlyZWxlYXNlID0gLjEKICAgIGFzaWcgKj0gbGluc2VncjphKDAsIGlhdHRhY2ssIDEsIGlkZWNheSwgaXN1c3RhaW4sIGlkdXJhdGlvbiwgaXN1c3RhaW4sIGlyZWxlYXNlLCAwKQogICAgCiAgICBvdXQoYXNpZywgYXNpZykKZW5kaW5gCgoiPDAgMiBbNCA2XSgzLDQsMikgMyoyPiIKLm9mZigxLzQsIGFkZCgyKSkKLm9mZigxLzIsIGFkZCg2KSkKLnNjYWxlKCdEIG1pbm9yJykKLm5vdGUoKQovLy5waWFub3JvbGwoKQouY3NvdW5kKCdDb29sU3ludGgnKQ==) [Delay](https://strudel.cc/#Ly8gIkRlbGF5IgovLyBAbGljZW5zZSBDQyBCWS1OQy1TQSA0LjAgaHR0cHM6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC8KLy8gQGJ5IEZlbGl4IFJvb3MKCnN0YWNrKAogICAgcygiYmQgPHNkIGNwPiIpCiAgICAuZGVsYXkoIjwwIC41PiIpCiAgICAuZGVsYXl0aW1lKCIuMTYgfCAuMzMiKQogICAgLmRlbGF5ZmVlZGJhY2soIi42IHwgLjgiKQogICkuc29tZXRpbWVzKHg9Pnguc3BlZWQoIi0xIikp) [Dinofunk](https://strudel.cc/#Ly8gIkRpbm9mdW5rIgovLyBAbGljZW5zZSBDQyBCWS1OQy1TQSA0LjAgaHR0cHM6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC8KLy8gQGJ5IEZlbGl4IFJvb3MKCnNldGNwcygxKQoKc2FtcGxlcyh7YmFzczonaHR0cHM6Ly9jZG4uZnJlZXNvdW5kLm9yZy9wcmV2aWV3cy82MTQvNjE0NjM3XzI0MzQ5MjctaHEubXAzJywKZGlubzp7YjQ6J2h0dHBzOi8vY2RuLmZyZWVzb3VuZC5vcmcvcHJldmlld3MvMzE2LzMxNjQwM181MTIzODUxLWhxLm1wMyd9fSkKc2V0Vm9pY2luZ1JhbmdlKCdsZWZ0aGFuZCcsIFsnYzMnLCdhNCddKQoKc3RhY2soCnMoJ2Jhc3MnKS5sb29wQXQoOCkuY2xpcCgxKSwKcygiYmQqMiwgfiBzZCxoaCo0IiksCmNob3JkKCJBYm03IikKICAubW9kZSgiYmVsb3c6RzQiKQogIC5kaWN0KCdsZWZ0aGFuZCcpCiAgLnZvaWNpbmcoKQogIC5zdHJ1Y3QoIngoMyw4LDEpIi5zbG93KDIpKSwKIjAgMSAyIDMiLnNjYWxlKCdhYjQgbWlub3IgcGVudGF0b25pYycpCi5zdXBlcmltcG9zZSh4PT54LmFkZCguMSkpCi5zb21ldGltZXMoeD0+eC5hZGQoMTIpKQoubm90ZSgpLnMoJ3Nhd3Rvb3RoJykKLmN1dG9mZihzaW5lLnJhbmdlKDQwMCwyMDAwKS5zbG93KDE2KSkuZ2FpbiguOCkKLmRlY2F5KHBlcmxpbi5yYW5nZSguMDUsLjIpKS5zdXN0YWluKDApCi5kZWxheShzaW5lLnJhbmdlKDAsLjUpLnNsb3coMzIpKQouZGVncmFkZUJ5KC40KS5yb29tKDEpLApub3RlKCI8YjQgZWI0PiIpLnMoJ2Rpbm8nKS5kZWxheSguOCkuc2xvdyg4KS5yb29tKC41KQop) [Echo piano](https://strudel.cc/#Ly8gIkVjaG8gcGlhbm8iCi8vIEBsaWNlbnNlIENDIEJZLU5DLVNBIDQuMCBodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wLwovLyBAYnkgRmVsaXggUm9vcwoKbigiPDAgMiBbNCA2XSgzLDQsMikgMyoyPiIpLmNvbG9yKCdzYWxtb24nKQoub2ZmKDEvNCwgeD0+eC5hZGQobigyKSkuY29sb3IoJ2dyZWVuJykpCi5vZmYoMS8yLCB4PT54LmFkZChuKDYpKS5jb2xvcignc3RlZWxibHVlJykpCi5zY2FsZSgnRCBtaW5vcicpCi5lY2hvKDQsIDEvOCwgLjUpCi5jbGlwKC41KQoucGlhbm8oKQoucGlhbm9yb2xsKCk=) [Festival of fingers](https://strudel.cc/#Ly8gIkZlc3RpdmFsIG9mIGZpbmdlcnMiCi8vIEBsaWNlbnNlIENDIEJZLU5DLVNBIDQuMCBodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wLwovLyBAYnkgRmVsaXggUm9vcwoKY29uc3QgY2hvcmRzID0gIjxDbTcgRm03IEc3IEYjNz4iOwpzdGFjaygKICBjaG9yZChjaG9yZHMpLmRpY3QoJ2xlZnRoYW5kJykudm9pY2luZygpCiAgLnN0cnVjdCgieCgzLDgsLTEpIikKICAuZ2FpbiguNSkub2ZmKDEvNyx4PT54LmFkZChub3RlKDEyKSkubXVsKGdhaW4oLjIpKSksCiAgY2hvcmRzLnJvb3ROb3RlcygyKS5zdHJ1Y3QoIngoNCw4LC0yKSIpLm5vdGUoKSwKICBjaG9yZHMucm9vdE5vdGVzKDQpCiAgLnNjYWxlKGNhdCgnQyBtaW5vcicsJ0YgZG9yaWFuJywnRyBkb3JpYW4nLCdGIyBtaXhvbHlkaWFuJykpCiAgLnN0cnVjdCgieCgzLDgsLTIpIi5mYXN0KDIpKQogIC5zY2FsZVRyYW5zcG9zZSgiMCA0IDAgNiIuZWFybHkoIi4xMjUgLjUiKSkKICAubGF5ZXIoc2NhbGVUcmFuc3Bvc2UoIjAsPDIgWzQsNl0gWzUsN10+LzQiKSkKICAubm90ZSgpCikuc2xvdygyKQogLm11bChnYWluKHNpbmUuc3RydWN0KCJ4KjgiKS5hZGQoMy81KS5tdWwoMi81KS5mYXN0KDgpKSkKIC5waWFubygp) [Festival of fingers 3](https://strudel.cc/#Ly8gIkZlc3RpdmFsIG9mIGZpbmdlcnMgMyIKLy8gQGxpY2Vuc2UgQ0MgQlktTkMtU0EgNC4wIGh0dHBzOi8vY3JlYXRpdmVjb21tb25zLm9yZy9saWNlbnNlcy9ieS1uYy1zYS80LjAvCi8vIEBieSBGZWxpeCBSb29zCgpzZXRjcHMoMSkKCm4oIlstNyozXSwwLDIsNixbOCA3XSIpCiAgLmVjaG9XaXRoKAogICAgNCwgLy8gZWNobyA0IHRpbWVzCiAgICAxLzQsIC8vIDEvNHMgYmV0d2VlbiBlY2hvcwogICAgKHgsaSk9PngKICAgICAgLmFkZChuKGkqNykpIC8vIGFkZCBvY3RhdmVzCiAgICAgIC5nYWluKDEvKGkrMSkpIC8vIHJlZHVjZSBnYWluCiAgICAgIC5jbGlwKDEvKGkrMSkpCiAgICApCiAgLm11bChnYWluKHBlcmxpbi5yYW5nZSguNSwuOSkuc2xvdyg4KSkpCiAgLnN0YWNrKG4oIlsyMiAyNV0qMyIpCiAgICAgICAgIC5jbGlwKHNpbmUucmFuZ2UoLjUsMikuc2xvdyg4KSkKICAgICAgICAgLmdhaW4oc2luZS5yYW5nZSguNCwuOCkuc2xvdyg1KSkKICAgICAgICAgLmVjaG8oNCwxLzEyLC41KSkKICAuc2NhbGUoIjxEOmRvcmlhbiBHOm1peG9seWRpYW4gQzpkb3JpYW4gRjptaXhvbHlkaWFuPiIpCiAgLnNsb3coMikucGlhbm8oKQovLy5waWFub3JvbGwoe21heE1pZGk6MTYwfSk=) [Flatrave](https://strudel.cc/#Ly8gIkZsYXRyYXZlIgovLyBAbGljZW5zZSBDQyBCWS1OQy1TQSA0LjAgaHR0cHM6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC8KLy8gQGJ5IEZlbGl4IFJvb3MKCnN0YWNrKAogIHMoImJkKjIsfiBbY3Asc2RdIikuYmFuaygnUm9sYW5kVFI5MDknKSwKICAKICBzKCJoaDoxKjQiKS5zb21ldGltZXMoZmFzdCgiMiIpKQogIC5yYXJlbHkoeD0+eC5zcGVlZCgiLjUiKS5kZWxheSguNSkpCiAgLmVuZChwZXJsaW4ucmFuZ2UoMC4wMiwuMDUpLnNsb3coOCkpCiAgLmJhbmsoJ1JvbGFuZFRSOTA5Jykucm9vbSguNSkKICAuZ2FpbigiMC40LDAuNCg1LDgsLTEpIiksCiAgCiAgbm90ZSgiPDAgMiA1IDM+Ii5zY2FsZSgnRzEgbWlub3InKSkuc3RydWN0KCJ4KDUsOCwtMSkiKQogIC5zKCdzYXd0b290aCcpLmRlY2F5KC4xKS5zdXN0YWluKDApCiAgLmxwYSguMSkubHBlbnYoLTQpLmxwZig4MDApLmxwcSg4KSwKICAKICBub3RlKCI8RzQgQTQgQmI0IEE0PixCYjMsRDMiKS5zdHJ1Y3QoIn4geCoyIikucygnc3F1YXJlJykuY2xpcCgxKQogIC5jdXRvZmYoc2luZS5yYW5nZSg1MDAsNDAwMCkuc2xvdygxNikpLnJlc29uYW5jZSgxMCkKICAuZGVjYXkoc2luZS5zbG93KDE1KS5yYW5nZSguMDUsLjIpKS5zdXN0YWluKDApCiAgLnJvb20oLjUpLmdhaW4oLjMpLmRlbGF5KC4yKS5tYXNrKCI8MCAxQDM+LzgiKSwKICAKICAiMCA1IDMgMiIuc29tZXRpbWVzKHNsb3coMikpLm9mZigxLzgsYWRkKDUpKS5zY2FsZSgnRzQgbWlub3InKS5ub3RlKCkKICAuZGVjYXkoLjA1KS5zdXN0YWluKDApLmRlbGF5KC4yKS5kZWdyYWRlQnkoLjUpLm1hc2soIjwwIDE+LzE2IikKKQ==) [giantSteps](https://strudel.cc/#Ly8gSm9obiBDb2x0cmFuZSAtIEdpYW50IFN0ZXBzCgpsZXQgbWVsb2R5ID0gc2VxKAogICJbRiM1IEQ1XSBbQjQgRzRdIEJiNCBbQjQgQTRdIiwKICAiW0Q1IEJiNF0gW0c0IEViNF0gRiM0IFtHNCBGNF0iLAogICJCYjQgW0I0IEE0XSBENSBbRCM1IEMjNV0iLAogICJGIzUgW0c1IEY1XSBCYjUgW0YjNSBGIzVdIiwKKS5ub3RlKCkKCnN0YWNrKAogIC8vIG1lbG9keQogIG1lbG9keS5jb2xvcignI0Y4RTcxQycpLAogIC8vIGNob3JkcwogIHNlcSgKICAgICJbQl43IEQ3XSBbR143IEJiN10gRWJeNyBbQW03IEQ3XSIsCiAgICAiW0deNyBCYjddIFtFYl43IEYjN10gQl43IFtGbTcgQmI3XSIsCiAgICAiRWJeNyBbQW03IEQ3XSBHXjcgW0MjbTcgRiM3XSIsCiAgICAiQl43IFtGbTcgQmI3XSBFYl43IFtDI203IEYjN10iCiAgKS5jaG9yZCgpLmRpY3QoJ2xlZnRoYW5kJykKICAuYW5jaG9yKG1lbG9keSkubW9kZSgnZHVjaycpCiAgLnZvaWNpbmcoKS5jb2xvcignIzdFRDMyMScpLAogIC8vIGJhc3MKICBzZXEoCiAgICAiW0IyIEQyXSBbRzIgQmIyXSBbRWIyIEJiM10gW0EyIEQyXSIsCiAgICAiW0cyIEJiMl0gW0ViMiBGIzJdIFtCMiBGIzJdIFtGMiBCYjJdIiwKICAgICJbRWIyIEJiMl0gW0EyIEQyXSBbRzIgRDJdIFtDIzIgRiMyXSIsCiAgICAiW0IyIEYjMl0gW0YyIEJiMl0gW0ViMiBCYjNdIFtDIzIgRiMyXSIKICApLm5vdGUoKS5jb2xvcignIzAwQjhENCcpCikuc2xvdygyMCkKLnBpYW5vcm9sbCh7Zm9sZDoxfSk=) [Good times](https://strudel.cc/#Ly8gIkdvb2QgdGltZXMiCi8vIEBsaWNlbnNlIENDIEJZLU5DLVNBIDQuMCBodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wLwovLyBAYnkgRmVsaXggUm9vcwoKY29uc3Qgc2NhbGUgPSBjYXQoJ0MzIGRvcmlhbicsJ0JiMiBtYWpvcicpLnNsb3coNCk7CnN0YWNrKAogIG4oIjIqNCIuYWRkKDEyKSkub2ZmKDEvOCwgYWRkKDIpKQogIC5zY2FsZShzY2FsZSkKICAuZmFzdCgyKQogIC5hZGQoIjwwIDEgMiAxPiIpLmh1c2goKSwKICAiPDAgMSAyIDM+KDMsOCwyKSIub2ZmKDEvNCwgYWRkKCIyLDQiKSkKICAubigpLnNjYWxlKHNjYWxlKSwKICBuKCI8MCA0Pig1LDgsLTEpIikuc2NhbGUoc2NhbGUpLnN1Yihub3RlKDEyKSkKKQogIC5nYWluKCIuNiAuNyIuZmFzdCg0KSkKICAuYWRkKG5vdGUoNCkpCiAgLnBpYW5vKCkKICAuY2xpcCgyKQogIC5tdWwoZ2FpbiguOCkpCiAgLnNsb3coMikKICAucGlhbm9yb2xsKCk=) [Holy flute](https://strudel.cc/#Ly8gIkhvbHkgZmx1dGUiCi8vIEBsaWNlbnNlIENDIEJZLU5DLVNBIDQuMCBodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wLwovLyBAYnkgRmVsaXggUm9vcwoKImMzIGViMygzLDgpIGM0LzIgZzMqMiIKLnN1cGVyaW1wb3NlKAogIHg9Pnguc2xvdygyKS5hZGQoMTIpLAogIHg9Pnguc2xvdyg0KS5zdWIoNSkKKS5hZGQoIjwwIDE+LzE2IikKLm5vdGUoKS5zKCdvY2FyaW5hX3ZpYicpLmNsaXAoMSkKLnJlbGVhc2UoLjEpLnJvb20oMSkuZ2FpbiguMikKLmNvbG9yKCJzYWxtb24gfCBvcmFuZ2UgfCBkYXJrc2VhZ3JlZW4iKQoucGlhbm9yb2xsKHtmb2xkOjAsYXV0b3JhbmdlOjAsdmVydGljYWw6MCxjeWNsZXM6MTIsc21lYXI6MCxtaW5NaWRpOjQwfSkK) [Jux und tollerei](https://strudel.cc/#Ly8gIkp1eCB1bmQgdG9sbGVyZWkiCi8vIEBsaWNlbnNlIENDIEJZLU5DLVNBIDQuMCBodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wLwovLyBAYnkgRmVsaXggUm9vcwoKbm90ZSgiYzMgZWIzIGczIGJiMyIpLnBhbGluZHJvbWUoKQoucygnc2F3dG9vdGgnKQouanV4KHg9PngucmV2KCkuY29sb3IoJ2dyZWVuJykucygnc2F3dG9vdGgnKSkKLm9mZigxLzQsIHg9PnguYWRkKG5vdGUoIjw3IDEyPi8yIikpLnNsb3coMikubGF0ZSguMDA1KS5zKCd0cmlhbmdsZScpKQoubHBmKHNpbmUucmFuZ2UoMjAwLDIwMDApLnNsb3coOCkpCi5scGEoLjIpLmxwZW52KC0yKQouZGVjYXkoLjA1KS5zdXN0YWluKDApCi5yb29tKC42KQouZGVsYXkoLjUpLmRlbGF5dGltZSguMSkuZGVsYXlmZWVkYmFjayguNCkKLnBpYW5vcm9sbCgp) [Lounge sponge](https://strudel.cc/#Ly8gIkxvdW5nZSBzcG9uZ2UiCi8vIEBsaWNlbnNlIENDIEJZLU5DLVNBIDQuMCBodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wLwovLyBAYnkgRmVsaXggUm9vcywgbGl2ZWNvZGUub3JjIGJ5IFN0ZXZlbiBZaQoKYXdhaXQgbG9hZE9yYygnZ2l0aHViOmt1bnN0bXVzaWsvY3NvdW5kLWxpdmUtY29kZS9tYXN0ZXIvbGl2ZWNvZGUub3JjJykKCnN0YWNrKAogIGNob3JkKCI8Q143IEE3IERtNyBGbTc+LzIiKS5kaWN0KCdsZWZ0aGFuZCcpLnZvaWNpbmcoKQogIC5jdXRvZmYoc2luZS5yYW5nZSg1MDAsMjAwMCkucm91bmQoKS5zbG93KDE2KSkKICAuZXVjbGlkTGVnYXRvKDMsOCkuY3NvdW5kKCdGTTEnKQogICwKICBub3RlKCI8QzIgQTEgRDIgRjI+LzIiKS5wbHkoOCkuY3NvdW5kKCdCYXNzJykuZ2FpbigiMSA0IDEgNCIpCiAgLAogIG4oIjAgNyBbNCAzXSAyIi5mYXN0KDIvMykub2ZmKCIuMjUgLjEyNSIsIGFkZCgiPDIgNCAtMyAtMT4iKSkKICAuc2xvdygyKS5zY2FsZSgnQTQgbWlub3InKSkKICAuY2xpcCguMjUpLmNzb3VuZCgnU3luSGFycCcpCiAgLAogIHMoImJkKjIsW34gaGhdKjIsfiBjcCIpLmJhbmsoJ1JvbGFuZFRSOTA5JykKKQ==) [Melting submarine](https://strudel.cc/#Ly8gIk1lbHRpbmcgc3VibWFyaW5lIgovLyBAbGljZW5zZSBDQyBCWS1OQy1TQSA0LjAgaHR0cHM6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC8KLy8gQGJ5IEZlbGl4IFJvb3MKCnNhbXBsZXMoJ2dpdGh1Yjp0aWRhbGN5Y2xlcy9kaXJ0LXNhbXBsZXMnKQpzdGFjaygKICBzKCJiZDo1LFt+IDxzZDoxITMgc2Q6MSgzLDQsMyk+XSxoaDI3KDMsNCwxKSIpIC8vIGRydW1zCiAgLnNwZWVkKHBlcmxpbi5yYW5nZSguNywuOSkpIC8vIHJhbmRvbSBzYW1wbGUgc3BlZWQgdmFyaWF0aW9uCiAgLy8uaHVzaCgpCiAgLCI8YTEgYjEqMiBhMSgzLDgpIGUyPiIgLy8gYmFzc2xpbmUKICAub2ZmKDEvOCx4PT54LmFkZCgxMikuZGVncmFkZUJ5KC41KSkgLy8gcmFuZG9tIG9jdGF2ZSBqdW1wcwogIC5hZGQocGVybGluLnJhbmdlKDAsLjUpKSAvLyByYW5kb20gcGl0Y2ggdmFyaWF0aW9uCiAgLnN1cGVyaW1wb3NlKGFkZCguMDUpKSAvLyBhZGQgc2Vjb25kLCBzbGlnaHRseSBkZXR1bmVkIHZvaWNlCiAgLm5vdGUoKSAvLyB3cmFwIGluICJub3RlIgogIC5kZWNheSguMTUpLnN1c3RhaW4oMCkgLy8gbWFrZSBlYWNoIG5vdGUgb2YgZXF1YWwgbGVuZ3RoCiAgLnMoJ3Nhd3Rvb3RoJykgLy8gd2F2ZWZvcm0KICAuZ2FpbiguNCkgLy8gdHVybiBkb3duCiAgLmN1dG9mZihzaW5lLnNsb3coNykucmFuZ2UoMzAwLDUwMDApKSAvLyBhdXRvbWF0ZSBjdXRvZmYKICAubHBhKC4xKS5scGVudigtMikKICAvLy5odXNoKCkKICAsY2hvcmQoIjxBbTchMyA8RW03IEU3YjEzIEVtNyBFYm03YjU+PiIpCiAgLmRpY3QoJ2xlZnRoYW5kJykudm9pY2luZygpIC8vIGNob3JkcwogIC5hZGQobm90ZSgiMCwuMDQiKSkgLy8gYWRkIHNlY29uZCwgc2xpZ2h0bHkgZGV0dW5lZCB2b2ljZQogIC5hZGQobm90ZShwZXJsaW4ucmFuZ2UoMCwuNSkpKSAvLyByYW5kb20gcGl0Y2ggdmFyaWF0aW9uCiAgLnMoJ3Nhd3Rvb3RoJykgLy8gd2F2ZWZvcm0KICAuZ2FpbiguMTYpIC8vIHR1cm4gZG93bgogIC5jdXRvZmYoNTAwKSAvLyBmaXhlZCBjdXRvZmYKICAuYXR0YWNrKDEpIC8vIHNsb3dseSBmYWRlIGluCiAgLy8uaHVzaCgpCiAgLCJhNCBjNSA8ZTYgYTY+Ii5zdHJ1Y3QoIngoNSw4LC0xKSIpCiAgLnN1cGVyaW1wb3NlKHg9PnguYWRkKC4wNCkpIC8vIGFkZCBzZWNvbmQsIHNsaWdodGx5IGRldHVuZWQgdm9pY2UKICAuYWRkKHBlcmxpbi5yYW5nZSgwLC41KSkgLy8gcmFuZG9tIHBpdGNoIHZhcmlhdGlvbgogIC5ub3RlKCkgLy8gd3JhcCBpbiAibm90ZSIKICAuZGVjYXkoLjEpLnN1c3RhaW4oMCkgLy8gbWFrZSBub3RlcyBzaG9ydAogIC5zKCd0cmlhbmdsZScpIC8vIHdhdmVmb3JtCiAgLmRlZ3JhZGVCeShwZXJsaW4ucmFuZ2UoMCwuNSkpIC8vIHJhbmRvbWx5IGNvbnRyb2xsZWQgcmFuZG9tIHJlbW92YWwgOikKICAuZWNob1dpdGgoNCwuMTI1LCh4LG4pPT54LmdhaW4oLjE1KjEvKG4rMSkpKSAvLyBlY2hvIG5vdGVzCiAgLy8uaHVzaCgpCikKICAuc2xvdygzLzIp) [Orbit](https://strudel.cc/#Ly8gIk9yYml0IgovLyBAbGljZW5zZSBDQyBCWS1OQy1TQSA0LjAgaHR0cHM6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC8KLy8gQGJ5IEZlbGl4IFJvb3MKCnN0YWNrKAogICAgcygiYmQgPHNkIGNwPiIpCiAgICAuZGVsYXkoLjUpCiAgICAuZGVsYXl0aW1lKC4zMykKICAgIC5kZWxheWZlZWRiYWNrKC42KSwKICAgIHMoImhoKjIiKQogICAgLmRlbGF5KC44KQogICAgLmRlbGF5dGltZSguMDgpCiAgICAuZGVsYXlmZWVkYmFjayguNykKICAgIC5vcmJpdCgyKQogICkuc29tZXRpbWVzKHg9Pnguc3BlZWQoIi0xIikp) [Random bells](https://strudel.cc/#Ly8gIlJhbmRvbSBiZWxscyIKLy8gQGxpY2Vuc2UgQ0MgQlktTkMtU0EgNC4wIGh0dHBzOi8vY3JlYXRpdmVjb21tb25zLm9yZy9saWNlbnNlcy9ieS1uYy1zYS80LjAvCi8vIEBieSBGZWxpeCBSb29zCgpzYW1wbGVzKHsKICBiZWxsOiB7IGM2OiAnaHR0cHM6Ly9jZG4uZnJlZXNvdW5kLm9yZy9wcmV2aWV3cy80MTEvNDExMDg5XzUxMjEyMzYtbHEubXAzJyB9LAogIGJhc3M6IHsgZDI6ICdodHRwczovL2Nkbi5mcmVlc291bmQub3JnL3ByZXZpZXdzLzYwOC82MDgyODZfMTMwNzQwMjItbHEubXAzJyB9Cn0pCgpzdGFjaygKICAvLyBiZWxscwogIG4oIjAiKS5ldWNsaWRMZWdhdG8oMyw4KQogIC5lY2hvKDMsIDEvMTYsIC41KQogIC5hZGQobihyYW5kLnJhbmdlKDAsMTIpKSkKICAuc2NhbGUoIkQ6bWlub3I6cGVudGF0b25pYyIpCiAgLnZlbG9jaXR5KHJhbmQucmFuZ2UoLjUsMSkpCiAgLnMoJ2JlbGwnKS5nYWluKC42KS5kZWxheSguMikuZGVsYXl0aW1lKDEvMykuZGVsYXlmZWVkYmFjayguOCksCiAgLy8gYmFzcwogIG5vdGUoIjxEMiBBMiBHMiBGMj4iKS5ldWNsaWRMZWdhdG9Sb3QoNiw4LDQpLnMoJ2Jhc3MnKS5jbGlwKDEpLmdhaW4oLjgpCikKICAuc2xvdyg2KQogIC5waWFub3JvbGwoe3ZlcnRpY2FsOjF9KQ==) [Sample demo](https://strudel.cc/#Ly8gIlNhbXBsZSBkZW1vIgovLyBAbGljZW5zZSBDQyBCWS1OQy1TQSA0LjAgaHR0cHM6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC8KLy8gQGJ5IEZlbGl4IFJvb3MKCnN0YWNrKAogIC8vIHBlcmN1c3Npb24KICBzKCJbd29vZGJsb2NrOjEgd29vZGJsb2NrOjIqMl0gc25hcmVfcmltOjAsZ29uZy84LGJyYWtlZHJ1bToxKDMsOCksfkAzIGNvd2JlbGw6MyIpCiAgLnNvbWV0aW1lcyh4PT54LnNwZWVkKDIpKSwKICAvLyBtZWxvZHkKICBub3RlKCI8MCA0IDEgMyAyPiIub2ZmKCIuMjUgfCAuMTI1IixhZGQoMikpLnNjYWxlKCdEMyBoaXJham9zaGknKSkKICAucygiY2xhdmlzeW50aCIpLmdhaW4oLjIpLmRlbGF5KC4yNSkuanV4KHJldikKICAuZGVncmFkZUJ5KHNpbmUucmFuZ2UoMCwuNSkuc2xvdygzMikpLAogIC8vIGJhc3MKICBub3RlKCI8MEAzIDwyKDMsOCkgMygzLDgpPj4iLnNjYWxlKCdEMSBoaXJham9zaGknKSkKICAucygncHNhbHRlcnlfcGx1Y2snKS5nYWluKC42KS5jbGlwKDEpCiAgLnJlbGVhc2UoLjEpLnJvb20oLjUpCik=) [sampleDrums](https://strudel.cc/#c2FtcGxlcyh7CiAgYmQ6ICdiZC9CVDBBMEQwLndhdicsCiAgc246ICdzbi9TVDBUMFMzLndhdicsCiAgaGg6ICdoaC8wMDBfaGgzY2xvc2VkaGgud2F2Jwp9LCAnaHR0cHM6Ly9sb29waG9sZS1sZXR0ZXJzLnZlcmNlbC5hcHAvc2FtcGxlcy90aWRhbC8nKQoKc3RhY2soCiAgIjxiZCEzIGJkKDMsNCwzKT4iLmNvbG9yKCcjRjVBNjIzJyksCiAgImhoKjQiLmNvbG9yKCcjNjczQUI3JyksCiAgIn4gPHNuITMgc24oMyw0LDIpPiIuY29sb3IoJyM0Q0FGNTAnKQopLnMoKQoucGlhbm9yb2xsKHtmb2xkOjF9KQo=) [sml1](https://strudel.cc/#Ly8gSGlyb2thenUgVGFuYWthIC0gV29ybGQgMS0xCnN0YWNrKAogIC8vIG1lbG9keQogIG5vdGUoYDwKICBbZTUgfl0gW1tkNUAyIGM1XSBbfkAyIGU1XV0gfiBbfiBbYzVAMiBkNV1dIFtlNSBlNV0gW2Q1IGM1XSBbZTUgZjVdIFtnNSBhNV0KICBbfiBjNV0gW2M1IGQ1XSBbZTUgW2M1QDIgYzVdXSBbfiBjNV0gW2Y1IGU1XSBbYzUgZDVdIFt+IGc2XSBbZzYgfl0KICBbZTUgfl0gW1tkNUAyIGM1XSBbfkAyIGU1XV0gfiBbfiBbYzVAMiBkNV1dIFtlNSBlNV0gW2Q1IGM1XSBbYTUgZzVdIFtjNiBbZTVAMiBkNV1dCiAgW34gYzVdIFtjNSBkNV0gW2U1IFtjNUAyIGM1XV0gW34gYzVdIFtmNSBlNV0gW2M1IGQ1XSBbfiBbZzZAMiB+XSB+QDJdIFtnNSB+XSAKICBbfiBhNV0gW2I1IGM2XSBbYjVAMiB+QDIgZzVdIH4KICBbZjUgfl0gW1tnNUAyIGY1XSB+XSBbW2U1IH5dIFtmNSB+XV0gW1tmIzUgfl0gW2c1IH5dXQogIFt+IGE1XSBbYjUgYzZdIFtiNUAyIH5AMiBnNV0gfgogIFtlYjYgZDZdIFt+IGM2XSB+ITIKICA+YCkKICAuY2xpcCguOTUpLAogIC8vIHN1YiBtZWxvZHkKICBub3RlKGA8CiAgW34gZzRdITIgW34gYWI0XSEyIFt+IGE0XSEyIFt+IGJiNF0hMiAKICBbfiBhNF0hMiBbfiBnNF0hMiBbZDQgZTRdIFtmNCBnYjRdIH4hMgogIFt+IGc0XSEyIFt+IGFiNF0hMiBbfiBhNF0hMiBbfiBiYjRdITIgCiAgW34gYTRdITIgW34gZzRdITIgW2Q0IGU0XSBbZjQgZ2I0XSB+ITIKICBbfiBjNV0hNCBbfiBhNF0hMiBbW2M0IH5dIFtkNCB+XV0gW1tlYjQgfl0gW2U0IH5dXQogIFt+IGM1XSE0IFt+IGViNV0hMiBbZzQqMiBbZjQgfl1dIFtbZTQgfl0gW2Q0IH5dXQogID5gKSwKICAvLyBiYXNzCiAgbm90ZShgPAogIGMzITcgYTMgZjMhMgogIGUzITIgfiE0CiAgYzMhNyBhMyBmMyEyCiAgZTMhMiB+ITQKICBmMyEyIGUzITIgZDMhMiB+ITIKICBmMyEyIGUzITIgYWIzITIgfiEyCiAgPmApCiAgLmNsaXAoLjUpCikuZmFzdCgyKQ==) [swimming](https://strudel.cc/#Ly8gS29qaSBLb25kbyAtIFN3aW1taW5nIChTdXBlciBNYXJpbyBXb3JsZCkKc3RhY2soCiAgc2VxKAogICAgIn4iLAogICAgIn4iLAogICAgIn4iLAogICAgIkE1IFtGNUAyIEM1XSBbRDVAMiBGNV0gRjUiLAogICAgIltDNUAyIEY1XSBbRjVAMiBDNl0gQTUgRzUiLAogICAgIkE1IFtGNUAyIEM1XSBbRDVAMiBGNV0gRjUiLAogICAgIltDNUAyIEY1XSBbQmI1IEE1IEc1XSBGNUAyIiwKICAgICJBNSBbRjVAMiBDNV0gW0Q1QDIgRjVdIEY1IiwKICAgICJbQzVAMiBGNV0gW0Y1QDIgQzZdIEE1IEc1IiwKICAgICJBNSBbRjVAMiBDNV0gW0Q1QDIgRjVdIEY1IiwKICAgICJbQzVAMiBGNV0gW0JiNSBBNSBHNV0gRjVAMiIsCiAgICAiQTUgW0Y1QDIgQzVdIEE1IEY1IiwKICAgICJBYjUgW0Y1QDIgQWI1XSBHNUAyIiwKICAgICJBNSBbRjVAMiBDNV0gQTUgRjUiLAogICAgIkFiNSBbRjVAMiBDNV0gQzZAMiIsCiAgICAiQTUgW0Y1QDIgQzVdIFtENUAyIEY1XSBGNSIsCiAgICAiW0M1QDIgRjVdIFtCYjUgQTUgRzVdIEY1QDIiCiAgKS5jb2xvcignI0ZGRUJCNScpLAogIHNlcSgKICAgICJbRjQsQmI0LEQ1XSBbW0Q0LEc0LEJiNF1AMiBbQmIzLEQ0LEY0XV0gW1tHMyxDNCxFNF1AMiBbW0FiMyxGNF0gW0EzLEdiNF1dXSBbQmIzLEU0LEc0XSIsCiAgICAiW34gW0YzLCBBMywgQzNdIFtGMywgQTMsIEMzXV0gW34gW0YzLCBBMywgQzNdIFtGMywgQTMsIEMzXV0gW34gW0YzLCBCYjMsIEQzXSBbRjMsIEJiMywgRDNdXSBbfiBbRjMsIEJiMywgRGIzXSBbRjMsIEJiMywgRGIzXV0iLAogICAgIlt+IFtGMywgQTMsIEMzXSBbRjMsIEEzLCBDM11dIFt+IFtGMywgQTMsIEMzXSBbRjMsIEEzLCBDM11dIFt+IFtGMywgQmIzLCBEM10gW0YzLCBCYjMsIEQzXV0gW34gW0YzLCBCMywgRDNdIFtGMywgQjMsIEQzXV0iLAogICAgIlt+IFtGMywgQTMsIEMzXSBbRjMsIEEzLCBDM11dIFt+IFtGMywgQTMsIEMzXSBbRjMsIEEzLCBDM11dIFt+IFtGMywgQmIzLCBEM10gW0YzLCBCYjMsIEQzXV0gW34gW0YzLCBCMywgRDNdIFtGMywgQjMsIEQzXV0iLAogICAgIlt+IFtBMywgQzQsIEU0XSBbQTMsIEM0LCBFNF1dIFt+IFtBYjMsIEM0LCBFYjRdIFtBYjMsIEM0LCBFYjRdXSBbfiBbRjMsIEJiMywgRDNdIFtGMywgQmIzLCBEM11dIFt+IFtHMywgQzQsIEU0XSBbRzMsIEM0LCBFNF1dIiwKICAgICJbfiBbRjMsIEEzLCBDNF0gW0YzLCBBMywgQzRdXSBbfiBbRjMsIEEzLCBDNF0gW0YzLCBBMywgQzRdXSBbfiBbRjMsIEJiMywgRDNdIFtGMywgQmIzLCBEM11dIFt+IFtGMywgQjMsIEQzXSBbRjMsIEIzLCBEM11dIiwKICAgICJbfiBbRjMsIEJiMywgRDRdIFtGMywgQmIzLCBENF1dIFt+IFtGMywgQmIzLCBDNF0gW0YzLCBCYjMsIEM0XV0gW34gW0YzLCBBMywgQzRdIFtGMywgQTMsIEM0XV0gW34gW0YzLCBBMywgQzRdIFtGMywgQTMsIEM0XV0iLAogICAgIlt+IFtGMywgQTMsIEMzXSBbRjMsIEEzLCBDM11dIFt+IFtGMywgQTMsIEMzXSBbRjMsIEEzLCBDM11dIFt+IFtGMywgQmIzLCBEM10gW0YzLCBCYjMsIEQzXV0gW34gW0YzLCBCMywgRDNdIFtGMywgQjMsIEQzXV0iLAogICAgIlt+IFtBMywgQzQsIEU0XSBbQTMsIEM0LCBFNF1dIFt+IFtBYjMsIEM0LCBFYjRdIFtBYjMsIEM0LCBFYjRdXSBbfiBbRjMsIEJiMywgRDNdIFtGMywgQmIzLCBEM11dIFt+IFtHMywgQzQsIEU0XSBbRzMsIEM0LCBFNF1dIiwKICAgICJbfiBbRjMsIEEzLCBDM10gW0YzLCBBMywgQzNdXSBbfiBbRjMsIEEzLCBDM10gW0YzLCBBMywgQzNdXSBbfiBbRjMsIEJiMywgRDNdIFtGMywgQmIzLCBEM11dIFt+IFtGMywgQjMsIEQzXSBbRjMsIEIzLCBEM11dIiwKICAgICJbfiBbRjMsIEJiMywgRDRdIFtGMywgQmIzLCBENF1dIFt+IFtGMywgQmIzLCBDNF0gW0YzLCBCYjMsIEM0XV0gW34gW0YzLCBBMywgQzRdIFtGMywgQTMsIEM0XV0gW34gW0YzLCBBMywgQzRdIFtGMywgQTMsIEM0XV0iLAogICAgIlt+IFtCYjMsIEQzLCBGNF0gW0JiMywgRDMsIEY0XV0gW34gW0JiMywgRDMsIEY0XSBbQmIzLCBEMywgRjRdXSBbfiBbQTMsIEM0LCBGNF0gW0EzLCBDNCwgRjRdXSBbfiBbQTMsIEM0LCBGNF0gW0EzLCBDNCwgRjRdXSIsCiAgICAiW34gW0FiMywgQjMsIEY0XSBbQWIzLCBCMywgRjRdXSBbfiBbQWIzLCBCMywgRjRdIFtBYjMsIEIzLCBGNF1dIFt+IFtHMywgQmIzLCBGNF0gW0czLCBCYjMsIEY0XV0gW34gW0czLCBCYjMsIEU0XSBbRzMsIEJiMywgRTRdXSIsCiAgICAiW34gW0JiMywgRDMsIEY0XSBbQmIzLCBEMywgRjRdXSBbfiBbQmIzLCBEMywgRjRdIFtCYjMsIEQzLCBGNF1dIFt+IFtBMywgQzQsIEY0XSBbQTMsIEM0LCBGNF1dIFt+IFtBMywgQzQsIEY0XSBbQTMsIEM0LCBGNF1dIiwKICAgICJbfiBbQWIzLCBCMywgRjRdIFtBYjMsIEIzLCBGNF1dIFt+IFtBYjMsIEIzLCBGNF0gW0FiMywgQjMsIEY0XV0gW34gW0czLCBCYjMsIEY0XSBbRzMsIEJiMywgRjRdXSBbfiBbRzMsIEJiMywgRTRdIFtHMywgQmIzLCBFNF1dIiwKICAgICJbfiBbRjMsIEEzLCBDM10gW0YzLCBBMywgQzNdXSBbfiBbRjMsIEEzLCBDM10gW0YzLCBBMywgQzNdXSBbfiBbRjMsIEJiMywgRDNdIFtGMywgQmIzLCBEM11dIFt+IFtGMywgQjMsIEQzXSBbRjMsIEIzLCBEM11dIiwKICAgICJbfiBbRjMsIEJiMywgRDRdIFtGMywgQmIzLCBENF1dIFt+IFtGMywgQmIzLCBDNF0gW0YzLCBCYjMsIEM0XV0gW34gW0YzLCBBMywgQzRdIFtGMywgQTMsIEM0XV0gW34gW0YzLCBBMywgQzRdIFtGMywgQTMsIEM0XV0iCiAgKS5jb2xvcignIzU0QzU3MScpLAogIHNlcSgKICAgICJbRzMgRzMgQzMgRTNdIiwKICAgICJbRjIgRDIgRzIgQzJdIiwKICAgICJbRjIgRDIgRzIgQzJdIiwKICAgICJbRjIgQTIgQmIyIEIyXSIsCiAgICAiW0EyIEFiMiBHMiBDMl0iLAogICAgIltGMiBBMiBCYjIgQjJdIiwKICAgICJbRzIgQzIgRjIgRjJdIiwKICAgICJbRjIgQTIgQmIyIEIyXSIsCiAgICAiW0EyIEFiMiBHMiBDMl0iLAogICAgIltGMiBBMiBCYjIgQjJdIiwKICAgICJbRzIgQzIgRjIgRjJdIiwKICAgICJbQmIyIEJiMiBBMiBBMl0iLAogICAgIltBYjIgQWIyIEcyIFtDMiBEMiBFMl1dIiwKICAgICJbQmIyIEJiMiBBMiBBMl0iLAogICAgIltBYjIgQWIyIEcyIFtDMiBEMiBFMl1dIiwKICAgICJbRjIgQTIgQmIyIEIyXSIsCiAgICAiW0cyIEMyIEYyIEYyXSIKICApLmNvbG9yKCcjMDA3N0M5JykKKS5ub3RlKCkuc2xvdyg1MSkKLy8ucGlhbm9yb2xsKHtmb2xkOjF9KQo=) [Underground plumber](https://strudel.cc/#Ly8gIlVuZGVyZ3JvdW5kIHBsdW1iZXIiCi8vIEBsaWNlbnNlIENDIEJZLU5DLVNBIDQuMCBodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wLwovLyBAYnkgRmVsaXggUm9vcwovLyBAZGV0YWlscyBpbnNwaXJlZCBieSBGcmllbmRzaGlwIC0gTGV0J3Mgbm90IHRhbGsgYWJvdXQgaXQgKDE5NzkpIDopCgpzYW1wbGVzKHsgYmQ6ICdiZC9CVDBBMEQwLndhdicsIHNuOiAnc24vU1QwVDBTMy53YXYnLCBoaDogJ2hoLzAwMF9oaDNjbG9zZWRoaC53YXYnLCBjcDogJ2NwL0hBTkRDTFAwLndhdicsCn0sICdodHRwczovL2xvb3Bob2xlLWxldHRlcnMudmVyY2VsLmFwcC9zYW1wbGVzL3RpZGFsLycpCgpjb25zdCBoID0geD0+eC50cmFuc3Bvc2UoIjwwQDIgNSAwIDcgNSAwIC01Pi8yIikKCnN0YWNrKAogIHMoIjw8YmQqMiBiZD4gc24+IGhoIikuZmFzdCgyKS5nYWluKC43KSwKICAiW2MyIGExIGJiMSB+XSB+IgogIC5lY2hvKDIsIDEvMTYsIDEpCiAgLnNsb3coMikKICAubm90ZSgpLnMoJ3NxdWFyZScpCiAgLmxheWVyKGgpCiAgLmNsaXAoLjQpCiAgLmN1dG9mZig0MDApLmRlY2F5KC4xMikuc3VzdGFpbigwKQogICwKICAiW2cyLFtjMyBlYjNdXSIuaXRlcig0KQogIC5lY2hvV2l0aCg0LCAxLzgsICh4LG4pPT54LnRyYW5zcG9zZShuKjEyKS5nYWluKE1hdGgucG93KC40LG4pKSkKICAubm90ZSgpLmxheWVyKGgpCiAgLmNsaXAoLjEpCikKICAuZmFzdCgyLzMpCiAgLnBpYW5vcm9sbCgp) [Waa2](https://strudel.cc/#Ly8gIldhYTIiCi8vIEBsaWNlbnNlIENDIEJZLU5DLVNBIDQuMCBodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wLwovLyBAYnkgRmVsaXggUm9vcwoKbm90ZSgKICAiYTQgW2EzIGMzXSBhMyBjMyIKICAuc3ViKCI8NyAxMiA1IDEyPiIuc2xvdygyKSkKICAub2ZmKDEvNCx4PT54LmFkZCg3KSkKICAub2ZmKDEvOCx4PT54LmFkZCgxMikpCikKICAuc2xvdygyKQogIC5jbGlwKHNpbmUucmFuZ2UoMC4zLCAyKS5zbG93KDI4KSkKICAucygic2F3dG9vdGggc3F1YXJlIi5mYXN0KDIpKQogIC5jdXRvZmYoY29zaW5lLnJhbmdlKDUwMCw0MDAwKS5zbG93KDE2KSkKICAuZ2FpbiguNSkKICAucm9vbSguNSkKICAubHBhKC4xMjUpLmxwZW52KC0yKS52KCI4Oi4xMjUiKS5mYW5jaG9yKC4yNSk=) [Wavy kalimba](https://strudel.cc/#Ly8gIldhdnkga2FsaW1iYSIKLy8gQGxpY2Vuc2UgQ0MgQlktTkMtU0EgNC4wIGh0dHBzOi8vY3JlYXRpdmVjb21tb25zLm9yZy9saWNlbnNlcy9ieS1uYy1zYS80LjAvCi8vIEBieSBGZWxpeCBSb29zCgpzZXRjcHMoMSkKCnNhbXBsZXMoewogICdrYWxpbWJhJzogeyBjNTonaHR0cHM6Ly9jZG4uZnJlZXNvdW5kLm9yZy9wcmV2aWV3cy81MzYvNTM2NTQ5XzExOTM1Njk4LWxxLm1wMycgfQp9KQpjb25zdCBzY2FsZXMgPSAiPEM6bWFqb3IgQzptaXhvbHlkaWFuIEY6bHlkaWFuIFtGOm1pbm9yIERiOm1ham9yXT4iCgpzdGFjaygKICAiWzAgMiA0IDYgOSAyIDAgLTJdKjMiCiAgLmFkZCgiPDAgMj4vNCIpCiAgLnNjYWxlKHNjYWxlcykKICAuc3RydWN0KCJ4KjgiKQogIC52ZWxvY2l0eSgiPC44IC4zIC42Pio4IikKICAuc2xvdygyKSwKICAiPGMyIGMyIGYyIFtbRjIgQzJdIGRiMl0+IgogIC5zY2FsZShzY2FsZXMpCiAgLnNjYWxlVHJhbnNwb3NlKCJbMCA8MiA0Pl0qMiIpCiAgLnN0cnVjdCgieCo0IikKICAudmVsb2NpdHkoIjwuOCAuNT4qNCIpCiAgLnZlbG9jaXR5KDAuOCkKICAuc2xvdygyKQopCiAgLmZhc3QoMSkKICAubm90ZSgpCiAgLmNsaXAoIjwuNCAuOCAxIDEuMiAxLjQgMS42IDEuOCAyPi84IikKICAucygna2FsaW1iYScpCiAgLmRlbGF5KC4yKQ==) [zeldasRescue](https://strudel.cc/#Ly8gS29qaSBLb25kbyAtIFByaW5jZXNzIFplbGRhJ3MgUmVzY3VlCnN0YWNrKAogIC8vIG1lbG9keQogIGBbQjNAMiBENF0gW0EzQDIgW0czIEEzXV0gW0IzQDIgRDRdIFtBM10gCiAgW0IzQDIgRDRdIFtBNEAyIEc0XSBbRDRAMiBbQzQgQjNdXSBbQTNdCiAgW0IzQDIgRDRdIFtBM0AyIFtHMyBBM11dIFtCM0AyIEQ0XSBbQTNdCiAgW0IzQDIgRDRdIFtBNEAyIEc0XSBENUAyIAogIFtENUAyIFtDNSBCNF1dIFtbQzUgQjRdIEc0QDJdIFtDNUAyIFtCNCBBNF1dIFtbQjQgQTRdIEU0QDJdCiAgW0Q1QDIgW0M1IEI0XV0gW1tDNSBCNF0gRzQgQzVdIFtHNV0gW34gfiBCM11gLAogIC8vIGJhc3MKICBgW1tDMiBHMl0gRTNAMl0gW1tDMiBHMl0gRiMzQDJdIFtbQzIgRzJdIEUzQDJdIFtbQzIgRzJdIEYjM0AyXQogIFtbQjEgRDNdIEczQDJdIFtbQmIxIERiM10gRzNAMl0gW1tBMSBDM10gRzNAMl0gW1tEMiBDM10gRiMzQDJdCiAgW1tDMiBHMl0gRTNAMl0gW1tDMiBHMl0gRiMzQDJdIFtbQzIgRzJdIEUzQDJdIFtbQzIgRzJdIEYjM0AyXQogIFtbQjEgRDNdIEczQDJdIFtbQmIxIERiM10gRzNAMl0gW1tBMSBDM10gRzNAMl0gW1tEMiBDM10gRiMzQDJdCiAgW1tGMiBDM10gRTNAMl0gW1tFMiBCMl0gRDNAMl0gW1tEMiBBMl0gQzNAMl0gW1tDMiBHMl0gQjJAMl0KICBbW0YyIEMzXSBFM0AyXSBbW0UyIEIyXSBEM0AyXSBbW0ViMiBCYjJdIERiM0AyXSBbW0QyIEEyXSBDMyBbRjMsRzJdXWAKKS50cmFuc3Bvc2UoMTIpLnNsb3coNDgpCiAgLnN1cGVyaW1wb3NlKHg9PnguYWRkKDAuMDYpKSAvLyBhZGQgc2xpZ2h0bHkgZGV0dW5lZCB2b2ljZQogIC5ub3RlKCkKICAuZ2FpbiguMSkKICAucygndHJpYW5nbGUnKQogIC5yb29tKDEpCiAgLy8ucGlhbm9yb2xsKHtmb2xkOjF9KQ==) ../../metadata\_parser --- # Intro 🌀 Strudel Willkommen[](https://strudel.cc/de/workshop/getting-started/#willkommen) ========================================================================= ![Strudel Icon](https://strudel.cc/icons/strudel_icon.png) Willkommen zum Strudel Workshop! Du hast den richtigen Ort gefunden wenn du lernen möchtest wie man mit Code Musik macht. Was ist Strudel[](https://strudel.cc/de/workshop/getting-started/#was-ist-strudel) ----------------------------------------------------------------------------------- Mit Strudel kann man dynamische MusikstĂŒcke in Echtzeit schreiben. Es ist eine in JavaScript entwickelte Version von [Tidal Cycles](https://tidalcycles.org/) und wurde 2022 von Alex McLean und Felix Roos initiiert. Tidal Cycles, auch bekannt unter dem Namen Tidal, ist eine Computersprache fĂŒr algorithmische Muster. Obwohl sie meistens fĂŒr die Erzeugung von Musik eingesetzt wird, kann sie fĂŒr jede Art von TĂ€tigkeit eingesetzt werden, in der Muster eine Rolle spielen. Du brauchst keine Erfahrung in JavaScript oder Tidal Cycles um mit Strudel Musik zu machen. Dieser interaktive Workshop leitet dich spielerisch durch die Grundlagen von Strudel. Der beste Ort um mit Strudel Musik zu machen ist das [Strudel REPL](https://strudel.cc/) . Was kann man mit Strudel machen?[](https://strudel.cc/de/workshop/getting-started/#was-kann-man-mit-strudel-machen) -------------------------------------------------------------------------------------------------------------------- * Musik Live Coding: In Echtzeit mit Code Musik machen * Algorithmische Komposition: Schreibe Musik mithilfe Tidals einzigartiger Sprache fĂŒr Muster * Lehren: Strudel eignet sich gut fĂŒr Lehrende, da keine Installation nötig ist und die Sprache kein theoretisches Vorwissen erfordert. * Mit anderen Musik-Anwendungen kombinieren: Per MIDI oder OSC kann Strudel als flexibler Sequencer mit jedem Setup kombiniert werden Beispiel[](https://strudel.cc/de/workshop/getting-started/#beispiel) --------------------------------------------------------------------- Hier ist ein Beispiel wie Strudel klingen kann: stack( // drums s("bd,\[~ \],hh\*8") .speed(perlin.range(.8,.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 .note() // wrap in "note" .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 // chords ">".voicings('lefthand') .superimpose(x=>x.add(.04)) // add second, slightly detuned voice .add(perlin.range(0,.5)) // random pitch variation .note() // wrap in "note" .s('sawtooth') // waveform .gain(.16) // turn down .cutoff(500) // fixed cutoff .attack(1) // slowly fade in ) .slow(3/2) Mehr Beispiele gibt es [hier](https://strudel.cc/examples/) . Du kannst auch im [Strudel REPL](https://strudel.cc/) auf `shuffle` klicken um ein zufĂ€lliges Beispiel zu hören. Workshop[](https://strudel.cc/de/workshop/getting-started/#workshop) --------------------------------------------------------------------- Der beste Weg um Strudel zu lernen ist der nun folgende Workshop. Wenn du bereit bist, lass uns loslegen mit deinen [ersten Sounds](https://strudel.cc/de/workshop/first-sounds/) . --- # First Sounds 🌀 Strudel First Sounds[](https://strudel.cc/workshop/first-sounds/#first-sounds) ======================================================================= This is the first chapter of the Strudel Workshop, nice to have you on board! Code Fields[](https://strudel.cc/workshop/first-sounds/#code-fields) --------------------------------------------------------------------- The workshop is full of interactive code fields. Let’s learn how to use those. Here is one: sound("casio") 1. âŹ†ïž click into the text field above âŹ†ïž 2. press `ctrl`+`enter` to play 3. change `casio` to `metal` 4. press `ctrl`+`enter` to update 5. press `ctrl`+`.` to stop Congratulations, you are now live coding! Sounds[](https://strudel.cc/workshop/first-sounds/#sounds) ----------------------------------------------------------- We have just played a sound with `sound` like this: sound("casio") `casio` is one of many standard sounds. Try out a few other sounds: insect wind jazz metal east crow casio space numbers You might hear a little pause while the sound is loading **Change Sample Number with :** One Sound can contain multiple samples (audio files). You can select the sample by appending `:` followed by a number to the name: sound("casio:1") Try different sound / sample number combinations. Not adding a number is like doing `:0` Now you know how to use different sounds. For now we’ll stick to this little selection of sounds, but we’ll find out how to load your own sounds later. Drum Sounds[](https://strudel.cc/workshop/first-sounds/#drum-sounds) --------------------------------------------------------------------- By default, Strudel comes with a wide selection of drum sounds: sound("bd hh sd oh") These letter combinations stand for different parts of a drum set: ![](https://strudel.cc/img/drumset.png)[original image by Pbroks13](https://de.wikipedia.org/wiki/Schlagzeug#/media/Datei:Drum_set.svg) * `bd` = **b**ass **d**rum * `sd` = **s**nare **d**rum * `rim` = **rim**shot * `hh` = **h**i**h**at * `oh` = **o**pen **h**ihat * `lt` = **l**ow tom * `mt` = **m**iddle tom * `ht` = **h**igh tom * `rd` = **r**i**d**e cymbal * `cr` = **cr**ash cymbal Try out different drum sounds! To change the sound character of our drums, we can use `bank` to change the drum machine: sound("bd hh sd oh").bank("RolandTR909") In this example `RolandTR909` is the name of the drum machine that we’re using. It is a famous drum machine for house and techno beats. Try changing `RolandTR909` to one of * `AkaiLinn` * `RhythmAce` * `RolandTR808` * `RolandTR707` * `ViscoSpaceDrum` There are a lot more, but let’s keep it simple for now đŸŠ„ Pro-Tip: Mark a name via double click. Then just copy and paste! Sequences[](https://strudel.cc/workshop/first-sounds/#sequences) ----------------------------------------------------------------- In the last example, we already saw that you can play multiple sounds in a sequence by separating them with a space: sound("bd hh sd hh") Notice how the currently playing sound is highlighted in the code and also visualized below. Try adding more sounds to the sequence! **The longer the sequence, the faster it runs** sound("bd bd hh bd rim bd hh bd") The content of a sequence will be squished into what’s called a cycle. A cycle is 2s long by default. **One per cycle with `< .. >`** Here is the same sequence, but this time sourrounded with `< .. >` (angle brackets): sound("") This will play only one sound per cycle. With these brackets, the tempo doesn’t change when we add or remove elements! Because this is now very slow, we can speed it up again like this: sound("\*8") Here, the `*8` means we make the whole thing 8 times faster. Wait a minute, isn’t this the same as without `< ... >*8`? Why do we need it then? That’s true, the special thing about this notation is that the tempo won’t change when you add or remove elements, try it! Try also changing the number at the end to change the tempo! **changing the tempo with setcpm** setcpm(90/4) sound("\*8") cpm = cycles per minute By default, the tempo is 30 cycles per minute = 120/4 = 1 cycle every 2 seconds In western music terms, you could say the above are 8ths notes at 90bpm in 4/4 time. But don’t worry if you don’t know these terms, as they are not required to make music with Strudel. **Add a rests in a sequence with ’-’ or ’~’** sound("bd hh - rim - bd hh rim") **Sub-Sequences with \[brackets\]** sound("bd \[hh hh\] sd \[hh bd\] bd - \[hh sd\] cp") Try adding more sounds inside a bracket! Similar to the whole sequence, the content of a sub-sequence will be squished to its own length. **Multiplication: Speed things up** sound("bd hh\*2 rim hh\*3 bd \[- hh\*2\] rim hh\*2") **Multiplication: Speed up subsequences** sound("bd \[hh rim\]\*2 bd \[hh rim\]\*1.5") **Multiplication: Speeeeeeeeed things up** sound("bd hh\*32 rim hh\*16") Pitch = really fast rhythm **Sub-Sub-Sequences with \[\[brackets\]\]** sound("bd \[\[rim rim\] hh\] bd cp") You can go as deep as you want! **Play sequences in parallel with comma** sound("hh hh hh, bd casio") You can use as many commas as you want: sound("hh hh hh, bd bd, - casio") Commas can also be used inside sub-sequences: sound("hh hh hh, bd \[bd,casio\]") Notice how the 2 above are the same? It is quite common that there are many ways to express the same idea. **Multiple Lines with backticks** sound(\`bd\*2, - cp, - - - oh, hh\*4, \[- casio\]\*2\`) **selecting sample numbers separately** Instead of selecting sample numbers one by one: sound("jazz:0 jazz:1 \[jazz:4 jazz:2\] jazz:3\*2") We can also use the `n` function to make it shorter and more readable: n("0 1 \[4 2\] 3\*2").sound("jazz") Recap[](https://strudel.cc/workshop/first-sounds/#recap) --------------------------------------------------------- Now we’ve learned the basics of the so called Mini-Notation, the rhythm language of Tidal. This is what we’ve learned so far: | Concept | Syntax | Example | | --- | --- | --- | | Sequence | space | sound("bd bd sd hh") | | Sample Number | :x | sound("hh:0 hh:1 hh:2 hh:3") | | Rests | \- or ~ | sound("metal - jazz jazz:1") | | Alternate | <> | sound("") | | Sub-Sequences | \[\] | sound("bd wind \[metal jazz\] hh") | | Sub-Sub-Sequences | \[\[\]\] | sound("bd \[metal \[jazz \[sd cp\]\]\]") | | Speed up | \* | sound("bd sd\*2 cp\*3") | | Parallel | , | sound("bd\*2, hh\*2 \[hh oh\]") | The Mini-Notation is usually used inside some function. These are the functions we’ve seen so far: | Name | Description | Example | | --- | --- | --- | | sound | plays the sound of the given name | sound("bd sd \[- bd\] sd") | | bank | selects the sound bank | sound("bd sd \[- bd\] sd").bank("RolandTR909") | | setcpm | sets the tempo in cycles per minute | setcpm(45); sound("bd sd \[- bd\] sd") | | n | select sample number | n("0 1 4 2 0 6 3 2").sound("jazz") | Examples[](https://strudel.cc/workshop/first-sounds/#examples) --------------------------------------------------------------- **Basic rock beat** setcpm(100/4) sound("\[bd sd\]\*2, hh\*8").bank("RolandTR505") **Classic house** sound("bd\*4, \[- cp\]\*2, \[- hh\]\*4").bank("RolandTR909") Notice that the two patterns are extremely similar. Certain drum patterns are reused across genres. We Will Rock you setcpm(81/2) sound("bd\*2 cp").bank("RolandTR707") **Yellow Magic Orchestra - Firecracker** setcpm(120/2) sound("bd sd, - - - hh - hh - -, - perc - perc:1\*2") .bank("RolandCompurhythm1000") **Imitation of a 16 step sequencer** setcpm(90/4) sound(\` \[- - oh - \] \[- - - - \] \[- - - - \] \[- - - - \], \[hh hh - - \] \[hh - hh - \] \[hh - hh - \] \[hh - hh - \], \[- - - - \] \[cp - - - \] \[- - - - \] \[cp - - - \], \[bd - - - \] \[- - - bd\] \[- - bd - \] \[- - - bd\] \`) **Another one** setcpm(88/4) sound(\` \[- - - - \] \[- - - - \] \[- - - - \] \[- - oh:1 - \], \[hh hh hh hh\] \[hh hh hh hh\] \[hh hh hh hh\] \[hh hh - - \], \[- - - - \] \[cp - - - \] \[- - - - \] \[~ cp - - \], \[bd bd - - \] \[- - bd - \] \[bd bd - bd \] \[- - - - \] \`).bank("RolandTR808") **Not your average drums** setcpm(100/2) s(\`jazz\*2, insect \[crow metal\] - -, - space:4 - space:1, - wind\`) Now that we know the basics of how to make beats, let’s look at how we can play [notes](https://strudel.cc/workshop/first-notes/) --- # First Notes 🌀 Strudel First Notes[](https://strudel.cc/workshop/first-notes/#first-notes) ==================================================================== Let’s look at how we can play notes numbers and notes[](https://strudel.cc/workshop/first-notes/#numbers-and-notes) -------------------------------------------------------------------------------- **play notes with numbers** note("48 52 55 59").sound("piano") Try out different numbers! Try decimal numbers, like 55.5 **play notes with letters** note("c e g b").sound("piano") Try out different letters (a - g). Can you find melodies that are actual words? Hint: ☕ 😉 âšȘ **add flats or sharps to play the black keys** note("db eb gb ab bb").sound("piano") note("c# d# f# g# a#").sound("piano") **play notes with letters in different octaves** note("c2 e3 g4 b5").sound("piano") Try out different octaves (1-8) If you are not comfortable with the note letter system, it should be easier to use numbers instead. Most of the examples below will use numbers for that reason. We will also look at ways to make it easier to play the right notes later. changing the sound[](https://strudel.cc/workshop/first-notes/#changing-the-sound) ---------------------------------------------------------------------------------- Just like with unpitched sounds, we can change the sound of our notes with `sound`: note("36 43, 52 59 62 64").sound("piano") Try out different sounds: * gm\_electric\_guitar\_muted * gm\_acoustic\_bass * gm\_voice\_oohs * gm\_blown\_bottle * sawtooth * square * triangle * how about bd, sd or hh? * remove `.sound('...')` completely **switch between sounds** note("48 67 63 \[62, 58\]") .sound("piano gm\_electric\_guitar\_muted") **stack multiple sounds** note("48 67 63 \[62, 58\]") .sound("piano, gm\_electric\_guitar\_muted") The `note` and `sound` patterns are combined! We will see more ways to combine patterns later.. Longer Sequences[](https://strudel.cc/workshop/first-notes/#longer-sequences) ------------------------------------------------------------------------------ **Divide sequences with `/` to slow them down** note("\[36 34 41 39\]/4").sound("gm\_acoustic\_bass") The `/4` plays the sequence in brackets over 4 cycles (=8s). So each of the 4 notes is 2s long. Try adding more notes inside the brackets and notice how it gets faster. **Play one per cycle with `< ... >`** In the last section, we learned that `< ... >` (angle brackets) can be used to play only one thing per cycle, which is useful for longer melodies too: note("<36 34 41 39>").sound("gm\_acoustic\_bass") Try adding more notes inside the brackets and notice how the tempo stays the same. The angle brackets are actually just a shortcut: `` = `[a b c]/3` `` = `[a b c d]/4` 
 **Play one sequence per cycle** We can combine the 2 types of brackets in all sorts of different ways. Here is an example of a repetitive bassline: note("<\[36 48\]\*4 \[34 46\]\*4 \[41 53\]\*4 \[39 51\]\*4>") .sound("gm\_acoustic\_bass") **Alternate between multiple things** note("60 <63 62 65 63>") .sound("gm\_xylophone") This is also useful for unpitched sounds: sound("bd\*4, \[~ \]\*2, \[~ hh\]\*4") .bank("RolandTR909") Scales[](https://strudel.cc/workshop/first-notes/#scales) ---------------------------------------------------------- Finding the right notes can be difficult.. Scales are here to help: setcpm(60) n("0 2 4 <\[6,8\] \[7,9\]>") .scale("C:minor").sound("piano") Try out different numbers. Any number should sound good! Try out different scales: * C:major * A2:minor * D:dorian * G:mixolydian * A2:minor:pentatonic * F:major:pentatonic **automate scales** Just like anything, we can automate the scale with a pattern: setcpm(60) n("<0 -3>, 2 4 <\[6,8\] \[7,9\]>") .scale("/4") .sound("piano") If you have no idea what these scale mean, don’t worry. These are just labels for different sets of notes that go well together. Take your time and you’ll find scales you like! Repeat & Elongate[](https://strudel.cc/workshop/first-notes/#repeat--elongate) ------------------------------------------------------------------------------- **Elongate with @** note("c@3 eb").sound("gm\_acoustic\_bass") Not using `@` is like using `@1`. In the above example, c is 3 units long and eb is 1 unit long. Try changing that number! **Elongate within sub-sequences** setcpm(60) n("<\[4@2 4\] \[5@2 5\] \[6@2 6\] \[5@2 5\]>\*2") .scale("/4") .sound("gm\_acoustic\_bass") This groove is called a `shuffle`. Each beat has two notes, where the first is twice as long as the second. This is also sometimes called triplet swing. You’ll often find it in blues and jazz. **Replicate** setcpm(60) note("c!2 \[eb,\]").sound("piano") Try switching between `!`, `*` and `@` What’s the difference? Recap[](https://strudel.cc/workshop/first-notes/#recap) -------------------------------------------------------- Let’s recap what we’ve learned in this chapter: | Concept | Syntax | Example | | --- | --- | --- | | Slow down | / | note("\[c a f e\]/2") | | Alternate | <> | note("c a f ") | | Elongate | @ | note("c@3 e") | | Replicate | ! | note("c!3 e") | New functions: | Name | Description | Example | | --- | --- | --- | | note | set pitch as number or letter | note("b g e c").sound("piano") | | scale | interpret `n` as scale degree | n("6 4 2 0").scale("C:minor").sound("piano") | | $: | play patterns in parallel | $: s("bd sd")
$: note("c eb g") | Examples[](https://strudel.cc/workshop/first-notes/#examples) -------------------------------------------------------------- **Classy Bassline** note("<\[c2 c3\]\*4 \[bb1 bb2\]\*4 \[f2 f3\]\*4 \[eb2 eb3\]\*4>") .sound("gm\_synth\_bass\_1") .lpf(800) // <-- we'll learn about this soon **Classy Melody** n(\`< \[~ 0\] 2 \[0 2\] \[~ 2\] \[~ 0\] 1 \[0 1\] \[~ 1\] \[~ 0\] 3 \[0 3\] \[~ 3\] \[~ 0\] 2 \[0 2\] \[~ 2\] >\*4\`).scale("C4:minor") .sound("gm\_synth\_strings\_1") **Classy Drums** sound("bd\*4, \[~ \]\*2, \[~ hh\]\*4") .bank("RolandTR909") **If there just was a way to play all the above at the same time
** You can use `$:` 😙 Playing multiple patterns[](https://strudel.cc/workshop/first-notes/#playing-multiple-patterns) ------------------------------------------------------------------------------------------------ If you want to play multiple patterns at the same time, make sure to write `$:` before each: $: note("<\[c2 c3\]\*4 \[bb1 bb2\]\*4 \[f2 f3\]\*4 \[eb2 eb3\]\*4>") .sound("gm\_synth\_bass\_1").lpf(800) $: n(\`< \[~ 0\] 2 \[0 2\] \[~ 2\] \[~ 0\] 1 \[0 1\] \[~ 1\] \[~ 0\] 3 \[0 3\] \[~ 3\] \[~ 0\] 2 \[0 2\] \[~ 2\] >\*4\`).scale("C4:minor") .sound("gm\_synth\_strings\_1") $: sound("bd\*4, \[~ \]\*2, \[~ hh\]\*4") .bank("RolandTR909") Try changing `$` to `_$` to mute a part! This is starting to sound like actual music! We have sounds, we have notes, now the last piece of the puzzle is missing: [effects](https://strudel.cc/workshop/first-effects/) --- # Recap 🌀 Strudel Workshop Recap[](https://strudel.cc/workshop/recap/#workshop-recap) ==================================================================== This page is just a listing of all functions covered in the workshop! Mini Notation[](https://strudel.cc/workshop/recap/#mini-notation) ------------------------------------------------------------------ | Concept | Syntax | Example | | --- | --- | --- | | Sequence | space | sound("bd bd sd hh bd cp sd hh") | | Sample Number | :x | sound("hh:0 hh:1 hh:2 hh:3") | | Rests | ~ | sound("metal ~ jazz jazz:1") | | Sub-Sequences | \[\] | sound("bd wind \[metal jazz\] hh") | | Sub-Sub-Sequences | \[\[\]\] | sound("bd \[metal \[jazz sd\]\]") | | Speed up | \* | sound("bd sd\*2 cp\*3") | | Parallel | , | sound("bd\*2, hh\*2 \[hh oh\]") | | Slow down | / | note("\[c a f e\]/2") | | Alternate | <> | note("c ") | | Elongate | @ | note("c@3 e") | | Replicate | ! | note("c!3 e") | Sounds[](https://strudel.cc/workshop/recap/#sounds) ---------------------------------------------------- | Name | Description | Example | | --- | --- | --- | | sound | plays the sound of the given name | sound("bd sd") | | bank | selects the sound bank | sound("bd sd").bank("RolandTR909") | | n | select sample number | n("0 1 4 2").sound("jazz") | Notes[](https://strudel.cc/workshop/recap/#notes) -------------------------------------------------- | Name | Description | Example | | --- | --- | --- | | note | set pitch as number or letter | note("b g e c").sound("piano") | | n + scale | set note in scale | n("6 4 2 0").scale("C:minor").sound("piano") | | $: | play patterns in parallel | $: s("bd sd")
$: note("c eb g") | Audio Effects[](https://strudel.cc/workshop/recap/#audio-effects) ------------------------------------------------------------------ | name | example | | --- | --- | | lpf | note("c2 c3 c2 c3").s("sawtooth").lpf("400 2000") | | vowel | note("c3 eb3 g3").s("sawtooth").vowel("
") | | gain | s("hh\*16").gain("\[.25 1\]\*4") | | delay | s("bd rim bd cp").delay(.5) | | room | s("bd rim bd cp").room(.5) | | pan | s("bd rim bd cp").pan("0 1") | | speed | s("bd rim bd cp").speed("<1 2 -1 -2>") | | range | s("hh\*32").lpf(saw.range(200,4000)) | Pattern Effects[](https://strudel.cc/workshop/recap/#pattern-effects) ---------------------------------------------------------------------- | name | description | example | | --- | --- | --- | | setcpm | sets the tempo in cycles per minute | setcpm(45); sound("bd sd \[~ bd\] sd") | | fast | speed up | sound("bd sd \[~ bd\] sd").fast(2) | | slow | slow down | sound("bd sd \[~ bd\] sd").slow(2) | | rev | reverse | n("0 2 4 6").scale("C:minor").rev() | | jux | split left/right, modify right | n("0 2 4 6").scale("C:minor").jux(rev) | | add | add numbers / notes | n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor") | | ply | speed up each event n times | s("bd sd").ply("<1 2 3>") | | off | copy, shift time & modify | s("bd sd, hh\*4").off(1/8, x=>x.speed(2)) | --- # Pattern Effects 🌀 Strudel Pattern Effects[](https://strudel.cc/workshop/pattern-effects/#pattern-effects) ================================================================================ Up until now, most of the functions we’ve seen are what other music programs are typically capable of: sequencing sounds, playing notes, controlling effects. In this chapter, we are going to look at functions that are more unique to tidal. **reverse patterns with rev** n("0 1 \[4 3\] 2 0 2 \[~ 3\] 4").sound("jazz").rev() **play pattern left and modify it right with jux** n("0 1 \[4 3\] 2 0 2 \[~ 3\] 4").sound("jazz").jux(rev) This is the same as: $: n("0 1 \[4 3\] 2 0 2 \[~ 3\] 4").sound("jazz").pan(0) $: n("0 1 \[4 3\] 2 0 2 \[~ 3\] 4").sound("jazz").pan(1).rev() Let’s visualize what happens here: $: n("0 1 \[4 3\] 2 0 2 \[~ 3\] 4").sound("jazz").pan(0).color("cyan") $: n("0 1 \[4 3\] 2 0 2 \[~ 3\] 4").sound("jazz").pan(1).color("magenta").rev() Try commenting out one of the two by adding `//` before a line **multiple tempos** note("c2, eb3 g3 \[bb3 c4\]").sound("piano").slow("0.5,1,1.5") This is like doing $: note("c2, eb3 g3 \[bb3 c4\]").s("piano").slow(0.5).color('cyan') $: note("c2, eb3 g3 \[bb3 c4\]").s("piano").slow(1).color('magenta') $: note("c2, eb3 g3 \[bb3 c4\]").s("piano").slow(1.5).color('yellow') Try commenting out one or more by adding `//` before a line **add** setcpm(60) note("c2 \[eb3,g3\] ".add("<0 <1 -1>>")) .color(">").adsr("\[.1 0\]:.2:\[1 0\]") .sound("gm\_acoustic\_bass").room(.5) If you add a number to a note, the note will be treated as if it was a number We can add as often as we like: setcpm(60) note("c2 \[eb3,g3\]".add("<0 <1 -1>>").add("0,7")) .color(">").adsr("\[.1 0\]:.2:\[1 0\]") .sound("gm\_acoustic\_bass").room(.5) **add with scale** n("0 \[2 4\] <3 5> \[~ <4 1>\]".add("<0 \[0,2,4\]>")) .scale("C5:minor").release(.5) .sound("gm\_xylophone").room(.5) **time to stack** $: n("0 \[2 4\] <3 5> \[~ <4 1>\]".add("<0 \[0,2,4\]>")) .scale("C5:minor") .sound("gm\_xylophone") .room(.4).delay(.125) $: note("c2 \[eb3,g3\]".add("<0 <1 -1>>")) .adsr("\[.1 0\]:.2:\[1 0\]") .sound("gm\_acoustic\_bass") .room(.5) $: n("0 1 \[2 3\] 2").sound("jazz").jux(rev) **ply** sound("hh hh, bd rim \[~ cp\] rim").bank("RolandTR707").ply(2) this is like writing: sound("hh\*2 hh\*2, bd\*2 rim\*2 \[~ cp\*2\] rim\*2").bank("RolandTR707") Try patterning the `ply` function, for example using `"<1 2 1 3>"` **off** n("0 \[4 <3 2>\] <2 3> \[~ 1\]" .off(1/16, x=>x.add(4)) //.off(1/8, x=>x.add(7)) ).scale("/2") .s("triangle").room(.5).dec(.1) In the notation `.off(1/16, x=>x.add(4))`, says: * take the original pattern named as `x` * modify `x` with `.add(4)`, and * play it offset to the original pattern by `1/16` of a cycle. off is also useful for modifying other sounds, and can even be nested: s("bd sd \[rim bd\] sd,\[~ hh\]\*4").bank("CasioRZ1") .off(2/16, x=>x.speed(1.5).gain(.25) .off(3/16, y=>y.vowel("\*8"))) | name | description | example | | --- | --- | --- | | rev | reverse | n("0 2 4 6 ~ 7 9 5").scale("C:minor").rev() | | jux | split left/right, modify right | n("0 2 4 6 ~ 7 9 5").scale("C:minor").jux(rev) | | add | add numbers / notes | n("0 2 4 6 ~ 7 9 5".add("<0 1 2 1>")).scale("C:minor") | | ply | speed up each event n times | s("bd sd \[~ bd\] sd").ply("<1 2 3>") | | off | copy, shift time & modify | s("bd sd \[~ bd\] sd, hh\*8").off(1/16, x=>x.speed(2)) | --- # First Effects 🌀 Strudel First Effects[](https://strudel.cc/workshop/first-effects/#first-effects) ========================================================================== We have sounds, we have notes, now let’s look at effects! Some basic effects[](https://strudel.cc/workshop/first-effects/#some-basic-effects) ------------------------------------------------------------------------------------ **low-pass filter** note("<\[c2 c3\]\*4 \[bb1 bb2\]\*4 \[f2 f3\]\*4 \[eb2 eb3\]\*4>") .sound("sawtooth").lpf(800) lpf = **l**ow **p**ass **f**ilter * Change lpf to 200. Notice how it gets muffled. Think of it as standing in front of the club with the door closed đŸšȘ. * Now let’s open the door
 change it to 5000. Notice how it gets brighter ✹đŸȘ© **pattern the filter** note("<\[c2 c3\]\*4 \[bb1 bb2\]\*4 \[f2 f3\]\*4 \[eb2 eb3\]\*4>") .sound("sawtooth").lpf("200 1000 200 1000") * Try adding more values * Notice how the pattern in lpf does not change the overall rhythm We will learn how to automate with waves later
 **vowel** note("<\[c3,g3,e4\] \[bb2,f3,d4\] \[a2,f3,c4\] \[bb2,g3,eb4\]>") .sound("sawtooth").vowel("") **gain** $: sound("hh\*16").gain("\[.25 1\]\*4") $: sound("bd\*4,\[~ sd:1\]\*2") Rhythm is all about dynamics! * Remove `.gain(...)` and notice how flat it sounds. * Bring it back by undoing (ctrl+z) Let’s combine all of the above into a little tune: $: sound("hh\*8").gain("\[.25 1\]\*4") $: sound("bd\*4,\[~ sd:1\]\*2") $: note("<\[c2 c3\]\*4 \[bb1 bb2\]\*4 \[f2 f3\]\*4 \[eb2 eb3\]\*4>") .sound("sawtooth").lpf("200 1000 200 1000") $: note("<\[c3,g3,e4\] \[bb2,f3,d4\] \[a2,f3,c4\] \[bb2,g3,eb4\]>") .sound("sawtooth").vowel("") **shape the sound with an adsr envelope** note("c3 bb2 f3 eb3") .sound("sawtooth").lpf(600) .attack(.1) .decay(.1) .sustain(.25) .release(.2) Try to find out what the numbers do.. Compare the following * attack: `.5` vs `0` * decay: `.5` vs `0` * sustain: `1` vs `.25` vs `0` * release: `0` vs `.5` vs `1` Can you guess what they do? Click to see solution **adsr short notation** note("c3 bb2 f3 eb3") .sound("sawtooth").lpf(600) .adsr(".1:.1:.5:.2") **delay** $: note("\[~ \[<\[d3,a3,f4\]!2 \[d3,bb3,g4\]!2> ~\]\]\*2") .sound("gm\_electric\_guitar\_muted").delay(.5) $: sound("bd rim").bank("RolandTR707").delay(".5") Try some `delay` values between 0 and 1. Btw, `.5` is short for `0.5` What happens if you use `.delay(".8:.125")` ? Can you guess what the second number does? What happens if you use `.delay(".8:.06:.8")` ? Can you guess what the third number does? Click to see solution **room aka reverb** n("<4 \[3@3 4\] \[<2 0> ~@16\] ~>") .scale("D4:minor").sound("gm\_accordion:2") .room(2) Try different values! Add a delay too! **little dub tune** $: note("\[~ \[<\[d3,a3,f4\]!2 \[d3,bb3,g4\]!2> ~\]\]\*2") .sound("gm\_electric\_guitar\_muted").delay(.5) $: sound("bd rim").bank("RolandTR707").delay(.5) $: n("<4 \[3@3 4\] \[<2 0> ~@16\] ~>") .scale("D4:minor").sound("gm\_accordion:2") .room(2).gain(.5) Let’s add a bass to make this complete: $: note("\[~ \[<\[d3,a3,f4\]!2 \[d3,bb3,g4\]!2> ~\]\]\*2") .sound("gm\_electric\_guitar\_muted").delay(.5) $: sound("bd rim").bank("RolandTR707").delay(.5) $: n("<4 \[3@3 4\] \[<2 0> ~@16\] ~>") .scale("D4:minor").sound("gm\_accordion:2") .room(2).gain(.4) $: n("\[0 \[~ 0\] 4 \[3 2\] \[0 ~\] \[0 ~\] <0 2> ~\]/2") .scale("D2:minor") .sound("sawtooth,triangle").lpf(800) Try adding `.hush()` at the end of one of the patterns in the stack
 **pan** sound("numbers:1 numbers:2 numbers:3 numbers:4") .pan("0 0.3 .6 1") **speed** sound("bd rim \[~ bd\] rim").speed("<1 2 -1 -2>").room(.2) **fast and slow** We can use `fast` and `slow` to change the tempo of a pattern outside of Mini-Notation: sound("bd\*4,~ rim ~ cp").slow(2) Change the `slow` value. Try replacing it with `fast`. What happens if you use a pattern like `.fast("<1 [2 4]>")`? By the way, inside Mini-Notation, `fast` is `*` and `slow` is `/`. sound("\[bd\*4,~ rim ~ cp\]\*<1 \[2 4\]>") modulation with signals[](https://strudel.cc/workshop/first-effects/#modulation-with-signals) ---------------------------------------------------------------------------------------------- Instead of changing values stepwise, we can also control them with signals: sound("hh\*16").gain(sine) The basic waveforms for signals are `sine`, `saw`, `square`, `tri` 🌊 Try also random signals `rand` and `perlin`! The gain is visualized as transparency in the pianoroll. **setting a range** By default, waves oscillate between 0 to 1. We can change that with `range`: sound("hh\*16").lpf(saw.range(500, 2000)) What happens if you flip the range values? We can change the modulation speed with slow / fast: note("<\[c2 c3\]\*4 \[bb1 bb2\]\*4 \[f2 f3\]\*4 \[eb2 eb3\]\*4>") .sound("sawtooth") .lpf(sine.range(100, 2000).slow(4)) The whole modulation will now take 8 cycles to repeat. Recap[](https://strudel.cc/workshop/first-effects/#recap) ---------------------------------------------------------- | name | example | | --- | --- | | lpf | note("c2 c3 c2 c3").s("sawtooth").lpf("<400 2000>") | | vowel | note("c3 eb3 g3").s("sawtooth").vowel("") | | gain | s("hh\*16").gain("\[.25 1\]\*2") | | delay | s("bd rim bd cp").delay(.5) | | room | s("bd rim bd cp").room(.5) | | pan | s("bd rim bd cp").pan("0 1") | | speed | s("bd rim bd cp").speed("<1 2 -1 -2>") | | signals | `sine`, `saw`, `square`, `tri`, `rand`, `perlin`

s("hh\*16").gain (saw) | | range | s("hh\*16").lpf(saw.range(200,4000)) | Let us now take a look at some of Tidal’s typical [pattern effects](https://strudel.cc/workshop/pattern-effects/) . --- # Synths 🌀 Strudel Synths[](https://strudel.cc/learn/synths/#synths) ================================================== In addition to the sampling engine, strudel comes with a synthesizer to create sounds on the fly. Basic Waveforms[](https://strudel.cc/learn/synths/#basic-waveforms) -------------------------------------------------------------------- The basic waveforms are `sine`, `sawtooth`, `square` and `triangle`, which can be selected via `sound` (or `s`): note("c2 >".fast(2)) .sound("") .\_scope() If you don’t set a `sound` but a `note` the default value for `sound` is `triangle`! Noise[](https://strudel.cc/learn/synths/#noise) ------------------------------------------------ You can also use noise as a source by setting the waveform to: `white`, `pink` or `brown`. These are different flavours of noise, here written from hard to soft. sound("").\_scope() Here’s a more musical example of how to use noise for hihats: sound("bd\*2,\*8") .decay(.04).sustain(0).\_scope() Some amount of pink noise can also be added to any oscillator by using the `noise` paremeter: note("c3").noise("<0.1 0.25 0.5>").\_scope() You can also use the `crackle` type to play some subtle noise crackles. You can control noise amount by using the `density` parameter: s("crackle\*4").density("<0.01 0.04 0.2 0.5>".slow(2)).\_scope() ### Additive Synthesis[](https://strudel.cc/learn/synths/#additive-synthesis) Periodic waveforms are composed of several [harmonics](https://en.wikipedia.org/wiki/Harmonic) above a fundamental frequency, lying at integer multiples. These overtones combine to give a sound its unique timbral quality. For the basic waveforms, we offer you control over these harmonics with the `partials` and `phases` functions. #### Partials[](https://strudel.cc/learn/synths/#partials) `partials` refers to the magnitude of each harmonic relative to the fundamental frequency. They can thus be used to spectrally filter these waveforms and tame some of their harshness: note("c2 >".fast(2)) .sound("sawtooth") .partials(\[1, 1, "<1 0>", "<1 0>", "<1 0>", "<1 0>", "<1 0>"\]) .\_scope() `partials` can also be used to construct _new_ waveforms not present in our basic set with the ‘user’ sound source: note("c2 >".fast(2)) .sound("user") .partials(\[1, 0, 0.3, 0, 0.1, 0, 0, 0.3\]) .\_scope() We may algorithmically construct lists of magnitudes with Javascript code like: const numHarmonics = 22; note("c2 >".fast(2)) .sound("saw") .partials(new Array(numHarmonics).fill(1)) .\_scope() which acts as a spectral filter. Or: note("c2 >").fast(2) .sound("user") .partials(new Array(50).fill(0) .map((\_, idx) => ((-1) \*\* (idx + 1)) / (idx + 1)) ) .\_scope() which recovers a familiar waveform. `partials` is also compatible with pattern functions designed to produce lists, like `randL` or `binaryL`: note("c2 >").fast(2) .sound("user") .partials(randL(10)) .\_scope() and with lists _of_ patterns: note("c2 >".fast(4)) .sound("user") .partials(\[1, 0, "0 1", "0 1 0.3", rand\]) .\_scope() Note that the first value in the `partials` array controls the magnitude of the fundamental harmonic rather than the DC offset, which is fixed at 0. #### Phases[](https://strudel.cc/learn/synths/#phases) Earlier, we mentioned that periodic waveforms can be broken into a set of harmonics above a fundamental frequency. Each harmonic has two defining properties: its magnitude (how loud it is) and its phase, which determines where in its cycle that sine wave starts when the waveform is built. These phases too can be declared in Strudel and can give your sounds interesting depth. s("saw").seg(16).n(irand(12)).scale("F1:minor") .penv(48).panchor(0).pdec(0.05) .delay(0.25).room(0.25) .compressor(-20).vib(0.3) .partials(randL(200)) .phases(randL(200)) Vibrato[](https://strudel.cc/learn/synths/#vibrato) ---------------------------------------------------- ### vib[](https://strudel.cc/learn/synths/#vib) Synonyms: `vibrato, v` Applies a vibrato to the frequency of the oscillator. * frequency (number|Pattern): of the vibrato in hertz note("a e") .vib("<.5 1 2 4 8 16>") .\_scope() // change the modulation depth with ":" note("a e") .vib("<.5 1 2 4 8 16>:12") .\_scope() ### vibmod[](https://strudel.cc/learn/synths/#vibmod) Synonyms: `vmod` Sets the vibrato depth in semitones. Only has an effect if `vibrato` | `vib` | `v` is is also set * depth (number|Pattern): of vibrato (in semitones) note("a e").vib(4) .vibmod("<.25 .5 1 2 12>") .\_scope() // change the vibrato frequency with ":" note("a e") .vibmod("<.25 .5 1 2 12>:8") .\_scope() FM Synthesis[](https://strudel.cc/learn/synths/#fm-synthesis) -------------------------------------------------------------- FM Synthesis is a technique that changes the frequency of a basic waveform rapidly to alter the timbre. You can use fm with any of the above waveforms, although the below examples all use the default triangle wave. ### fm[](https://strudel.cc/learn/synths/#fm) Synonyms: `fmi` Sets the Frequency Modulation of the synth. Controls the modulation index, which defines the brightness of the sound. * brightness (number|Pattern): modulation index note("c e g b g e") .fm("<0 1 2 8 32>") .\_scope() ### fmh[](https://strudel.cc/learn/synths/#fmh) Sets the Frequency Modulation Harmonicity Ratio. Controls the timbre of the sound. Whole numbers and simple ratios sound more natural, while decimal numbers and complex ratios sound metallic. * harmonicity (number|Pattern): note("c e g b g e") .fm(4) .fmh("<1 2 1.5 1.61>") .\_scope() ### fmattack[](https://strudel.cc/learn/synths/#fmattack) Attack time for the FM envelope: time it takes to reach maximum modulation * time (number|Pattern): attack time note("c e g b g e") .fm(4) .fmattack("<0 .05 .1 .2>") .\_scope() ### fmdecay[](https://strudel.cc/learn/synths/#fmdecay) Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase. * time (number|Pattern): decay time note("c e g b g e") .fm(4) .fmdecay("<.01 .05 .1 .2>") .fmsustain(.4) .\_scope() ### fmsustain[](https://strudel.cc/learn/synths/#fmsustain) Sustain level for the FM envelope: how much modulation is applied after the decay phase * level (number|Pattern): sustain level note("c e g b g e") .fm(4) .fmdecay(.1) .fmsustain("<1 .75 .5 0>") .\_scope() ### fmenv[](https://strudel.cc/learn/synths/#fmenv) Ramp type of fm envelope. Exp might be a bit broken.. * type (number|Pattern): lin | exp note("c e g b g e") .fm(4) .fmdecay(.2) .fmsustain(0) .fmenv("") .\_scope() Wavetable Synthesis[](https://strudel.cc/learn/synths/#wavetable-synthesis) ---------------------------------------------------------------------------- Strudel can also use the sampler to load custom waveforms as a replacement of the default waveforms used by WebAudio for the base synth. A default set of more than 1000 wavetables is accessible by default (coming from the [AKWF](https://www.adventurekid.se/akrt/waveforms/adventure-kid-waveforms/) set). You can also import/use your own. A wavetable is a one-cycle waveform, which is then repeated to create a sound at the desired frequency. It is a classic but very effective synthesis technique. Any sample preceded by the `wt_` prefix will be loaded as a wavetable. This means that the `loop` argument will be set to `1` by default. You can scan over the wavetable by using `loopBegin` and `loopEnd` as well. samples('bubo:waveforms'); note("<\[g3,b3,e4\]!2 \[a3,c3,e4\] \[b3,d3,f#4\]>") .n("<1 2 3 4 5 6 7 8 9 10>/2").room(0.5).size(0.9) .s('wt\_flute').velocity(0.25).often(n => n.ply(2)) .release(0.125).decay("<0.1 0.25 0.3 0.4>").sustain(0) .cutoff(2000).cutoff("<1000 2000 4000>").fast(4) .\_scope() ZZFX[](https://strudel.cc/learn/synths/#zzfx) ---------------------------------------------- The “Zuper Zmall Zound Zynth” [ZZFX](https://github.com/KilledByAPixel/ZzFX) is also integrated in strudel. Developed by [Frank Force](https://frankforce.com/) , it is a synth and FX engine originally intended to be used for size coding games. It has 20 parameters in total, here is a snippet that uses all: note("c2 eb2 f2 g2") // also supports freq .s("{z\_sawtooth z\_tan z\_noise z\_sine z\_square}%4") .zrand(0) // randomization // zzfx envelope .attack(0.001) .decay(0.1) .sustain(.8) .release(.1) // special zzfx params .curve(1) // waveshape 1-3 .slide(0) // +/- pitch slide .deltaSlide(0) // +/- pitch slide (?) .noise(0) // make it dirty .zmod(0) // fm speed .zcrush(0) // bit crush 0 - 1 .zdelay(0) // simple delay .pitchJump(0) // +/- pitch change after pitchJumpTime .pitchJumpTime(0) // >0 time after pitchJump is applied .lfo(0) // >0 resets slide + pitchJump + sets tremolo speed .tremolo(0.5) // 0-1 lfo volume modulation amount //.duration(.2) // overwrite strudel event duration //.gain(1) // change volume .\_scope() // vizualise waveform (not zzfx related) Note that you can also combine zzfx with all the other audio fx (next chapter). Next up: [Audio Effects](https://strudel.cc/learn/effects/) 
 --- # Recipes 🌀 Strudel Recipes[](https://strudel.cc/recipes/recipes/#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[](https://strudel.cc/recipes/recipes/#arpeggios) ----------------------------------------------------------- An arpeggio is when the notes of a chord are played in sequence. We can either write the notes by hand: note("c eb g c4") .clip(2).s("gm\_electric\_guitar\_clean") 
or use scales: n("0 2 4 7").scale("C:minor") .clip(2).s("gm\_electric\_guitar\_clean") 
or chord symbols: n("0 1 2 3").chord("Cm").mode("above:c3").voicing() .clip(2).s("gm\_electric\_guitar\_clean") 
using off: "0" .off(1/3, add(2)) .off(1/2, add(4)) .n() .scale("C:minor") .s("gm\_electric\_guitar\_clean") Chopping Breaks[](https://strudel.cc/recipes/recipes/#chopping-breaks) ----------------------------------------------------------------------- A sample can be looped and chopped like this: samples('github:yaxu/clean-breaks') s("amen/4").fit().chop(32) 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: samples('github:yaxu/clean-breaks') s("amen/4").fit().chop(16).cut(1) .sometimesBy(.5, ply("2")) .sometimesBy(.25, mul(speed("-1"))) If we want to specify the order of samples, we can replace `chop` with `slice`: samples('github:yaxu/clean-breaks') s("amen/4").fit() .slice(8, "<0 1 2 3 4\*2 5 6 \[6 7\]>\*2") .cut(1).rarely(ply("2")) If we use `splice` instead of `slice`, the speed adjusts to the duration of the event: samples('github:yaxu/clean-breaks') s("amen") .splice(8, "<0 1 2 3 4\*2 5 6 \[6 7\]>\*2") .cut(1).rarely(ply("2")) Note that we don’t need `fit`, because `splice` will do that by itself. Filter Envelopes[](https://strudel.cc/recipes/recipes/#filter-envelopes) ------------------------------------------------------------------------- Using `lpenv`, we can make the filter move: note("g1 bb1 d2") .s("sawtooth") .lpf(400).lpenv(4) .scope() The type of envelope depends on the methods you’re setting. Let’s set `lpa`: note("g1 bb1 d2") .s("sawtooth").lpq(8) .lpf(400).lpa(.2).lpenv(4) .scope() Now the filter is attacking, rather than decaying as before (decay is the default). We can also do both note("g1 bb1 d2") .s("sawtooth").lpq(8) .lpf(400).lpa(.1).lpd(.1).lpenv(4) .scope() You can play around with `lpa` | `lpd` | `lps` | `lpd` to see what the filter envelope will do. Layering Sounds[](https://strudel.cc/recipes/recipes/#layering-sounds) ----------------------------------------------------------------------- We can layer sounds by separating them with ”,”: note("") .s("sawtooth, square") // <------ .scope() We can control the gain of individual sounds like this: note("") .s("sawtooth, square:0:.5") // <--- "name:number:gain" .scope() For more control over each voice, we can use `layer`: note("").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[](https://strudel.cc/recipes/recipes/#oscillator-detune) --------------------------------------------------------------------------- We can fatten a sound by adding a detuned version to itself: note("") .add(note("0,.1")) // <------ chorus .s("sawtooth").scope() Try out different values, or add another voice! Polyrhythms[](https://strudel.cc/recipes/recipes/#polyrhythms) --------------------------------------------------------------- Here is a simple example of a polyrhythm: s("bd\*2,hh\*3") A polyrhythm is when 2 different tempos happen at the same time. Polymeter[](https://strudel.cc/recipes/recipes/#polymeter) ----------------------------------------------------------- This is a polymeter: s("\*4") A polymeter is when 2 different bar lengths play at the same tempo. Phasing[](https://strudel.cc/recipes/recipes/#phasing) ------------------------------------------------------- This is a phasing: note("\*\[6,6.1\]").piano() Phasing happens when the same sequence plays at slightly different tempos. Running through samples[](https://strudel.cc/recipes/recipes/#running-through-samples) --------------------------------------------------------------------------------------- Using `run` with `n`, we can rush through a sample bank: samples('bubo:fox') n(run(8)).s("ftabla") 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`. samples('bubo:fox') n(run(8)).s("ftabla").early(2/8) Let’s add some randomness: samples('bubo:fox') n(run(8)).s("ftabla").early(2/8) .sometimes(mul(speed("1.5"))) Tape Warble[](https://strudel.cc/recipes/recipes/#tape-warble) --------------------------------------------------------------- We can emulate a pitch warbling effect like this: note("\*8") .add(note(perlin.range(0,.5))) // <------ warble .clip(2).s("gm\_electric\_guitar\_clean") Sound Duration[](https://strudel.cc/recipes/recipes/#sound-duration) --------------------------------------------------------------------- There are a number of ways to change the sound duration. Using clip: note("f ab bb c") .clip("<2 1 .5 .25>") The value of clip is relative to the duration of each event. We can also create overlaps using release: note("f ab bb c") .release("<2 1 .5 .25>") This will smoothly fade out each sound for the given number of seconds. We could also make the notes shorter by using a decay envelope: note("f ab bb c") .decay("<2 1 .5 .25>") When using samples, we also have `.end` to cut relative to the sample length: s("oh\*4").end("<1 .5 .25 .1>") Compare that to clip: s("oh\*4").clip("<1 .5 .25 .1>") or decay: s("oh\*4").decay("<1 .5 .25 .1>") Wavetable Synthesis[](https://strudel.cc/recipes/recipes/#wavetable-synthesis) ------------------------------------------------------------------------------- You can loop a sample with `loop` / `loopEnd`: note("").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: samples('github:bubobubobubobubo/dough-waveforms') note("c eb g bb").s("wt\_dbass").clip(2) Running through different wavetables can also give interesting variations: samples('github:bubobubobubobubo/dough-waveforms') note("c2\*8").s("wt\_dbass").n(run(8)).fast(2) 
adding a filter envelope + reverb: samples('github:bubobubobubobubo/dough-waveforms') note("c2\*8").s("wt\_dbass").n(run(8)) .lpf(perlin.range(100,1000).slow(8)) .lpenv(-3).lpa(.1).room(.5).fast(2) --- # MIDI, OSC & MQTT 🌀 Strudel MIDI, OSC and MQTT[](https://strudel.cc/learn/input-output/#midi-osc-and-mqtt) =============================================================================== Normally, Strudel is used to pattern sound, using its own ‘[web audio](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) ’-based synthesiser called [SuperDough](https://codeberg.org/uzu/strudel/src/branch/main/packages/superdough) . It is also possible to pattern other things with Strudel, such as software and hardware synthesisers with MIDI, other software using Open Sound Control/OSC (including the [SuperDirt](https://github.com/musikinformatik/SuperDirt/) synthesiser commonly used with Strudel’s sibling [TidalCycles](https://tidalcycles.org/) ), or the MQTT ‘internet of things’ protocol. MIDI[](https://strudel.cc/learn/input-output/#midi) ==================================================== Strudel supports MIDI without any additional software (thanks to [webmidi](https://npmjs.com/package/webmidi) ), just by adding methods to your pattern: midiin(inputName?)[](https://strudel.cc/learn/input-output/#midiininputname) ----------------------------------------------------------------------------- MIDI input: Opens a MIDI input port to receive MIDI control change messages. * input (string|number): MIDI device name or index defaulting to 0 let cc = await midin('IAC Driver Bus 1') note("c a f e").lpf(cc(0).range(0, 1000)).lpq(cc(1).range(0, 10)).sound("sawtooth") midi(outputName?,options?)[](https://strudel.cc/learn/input-output/#midioutputnameoptions) ------------------------------------------------------------------------------------------- Either connect a midi device or use the IAC Driver (Mac) or Midi Through Port (Linux) for internal midi messages. If no outputName is given, it uses the first midi output it finds. $: chord("").voicing().midi('IAC Driver') In the console, you will see a log of the available MIDI devices as soon as you run the code, e.g. `Midi connected! Using "Midi Through Port-0".` The `.midi()` function accepts an options object with the following properties: $: note("d e c a f").midi('IAC Driver', { isController: true, midimap: 'default'}) Available Options | Option | Type | Default | Description | | --- | --- | --- | --- | | isController | boolean | false | When true, disables sending note messages. Useful for MIDI controllers | | latencyMs | number | 34 | Latency in milliseconds to align MIDI with audio engine | | noteOffsetMs | number | 10 | Offset in milliseconds for note-off messages to prevent glitching | | midichannel | number | 1 | Default MIDI channel (1-16) | | velocity | number | 0.9 | Default note velocity (0-1) | | gain | number | 1 | Default gain multiplier for velocity (0-1) | | midimap | string | ’default’ | Name of MIDI mapping to use for control changes | | midiport | string/number | \- | MIDI device name or index | ### midiport(outputName)[](https://strudel.cc/learn/input-output/#midiportoutputname) Selects the MIDI output device to use, pattern can be used to switch between devices. $: midiport('IAC Driver'); $: note('c a f e').midiport('<0 1 2 3>').midi(); MIDI port: Sets the MIDI port for the event. * port (number|Pattern): MIDI port note("c a f e").midiport("<0 1 2 3>").midi() midichan(number)[](https://strudel.cc/learn/input-output/#midichannumber) -------------------------------------------------------------------------- Selects the MIDI channel to use. If not used, `.midi` will use channel 1 by default. midicmd(command)[](https://strudel.cc/learn/input-output/#midicmdcommand) -------------------------------------------------------------------------- `midicmd` sends MIDI system real-time messages to control timing and transport on MIDI devices. It supports the following commands: * `clock`/`midiClock` - Sends MIDI timing clock messages * `start` - Sends MIDI start message * `stop` - Sends MIDI stop message * `continue` - Sends MIDI continue message // You can control the clock with a pattern and ensure it starts in sync when the repl begins. // Note: It might act unexpectedly if MIDI isn’t set up initially. $:stack( midicmd("clock\*48,/2").midi('IAC Driver') ) control, ccn && ccv[](https://strudel.cc/learn/input-output/#control-ccn--ccv) ------------------------------------------------------------------------------- * `control` sends MIDI control change messages to your MIDI device. * `ccn` sets the cc number. Depends on your synths midi mapping * `ccv` sets the cc value. normalized from 0 to 1. note("c a f e").control(\[74, sine.slow(4)\]).midi() note("c a f e").ccn(74).ccv(sine.slow(4)).midi() In the above snippet, `ccn` is set to 74, which is the filter cutoff for many synths. `ccv` is controlled by a saw pattern. Having everything in one pattern, the `ccv` pattern will be aligned to the note pattern, because the structure comes from the left by default. But you can also control cc messages separately like this: $: note("c a f e").midi() $: ccv(sine.segment(16).slow(4)).ccn(74).midi() Instead of setting `ccn` and `ccv` directly, you can also create mappings with `midimaps`: midimaps[](https://strudel.cc/learn/input-output/#midimaps) ------------------------------------------------------------ Adds midimaps to the registry. Inside each midimap, control names (e.g. lpf) are mapped to cc numbers. midimaps({ mymap: { lpf: 74 } }) $: note("c a f e") .lpf(sine.slow(4)) .midimap('mymap') .midi() midimaps({ mymap: { lpf: { ccn: 74, min: 0, max: 20000, exp: 0.5 } }}) $: note("c a f e") .lpf(sine.slow(2).range(400,2000)) .midimap('mymap') .midi() defaultmidimap[](https://strudel.cc/learn/input-output/#defaultmidimap) ------------------------------------------------------------------------ configures the default midimap, which is used when no "midimap" port is set defaultmidimap({ lpf: 74 }) $: note("c a f e").midi(); $: lpf(sine.slow(4).segment(16)).midi(); progNum (Program Change)[](https://strudel.cc/learn/input-output/#prognum-program-change) ------------------------------------------------------------------------------------------ `progNum` sends MIDI program change messages to switch between different presets/patches on your MIDI device. Program change values should be numbers between 0 and 127. // Switch between programs 0 and 1 every cycle progNum("<0 1>").midi() // Play notes while changing programs note("c3 e3 g3").progNum("<0 1 2>").midi() Program change messages are useful for switching between different instrument sounds or presets during a performance. The exact sound that each program number maps to depends on your MIDI device’s configuration. sysex, sysexid && sysexdata (System Exclusive Message)[](https://strudel.cc/learn/input-output/#sysex-sysexid--sysexdata-system-exclusive-message) --------------------------------------------------------------------------------------------------------------------------------------------------- `sysex` sends MIDI System Exclusive (SysEx) messages to your MIDI device. ysEx messages are device-specific commands that allow deeper control over synthesizer parameters. The value should be an array of numbers between 0-255 representing the SysEx data bytes. // Send a simple SysEx message let id = 0x43; //Yamaha //let id = "0x00:0x20:0x32"; //Behringer ID can be an array of numbers let data = "0x79:0x09:0x11:0x0A:0x00:0x00"; // Set NSX-39 voice to say "Aa" $: note("c a f e").sysex(id, data).midi(); $: note("c a f e").sysexid(id).sysexdata(data).midi(); The exact format of SysEx messages depends on your MIDI device’s specification. Consult your device’s MIDI implementation guide for details on supported SysEx messages. midibend && miditouch[](https://strudel.cc/learn/input-output/#midibend--miditouch) ------------------------------------------------------------------------------------ `midibend` sets MIDI pitch bend (-1 - 1) `miditouch` sets MIDI key after touch (0-1) note("c a f e").midibend(sine.slow(4).range(-0.4,0.4)).midi() note("c a f e").miditouch(sine.slow(4).range(0,1)).midi() OSC/SuperDirt/StrudelDirt[](https://strudel.cc/learn/input-output/#oscsuperdirtstrudeldirt) ============================================================================================ In TidalCycles, sound is usually generated using [SuperDirt](https://github.com/musikinformatik/SuperDirt/) , which runs inside SuperCollider. Strudel also supports using SuperDirt, although it requires installing some additional software. There is also [StrudelDirt](https://github.com/daslyfe/StrudelDirt) which is SuperDirt with some optimisations for working with Strudel. (A longer term aim is to merge these optimisations back into mainline SuperDirt) Prequisites[](https://strudel.cc/learn/input-output/#prequisites) ------------------------------------------------------------------ To get SuperDirt to work with Strudel, you need to 1. install SuperCollider + sc3 plugins, see [Tidal Docs](https://tidalcycles.org/docs/) (Install Tidal) for more info. 2. install SuperDirt, or the [StrudelDirt](https://github.com/daslyfe/StrudelDirt) fork which is optimised for use with Strudel 3. install [node.js](https://nodejs.org/en/) 4. download [Strudel Repo](https://codeberg.org/uzu/strudel/) (or git clone, if you have git installed) 5. run `pnpm i` in the strudel directory 6. run `pnpm run osc` to start the osc server, which forwards OSC messages from Strudel REPL to SuperCollider Now you’re all set! Usage[](https://strudel.cc/learn/input-output/#usage) ------------------------------------------------------ 1. Start SuperCollider, either using SuperCollider IDE or by running `sclang` in a terminal 2. Open the [Strudel REPL](https://strudel.cc/#cygiYmQgc2QiKS5vc2MoKQ%3D%3D) 
or test it here: If you now hear sound, congratulations! If not, you can get help on the [#strudel channel in the TidalCycles discord](https://discord.com/invite/HGEdXmRkzT) . Note: if you have the ‘Audio Engine Target’ in settings set to ‘OSC’, you do not need to add .osc() to the end of your pattern. ### Pattern.osc[](https://strudel.cc/learn/input-output/#patternosc) Sends each hap as an OSC message, which can be picked up by SuperCollider or any other OSC-enabled software. For more info, read [MIDI & OSC in the docs](https://strudel.cc/learn/input-output/) SuperDirt Params[](https://strudel.cc/learn/input-output/#superdirt-params) ---------------------------------------------------------------------------- Please refer to [Tidal Docs](https://tidalcycles.org/) for more info. But can we use Strudel [offline](https://strudel.cc/learn/pwa/) ? MQTT[](https://strudel.cc/learn/input-output/#mqtt) ==================================================== MQTT is a lightweight network protocol, designed for ‘internet of things’ devices. For use with strudel, you will need access to an MQTT server known as a ‘broker’ configured to accept secure ‘websocket’ connections. You could run one yourself (e.g. by running [mosquitto](https://mosquitto.org/) ), although getting an SSL certificate that your web browser will trust might be a bit tricky for those without systems administration experience. Alternatively, you can use [a public broker](https://www.hivemq.com/mqtt/public-mqtt-broker/) . Strudel does not yet support receiving messages over MQTT, only sending them. Usage[](https://strudel.cc/learn/input-output/#usage-1) -------------------------------------------------------- The following example shows how to send a pattern to an MQTT broker: Other software can then receive the messages. For example using the [mosquitto](https://mosquitto.org/) commandline client tools: > mosquitto_sub -h mqtt.eclipseprojects.io -p 1883 -t "/strudel-pattern" > hello > world > hello > world > ... Control patterns will be encoded as JSON, for example: Will send messages like the following: {"s":"sax","speed":2} {"s":"sax","speed":2} {"s":"sax","speed":3} {"s":"sax","speed":2} ... Libraries for receiving MQTT are available for many programming languages. --- # Offline 🌀 Strudel Using Strudel Offline[](https://strudel.cc/learn/pwa/#using-strudel-offline) ============================================================================= You can use Strudel even without a network! When you first visit the [Strudel REPL](https://strudel.cc/) , your browser will download the whole web app including documentation. When the download is finished (<1MB), you can visit the website even when offline, getting the downloaded website instead of the online one. When the site gets updated, your browser will download that update on the next online visit. When an update is available, the site will refresh after the download is finished. This works because Strudel is implemented as progessive web app (using [Vite PWA](https://vite-pwa-org.netlify.app/) ). Samples[](https://strudel.cc/learn/pwa/#samples) ------------------------------------------------- While the browser will download the app itself, samples are only downloaded when you’re actively using them. So to make sure a specific set of samples is available when offline, just use them. Also, only samples from these domains will be cached for offline use: * `https://raw.githubusercontent.com/*` for samples uploaded to github * `https://freesound.org/*` / `https://cdn.freesound.org/*` for freesound * `https://shabda.ndre.gr/.*` for shabda Inspecting / Clearing Cache[](https://strudel.cc/learn/pwa/#inspecting--clearing-cache) ---------------------------------------------------------------------------------------- You can view all cached files in your browser. ### Firefox[](https://strudel.cc/learn/pwa/#firefox) * Open the Developer Tools (`Tools > Web Developer > Web Developer Tools`) * go to `Storage` tab and expand `Cache Storage > https://strudel.cc`. * or go to the `Application` tab and view the latest updates in `Service Workers` ### Chromium based Browsers[](https://strudel.cc/learn/pwa/#chromium-based-browsers) * Open Developer Tools (`Right Click > Inspect`) * go to the `Application` tab * view downloaded files under `Cache > Cache Storage` * view the latest updates in `Service Workers` Strudel Standalone App[](https://strudel.cc/learn/pwa/#strudel-standalone-app) ------------------------------------------------------------------------------- You can also install Strudel as a standalone app on most devices. A standalone app has its own desktop / homescreen icon and launches in a separate window, without the browser ui. ![Strudel on MacOS](https://strudel.cc/pwa/strudel-macos.png) Strudel on MacOS ### Desktop[](https://strudel.cc/learn/pwa/#desktop) With a chromium based browser: 1. go to the [Strudel REPL](https://strudel.cc/) . 2. on the right of the adress bar, click `install Strudel REPL` 3. the REPL should now run as a standalone chromium app Without a chromium based browser, you can use [nativefier](https://github.com/nativefier/nativefier) to generate a desktop app: 1. make sure you have NodeJS installed 2. run `npx nativefier strudel.cc` ![Strudel on Linux](https://strudel.cc/pwa/strudel-linux.png) Strudel on Linux ### iOS[](https://strudel.cc/learn/pwa/#ios) 1. open to the [Strudel REPL](https://strudel.cc/) in safari 2. press the share icon and tab `Add to homescreen` 3. You should now have a strudel app icon that opens the repl in full screen ### Android[](https://strudel.cc/learn/pwa/#android) 1. open to the [Strudel REPL](https://strudel.cc/) 2. Tab the install button at the bottom Ok, what are [Patterns](https://strudel.cc/technical-manual/patterns/) all about? --- # Patterns 🌀 Strudel Patterns[](https://strudel.cc/technical-manual/patterns/#patterns) =================================================================== Patterns are the essence of Tidal. Its patterns are abstract entities that represent flows of time as functions, adapting a technique called pure functional reactive programming. Taking a time span as its input, a Pattern can output a set of events that happen within that time span. It depends on the structure of the Pattern how the events are located in time. From now on, this process of generating events from a time span will be called **querying**. Example: const pattern = sequence("c3", \["e3", "g3"\]) const events = pattern.queryArc(0, 1) console.log(events.map((e) => e.show())) silence In this example, we create a pattern using the `sequence` function and **query** it for the time span from `0` to `1`. Those numbers represent units of time called **cycles**. The length of one cycle depends on the tempo, which defaults to one cycle per second. The resulting events are: [\ '[ 0/1 -> 1/2 | c3 ]', //\ '[ 1/2 -> 3/4 | e3 ]',\ '[ 3/4 -> 1/1 | g3 ]',\ ]; Each event has a value, a begin time and an end time, where time is represented as a fraction. In the above case, the events are placed in sequential order, where c3 takes the first half, and e3 and g3 together take the second half. This temporal placement is the result of the `sequence` function, which divides its arguments equally over one cycle. If an argument is an array, the same rule applies to that part of the cycle. In the example, e3 and g3 are divided equally over the second half of the whole cycle. Note that the query function is not just a way to access a pattern, but true to the principles of functional programming, is the pattern itself. This means that in theory there is no way to change a pattern, it is opaque as a pure function. In practice though, Strudel and Tidal are all about transforming patterns, so how is this done? The answer is, by replacing the pattern with a new one, that calls the old one. This new one is only able to manipulate the query before passing it to the old pattern, and manipulate the results from it before returning them to caller. But, this is enough to support all the temporal and structural manipulations provided by Strudel (and Tidal’s) extensive library of functions. The above examples do not represent how Strudel is used in practice. In the live coding editor, the user only has to type in the pattern itself, the querying will be handled by the scheduler. The scheduler will repeatedly query the pattern for events, which are then scheduled as sound synthesis or other event triggers. Can we [align](https://strudel.cc/technical-manual/alignment/) patterns? --- # Mini Notation 🌀 Strudel Mini-notation[](https://strudel.cc/learn/mini-notation/#mini-notation) ======================================================================= Just like [Tidal Cycles](https://tidalcycles.org/) , Strudel uses a so called “Mini-Notation”, which is a custom language that is designed for writing rhythmic patterns using little amounts of text. Note[](https://strudel.cc/learn/mini-notation/#note) ----------------------------------------------------- This page just explains the entirety of the Mini-Notation syntax. If you are just getting started with Strudel, you can learn the basics of the Mini-Notation in a more practical manner in the [workshop](https://strudel.cc/workshop/first-sounds/) . After that, you can come back here if you want to understand every little detail. Example[](https://strudel.cc/learn/mini-notation/#example) ----------------------------------------------------------- Before diving deeper into the details, here is a flavour of how the Mini-Notation looks like: note(\`< \[e5 \[b4 c5\] d5 \[c5 b4\]\] \[a4 \[a4 c5\] e5 \[d5 c5\]\] \[b4 \[~ c5\] d5 e5\] \[c5 a4 a4 ~\] \[\[~ d5\] \[~ f5\] a5 \[g5 f5\]\] \[e5 \[~ c5\] e5 \[d5 c5\]\] \[b4 \[b4 c5\] d5 e5\] \[c5 a4 a4 ~\] , \[\[e2 e3\]\*4\] \[\[a2 a3\]\*4\] \[\[g#2 g#3\]\*2 \[e2 e3\]\*2\] \[a2 a3 a2 a3 a2 a3 b1 c2\] \[\[d2 d3\]\*4\] \[\[c2 c3\]\*4\] \[\[b1 b2\]\*2 \[e2 e3\]\*2\] \[\[a1 a2\]\*4\] >\`) Mini Notation Format[](https://strudel.cc/learn/mini-notation/#mini-notation-format) ------------------------------------------------------------------------------------- The snippet above is enclosed in backticks (\`), which allows you to write multi-line strings. You can also use regular double quotes (`"`) for single line mini-notation, as we have done already. If you do just want to get a regular string that is _not_ parsed as mini-notation, use single quotes (`'`). Sequences of events in a cycle[](https://strudel.cc/learn/mini-notation/#sequences-of-events-in-a-cycle) --------------------------------------------------------------------------------------------------------- We can play more notes by separating them with spaces: note("c e g b") Here, those four notes are squashed into one cycle, so each note is a quarter second long. Try adding or removing notes and notice how the tempo changes! note("c d e f g a b") Note that the overall duration of time does not change, and instead each note length decreases. This is a key idea, as it illustrates the ‘Cycle’ in TidalCycles! Each space-separated note in this sequence is an _event_. The time duration of each event is based on the speed or tempo of the cycle, and how many events are present. Taking the two examples above, we have four and eight events respectively, and since they have the same cycle duration, they each have to fit their events inside the same amount of time. This is perhaps counter-intuitive if you are used to adding notes in a sequencer or piano roll and the overall length increasing. But, it will begin to make sense as we go through more elements of mini-notation. Multiplication[](https://strudel.cc/learn/mini-notation/#multiplication) ------------------------------------------------------------------------- A sequence can be sped up by multiplying it by a number using the asterisk symbol (`*`): note("\[e5 b4 d5 c5\]\*2") The multiplication by two here means that the sequence will play twice per cycle. Multiplications can also be decimal (`*2.75`): note("\[e5 b4 d5 c5\]\*2.75") Division[](https://strudel.cc/learn/mini-notation/#division) ------------------------------------------------------------- Contrary to multiplication, division can slow the sequence down by enclosing it in brackets and dividing it by a number (`/2`): note("\[e5 b4 d5 c5\]/2") The division by two means that the sequence will be played over the course of two cycles. You can also use decimal numbers for any tempo you like (`/2.75`). note("\[e5 b4 d5 c5\]/2.75") Angle Brackets[](https://strudel.cc/learn/mini-notation/#angle-brackets) ------------------------------------------------------------------------- Using angle brackets `<>`, we can define the sequence length based on the number of events: note("") The above snippet is the same as: note("\[e5 b4 d5 c5\]/4") The advantage of the angle brackets, is that we can add more events without needing to change the number at the end. note("") note("") This is more similar to traditional music sequencers and piano rolls, where adding a note increases the perceived overall duration. We can also play a certain number of notes per cycle by using angle brackets with multiplication: note("\*8") Now we are playing 8 notes per cycle! Subdividing time with bracket nesting[](https://strudel.cc/learn/mini-notation/#subdividing-time-with-bracket-nesting) ----------------------------------------------------------------------------------------------------------------------- To create more interesting rhythms, you can _nest_ or _enclose_ sequences (put sequences inside sequences) with brackets `[]`, like this: Compare the difference between the following: note("e5 b4 c5 d5 c5 b4") note("e5 \[b4 c5\] d5 c5 b4") note("e5 \[b4 c5\] d5 \[c5 b4\]") note("e5 \[b4 c5\] d5 \[c5 b4 d5 e5\]") note("e5 \[b4 c5\] d5 \[c5 b4 \[d5 e5\]\]") What’s going on here? When we nest/enclose multiple events inside brackets (`[]`), their duration becomes the length of one event in the outer sequence. This is a very simple change to make, but it has profound consequences. Remember what we said earlier about how the cycles in tidal stay the same length, and the individual event lengths are divided up in this cycle? Well, what this means is that in TidalCycles, not only can you divide time any way you want, and you can also subdivide time any way you want! Rests[](https://strudel.cc/learn/mini-notation/#rests) ------------------------------------------------------- The ”~” represents a rest, and will create silence between other events: note("\[b4 \[~ c5\] d5 e5\]") Parallel / polyphony[](https://strudel.cc/learn/mini-notation/#parallel--polyphony) ------------------------------------------------------------------------------------ Using commas, we can play chords. The following are the same: note("\[g3,b3,e4\]") note("g3,b3,e4") But to play multiple chords in a sequence, we have to wrap them in brackets: note("<\[g3,b3,e4\] \[a3,c3,e4\] \[b3,d3,f#4\] \[b3,e4,g4\]>\*2") Elongation[](https://strudel.cc/learn/mini-notation/#elongation) ----------------------------------------------------------------- With the ”@” symbol, we can specify temporal “weight” of a sequence child: note("<\[g3,b3,e4\]@2 \[a3,c3,e4\] \[b3,d3,f#4\]>\*2") Here, the first chord has a weight of 2, making it twice the length of the other chords. The default weight is 1. Replication[](https://strudel.cc/learn/mini-notation/#replication) ------------------------------------------------------------------- Using ”!” we can repeat without speeding up: note("<\[g3,b3,e4\]!2 \[a3,c3,e4\] \[b3,d3,f#4\]>\*2") Randomness[](https://strudel.cc/learn/mini-notation/#randomness) ----------------------------------------------------------------- Events with a ”?” placed after them will have a 50% chance of being removed from the pattern: note("\[g3,b3,e4\]\*8?") Adding a number between 0 and 1 after the ”?” will affect the likelihood of the event being removed. For example, events with “?0.1” placed after them will have a 10% chance of being removed: note("\[g3,b3,e4\]\*8?0.1") Events separated by a ”|” will be chosen from at random: note("\[g3,b3,e4\] | \[a3,c3,e4\] | \[b3,d3,f#4\]") Mini-notation review[](https://strudel.cc/learn/mini-notation/#mini-notation-review) ------------------------------------------------------------------------------------- To recap what we’ve learned so far, compare the following patterns: note("\*2") note("<\[g3,b3,e4\] \[a3,c3,e4\] \[b3,d3,f#4\]>\*2") note("<\[g3,b3,e4\]/2 \[a3,c3,e4\] \[b3,d3,f#4\]>\*2") note("<\[g3,b3,e4\]\*2 \[a3,c3,e4\] \[b3,d3,f#4\]>\*2") note("<\[g3,b3,e4\] \_ \[a3,c3,e4\] \[b3,d3,f#4\]>\*2") note("<\[g3,b3,e4\]@2 \[a3,c3,e4\] \[b3,d3,f#4\]>\*2") note("<\[g3,b3,e4\]!2 \[a3,c3,e4\] \[b3,d3,f#4\]>\*2") note("<\[g3,b3,e4\]? \[a3,c3,e4\] \[b3,d3,f#4\]>\*2") note("<\[g3|b3|e4\] \[a3,c3,e4\] \[b3,d3,f#4\]>\*2") Euclidian rhythms[](https://strudel.cc/learn/mini-notation/#euclidian-rhythms) ------------------------------------------------------------------------------- Using round brackets after an event, we can create rhythmical sub-divisions based on three parameters: `beats`, `segments` and `offset`. This algorithm can be found in many different types of music software, and is often referred to as a [Euclidean rhythm](https://en.wikipedia.org/wiki/Euclidean_rhythm) sequencer, after computer scientist Godfriend Toussaint. Why is it interesting? Well, consider the following simple example: s("bd(3,8,0)") Sound familiar? This is a popular Euclidian rhythm going by various names, such as “Pop Clave”. These rhythms can be found in all musical cultures, and the Euclidian rhythm algorithm allows us to express them extremely easily. Writing this rhythm out in full require describing: s("bd ~ ~ bd ~ ~ bd ~") But using the Euclidian rhythm notation, we only need to express “3 beats over 8 segments, starting on position 1”. This makes it easy to write patterns with interesting rhythmic structures and variations that still sound familiar: note("e5(2,8) b4(3,8) d5(2,8) c5(3,8)").slow(2) Note that since the example above does not use the third `offset` parameter, it can be written simply as `"(3,8)"`. s("bd(3,8)") Let’s look at those three parameters in detail. ### Beats[](https://strudel.cc/learn/mini-notation/#beats) `beats`: the first parameter controls how may beats will be played. Compare these: s("bd(2,8)") s("bd(5,8)") s("bd(7,8)") ### Segments[](https://strudel.cc/learn/mini-notation/#segments) `segments`: the second parameter controls the total amount of segments the beats will be distributed over: s("bd(3,4)") s("bd(3,8)") s("bd(3,13)") ### Offsets[](https://strudel.cc/learn/mini-notation/#offsets) `offset`: the third (optional) parameter controls the starting position for distributing the beats. We need a secondary rhythm to hear the difference: s("bd(3,8,0), hh cp") s("bd(3,8,3), hh cp") s("bd(3,8,5), hh cp") Mini-notation exercise[](https://strudel.cc/learn/mini-notation/#mini-notation-exercise) ----------------------------------------------------------------------------------------- The most fun thing about the mini-notation, is that everything you have just learned can be combined in various ways! Starting with this one `n`, can you make a _pattern string_ that uses every single mini-notation element above? n("60") Next: How do [Samples](https://strudel.cc/learn/samples/) play into this? --- # Samples 🌀 Strudel Samples[](https://strudel.cc/learn/samples/#samples) ===================================================== Samples are the most common way to make sound with tidal and strudel. A sample is a (commonly short) piece of audio that is used as a basis for sound generation, undergoing various transformations. Music that is based on samples can be thought of as a collage of sound. [Read more about Sampling](https://en.wikipedia.org/wiki/Sampling_(music)) Strudel allows loading samples in the form of audio files of various formats (wav, mp3, ogg) from any publicly available URL. Default Samples[](https://strudel.cc/learn/samples/#default-samples) ===================================================================== By default, strudel comes with a built-in “sample map”, providing a solid base to play with. s("bd sd \[~ bd\] sd,hh\*16, misc") Here, we are using the `s` function to play back different default samples (`bd`, `sd`, `hh` and `misc`) to get a drum beat. For drum sounds, strudel uses the comprehensive [tidal-drum-machines](https://github.com/ritchse/tidal-drum-machines) library, with the following naming convention: | Drum | Abbreviation | | --- | --- | | Bass drum, Kick drum | bd | | Snare drum | sd | | Rimshot | rim | | Clap | cp | | Closed hi-hat | hh | | Open hi-hat | oh | | Crash | cr | | Ride | rd | | High tom | ht | | Medium tom | mt | | Low tom | lt | ![](https://strudel.cc/img/drumset.png)[original von Pbroks13](https://de.wikipedia.org/wiki/Schlagzeug#/media/Datei:Drum_set.svg) More percussive sounds: | Source | Abbreviation | | --- | --- | | Shakers (and maracas, cabasas, etc) | sh | | Cowbell | cb | | Tambourine | tb | | Other percussions | perc | | Miscellaneous samples | misc | | Effects | fx | Furthermore, strudel also loads instrument samples from [VCSL](https://github.com/sgossner/VCSL) by default. To see which sample names are available, open the `sounds` tab in the [REPL](https://strudel.cc/) . You can also create custom aliases for existing sounds using the `soundAlias` function: soundAlias('RolandTR808\_bd', 'kick') s("kick") Note that only the sample maps (mapping names to URLs) are loaded initially, while the audio samples themselves are not loaded until they are actually played. This behaviour of loading things only when they are needed is also called `lazy loading`. While it saves resources, it can also lead to sounds not being audible the first time they are triggered, because the sound is still loading. [This might be fixed in the future](https://codeberg.org/uzu/strudel/issues/187) Sound Banks[](https://strudel.cc/learn/samples/#sound-banks) ============================================================= If we open the `sounds` tab and then `drum-machines`, we can see that the drum samples are all prefixed with drum machine names: `RolandTR808_bd`, `RolandTR808_sd`, `RolandTR808_hh` etc.. We _could_ use them like this: s("RolandTR808\_bd RolandTR808\_sd,RolandTR808\_hh\*16") 
 but thats obviously a bit much to write. Using the `bank` function, we can shorten this to: s("bd sd,hh\*16").bank("RolandTR808") You could even pattern the bank to switch between different drum machines: s("bd sd,hh\*16").bank("") Behind the scenes, `bank` will just prepend the drum machine name to the sample name with `_` to get the full name. This of course only works because the name after `_` (`bd`, `sd` etc..) is standardized. Also note that some banks won’t have samples for all sounds! Selecting Sounds[](https://strudel.cc/learn/samples/#selecting-sounds) ======================================================================= If we open the `sounds` tab again, followed by tab `drum machines`, there is also a number behind each name, indicating how many individual samples are available. For example `RolandTR909_hh(4)` means there are 4 samples of a TR909 hihat available. By default, `s` will play the first sample, but we can select the other ones using `n`, starting from 0: s("hh\*8").bank("RolandTR909").n("0 1 2 3") Numbers that are too high will just wrap around to the beginning s("hh\*8").bank("RolandTR909").n("0 1 2 3 4 5 6 7") Here, 0-3 will play the same sounds as 4-7, because `RolandTR909_hh` only has 4 sounds. Selecting sounds also works inside the mini notation, using “`:`” like this: s("bd\*4,hh:0 hh:1 hh:2 hh:3 hh:4 hh:5 hh:6 hh:7") .bank("RolandTR909") Loading Custom Samples[](https://strudel.cc/learn/samples/#loading-custom-samples) =================================================================================== You can load a non-standard sample map using the `samples` function. Loading samples from file URLs[](https://strudel.cc/learn/samples/#loading-samples-from-file-urls) --------------------------------------------------------------------------------------------------- In this example we assign names `bassdrum`, `hihat` and `snaredrum` to specific audio files on a server: samples({ bassdrum: 'bd/BT0AADA.wav', hihat: 'hh27/000\_hh27closedhh.wav', snaredrum: \['sd/rytm-01-classic.wav', 'sd/rytm-00-hard.wav'\], }, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/'); s("bassdrum snaredrum:0 bassdrum snaredrum:1, hihat\*16") You can freely choose any combination of letters for each sample name. It is even possible to override the default sounds. The names you pick will be made available in the `s` function. Make sure that the URL and each sample path form a correct URL! In the above example, `bassdrum` will load: https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/bd/BT0AADA.wav |----------------------base path --------------------------------|--sample path-| Note that we can either load a single file, like for `bassdrum` and `hihat`, or a list of files like for `snaredrum`! As soon as you run the code, your chosen sample names will be listed in `sounds` -> `user`. Loading Samples from a strudel.json file[](https://strudel.cc/learn/samples/#loading-samples-from-a-strudeljson-file) ---------------------------------------------------------------------------------------------------------------------- The above way to load samples might be tedious to write out / copy paste each time you write a new pattern. To avoid that, you can simply pass a URL to a `strudel.json` file somewhere on the internet: samples('https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/strudel.json') s("bd sd bd sd,hh\*16") The file is expected to define a sample map using JSON, in the same format as described above. Additionally, the base path can be defined with the `_base` key. The last section could be written as: { "_base": "https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/", "bassdrum": "bd/BT0AADA.wav", "snaredrum": "sd/rytm-01-classic.wav", "hihat": "hh27/000_hh27closedhh.wav" } Please note that browsers will often cache `strudel.json` on first load, and keep using the cached version even if the orginal has been updated. If this bites you (for example while developing a new sample pack), you can force the browser to download a new copy by i.e. changing capitalization of one character in the URL, or adding a URL attribute, such as: samples('https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/strudel.json?version=2'); that gets ignored by GitHub (but changes the URL, forcing the browser to reload every time we increase the version number). It is also possible, of course, to just remove it from cache (deleting cache in browser Privacy settings, or from the dev console if you’re technically minded, or by using a cache deleting extension). Generating strudel.json[](https://strudel.cc/learn/samples/#generating-strudeljson) ------------------------------------------------------------------------------------ You can use [@strudel/sampler](https://www.npmjs.com/package/@strudel/sampler) to generate a strudel.json file for you, by running: npx --yes @strudel/sampler --json > strudel.json See other uses of strudel/sampler further below, under “From Disk via @strudel/sampler”. Github Shortcut[](https://strudel.cc/learn/samples/#github-shortcut) --------------------------------------------------------------------- Because loading samples from github is common, there is a shortcut: samples('github:tidalcycles/dirt-samples') s("bd sd bd sd,hh\*16") The format is `samples('github://')`. If you omit `branch` (like above), the `main` branch will be used. It assumes a `strudel.json` file to be present at the root of the repository: https://raw.githubusercontent.com////strudel.json From Disk via “Import Sounds Folder”[](https://strudel.cc/learn/samples/#from-disk-via-import-sounds-folder) ------------------------------------------------------------------------------------------------------------- If you don’t want to upload your samples to the internet, you can also load them from your local disk. Go to the `sounds` tab in the REPL and open the `import-sounds` tab below the search bar. Press the “import sounds folder” button and select a folder that contains audio files. The folder you select can also contain subfolders with audio files. Example: └─ samples ├─ swoop │ ├─ swoopshort.wav │ ├─ swooplong.wav │ └─ swooptight.wav └─ smash ├─ smashhigh.wav ├─ smashlow.wav └─ smashmiddle.wav In the above example the folder `samples` contains 2 subfolders `swoop` and `smash`, which contain audio files. If you select that `samples` folder, the `user` tab (next to the `import-sounds` tab) will then contain 2 new sounds: `swoop(3) smash(3)` The individual samples can the be played normally like `s("swoop:0 swoop:1 smash:2")`. The samples within each sound use zero-based indexing in alphabetical order. From Disk via @strudel/sampler[](https://strudel.cc/learn/samples/#from-disk-via-strudelsampler) ------------------------------------------------------------------------------------------------- Instead of loading your samples into your browser with the “import sounds folder” button, you can also serve the samples from a local file server. The easiest way to do this is using [@strudel/sampler](https://www.npmjs.com/package/@strudel/sampler) : cd samples npx @strudel/sampler Then you can load it via: samples('http://localhost:5432/'); n("<0 1 2>").s("swoop smash") The handy thing about `@strudel/sampler` is that it auto-generates the `strudel.json` file based on your folder structure. You can see what it generated by going to `http://localhost:5432` with your browser. Note: You need [NodeJS](https://nodejs.org/) installed on your system for this to work. Specifying Pitch[](https://strudel.cc/learn/samples/#specifying-pitch) ----------------------------------------------------------------------- To make sure your samples are in tune when playing them with `note`, you can specify a base pitch like this: samples({ 'gtr': 'gtr/0001\_cleanC.wav', 'moog': { 'g3': 'moog/005\_Mighty%20Moog%20G3.wav' }, }, 'github:tidalcycles/dirt-samples'); note("g3 \[bb3 c4\] @2").s("gtr,moog").clip(1) .gain(.5) We can also declare different samples for different regions of the keyboard: setcpm(60) 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') note("g2!2 !2, g4 f4\]>") .s('moog').clip(1) .gain(.5) The sampler will always pick the closest matching sample for the current note! Note that this notation for pitched sounds also works inside a `strudel.json` file. Shabda[](https://strudel.cc/learn/samples/#shabda) --------------------------------------------------- If you don’t want to select samples by hand, there is also the wonderful tool called [shabda](https://shabda.ndre.gr/) . With it, you can enter any sample name(s) to query from [freesound.org](https://freesound.org/) . Example: samples('shabda:bass:4,hihat:4,rimshot:2') $: n("0 1 2 3 0 1 2 3").s('bass') $: n("0 1\*2 2 3\*2").s('hihat').clip(1) $: n("~ 0 ~ 1 ~ 0 0 1").s('rimshot') You can also generate artificial voice samples with any text, in multiple languages. Note that the language code and the gender parameters are optional and default to `en-GB` and `f` samples('shabda/speech:the\_drum,forever') samples('shabda/speech/fr-FR/m:magnifique') $: s("the\_drum\*2").chop(16).speed(rand.range(0.85,1.1)) $: s("forever magnifique").slow(4).late(0.125) Sampler Effects[](https://strudel.cc/learn/samples/#sampler-effects) ===================================================================== Sampler effects are functions that can be used to change the behaviour of sample playback. ### begin[](https://strudel.cc/learn/samples/#begin) A pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample. * amount (number|Pattern): between 0 and 1, where 1 is the length of the sample samples({ rave: 'rave/AREUREADY.wav' }, 'github:tidalcycles/dirt-samples') s("rave").begin("<0 .25 .5 .75>").fast(2) ### end[](https://strudel.cc/learn/samples/#end) The same as .begin, but cuts off the end off each sample. * length (number|Pattern): 1 = whole sample, .5 = half sample, .25 = quarter sample etc.. s("bd\*2,oh\*4").end("<.1 .2 .5 1>").fast(2) ### loop[](https://strudel.cc/learn/samples/#loop) Loops the sample. Note that the tempo of the loop is not synced with the cycle tempo. To change the loop region, use loopBegin / loopEnd. * on (number|Pattern): If 1, the sample is looped s("casio").loop(1) ### loopBegin[](https://strudel.cc/learn/samples/#loopbegin) Synonyms: `loopb` Begin to loop at a specific point in the sample (inbetween `begin` and `end`). Note that the loop point must be inbetween `begin` and `end`, and before `loopEnd`! Note: Samples starting with wt\_ will automatically loop! (wt = wavetable) * time (number|Pattern): between 0 and 1, where 1 is the length of the sample s("space").loop(1) .loopBegin("<0 .125 .25>").\_scope() ### loopEnd[](https://strudel.cc/learn/samples/#loopend) Synonyms: `loope` End the looping section at a specific point in the sample (inbetween `begin` and `end`). Note that the loop point must be inbetween `begin` and `end`, and after `loopBegin`! * time (number|Pattern): between 0 and 1, where 1 is the length of the sample s("space").loop(1) .loopEnd("<1 .75 .5 .25>").\_scope() ### cut[](https://strudel.cc/learn/samples/#cut) In the style of classic drum-machines, `cut` will stop a playing sample as soon as another samples with in same cutgroup is to be played. An example would be an open hi-hat followed by a closed one, essentially muting the open. * group (number|Pattern): cut group number s("\[oh hh\]\*4").cut(1) ### clip[](https://strudel.cc/learn/samples/#clip) Synonyms: `legato` Multiplies the duration with the given number. Also cuts samples off at the end if they exceed the duration. * factor (number|Pattern): \= 0 note("c a f e").s("piano").clip("<.5 1 2>") ### loopAt[](https://strudel.cc/learn/samples/#loopat) Makes the sample fit the given number of cycles by changing the speed. samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051\_316502-lq.mp3' }) s("rhodes").loopAt(2) ### fit[](https://strudel.cc/learn/samples/#fit) Makes the sample fit its event duration. Good for rhythmical loops like drum breaks. Similar to `loopAt`. samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051\_316502-lq.mp3' }) s("rhodes/2").fit() ### chop[](https://strudel.cc/learn/samples/#chop) Cuts each sample into the given number of parts, allowing you to explore a technique known as 'granular synthesis'. It turns a pattern of samples into a pattern of parts of samples. samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051\_316502-lq.mp3' }) s("rhodes") .chop(4) .rev() // reverse order of chops .loopAt(2) // fit sample into 2 cycles ### striate[](https://strudel.cc/learn/samples/#striate) Cuts each sample into the given number of parts, triggering progressive portions of each sample at each loop. s("numbers:0 numbers:1 numbers:2").striate(6).slow(3) ### slice[](https://strudel.cc/learn/samples/#slice) Chops samples into the given number of slices, triggering those slices with a given pattern of slice numbers. Instead of a number, it also accepts a list of numbers from 0 to 1 to slice at specific points. samples('github:tidalcycles/dirt-samples') s("breaks165").slice(8, "0 1 <2 2\*2> 3 \[4 0\] 5 6 7".every(3, rev)).slow(0.75) samples('github:tidalcycles/dirt-samples') s("breaks125").fit().slice(\[0,.25,.5,.75\], "0 1 1 <2 3>") ### splice[](https://strudel.cc/learn/samples/#splice) Works the same as slice, but changes the playback speed of each slice to match the duration of its step. samples('github:tidalcycles/dirt-samples') s("breaks165") .splice(8, "0 1 \[2 3 0\]@2 3 0@2 7") ### scrub[](https://strudel.cc/learn/samples/#scrub) Allows you to scrub an audio file like a tape loop by passing values that represents the position in the audio file in the optional array syntax ex: "0.5:2", the second value controls the speed of playback samples('github:switchangel/pad') s("swpad:0").scrub("{0.1!2 .25@3 0.7!2 <0.8:1.5>}%8") samples('github:yaxu/clean-breaks/main'); s("amen/4").fit().scrub("{0@3 0@2 4@3}%8".div(16)) ### speed[](https://strudel.cc/learn/samples/#speed) Changes the speed of sample playback, i.e. a cheap way of changing pitch. * speed (number|Pattern): inf to inf, negative numbers play the sample backwards. s("bd\*6").speed("1 2 4 1 -2 -4") speed("1 1.5\*2 \[2 1.1\]").s("piano").clip(1) After samples, let’s see what [Synths](https://strudel.cc/learn/synths/) afford us. --- # Music metadata 🌀 Strudel Music metadata[](https://strudel.cc/learn/metadata/#music-metadata) ==================================================================== You can optionally add some music metadata in your Strudel code, by using tags in code comments: // @title Hey Hoo // @by Sam Tagada // @license CC BY-NC-SA Like other comments, those are ignored by Strudel, but it can be used by other tools to retrieve some information about the music. Alternative syntax[](https://strudel.cc/learn/metadata/#alternative-syntax) ---------------------------------------------------------------------------- You can also use comment blocks: /* @title Hey Hoo @by Sam Tagada @license CC BY-NC-SA */ Or define multiple tags in one line: // @title Hey Hoo @by Sam Tagada @license CC BY-NC-SA The `title` tag has an alternative syntax using quotes (must be defined at the very begining): // "Hey Hoo" @by Sam Tagada Tags list[](https://strudel.cc/learn/metadata/#tags-list) ---------------------------------------------------------- Available tags are: * `@title`: music title * `@by`: music author(s), separated by comma, eventually followed with a link in `<>` (ex: `@by John Doe `) * `@license`: music license(s), e.g. CC BY-NC-SA. Unsure? [Choose a creative commons license here](https://creativecommons.org/choose/) * `@details`: some additional information about the music * `@url`: web page(s) related to the music (git repo, soundcloud link, etc.) * `@genre`: music genre(s) (pop, jazz, etc) * `@album`: music album name Multiple values[](https://strudel.cc/learn/metadata/#multiple-values) ---------------------------------------------------------------------- Some of them accepts several values, using the comma or new line separator, or duplicating the tag: /* @by Sam Tagada Jimmy @genre pop, jazz @url https://example.com @url https://example.org */ You can also add optional prefixes and use tags where you want: /* song @by Sam Tagada samples @by Jimmy */ ... note("a3 c#4 e4 a4") // @by Sandy Multiline[](https://strudel.cc/learn/metadata/#multiline) ---------------------------------------------------------- If a tag doesn’t accept a list, it can take multi-line values: /* @details I wrote this song in February 19th, 2023. It was around midnight and I was lying on the sofa in the living room. */ --- # Csound 🌀 Strudel Using CSound with Strudel[](https://strudel.cc/learn/csound/#using-csound-with-strudel) ======================================================================================== đŸ§Ș Strudel has experimental support for csound, using [@csound/browser](https://www.npmjs.com/package/@csound/browser) . Importing .orc files[](https://strudel.cc/learn/csound/#importing-orc-files) ----------------------------------------------------------------------------- To use existing csound instruments, you can load and use an orc file from an URL like this: Note that the above url uses the `github:` shortcut, which resolves to the raw file on github, but you can use any URL you like. The awesome [`livecode.orc by Steven Yi`](https://github.com/kunstmusik/csound-live-code) comes packed with many sounds ready for use: Writing your own instruments[](https://strudel.cc/learn/csound/#writing-your-own-instruments) ---------------------------------------------------------------------------------------------- You can define your own instrument(s) with `loadCsound` like this: Parameters[](https://strudel.cc/learn/csound/#parameters) ---------------------------------------------------------- The `.csound` function sends the following p values: | | | | --- | --- | | p1 | instrument name e.g. `CoolSynth` | | p2 | time offset, when it should play | | p3 | the duration of the event / hap | | p4 | frequency in Hertz | | p5 | normalized `gain`, 0-1 | There is an alternative `.csoundm` function with a different flavor: | | | | --- | --- | | p4 | midi key number, unrounded, 0-127 | | p5 | midi velocity, 0-127 | In both cases, p4 is derived from the value of `freq` or `note`. Limitations / Future Plans[](https://strudel.cc/learn/csound/#limitations--future-plans) ----------------------------------------------------------------------------------------- Apart from the above listed p values, no other parameter can be patterned so far. This also means that [audio effects](https://strudel.cc/learn/effects/) will not work. In the future, the integration could be improved by passing all patterned control parameters to the csound instrument. This could work by a unique [channel](https://kunstmusik.github.io/icsc2022-csound-web/tutorial2-interacting-with-csound/#step-4---writing-continuous-data-channels) for each value. Channels could be read [like this](https://github.com/csound/csound/blob/master/Android/CsoundForAndroid/CsoundAndroidExamples/src/main/res/raw/multitouch_xy.csd) . Also, it might make sense to have a standard library of csound instruments for strudel’s effects. Now, let’s dive into the [Functional JavaScript API](https://strudel.cc/functions/intro/) --- # Mondo Notation 🌀 Strudel Mondo Notation[](https://strudel.cc/learn/mondo-notation/#mondo-notation) ========================================================================== “Mondo Notation” is a new kind of notation that is similar to [Mini Notation](https://strudel.cc/learn/mini-notation/) , but with enough abilities to make it work as a standalone pattern language. Here’s an example: $ note (c2 # euclid <3 6 3> <8 16>) # \*2 # s "sine" # add (note \[0 <12 24>\]\*2) # dec(sine # range .2 2) # room .5 # lpf (sine/3 # range 120 400) # lpenv (rand # range .5 4) # lpq (perlin # range 5 12 # \* 2) # dist 1 # fm 4 # fmh 5.01 # fmdecay <.1 .2> # postgain .6 # delay .1 # clip 5 $ s \[bd bd bd bd\] # bank tr909 # clip .5 # ply <1 \[1 \[2 4\]\]> $ s oh\*4 # press # bank tr909 # speed.8 # dec (<.02 .05>\*2 # add (saw/8 # range 0 1)) Mondo in the REPL[](https://strudel.cc/learn/mondo-notation/#mondo-in-the-repl) -------------------------------------------------------------------------------- For now, you can only use mondo in the repl like this: mondo\`s hh\*8\` The rest of this site will only use the mondo notation itself. In the future, the REPL might get a way to use mondo notation directly. Calling Functions[](https://strudel.cc/learn/mondo-notation/#calling-functions) -------------------------------------------------------------------------------- Compared to Mini Notation, the most notable feature of Mondo Notation is the ability to call functions using round brackets: (s hh\*8) The first element inside the brackets is the function name. In JS, this would look like: s("hh\*8") The outermost parens are not needed, so we can drop them: s hh\*8 Mini Notation Features[](https://strudel.cc/learn/mondo-notation/#mini-notation-features) ------------------------------------------------------------------------------------------ Besides function calling with round parens, Mondo Notation has a lot in common with Mini Notation: ### Brackets[](https://strudel.cc/learn/mondo-notation/#brackets) * `[]` for 1-cycle sequences * `<>` for multi-cycle sequences * `{}` for stepped sequences (more on that later) ### Infix Operators[](https://strudel.cc/learn/mondo-notation/#infix-operators) * \* => [fast](https://strudel.cc/learn/time-modifiers/#fast) * / => [slow](https://strudel.cc/learn/time-modifiers/#slow) * ! => [extend](https://strudel.cc/learn/stepwise/#extend) * @ => [expand](https://strudel.cc/learn/stepwise/#expand) * % => [pace](https://strudel.cc/learn/stepwise/#pace) * ? => [degradeBy](https://strudel.cc/learn/random-modifiers/#degradeby) (currently requires right operand) * : => tail (creates a list) * .. => range (between numbers) * , => [stack](https://strudel.cc/learn/factories/#stack) * | => [chooseIn](https://strudel.cc/learn/random-modifiers/#choose) ### Example[](https://strudel.cc/learn/mondo-notation/#example) note < \[e5 \[b4 c5\] d5 \[c5 b4\]\] \[a4 \[a4 c5\] e5 \[d5 c5\]\] \[b4 \[~ c5\] d5 e5\] \[c5 a4 a4 ~\] \[\[~ d5\] \[~ f5\] a5 \[g5 f5\]\] \[e5 \[~ c5\] e5 \[d5 c5\]\] \[b4 \[b4 c5\] d5 e5\] \[c5 a4 a4 ~\] > Chaining Functions[](https://strudel.cc/learn/mondo-notation/#chaining-functions) ---------------------------------------------------------------------------------- Similar to how ”.” works in javascript (JS), we can chain functions calls with the ”#” operator: n <0 2 4 \[3 1\] -1>\*4 # scale C4:minor # jux rev # dec .2 # delay .5 Here’s the same written in JS: n("<0 2 4 \[3 1\] -1>\*4") .scale("C4:minor") .jux(rev) .dec(.2) .delay(.5) ### Chaining Functions Locally[](https://strudel.cc/learn/mondo-notation/#chaining-functions-locally) A function can be applied to a single element by wrapping it in round parens: s \[bd hh bd (cp # delay .6)\] # bank tr909 in this case, `delay .6` will only be applied to `cp`. compare this with the JS version: s(seq("bd", "hh", "bd", "cp".delay(.6))).bank('tr909') here we can see how much we can save when there’s no boundary between mini notation and function calls! ### Chaining Infix Operators[](https://strudel.cc/learn/mondo-notation/#chaining-infix-operators) Infix operators exist as regular functions, so they can be chained as well: s \[bd hh\] # bank tr909 # \*2 In this case, the \*2 will be applied to the whole pattern. ### Lambda Functions[](https://strudel.cc/learn/mondo-notation/#lambda-functions) Some functions in strudel expect a function as input, for example: n("0 .. 7").scale("C:minor").sometimes(x=>x.dec(.1)) in mondo, the `x=>x.` can be shortened to: n 0..7 # scale C:minor # sometimes (# dec .1) chaining works as expected: n 0..7 # scale C:minor # sometimes (# dec .1 # jux rev) Strings[](https://strudel.cc/learn/mondo-notation/#strings) ------------------------------------------------------------ You can use “double quotes” and ‘single quotes’ to get a string: n 0..7 # scale 'C minor' Multiple Patterns[](https://strudel.cc/learn/mondo-notation/#multiple-patterns) -------------------------------------------------------------------------------- The `$` sign can be used to separate multiple patterns: $ s \[bd rim \[~ bd\] rim\] # bank tr707 $ chord # voicing # struct\[x ~ ~ x ~ x ~ ~\] # delay .5 The `$` sign is an alias for `,` so it will create a stack behind the scenes. variables[](https://strudel.cc/learn/mondo-notation/#variables) ---------------------------------------------------------------- using the `def` keyword, you can define variables: $ def melody \[0 1 2 3\] $ n melody # scale C:minor --- # Visual Feedback 🌀 Strudel Visual Feedback[](https://strudel.cc/learn/visual-feedback/#visual-feedback) ============================================================================= There are several function that add visual feedback to your patterns. Mini Notation Highlighting[](https://strudel.cc/learn/visual-feedback/#mini-notation-highlighting) --------------------------------------------------------------------------------------------------- When you write mini notation with “double quotes” or \`backticks\`, the active parts of the mini notation will be highlighted: n("<0 2 1 3 2>\*8") .scale("/4:minor:pentatonic") .s("supersaw").lpf(300).lpenv("<4 3 2>\*4") You can change the color as well, even pattern it: n("<0 2 1 3 2>\*8") .scale("/4:minor:pentatonic") .s("supersaw").lpf(300).lpenv("<4 3 2>\*4") .color("cyan magenta") Global vs Inline Visuals[](https://strudel.cc/learn/visual-feedback/#global-vs-inline-visuals) ----------------------------------------------------------------------------------------------- The following functions all come with in 2 variants. **Without prefix**: renders the visual to the background of the page: note("c a f e").color("white").punchcard() **With `_` prefix**: renders the visual inside the code. Allows for multiple visuals note("c a f e").color("white").\_punchcard() Here we see the 2 variants for `punchcard`. The same goes for all others below. To improve readability the following demos will all use the inline variant. Punchcard / Pianoroll[](https://strudel.cc/learn/visual-feedback/#punchcard--pianoroll) ---------------------------------------------------------------------------------------- These 2 functions render a pianoroll style visual. The only difference between the 2 is that `pianoroll` will render the pattern directly, while `punchcard` will also take the transformations into account that occur afterwards: note("c a f e").color("white") .\_punchcard() .color("cyan") Here, the `color` is still visible in the visual, even if it is applied after `_punchcard`. On the contrary, the color is not visible when using `_pianoroll`: note("c a f e").color("white") .\_pianoroll() .color("cyan") `punchcard` is less resource intensive because it uses the same data as used for the mini notation highlighting. The visual can be customized by passing options. Those options are the same for both functions. What follows is the API doc of all the options you can pass: Synonyms: `punchcard` Visualises a pattern as a scrolling 'pianoroll', displayed in the background of the editor. To show a pianoroll for all running patterns, use `all(pianoroll)`. To have a pianoroll appear below a pattern instead, prefix with `_`, e.g.: `sound("bd sd")._pianoroll()`. * options (Object): Object containing all the optional following parameters as key value pairs: * cycles (integer): number of cycles to be displayed at the same time - defaults to 4 * playhead (number): location of the active notes on the time axis - 0 to 1, defaults to 0.5 * vertical (boolean): displays the roll vertically - 0 by default * labels (boolean): displays labels on individual notes (see the label function) - 0 by default * flipTime (boolean): reverse the direction of the roll - 0 by default * flipValues (boolean): reverse the relative location of notes on the value axis - 0 by default * overscan (number): lookup X cycles outside of the cycles window to display notes in advance - 1 by default * hideNegative (boolean): hide notes with negative time (before starting playing the pattern) - 0 by default * smear (boolean): notes leave a solid trace - 0 by default * fold (boolean): notes takes the full value axis width - 0 by default * active (string): hexadecimal or CSS color of the active notes - defaults to #FFCA28 * inactive (string): hexadecimal or CSS color of the inactive notes - defaults to #7491D2 * background (string): hexadecimal or CSS color of the background - defaults to transparent * playheadColor (string): hexadecimal or CSS color of the line representing the play head - defaults to white * fill (boolean): notes are filled with color (otherwise only the label is displayed) - 0 by default * fillActive (boolean): active notes are filled with color - 0 by default * stroke (boolean): notes are shown with colored borders - 0 by default * strokeActive (boolean): active notes are shown with colored borders - 0 by default * hideInactive (boolean): only active notes are shown - 0 by default * colorizeInactive (boolean): use note color for inactive notes - 1 by default * fontFamily (string): define the font used by notes labels - defaults to 'monospace' * minMidi (integer): minimum note value to display on the value axis - defaults to 10 * maxMidi (integer): maximum note value to display on the value axis - defaults to 90 * autorange (boolean): automatically calculate the minMidi and maxMidi parameters - 0 by default note("c2 a2 eb2") .euclid(5,8) .s('sawtooth') .lpenv(4).lpf(300) .pianoroll({ labels: 1 }) Spiral[](https://strudel.cc/learn/visual-feedback/#spiral) ----------------------------------------------------------- Displays a spiral visual. * options (Object): Object containing all the optional following parameters as key value pairs: * stretch (number): controls the rotations per cycle ratio, where 1 = 1 cycle / 360 degrees * size (number): the diameter of the spiral * thickness (number): line thickness * cap (string): style of line ends: butt (default), round, square * inset (string): number of rotations before spiral starts (default 3) * playheadColor (string): color of playhead, defaults to white * playheadLength (number): length of playhead in rotations, defaults to 0.02 * playheadThickness (number): thickness of playheadrotations, defaults to thickness * padding (number): space around spiral * steady (number): steadyness of spiral vs playhead. 1 = spiral doesn't move, playhead does. * activeColor (number): color of active segment. defaults to foreground of theme * inactiveColor (number): color of inactive segments. defaults to gutterForeground of theme * colorizeInactive (boolean): wether or not to colorize inactive segments, defaults to 0 * fade (boolean): wether or not past and future should fade out. defaults to 1 * logSpiral (boolean): wether or not the spiral should be logarithmic. defaults to 0 note("c2 a2 eb2") .euclid(5,8) .s('sawtooth') .lpenv(4).lpf(300) .\_spiral({ steady: .96 }) Scope[](https://strudel.cc/learn/visual-feedback/#scope) --------------------------------------------------------- Synonyms: `tscope` Renders an oscilloscope for the time domain of the audio signal. * config (object): optional config with options: * align (boolean): if 1, the scope will be aligned to the first zero crossing. defaults to 1 * color (string): line color as hex or color name. defaults to white. * thickness (number): line thickness. defaults to 3 * scale (number): scales the y-axis. Defaults to 0.25 * pos (number): y-position relative to screen height. 0 = top, 1 = bottom of screen * trigger (number): amplitude value that is used to align the scope. defaults to 0. s("sawtooth").\_scope() Pitchwheel[](https://strudel.cc/learn/visual-feedback/#pitchwheel) ------------------------------------------------------------------- Renders a pitch circle to visualize frequencies within one octave * hapcircles (number): * circle (number): * edo (number): * root (string): * thickness (number): * hapRadius (number): * mode (string): * margin (number): n("0 .. 12").scale("C:chromatic") .s("sawtooth") .lpf(500) .\_pitchwheel() Spectrum[](https://strudel.cc/learn/visual-feedback/#spectrum) --------------------------------------------------------------- Renders a spectrum analyzer for the incoming audio signal. * config (object): optional config with options: * thickness (integer): line thickness in px (default 3) * speed (integer): scroll speed (default 1) * min (integer): min db (default -80) * max (integer): max db (default 0) n("<0 4 <2 3> 1>\*3") .off(1/8, add(n(5))) .off(1/5, add(n(7))) .scale("d3:minor:pentatonic") .s('sine') .dec(.3).room(.5) .\_spectrum() markcss[](https://strudel.cc/learn/visual-feedback/#markcss) ------------------------------------------------------------- Overrides the css of highlighted events. Make sure to use single quotes! note("c a f e") .markcss('text-decoration:underline') --- # Hydra 🌀 Strudel Using Hydra inside Strudel[](https://strudel.cc/learn/hydra/#using-hydra-inside-strudel) ========================================================================================= You can write [hydra](https://hydra.ojack.xyz/) code in strudel! All you have to do is to call `await initHydra()` at the top: H patterns[](https://strudel.cc/learn/hydra/#h-patterns) --------------------------------------------------------- There is a special function `H` that allows you to use a pattern as an input to hydra: detectAudio[](https://strudel.cc/learn/hydra/#detectaudio) ----------------------------------------------------------- To use hydra audio capture, call `initHydra` with `{detectAudio:true}` configuration param: You might now be able to see this properly here: [open in REPL](https://strudel.cc/#YXdhaXQgaW5pdEh5ZHJhKCkKbGV0IHBhdHRlcm4gPSAiMyA0IDUgWzYgN10qMiIKc2hhcGUoSChwYXR0ZXJuKSkub3V0KG8wKQpuKHBhdHRlcm4pLnNjYWxlKCJBOm1pbm9yIikucGlhbm8oKS5yb29tKDEpIA%3D%3D) Similar to `detectAudio`, all the [available hydra options](https://github.com/hydra-synth/hydra-synth#api) can be passed to `initHydra`. feedStrudel[](https://strudel.cc/learn/hydra/#feedstrudel) ----------------------------------------------------------- Using the `feedStrudel` option, you can transform strudel visualizations with hydra: --- # Device Motion 🌀 Strudel Device Motion[](https://workspace/uzu/strudel/packages/motion/docs/devicemotion/#device-motion) ================================================================================================ Devicemotion module allows you to use your mobile device’s motion sensors (accelerometer, gyroscope, and orientation sensors) to control musical parameters in real-time. This creates opportunities for expressive, movement-based musical interactions. Basic Setup[](https://workspace/uzu/strudel/packages/motion/docs/devicemotion/#basic-setup) -------------------------------------------------------------------------------------------- First, you need to enable device motion sensing: enableMotion() This will prompt the user for permission to access device motion sensors. Available Motion Parameters[](https://workspace/uzu/strudel/packages/motion/docs/devicemotion/#available-motion-parameters) ---------------------------------------------------------------------------------------------------------------------------- You can access different types of motion data: | Motion | Long Names & Aliases | Description | | --- | --- | --- | | Acceleration | accelerationX (accX), accelerationY (accY), accelerationZ (accZ) | Measures linear acceleration of the device, excluding gravity. Raw values are normalized from g-force. | | Gravity | gravityX (gravX), gravityY (gravY), gravityZ (gravZ) | Indicates device’s orientation relative to Earth’s gravity. Raw values are normalized from ±9.81 m/sÂČ. | | Rotation | rotationAlpha (rotA, rotZ), rotationBeta (rotB, rotX), rotationGamma (rotG, rotY) | Measures rotation rate around each axis. Raw values (±180°/s) are normalized. | | Orientation | orientationAlpha (oriA, oriZ), orientationBeta (oriB, oriX), orientationGamma (oriG, oriY) | Relative orientation from its starting device position. Normalized from:
\- Alpha: 0° to 360°
\- Beta: -180° to 180°
\- Gamma: -90° to 90° | | Absolute Orientation | absoluteOrientationAlpha (absOriA, absOriZ), absoluteOrientationBeta (absOriB, absOriX), absoluteOrientationGamma (absOriG, absOriY) | **Not available for iOS**
Earth-referenced orientation using magnetometer. Same normalization as Orientation. | Note: * All motion values are normalized to a range of 0 to 1. * Not all devices have the same sensors available Check [DeviceMotionEvent API](https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent) for browser compatibility * Refer to [Oritentation and motion data explained](https://developer.mozilla.org/en-US/docs/Web/API/Device_orientation_events/Orientation_and_motion_data_explained) for more details ### Orientation vs Absolute Orientation[](https://workspace/uzu/strudel/packages/motion/docs/devicemotion/#orientation-vs-absolute-orientation) The key difference between regular orientation and absolute orientation is: * Regular orientation (`oriX/Y/Z`) measures relative changes in device orientation from its starting position * Absolute orientation (`absOriX/Y/Z`) measures orientation relative to Earth’s magnetic field and gravity, providing consistent absolute values regardless of starting position For example, if you rotate your device 90 degrees clockwise and then back: * Regular orientation will show a change during rotation but return to initial values * Absolute orientation will show the actual compass heading throughout This makes absolute orientation particularly useful for creating direction-based musical interactions - for example, performers facing north could play one melody while those facing south play another, creating spatially-aware ensemble performances. Regular orientation, on the other hand, is better suited for detecting relative motion and gestures regardless of which direction the performer is facing. Basic Example[](https://workspace/uzu/strudel/packages/motion/docs/devicemotion/#basic-example) ------------------------------------------------------------------------------------------------ Here’s a simple example that uses device motion to control a synthesizer: enableMotion() // Create a simple melody $:n("0 1 3 5") .scale("C:major") // Use tilt (gravity) to control filter .lpf(gravityY.range(200, 2000)) // tilt forward/back for filter cutoff // Use rotation to control effects .room(rotZ.range(0, 0.8)) // rotate device for reverb amount .gain(oriX.range(0.2, 0.8)) // tilt left/right for volume .sound("sawtooth") Tips for Using Motion Controls[](https://workspace/uzu/strudel/packages/motion/docs/devicemotion/#tips-for-using-motion-controls) ---------------------------------------------------------------------------------------------------------------------------------- 1. Use `.range(min, max)` to map sensor values to musically useful ranges 2. Consider using `.segment()` to smooth out rapid changes in sensor values Debugging[](https://workspace/uzu/strudel/packages/motion/docs/devicemotion/#debugging) ---------------------------------------------------------------------------------------- You can use `segment(16).log()` to see the raw values from any motion sensor: $_: accX.segment(16).log(); // logs acceleration values to the console This is helpful when calibrating your ranges and understanding how your device responds to different movements. Remember that device motion works best on mobile devices and may not be available on all desktop browsers. Always test your motion-controlled pieces on the target device type! --- # Input Devices 🌀 Strudel Input Devices[](https://strudel.cc/learn/input-devices/#input-devices) ======================================================================= Strudel supports various input devices like Gamepads and MIDI controllers to manipulate patterns in real-time. Gamepad[](https://workspace/uzu/strudel/packages/gamepad/docs/gamepad/#gamepad) ================================================================================ The Gamepad module allows you to integrate gamepad input functionality into your musical patterns. This can be particularly useful for live performances or interactive installations where you want to manipulate sounds using a game controller. Getting Started[](https://workspace/uzu/strudel/packages/gamepad/docs/gamepad/#getting-started) ------------------------------------------------------------------------------------------------ Initialize a gamepad by calling the gamepad() function with an optional index parameter. // Initialize gamepad (optional index parameter, defaults to 0) const gp = gamepad(0) note("c a f e").mask(gp.a) Available Controls[](https://workspace/uzu/strudel/packages/gamepad/docs/gamepad/#available-controls) ------------------------------------------------------------------------------------------------------ The gamepad module provides access to buttons and analog sticks as normalized signals (0-1) that can modulate your patterns. ### Buttons[](https://workspace/uzu/strudel/packages/gamepad/docs/gamepad/#buttons) | Type | Controls | | --- | --- | | Face Buttons | `a`, `b`, `x`, `y` (or uppercase `A`, `B`, `X`, `Y`) | | | Toggle versions: `tglA`, `tglB`, `tglX`, `tglY` | | Shoulder Buttons | `lb`, `rb`, `lt`, `rt` (or uppercase `LB`, `RB`, `LT`, `RT`) | | | Toggle versions: `tglLB`, `tglRB`, `tglLT`, `tglRT` | | D-Pad | `up`, `down`, `left`, `right` (or `u`, `d`, `l`, `r` or uppercase) | | | Toggle versions: `tglUp`, `tglDown`, `tglLeft`, `tglRight` (or `tglU`, `tglD`, `tglL`, `tglR`) | | Stick Buttons | `l3`, ‘r3’ (or `ls`, `rs`) | | | Toggle versions: `tglL3`, ‘tglR3’ (or `tglLs`, `tglRs`) | | System Buttons | `start`, `back` (or uppercase `START`, `BACK`) | | | Toggle versions: `tglStart`, `tglBack` (or `tglSTART`, `tglBACK`) | ### Analog Sticks[](https://workspace/uzu/strudel/packages/gamepad/docs/gamepad/#analog-sticks) | Stick | Controls | | --- | --- | | Left Stick | `x1`, `y1` (0 to 1 range) | | | `x1_2`, `y1_2` (-1 to 1 range) | | Right Stick | `x2`, `y2` (0 to 1 range) | | | `x2_2`, `y2_2` (-1 to 1 range) | ### Button Sequence[](https://workspace/uzu/strudel/packages/gamepad/docs/gamepad/#button-sequence) | Stick | Controls | | --- | --- | | Button Sequence | `btnSequence()`, `btnSeq()`, `btnseq()` | Using Gamepad Inputs[](https://workspace/uzu/strudel/packages/gamepad/docs/gamepad/#using-gamepad-inputs) ---------------------------------------------------------------------------------------------------------- Once initialized, you can use various gamepad inputs in your patterns. Here are some examples: ### Button Inputs[](https://workspace/uzu/strudel/packages/gamepad/docs/gamepad/#button-inputs) You can use button inputs to control different aspects of your music, such as gain or triggering events. const gp = gamepad(0) setcpm(120) // Use button values to control amplitude $: stack( s("\[\[hh hh\] oh hh oh\]/2").mask(gp.tglX).bank("RolandTR909"), // X btn for HH s("cr\*1").mask(gp.Y).bank("RolandTR909"), // LB btn for CR s("bd").mask(gp.tglA).bank("RolandTR909"), // A btn for BD s("\[ht - - mt - - lt - \]/2").mask(gp.tglB).bank("RolandTR909"), // B btn for Toms s("sd\*4").mask(gp.RB).bank("RolandTR909"), // RB btn for SD ) ### Analog Stick Inputs[](https://workspace/uzu/strudel/packages/gamepad/docs/gamepad/#analog-stick-inputs) Analog sticks can be used for continuous control, such as pitch shifting or panning. const gp = gamepad(0) setcpm(120) // Use analog stick for continuous control $: note("c4 d3 a3 e3").sound("sawtooth") .lpf(gp.x1.range(100,4000)) .lpq(gp.y1.range(5,30)) .decay(gp.y2.range(0.1,2)) .lpenv(gp.x2.range(-5,5)) ### Button Sequences[](https://workspace/uzu/strudel/packages/gamepad/docs/gamepad/#button-sequences) You can define button sequences to trigger specific actions, like playing a sound when a sequence is detected. const gp = gamepad(0) setcpm(120) // Define button sequences const HADOUKEN = \[\ 'd', // Down\ 'r', // Right\ 'a', // A\ \] const KONAMI = 'uuddlrlrba' //Konami Code ↑↑↓↓←→←→BA // Check butto-n sequence (returns 1 while detected, 0 when not within last 1 second) $: s("free\_hadouken -").slow(2) .mask(gp.btnSequence(HADOUKEN)).room(1) // hadouken.wav by Syna-Max //https://freesound.org/people/Syna-Max/sounds/67674/ samples({free\_hadouken: 'https://cdn.freesound.org/previews/67/67674\_111920-lq.mp3'}) Multiple Gamepads[](https://workspace/uzu/strudel/packages/gamepad/docs/gamepad/#multiple-gamepads) ---------------------------------------------------------------------------------------------------- Strudel supports multiple gamepads. You can specify the gamepad index to connect to different devices. const pad1 = gamepad(0); // First gamepad const pad2 = gamepad(1); // Second gamepad --- # JavaScript API 🌀 Strudel Pattern Functions[](https://strudel.cc/functions/intro/#pattern-functions) =========================================================================== Let’s learn all about functions to create and modify patterns. At the core of Strudel, everything is made of functions. For example, everything you can do with the Mini-Notation can also be done with a function. This Pattern in Mini Notation: is equivalent to this Pattern without Mini Notation: Similarly, there is an equivalent function for every aspect of the mini notation. Which representation to use is a matter of context. As a rule of thumb, functions are better suited in a larger context, while mini notation is more practical for individual rhythms. Limits of Mini Notation[](https://strudel.cc/functions/intro/#limits-of-mini-notation) --------------------------------------------------------------------------------------- While the Mini Notation is a powerful way to write rhythms concisely, it also has its limits. Take this example: stack( note("c2 eb2(3,8)").s('sawtooth').cutoff(800), s("bd(5,8), hh\*8") ) Here, we are using mini notation for the individual rhythms, while using the function `stack` to mix them. While stack is also available as `,` in mini notation, we cannot use it here, because we have different types of sounds. Combining Patterns[](https://strudel.cc/functions/intro/#combining-patterns) ----------------------------------------------------------------------------- You can freely mix JS patterns, mini patterns and values! For example, this pattern: cat( stack("g3","b3","e4"), stack("a3","c3","e4"), stack("b3","d3","fs4"), stack("b3","e4","g4") ).note() 
is equivalent to: cat( "g3,b3,e4", "a3,c3,e4", "b3,d3,f#4", "b3,e4,g4" ).note() 
 as well as: While mini notation is almost always shorter, it only has a handful of modifiers: \* / ! @. When using JS patterns, there is a lot more you can do. Next, let’s look at how you can [create patterns](https://strudel.cc/learn/factories/) --- # Audio effects 🌀 Strudel Audio Effects[](https://strudel.cc/learn/effects/#audio-effects) ================================================================= Whether you’re using a synth or a sample, you can apply any of the following built-in audio effects. As you might suspect, the effects can be chained together, and they accept a pattern string as their argument. Signal chain[](https://strudel.cc/learn/effects/#signal-chain) =============================================================== ![](https://strudel.cc/img/strudel-signal-flow.png) The signal chain in Strudel is as follows: * An sound-generating event is triggered by a pattern * This has a start time and a duration, which is usually controlled by the note length and ADSR parameters * If we exceed the max polyphony, old sounds begin to die off * Muted sounds (one whose `s` value is `-`, `~`, or `_`) are skipped * A sound is produced (through, say, a sample or an oscillator) * This is where detune-based effects (like `detune`, `penv`, etc. occur) * The following will occur _in order_ and only if they’ve been called in the pattern. Note that all of these are single use effects, meaning that multiple occurrences of them in a pattern will simply override the values (e.g. you can’t do `s("bd").lpf(100).distort(2).lpf(800)` to lowpass, distort, and then lowpass again) * Phase vocoder (`stretch`) * Gain is applied (`gain`) * This is where the main (volume) ADSR happens * A lowpass filter (`lpf`) * A highpass filter (`hpf`) * A bandpass filter (`bandpass`) * A vowel filter (`vowel`) * Sample rate reduction (`coarse`) * Bit crushing (`crush`) * Waveshape distortion (`shape`) * Normal distortion (`distort`) * Tremolo (`tremolo`) * Compressor (`compressor`) * Panning (`pan`) * Phaser (`phaser`) * Postgain (`post`) * The sound is then split into multiple destinations * Dry output (amount controlled by `dry` parameter) * The sends * Analyzers * These are used for tooling like `scope` and `spectrum` and their setup usually happens behind the scenes * Delay (amount controlled by `delay` parameter) * Reverb (amount controlled by `room` parameter) * The dry output, delay, and reverb are joined into what is called the “orbit” of the pattern (see more in the section below) * The `duck` effect affects the volume of all signals in the orbit * The orbit is then sent to the mixer Orbits[](https://strudel.cc/learn/effects/#orbits) --------------------------------------------------- Orbits are the way in which outputs are handled in Strudel. They also prescribe which delay and reverb to associate with the dry signal. By default, all orbits are mixed down to channels `1` and `2` in stereo, however with the “Multi Channel Orbits” setting (under Settings at the right) you can use them as individual 2 channel stereo outs (orbit `i` will be mapped to to channels `2i` and `2i + 1`). You can then use routers like Blackhole 16 to retrieve and record all of the channels in a DAW for later processing. The default orbit is `1` and it is set with `orbit`. You may send a sound to multiple orbits via mininotation s("white").orbit("2,3,4").gain(0.2) but please be careful as this will create three copies of the sound behind the scenes, meaning that if they are mixed down to a single output, they will triple the volume. We’ve reduced the gain here to save your ears. ⚠ There is only one delay and reverb per orbit, so please be aware that if you attempt to change the parameters on two patterns pointing to the same orbit, it can lead to unpredictable results. Compare, for example, this pretty pluck with a large reverb: $: s("triangle\*4").decay(0.5).n(irand(12)).scale('C minor') .room(1).roomsize(10) versus the same pluck with a muted kick drum coming in and overwriting the `roomsize` value: $: s("triangle\*4").decay(0.5).n(irand(12)).scale('C minor') .room(1).roomsize(10) $: s("bd\*4").room(0.01).roomsize(0.01).postgain(0) This is due to them sharing the same orbit: the default of `1`. It can be corrected simply by updating the orbits to be distinct: $: s("triangle\*4").decay(0.5).n(irand(12)).scale('C minor') .room(1).roomsize(10).orbit(2) $: s("bd\*4").room(0.01).roomsize(0.01).postgain(0) Continuous changes[](https://strudel.cc/learn/effects/#continuous-changes) --------------------------------------------------------------------------- As all of the above is triggered by a _sound occurring_, it is often the case that parameters may not be modified continuously in time. For example, s("supersaw").lpf(tri.range(100, 5000).slow(2)) Will not produce a continually LFO’d low-pass filter due to the `tri` only being sampled every time the note hits (in this case the default of once per cycle). You can fake it by introducing more sound-generating events, e.g.: s("supersaw").seg(16).lpf(tri.range(100, 5000).slow(2)) Some parameters _do_ induce continuous variations in time, though: * The ADSR curve (governed by `attack`, `sustain`, `decay`, `release`) * The pitch envelope curve (governed by `penv` and its associated ADSR) * The FM curve (`fmenv`) * The filter envelopes (`lpenv`, `hpenv`, `bpenv`) * Tremolo (`tremolo`) * Phaser (`phaser`) * Vibrato (`vib`) * Ducking (`duckorbit`) Filters[](https://strudel.cc/learn/effects/#filters) ===================================================== Filters are an essential building block of [subtractive synthesis](https://en.wikipedia.org/wiki/Subtractive_synthesis) . Strudel comes with 3 types of filters: * low-pass filter: low frequencies may _pass_, high frequencies are cut off * high-pass filter: high frequencies may _pass_, low frequencies are cut off * band-pass filters: only a frequency band may _pass_, low and high frequencies around are cut off Each filter has 2 parameters: * cutoff: the frequency at which the filter starts to work. e.g. a low-pass filter with a cutoff of 1000Hz allows frequencies below 1000Hz to pass. * q-value: Controls the resonance of the filter. Higher values sound more aggressive. Also see [Q-Factor](https://en.wikipedia.org/wiki/Q_factor) lpf[](https://strudel.cc/learn/effects/#lpf) --------------------------------------------- Synonyms: `cutoff, ctf, lp` Applies the cutoff frequency of the **l**ow-**p**ass **f**ilter. When using mininotation, you can also optionally add the 'lpq' parameter, separated by ':'. * frequency (number|Pattern): audible between 0 and 20000 s("bd sd \[~ bd\] sd,hh\*6").lpf("<4000 2000 1000 500 200 100>") s("bd\*16").lpf("1000:0 1000:10 1000:20 1000:30") lpq[](https://strudel.cc/learn/effects/#lpq) --------------------------------------------- Synonyms: `resonance` Controls the **l**ow-**p**ass **q**\-value. * q (number|Pattern): resonance factor between 0 and 50 s("bd sd \[~ bd\] sd,hh\*8").lpf(2000).lpq("<0 10 20 30>") hpf[](https://strudel.cc/learn/effects/#hpf) --------------------------------------------- Synonyms: `hp, hcutoff` Applies the cutoff frequency of the **h**igh-**p**ass **f**ilter. When using mininotation, you can also optionally add the 'hpq' parameter, separated by ':'. * frequency (number|Pattern): audible between 0 and 20000 s("bd sd \[~ bd\] sd,hh\*8").hpf("<4000 2000 1000 500 200 100>") s("bd sd \[~ bd\] sd,hh\*8").hpf("<2000 2000:25>") hpq[](https://strudel.cc/learn/effects/#hpq) --------------------------------------------- Synonyms: `hresonance` Controls the **h**igh-**p**ass **q**\-value. * q (number|Pattern): resonance factor between 0 and 50 s("bd sd \[~ bd\] sd,hh\*8").hpf(2000).hpq("<0 10 20 30>") bpf[](https://strudel.cc/learn/effects/#bpf) --------------------------------------------- Synonyms: `bandf, bp` Sets the center frequency of the **b**and-**p**ass **f**ilter. When using mininotation, you can also optionally supply the 'bpq' parameter separated by ':'. * frequency (number|Pattern): center frequency s("bd sd \[~ bd\] sd,hh\*6").bpf("<1000 2000 4000 8000>") bpq[](https://strudel.cc/learn/effects/#bpq) --------------------------------------------- Synonyms: `bandq` Sets the **b**and-**p**ass **q**\-factor (resonance). * q (number|Pattern): q factor s("bd sd \[~ bd\] sd").bpf(500).bpq("<0 1 2 3>") ftype[](https://strudel.cc/learn/effects/#ftype) ------------------------------------------------- Sets the filter type. The ladder filter is more aggressive. More types might be added in the future. * type (number|Pattern): 12db (0), ladder (1), or 24db (2) note("{f g g c d a a#}%8").s("sawtooth").lpenv(4).lpf(500).ftype("<0 1 2>").lpq(1) note("c f g g a c d4").fast(2) .sound('sawtooth') .lpf(200).fanchor(0) .lpenv(3).lpq(1) .ftype("") vowel[](https://strudel.cc/learn/effects/#vowel) ------------------------------------------------- Formant filter to make things sound like vowels. * vowel (string|Pattern): You can use a e i o u ae aa oe ue y uh un en an on, corresponding to \[a\] \[e\] \[i\] \[o\] \[u\] \[ĂŠ\] \[ɑ\] \[Ăž\] \[y\] \[ÉŻ\] \[ʌ\] \[Ć“Ìƒ\] \[ɛ̃\] \[ɑ̃\] \[ɔ̃\]. Aliases: aa = Ă„ = ɑ, oe = Ăž = ö, y = ı, ae = ĂŠ. note("\[c2 >\]\*2").s('sawtooth') .vowel("
>") s("bd sd mt ht bd \[~ cp\] ht lt").vowel("\[a|e|i|o|u\]") Amplitude Modulation[](https://strudel.cc/learn/effects/#amplitude-modulation) =============================================================================== Amplitude modulation changes the amplitude (gain) periodically over time. am[](https://strudel.cc/learn/effects/#am) ------------------------------------------- tremolosync[](https://strudel.cc/learn/effects/#tremolosync) ------------------------------------------------------------- Synonyms: `tremsync` Modulate the amplitude of a sound with a continuous waveform * cycles (number|Pattern): modulation speed in cycles note("d d d# d".fast(4)).s("supersaw").tremolosync("4").tremoloskew("<1 .5 0>") tremolodepth[](https://strudel.cc/learn/effects/#tremolodepth) --------------------------------------------------------------- Synonyms: `tremdepth` Depth of amplitude modulation * depth (number|Pattern): note("a1 a1 a#1 a1".fast(4)).s("pulse").tremsync(4).tremolodepth("<1 2 .7>") tremoloskew[](https://strudel.cc/learn/effects/#tremoloskew) ------------------------------------------------------------- Synonyms: `tremskew` Alter the shape of the modulation waveform * amount (number|Pattern): between 0 & 1, the shape of the waveform note("{f a c e}%16").s("sawtooth").tremsync(4).tremoloskew("<.5 0 1>") tremolophase[](https://strudel.cc/learn/effects/#tremolophase) --------------------------------------------------------------- Synonyms: `tremphase` Alter the phase of the modulation waveform * offset (number|Pattern): the offset in cycles of the modulation note("{f a c e}%16").s("sawtooth").tremsync(4).tremolophase("<0 .25 .66>") tremoloshape[](https://strudel.cc/learn/effects/#tremoloshape) --------------------------------------------------------------- Synonyms: `tremshape` Shape of amplitude modulation * shape (number|Pattern): tri | square | sine | saw | ramp note("{f g c d}%16").tremsync(4).tremoloshape("").s("sawtooth") Amplitude Envelope[](https://strudel.cc/learn/effects/#amplitude-envelope) =========================================================================== The amplitude [envelope](https://en.wikipedia.org/wiki/Envelope_(music)) controls the dynamic contour of a sound. Strudel uses ADSR envelopes, which are probably the most common way to describe an envelope: ![ADSR](https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/ADSR_parameter.svg/1920px-ADSR_parameter.svg.png) [image link](https://commons.wikimedia.org/wiki/File:ADSR_parameter.svg) attack[](https://strudel.cc/learn/effects/#attack) --------------------------------------------------- Synonyms: `att` Amplitude envelope attack time: Specifies how long it takes for the sound to reach its peak value, relative to the onset. * attack (number|Pattern): time in seconds. note("c3 e3 f3 g3").attack("<0 .1 .5>") decay[](https://strudel.cc/learn/effects/#decay) ------------------------------------------------- Synonyms: `dec` Amplitude envelope decay time: the time it takes after the attack time to reach the sustain level. Note that the decay is only audible if the sustain value is lower than 1. * time (number|Pattern): decay time in seconds note("c3 e3 f3 g3").decay("<.1 .2 .3 .4>").sustain(0) sustain[](https://strudel.cc/learn/effects/#sustain) ----------------------------------------------------- Synonyms: `sus` Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset. * gain (number|Pattern): sustain level between 0 and 1 note("c3 e3 f3 g3").decay(.2).sustain("<0 .1 .4 .6 1>") release[](https://strudel.cc/learn/effects/#release) ----------------------------------------------------- Synonyms: `rel` Amplitude envelope release time: The time it takes after the offset to go from sustain level to zero. * time (number|Pattern): release time in seconds note("c3 e3 g3 c4").release("<0 .1 .4 .6 1>/2") adsr[](https://strudel.cc/learn/effects/#adsr) ----------------------------------------------- ADSR envelope: Combination of Attack, Decay, Sustain, and Release. * time (number|Pattern): attack time in seconds * time (number|Pattern): decay time in seconds * gain (number|Pattern): sustain level (0 to 1) * time (number|Pattern): release time in seconds note("\[c3 bb2 f3 eb3\]\*2").sound("sawtooth").lpf(600).adsr(".1:.1:.5:.2") Filter Envelope[](https://strudel.cc/learn/effects/#filter-envelope) ===================================================================== Each filter can receive an additional filter envelope controlling the cutoff value dynamically. It uses an ADSR envelope similar to the one used for amplitude. There is an additional parameter to control the depth of the filter modulation: `lpenv`|`hpenv`|`bpenv`. This allows you to play subtle or huge filter modulations just the same by only increasing or decreasing the depth. note("\[c eb g \](3,8,<0 1>)".sub(12)) .s("/64") .lpf(sine.range(300,2000).slow(16)) .lpa(0.005) .lpd(perlin.range(.02,.2)) .lps(perlin.range(0,.5).slow(3)) .lpq(sine.range(2,10).slow(32)) .release(.5) .lpenv(perlin.range(1,8).slow(2)) .ftype('24db') .room(1) .juxBy(.5,rev) .sometimes(add(note(12))) .stack(s("bd\*2").bank('RolandTR909')) .gain(.5).fast(2) There is one filter envelope for each filter type and thus one set of envelope filter parameters preceded either by `lp`, `hp` or `bp`: * `lpattack`, `lpdecay`, `lpsustain`, `lprelease`, `lpenv`: filter envelope for the lowpass filter. * alternatively: `lpa`, `lpd`, `lps`, `lpr` and `lpe`. * `hpattack`, `hpdecay`, `hpsustain`, `hprelease`, `hpenv`: filter envelope for the highpass filter. * alternatively: `hpa`, `hpd`, `hps`, `hpr` and `hpe`. * `bpattack`, `bpdecay`, `bpsustain`, `bprelease`, `bpenv`: filter envelope for the bandpass filter. * alternatively: `bpa`, `bpd`, `bps`, `bpr` and `bpe`. lpattack[](https://strudel.cc/learn/effects/#lpattack) ------------------------------------------------------- Synonyms: `lpa` Sets the attack duration for the lowpass filter envelope. * attack (number|Pattern): time of the filter envelope note("c2 e2 f2 g2") .sound('sawtooth') .lpf(300) .lpa("<.5 .25 .1 .01>/4") .lpenv(4) lpdecay[](https://strudel.cc/learn/effects/#lpdecay) ----------------------------------------------------- Synonyms: `lpd` Sets the decay duration for the lowpass filter envelope. * decay (number|Pattern): time of the filter envelope note("c2 e2 f2 g2") .sound('sawtooth') .lpf(300) .lpd("<.5 .25 .1 0>/4") .lpenv(4) lpsustain[](https://strudel.cc/learn/effects/#lpsustain) --------------------------------------------------------- Synonyms: `lps` Sets the sustain amplitude for the lowpass filter envelope. * sustain (number|Pattern): amplitude of the lowpass filter envelope note("c2 e2 f2 g2") .sound('sawtooth') .lpf(300) .lpd(.5) .lps("<0 .25 .5 1>/4") .lpenv(4) lprelease[](https://strudel.cc/learn/effects/#lprelease) --------------------------------------------------------- Synonyms: `lpr` Sets the release time for the lowpass filter envelope. * release (number|Pattern): time of the filter envelope note("c2 e2 f2 g2") .sound('sawtooth') .clip(.5) .lpf(300) .lpenv(4) .lpr("<.5 .25 .1 0>/4") .release(.5) lpenv[](https://strudel.cc/learn/effects/#lpenv) ------------------------------------------------- Synonyms: `lpe` Sets the lowpass filter envelope modulation depth. * modulation (number|Pattern): depth of the lowpass filter envelope between 0 and n note("c2 e2 f2 g2") .sound('sawtooth') .lpf(300) .lpa(.5) .lpenv("<4 2 1 0 -1 -2 -4>/4") Pitch Envelope[](https://strudel.cc/learn/effects/#pitch-envelope) =================================================================== You can also control the pitch with envelopes! Pitch envelopes can breathe life into static sounds: n("<-4,0 5 2 1>\*<2!3 4>") .scale("/8:pentatonic") .s("gm\_electric\_guitar\_jazz") .penv("<.5 0 7 -2>\*2").vib("4:.1") .phaser(2).delay(.25).room(.3) .size(4).fast(1.5) You also create some lovely chiptune-style sounds: n(run("<4 8>/16")).jux(rev) .chord(">") .dict('ireal') .voicing().add(note("<0 1>/8")) .dec(.1).room(.2) .segment("<4 \[2 8\]>") .penv("<0 <2 -2>>").patt(.02).fast(2) Let’s break down all pitch envelope controls: pattack[](https://strudel.cc/learn/effects/#pattack) ----------------------------------------------------- Synonyms: `patt` Attack time of pitch envelope. * time (number|Pattern): time in seconds note("c eb g bb").pattack("0 .1 .25 .5").slow(2) pdecay[](https://strudel.cc/learn/effects/#pdecay) --------------------------------------------------- Synonyms: `pdec` Decay time of pitch envelope. * time (number|Pattern): time in seconds note("").pdecay("<0 .1 .25 .5>") prelease[](https://strudel.cc/learn/effects/#prelease) ------------------------------------------------------- Synonyms: `prel` Release time of pitch envelope * time (number|Pattern): time in seconds note(" ~") .release(.5) // to hear the pitch release .prelease("<0 .1 .25 .5>") penv[](https://strudel.cc/learn/effects/#penv) ----------------------------------------------- Amount of pitch envelope. Negative values will flip the envelope. If you don't set other pitch envelope controls, `pattack:.2` will be the default. * semitones (number|Pattern): change in semitones note("c") .penv("<12 7 1 .5 0 -1 -7 -12>") pcurve[](https://strudel.cc/learn/effects/#pcurve) --------------------------------------------------- Curve of envelope. Defaults to linear. exponential is good for kicks * type (number|Pattern): 0 = linear, 1 = exponential note("g1\*4") .s("sine").pdec(.5) .penv(32) .pcurve("<0 1>") panchor[](https://strudel.cc/learn/effects/#panchor) ----------------------------------------------------- Sets the range anchor of the envelope: * anchor 0: range = \[note, note + penv\] * anchor 1: range = \[note - penv, note\] If you don't set an anchor, the value will default to the psustain value. * anchor (number|Pattern): anchor offset note("c c4").penv(12).panchor("<0 .5 1 .5>") Dynamics[](https://strudel.cc/learn/effects/#dynamics) ======================================================= gain[](https://strudel.cc/learn/effects/#gain) ----------------------------------------------- Controls the gain by an exponential amount. * amount (number|Pattern): gain. s("hh\*8").gain(".4!2 1 .4!2 1 .4 1").fast(2) velocity[](https://strudel.cc/learn/effects/#velocity) ------------------------------------------------------- Sets the velocity from 0 to 1. Is multiplied together with gain. s("hh\*8") .gain(".4!2 1 .4!2 1 .4 1") .velocity(".4 1") compressor[](https://strudel.cc/learn/effects/#compressor) ----------------------------------------------------------- Dynamics Compressor. The params are `compressor("threshold:ratio:knee:attack:release")` More info [here](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode?retiredLocale=de#instance_properties) s("bd sd \[~ bd\] sd,hh\*8") .compressor("-20:20:10:.002:.02") postgain[](https://strudel.cc/learn/effects/#postgain) ------------------------------------------------------- Gain applied after all effects have been processed. s("bd sd \[~ bd\] sd,hh\*8") .compressor("-20:20:10:.002:.02").postgain(1.5) xfade[](https://strudel.cc/learn/effects/#xfade) ------------------------------------------------- Cross-fades between left and right from 0 to 1: * 0 = (full left, no right) * .5 = (both equal) * 1 = (no left, full right) xfade(s("bd\*2"), "<0 .25 .5 .75 1>", s("hh\*8")) Panning[](https://strudel.cc/learn/effects/#panning) ===================================================== jux[](https://strudel.cc/learn/effects/#jux) --------------------------------------------- The jux function creates strange stereo effects, by applying a function to a pattern, but only in the right-hand channel. s("bd lt \[~ ht\] mt cp ~ bd hh").jux(rev) s("bd lt \[~ ht\] mt cp ~ bd hh").jux(press) s("bd lt \[~ ht\] mt cp ~ bd hh").jux(iter(4)) juxBy[](https://strudel.cc/learn/effects/#juxby) ------------------------------------------------- Synonyms: `juxby` Jux with adjustable stereo width. 0 = mono, 1 = full stereo. s("bd lt \[~ ht\] mt cp ~ bd hh").juxBy("<0 .5 1>/2", rev) pan[](https://strudel.cc/learn/effects/#pan) --------------------------------------------- Sets position in stereo. * pan (number|Pattern): between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel) s("\[bd hh\]\*2").pan("<.5 1 .5 0>") s("bd rim sd rim bd ~ cp rim").pan(sine.slow(2)) Waveshaping[](https://strudel.cc/learn/effects/#waveshaping) ============================================================= coarse[](https://strudel.cc/learn/effects/#coarse) --------------------------------------------------- Fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers * factor (number|Pattern): 1 for original 2 for half, 3 for a third and so on. s("bd sd \[~ bd\] sd,hh\*8").coarse("<1 4 8 16 32>") crush[](https://strudel.cc/learn/effects/#crush) ------------------------------------------------- Bit crusher effect. * depth (number|Pattern): between 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction). s(",hh\*3").fast(2).crush("<16 8 7 6 5 4 3 2>") distort[](https://strudel.cc/learn/effects/#distort) ----------------------------------------------------- Synonyms: `dist` Wave shaping distortion. CAUTION: it can get loud. Second option in optional array syntax (ex: ".9:.5") applies a postgain to the output. Third option sets the waveshaping type. Most useful values are usually between 0 and 10 (depending on source gain). If you are feeling adventurous, you can turn it up to 11 and beyond ;) * distortion (number|Pattern): amount of distortion to apply * volume (number|Pattern): linear postgain of the distortion * type (number|string|Pattern): type of distortion to apply s("bd sd \[~ bd\] sd,hh\*8").distort("<0 2 3 10:.5>") note("d1!8").s("sine").penv(36).pdecay(.12).decay(.23).distort("8:.4") s("bd:4\*4").bank("tr808").distort("3:0.5:diode") Global Effects[](https://strudel.cc/learn/effects/#global-effects) =================================================================== Local vs Global Effects[](https://strudel.cc/learn/effects/#local-vs-global-effects) ------------------------------------------------------------------------------------- While the above listed “local” effects will always create a separate effects chain for each event, global effects use the same chain for all events of the same orbit: orbit[](https://strudel.cc/learn/effects/#orbit) ------------------------------------------------- Synonyms: `o` An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects. * number (number|Pattern): stack( s("hh\*6").delay(.5).delaytime(.25).orbit(1), s("~ sd ~ sd").delay(.5).delaytime(.125).orbit(2) ) Delay[](https://strudel.cc/learn/effects/#delay) ------------------------------------------------- ### delay[](https://strudel.cc/learn/effects/#delay-1) Sets the level of the delay signal. When using mininotation, you can also optionally add the 'delaytime' and 'delayfeedback' parameter, separated by ':'. * level (number|Pattern): between 0 and 1 s("bd bd").delay("<0 .25 .5 1>") s("bd bd").delay("0.65:0.25:0.9 0.65:0.125:0.7") ### delaytime[](https://strudel.cc/learn/effects/#delaytime) ### delayfeedback[](https://strudel.cc/learn/effects/#delayfeedback) Synonyms: `delayfb, dfb` Sets the level of the signal that is fed back into the delay. Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it * feedback (number|Pattern): between 0 and 1 s("bd").delay(.25).delayfeedback("<.25 .5 .75 1>") Reverb[](https://strudel.cc/learn/effects/#reverb) --------------------------------------------------- ### room[](https://strudel.cc/learn/effects/#room) Sets the level of reverb. When using mininotation, you can also optionally add the 'size' parameter, separated by ':'. * level (number|Pattern): between 0 and 1 s("bd sd \[~ bd\] sd").room("<0 .2 .4 .6 .8 1>") s("bd sd \[~ bd\] sd").room("<0.9:1 0.9:4>") ### roomsize[](https://strudel.cc/learn/effects/#roomsize) Synonyms: `rsize, sz, size` Sets the room size of the reverb, see `room`. When this property is changed, the reverb will be recaculated, so only change this sparsely.. * size (number|Pattern): between 0 and 10 s("bd sd \[~ bd\] sd").room(.8).rsize(1) s("bd sd \[~ bd\] sd").room(.8).rsize(4) ### roomfade[](https://strudel.cc/learn/effects/#roomfade) Synonyms: `rfade` Reverb fade time (in seconds). When this property is changed, the reverb will be recaculated, so only change this sparsely.. * seconds (number): for the reverb to fade s("bd sd \[~ bd\] sd").room(0.5).rlp(10000).rfade(0.5) s("bd sd \[~ bd\] sd").room(0.5).rlp(5000).rfade(4) ### roomlp[](https://strudel.cc/learn/effects/#roomlp) Synonyms: `rlp` Reverb lowpass starting frequency (in hertz). When this property is changed, the reverb will be recaculated, so only change this sparsely.. * frequency (number): between 0 and 20000hz s("bd sd \[~ bd\] sd").room(0.5).rlp(10000) s("bd sd \[~ bd\] sd").room(0.5).rlp(5000) ### roomdim[](https://strudel.cc/learn/effects/#roomdim) Synonyms: `rdim` Reverb lowpass frequency at -60dB (in hertz). When this property is changed, the reverb will be recaculated, so only change this sparsely.. * frequency (number): between 0 and 20000hz s("bd sd \[~ bd\] sd").room(0.5).rlp(10000).rdim(8000) s("bd sd \[~ bd\] sd").room(0.5).rlp(5000).rdim(400) ### iresponse[](https://strudel.cc/learn/effects/#iresponse) Synonyms: `ir` Sets the sample to use as an impulse response for the reverb. * sample (string|Pattern): to use as an impulse response s("bd sd \[~ bd\] sd").room(.8).ir("") Phaser[](https://strudel.cc/learn/effects/#phaser) --------------------------------------------------- ### phaser[](https://strudel.cc/learn/effects/#phaser-1) Synonyms: `ph` Phaser audio effect that approximates popular guitar pedals. * speed (number|Pattern): speed of modulation n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5) .phaser("<1 2 4 8>") ### phaserdepth[](https://strudel.cc/learn/effects/#phaserdepth) Synonyms: `phd, phasdp` The amount the signal is affected by the phaser effect. Defaults to 0.75 * depth (number|Pattern): number between 0 and 1 n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5) .phaser(2).phaserdepth("<0 .5 .75 1>") ### phasercenter[](https://strudel.cc/learn/effects/#phasercenter) Synonyms: `phc` The center frequency of the phaser in HZ. Defaults to 1000 * centerfrequency (number|Pattern): in HZ n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5) .phaser(2).phasercenter("<800 2000 4000>") ### phasersweep[](https://strudel.cc/learn/effects/#phasersweep) Synonyms: `phs` The frequency sweep range of the lfo for the phaser effect. Defaults to 2000 * phasersweep (number|Pattern): most useful values are between 0 and 4000 n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5) .phaser(2).phasersweep("<800 2000 4000>") Duck[](https://strudel.cc/learn/effects/#duck) ----------------------------------------------- ### duckorbit[](https://strudel.cc/learn/effects/#duckorbit) Synonyms: `duck` Modulate the amplitude of an orbit to create a "sidechain" like effect. Can be applied to multiple orbits with the ':' mininotation, e.g. `duckorbit("2:3")` * orbit (number|Pattern): target orbit $: n(run(16)).scale("c:minor:pentatonic").s("sawtooth").delay(.7).orbit(2) $: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack(0.2).duckdepth(1) $: n(run(16)).scale("c:minor:pentatonic").s("sawtooth").delay(.7).orbit(2) $: s("hh\*16").orbit(3) $: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit("2:3").duckattack(0.2).duckdepth(1) ### duckattack[](https://strudel.cc/learn/effects/#duckattack) Synonyms: `duckatt` The time required for the ducked signal(s) to return to their normal volume. Can vary across orbits with the ':' mininotation, e.g. `duckonset("0:0.003")`. Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * time (number|Pattern): The attack time in seconds sound: n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2) ducker: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack("<0.2 0 0.4>").duckdepth(1) moreduck: n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2) lessduck: s("hh\*16").orbit(5) ducker: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit("2:5").duckattack("0.4:0.1") ### duckdepth[](https://strudel.cc/learn/effects/#duckdepth) The amount of ducking applied to target orbit Can vary across orbits with the ':' mininotation, e.g. `duckdepth("0.3:0.1")`. Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * depth (number|Pattern): depth of modulation from 0 to 1 stack( n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2), s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack(0.2).duckdepth("<1 .9 .6 0>")) $: n(run(16)).scale("c:minor:pentatonic").s("sawtooth").delay(.7).orbit(2) $: s("hh\*16").orbit(3) $: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit("2:3").duckattack(0.2).duckdepth("1:0.5") Next, we’ll look at input / output via [MIDI, OSC and other methods](https://strudel.cc/learn/input-output/) . --- # Creating Patterns 🌀 Strudel Creating Patterns[](https://strudel.cc/learn/factories/#creating-patterns) =========================================================================== The following functions will return a pattern. These are the equivalents used by the Mini Notation: | function | mini | | --- | --- | | `cat(x, y)` | `""` | | `seq(x, y)` | `"x y"` | | `stack(x, y)` | `"x,y"` | | `stepcat([3,x],[2,y])` | `"x@3 y@2"` | | `polymeter([a, b, c], [x, y])` | `"{a b c, x y}"` | | `polymeterSteps(2, x, y, z)` | `"{x y z}%2"` | | `silence` | `"~"` | cat[](https://strudel.cc/learn/factories/#cat) ----------------------------------------------- Synonyms: `slowcat` The given items are con**cat**enated, where each one takes one cycle. * items (any): The items to concatenate cat("e5", "b4", \["d5", "c5"\]).note() // "".note() // As a chained function: s("hh\*4").cat( note("c4(5,8)") ) seq[](https://strudel.cc/learn/factories/#seq) ----------------------------------------------- Synonyms: `sequence, fastcat` Like **cat**, but the items are crammed into one cycle. seq("e5", "b4", \["d5", "c5"\]).note() // "e5 b4 \[d5 c5\]".note() // As a chained function: s("hh\*4").seq( note("c4(5,8)") ) stack[](https://strudel.cc/learn/factories/#stack) --------------------------------------------------- Synonyms: `polyrhythm, pr` The given items are played at the same time at the same length. stack("g3", "b3", \["e4", "d4"\]).note() // "g3,b3,\[e4 d4\]".note() // As a chained function: s("hh\*4").stack( note("c4(5,8)") ) stepcat[](https://strudel.cc/learn/factories/#stepcat) ------------------------------------------------------- Synonyms: `timeCat, timecat` 'Concatenates' patterns like `fastcat`, but proportional to a number of steps per cycle. The steps can either be inferred from the pattern, or provided as a \[length, pattern\] pair. Has the alias `timecat`. stepcat(\[3,"e3"\],\[1, "g3"\]).note() // the same as "e3@3 g3".note() stepcat("bd sd cp","hh hh").sound() // the same as "bd sd cp hh hh".sound() arrange[](https://strudel.cc/learn/factories/#arrange) ------------------------------------------------------- Allows to arrange multiple patterns together over multiple cycles. Takes a variable number of arrays with two elements specifying the number of cycles and the pattern to use. arrange( \[4, "(3,8)"\], \[2, "(5,8)"\] ).note() polymeter[](https://strudel.cc/learn/factories/#polymeter) ----------------------------------------------------------- Synonyms: `pm` _Experimental_ Aligns the steps of the patterns, creating polymeters. The patterns are repeated until they all fit the cycle. For example, in the below the first pattern is repeated twice, and the second is repeated three times, to fit the lowest common multiple of six steps. // The same as note("{c eb g, c2 g2}%6") polymeter("c eb g", "c2 g2").note() polymeterSteps[](https://strudel.cc/learn/factories/#polymetersteps) --------------------------------------------------------------------- silence[](https://strudel.cc/learn/factories/#silence) ------------------------------------------------------- Does absolutely nothing.. silence // "~" run[](https://strudel.cc/learn/factories/#run) ----------------------------------------------- A discrete pattern of numbers from 0 to n-1 n(run(4)).scale("C4:pentatonic") // n("0 1 2 3").scale("C4:pentatonic") binary[](https://strudel.cc/learn/factories/#binary) ----------------------------------------------------- Creates a binary pattern from a number. * n (number): input number to convert to binary "hh".s().struct(binary(5)) // "hh".s().struct("1 0 1") binaryN[](https://strudel.cc/learn/factories/#binaryn) ------------------------------------------------------- Creates a binary pattern from a number, padded to n bits long. * n (number): input number to convert to binary * nBits (number): pattern length, defaults to 16 "hh".s().struct(binaryN(55532, 16)) // "hh".s().struct("1 1 0 1 1 0 0 0 1 1 1 0 1 1 0 0") After Pattern Constructors, let’s see what [Time Modifiers](https://strudel.cc/learn/time-modifiers/) are available. --- # Control Parameters 🌀 Strudel Control Parameters[](https://strudel.cc/functions/value-modifiers/#control-parameters) ======================================================================================= Besides functions that control time, we saw earlier that functions like `note` and `cutoff` control different parameters (short params) of an event. Let’s now look more closely at how these `param(eter) functions` work. Parameter Functions[](https://strudel.cc/functions/value-modifiers/#parameter-functions) ========================================================================================= A very powerful feature of tidal patterns is that each parameter can be controlled independently: In this example, the parameters `note`, `cutoff`, `gain` and `s` are controlled independently by either patterns or plain values (numbers / text). After pressing play, we can observe the time and parameter values of each event (hap) in the output created by `.log()`. Plain vs Parameterized Values[](https://strudel.cc/functions/value-modifiers/#plain-vs-parameterized-values) ------------------------------------------------------------------------------------------------------------- Patterns that are not wrapped inside a param function will contain unlabeled `plain values`: This will not generate any sound output, because Strudel could only guess which param is meant by these letters. Now compare that to the version wrapped in `note`: Now it is clear that these letters are meant to be played as notes. Under the hood, the `note` function (as well as all other param functions) will wrap each plain value in an object. If the note function did not exist, we would need to write: This will have the same output, though it is rather unwieldy to read and write. Wrapping Parameter Functions[](https://strudel.cc/functions/value-modifiers/#wrapping-parameter-functions) ----------------------------------------------------------------------------------------------------------- To avoid too much nesting, param functions can also be chained like this: This is equivalent to `note(cat('c','e','g')).log()`. You can use this with any function that declares a type (like `n`, `s`, `note`, `freq` etc), just make sure to leave the parens empty! Plain Value Modification[](https://strudel.cc/functions/value-modifiers/#plain-value-modification) --------------------------------------------------------------------------------------------------- Patterns of plain values can be modified with any of the following operators: Here, the add function modifies the numbers on the left. Again, there is no output because these numbers have no meaning without a param. Param Value Modification[](https://strudel.cc/functions/value-modifiers/#param-value-modification) --------------------------------------------------------------------------------------------------- To modify a parameter value, you can either: * Use the operator on the plain value pattern, inside the param function: * Similarly, use the operator on the plain value pattern and wrap it later: * Specify which param should be modified inside the operator function: Remember the execution of the chained functions goes from left to right. Operators[](https://strudel.cc/functions/value-modifiers/#operators) ===================================================================== This group of functions allows to modify the value of events. add[](https://strudel.cc/functions/value-modifiers/#add) --------------------------------------------------------- Assumes a pattern of numbers. Adds the given number to each item in the pattern. // Here, the triad 0, 2, 4 is shifted by different amounts n("0 2 4".add("<0 3 4 0>")).scale("C:major") // Without add, the equivalent would be: // n("<\[0 2 4\] \[3 5 7\] \[4 6 8\] \[0 2 4\]>").scale("C:major") // You can also use add with notes: note("c3 e3 g3".add("<0 5 7 0>")) // Behind the scenes, the notes are converted to midi numbers: // note("48 52 55".add("<0 5 7 0>")) sub[](https://strudel.cc/functions/value-modifiers/#sub) --------------------------------------------------------- Like add, but the given numbers are subtracted. n("0 2 4".sub("<0 1 2 3>")).scale("C4:minor") // See add for more information. mul[](https://strudel.cc/functions/value-modifiers/#mul) --------------------------------------------------------- Multiplies each number by the given factor. "<1 1.5 \[1.66, <2 2.33>\]>\*4".mul(150).freq() div[](https://strudel.cc/functions/value-modifiers/#div) --------------------------------------------------------- Divides each number by the given factor. round[](https://strudel.cc/functions/value-modifiers/#round) ------------------------------------------------------------- Assumes a numerical pattern. Returns a new pattern with all values rounded to the nearest integer. n("0.5 1.5 2.5".round()).scale("C:major") floor[](https://strudel.cc/functions/value-modifiers/#floor) ------------------------------------------------------------- Assumes a numerical pattern. Returns a new pattern with all values set to their mathematical floor. E.g. `3.7` replaced with to `3`, and `-4.2` replaced with `-5`. note("42 42.1 42.5 43".floor()) ceil[](https://strudel.cc/functions/value-modifiers/#ceil) ----------------------------------------------------------- Assumes a numerical pattern. Returns a new pattern with all values set to their mathematical ceiling. E.g. `3.2` replaced with `4`, and `-4.2` replaced with `-4`. note("42 42.1 42.5 43".ceil()) range[](https://strudel.cc/functions/value-modifiers/#range) ------------------------------------------------------------- Assumes a numerical pattern, containing unipolar values in the range 0 .. 1. Returns a new pattern with values scaled to the given min/max range. Most useful in combination with continuous patterns. s("\[bd sd\]\*2,hh\*8") .cutoff(sine.range(500,4000)) rangex[](https://strudel.cc/functions/value-modifiers/#rangex) --------------------------------------------------------------- Assumes a numerical pattern, containing unipolar values in the range 0 .. 1 Returns a new pattern with values scaled to the given min/max range, following an exponential curve. s("\[bd sd\]\*2,hh\*8") .cutoff(sine.rangex(500,4000)) range2[](https://strudel.cc/functions/value-modifiers/#range2) --------------------------------------------------------------- Assumes a numerical pattern, containing bipolar values in the range -1 .. 1 Returns a new pattern with values scaled to the given min/max range. s("\[bd sd\]\*2,hh\*8") .cutoff(sine2.range2(500,4000)) ratio[](https://strudel.cc/functions/value-modifiers/#ratio) ------------------------------------------------------------- Allows dividing numbers via list notation using ":". Returns a new pattern with just numbers. ratio("1, 5:4, 3:2").mul(110) .freq().s("piano") as[](https://strudel.cc/functions/value-modifiers/#as) ------------------------------------------------------- Sets properties in a batch. * mapping (String|Array): the control names that are set "c:.5 a:1 f:.25 e:.8".as("note:clip") "{0@2 0.25 0 0.5 .3 .5}%8".as("begin").s("sax\_vib").clip(1) Custom Parameters[](https://strudel.cc/functions/value-modifiers/#custom-parameters) ===================================================================================== You can also create your own parameters: Multiple params can also be created in a more consice way, using `createParams`: Note that these params will not do anything until you give them meaning in your custom output! From modifying parameters we transition to the concept of [Signals](https://strudel.cc/learn/signals/) . --- # Time Modifiers 🌀 Strudel Time Modifiers[](https://strudel.cc/learn/time-modifiers/#time-modifiers) ========================================================================== The following functions modify a pattern temporal structure in some way. Some of these have equivalent operators in the Mini Notation: | function | mini | | --- | --- | | `"x".slow(2)` | `"x/2"` | | `"x".fast(2)` | `"x*2"` | | `"x".euclid(3,8)` | `"x(3,8)"` | | `"x".euclidRot(3,8,1)` | `"x(3,8,1)"` | slow[](https://strudel.cc/learn/time-modifiers/#slow) ------------------------------------------------------ Synonyms: `sparsity` Slow down a pattern over the given number of cycles. Like the "/" operator in mini notation. * factor (number|Pattern): slow down factor s("bd hh sd hh").slow(2) // s("\[bd hh sd hh\]/2") fast[](https://strudel.cc/learn/time-modifiers/#fast) ------------------------------------------------------ Synonyms: `density` Speed up a pattern by the given factor. Used by "\*" in mini notation. * factor (number|Pattern): speed up factor s("bd hh sd hh").fast(2) // s("\[bd hh sd hh\]\*2") early[](https://strudel.cc/learn/time-modifiers/#early) -------------------------------------------------------- Nudge a pattern to start earlier in time. Equivalent of Tidal's <~ operator * cycles (number|Pattern): number of cycles to nudge left "bd ~".stack("hh ~".early(.1)).s() late[](https://strudel.cc/learn/time-modifiers/#late) ------------------------------------------------------ Nudge a pattern to start later in time. Equivalent of Tidal's ~> operator * cycles (number|Pattern): number of cycles to nudge right "bd ~".stack("hh ~".late(.1)).s() clip / legato[](https://strudel.cc/learn/time-modifiers/#clip--legato) ----------------------------------------------------------------------- Synonyms: `legato` Multiplies the duration with the given number. Also cuts samples off at the end if they exceed the duration. * factor (number|Pattern): \= 0 note("c a f e").s("piano").clip("<.5 1 2>") euclid[](https://strudel.cc/learn/time-modifiers/#euclid) ---------------------------------------------------------- Changes the structure of the pattern to form an Euclidean rhythm. Euclidean rhythms are rhythms obtained using the greatest common divisor of two numbers. They were described in 2004 by Godfried Toussaint, a Canadian computer scientist. Euclidean rhythms are really useful for computer/algorithmic music because they can describe a large number of rhythms with a couple of numbers. * pulses (number): the number of onsets/beats * steps (number): the number of steps to fill // The Cuban tresillo pattern. note("c3").euclid(3,8) ### euclidRot[](https://strudel.cc/learn/time-modifiers/#euclidrot) Like `euclid`, but has an additional parameter for 'rotating' the resulting sequence. * pulses (number): the number of onsets/beats * steps (number): the number of steps to fill * rotation (number): offset in steps // A Samba rhythm necklace from Brazil note("c3").euclidRot(3,16,14) ### euclidLegato[](https://strudel.cc/learn/time-modifiers/#euclidlegato) Similar to `euclid`, but each pulse is held until the next pulse, so there will be no gaps. * pulses (number): the number of onsets/beats * steps (number): the number of steps to fill * rotation (): offset in steps * pat (): note("c3").euclidLegato(3,8) rev[](https://strudel.cc/learn/time-modifiers/#rev) ---------------------------------------------------- Reverse all cycles in a pattern. See also `revv` for reversing a whole pattern. note("c d e g").rev() palindrome[](https://strudel.cc/learn/time-modifiers/#palindrome) ------------------------------------------------------------------ Applies `rev` to a pattern every other cycle, so that the pattern alternates between forwards and backwards. note("c d e g").palindrome() iter[](https://strudel.cc/learn/time-modifiers/#iter) ------------------------------------------------------ Divides a pattern into a given number of subdivisions, plays the subdivisions in order, but increments the starting subdivision each cycle. The pattern wraps to the first subdivision after the last subdivision is played. note("0 1 2 3".scale('A minor')).iter(4) ### iterBack[](https://strudel.cc/learn/time-modifiers/#iterback) Synonyms: `iterback` Like `iter`, but plays the subdivisions in reverse order. Known as iter' in tidalcycles note("0 1 2 3".scale('A minor')).iterBack(4) ply[](https://strudel.cc/learn/time-modifiers/#ply) ---------------------------------------------------- The ply function repeats each event the given number of times. s("bd ~ sd cp").ply("<1 2 3>") segment[](https://strudel.cc/learn/time-modifiers/#segment) ------------------------------------------------------------ Synonyms: `seg` Samples the pattern at a rate of n events per cycle. Useful for turning a continuous pattern into a discrete one. * segments (number): number of segments per cycle note(saw.range(40,52).segment(24)) compress[](https://strudel.cc/learn/time-modifiers/#compress) -------------------------------------------------------------- Compress each cycle into the given timespan, leaving a gap cat( s("bd sd").compress(.25,.75), s("~ bd sd ~") ) zoom[](https://strudel.cc/learn/time-modifiers/#zoom) ------------------------------------------------------ Plays a portion of a pattern, specified by the beginning and end of a time span. The new resulting pattern is played over the time period of the original pattern: s("bd\*2 hh\*3 \[sd bd\]\*2 perc").zoom(0.25, 0.75) // s("hh\*3 \[sd bd\]\*2") // equivalent linger[](https://strudel.cc/learn/time-modifiers/#linger) ---------------------------------------------------------- Selects the given fraction of the pattern and repeats that part to fill the remainder of the cycle. * fraction (number): fraction to select s("lt ht mt cp, \[hh oh\]\*2").linger("<1 .5 .25 .125>") fastGap[](https://strudel.cc/learn/time-modifiers/#fastgap) ------------------------------------------------------------ Synonyms: `fastgap` speeds up a pattern like fast, but rather than it playing multiple times as fast would it instead leaves a gap in the remaining space of the cycle. For example, the following will play the sound pattern "bd sn" only once but compressed into the first half of the cycle, i.e. twice as fast. s("bd sd").fastGap(2) inside[](https://strudel.cc/learn/time-modifiers/#inside) ---------------------------------------------------------- Carries out an operation 'inside' a cycle. "0 1 2 3 4 3 2 1".inside(4, rev).scale('C major').note() // "0 1 2 3 4 3 2 1".slow(4).rev().fast(4).scale('C major').note() outside[](https://strudel.cc/learn/time-modifiers/#outside) ------------------------------------------------------------ Carries out an operation 'outside' a cycle. "<\[0 1\] 2 \[3 4\] 5>".outside(4, rev).scale('C major').note() // "<\[0 1\] 2 \[3 4\] 5>".fast(4).rev().slow(4).scale('C major').note() cpm[](https://strudel.cc/learn/time-modifiers/#cpm) ---------------------------------------------------- Plays the pattern at the given cycles per minute. s(",hh\*2").cpm(90) // = 90 bpm ribbon[](https://strudel.cc/learn/time-modifiers/#ribbon) ---------------------------------------------------------- Synonyms: `rib` Loops the pattern inside an `offset` for `cycles`. If you think of the entire span of time in cycles as a ribbon, you can cut a single piece and loop it. * offset (number): start point of loop in cycles * cycles (number): loop length in cycles note("").ribbon(1, 2) // Looping a portion of randomness n(irand(8).segment(4)).scale("c:pentatonic").ribbon(1337, 2) // rhythm generator s("bd!16?").ribbon(29,.5) swingBy[](https://strudel.cc/learn/time-modifiers/#swingby) ------------------------------------------------------------ The function `swingBy x n` breaks each cycle into `n` slices, and then delays events in the second half of each slice by the amount `x`, which is relative to the size of the (half) slice. So if `x` is 0 it does nothing, `0.5` delays for half the note duration, and 1 will wrap around to doing nothing again. The end result is a shuffle or swing-like rhythm * subdivision (number): * offset (number): s("hh\*8").swingBy(1/3, 4) swing[](https://strudel.cc/learn/time-modifiers/#swing) -------------------------------------------------------- Shorthand for swingBy with 1/3: * subdivision (number): s("hh\*8").swing(4) // s("hh\*8").swingBy(1/3, 4) Apart from modifying time, there are ways to [Control Parameters](https://strudel.cc/functions/value-modifiers/) . --- # Tonal Functions 🌀 Strudel Tonal Functions[](https://strudel.cc/learn/tonal/#tonal-functions) =================================================================== These functions use [tonaljs](https://github.com/tonaljs/tonal) to provide helpers for musical operations. ### voicing()[](https://strudel.cc/learn/tonal/#voicing) Turns chord symbols into voicings. You can use the following control params: * `chord`: Note, followed by chord symbol, e.g. C Am G7 Bb^7 * `dict`: voicing dictionary to use, falls back to default dictionary * `anchor`: the note that is used to align the chord * `mode`: how the voicing is aligned to the anchor * `below`: top note <= anchor * `duck`: top note <= anchor, anchor excluded * `above`: bottom note >= anchor * `offset`: whole number that shifts the voicing up or down to the next voicing * `n`: if set, the voicing is played like a scale. Overshooting numbers will be octaved All of the above controls are optional, except `chord`. If you pass a pattern of strings to voicing, they will be interpreted as chords. n("0 1 2 3").chord("").voicing() Here’s an example of how you can play chords and a bassline: chord("\*2") .dict('ireal').layer( x=>x.struct("\[~ x\]\*2").voicing() , x=>n("0\*4").set(x).mode("root:g2").voicing() .s('sawtooth').cutoff("800:4:2") ) ### scale(name)[](https://strudel.cc/learn/tonal/#scalename) Turns numbers into notes in the scale (zero indexed) or quantizes notes to a scale. When describing notes via numbers, note that negative numbers can be used to wrap backwards in the scale as well as sharps or flats to produce notes outside of the scale. Also sets scale for other scale operations, like [Pattern#scaleTranspose](https://strudel.cc/learn/tonal/#pattern-scaleTranspose) . A scale consists of a root note (e.g. `c4`, `c`, `f#`, `bb4`) followed by semicolon (':') and then a [scale type](https://github.com/tonaljs/tonal/blob/main/packages/scale-type/data.ts) . The scale name must be written without spaces (because it would be interpreted as a multi-step pattern otherwise). If your scale name includes spaces, replace them with colons. The root note defaults to octave 3, if no octave number is given. * scale (string): Name of scale n("0 2 4 6 4 2").scale("C:major") n("\[0,7\] 4 \[2,7\] 4") .scale("C:/2") .s("piano") n(rand.range(0,12).segment(8)) .scale("C:ritusen") .s("piano") n("<\[0,7b\] \[-4# -4\] \[-2,7##\] 4 \[0,7\] \[-4# -4b\] \[-2,7###\] 4b>\*4") .scale("C:/2") .s("piano") note("C1\*16").transpose(irand(36)).scale('Cb2 major').scaleTranspose(3) n("\[0 0\] \[1 2\] \[3 4\] \[5 6\]").scale("C:major:blues") ### transpose(semitones)[](https://strudel.cc/learn/tonal/#transposesemitones) Transposes all notes to the given number of semitones: This method gets really exciting when we use it with a pattern as above. Instead of numbers, scientific interval notation can be used as well: ### scaleTranspose(steps)[](https://strudel.cc/learn/tonal/#scaletransposesteps) Transposes notes inside the scale by the number of steps: "\[-8 \[2,4,6\]\]\*2" .scale('C4 bebop major') .scaleTranspose("<0 -1 -2 -3 -4 -5 -6 -4>\*2") .note() ### rootNotes(octave = 2)[](https://strudel.cc/learn/tonal/#rootnotesoctave--2) Turns chord symbols into root notes of chords in given octave. Together with layer, struct and voicings, this can be used to create a basic backing track: "\*2".layer( x => x.voicings('lefthand').struct("\[~ x\]\*2").note(), x => x.rootNotes(2).note().s('sawtooth').cutoff(800) ) --- # Signals 🌀 Strudel Continuous Signals[](https://strudel.cc/learn/signals/#continuous-signals) =========================================================================== Signals are patterns with continuous values, meaning they have theoretically infinite steps. They can provide streams of numbers that can be sampled at discrete points in time. saw[](https://strudel.cc/learn/signals/#saw) --------------------------------------------- A sawtooth signal between 0 and 1. note("\*8") .clip(saw.slow(2)) n(saw.range(0,8).segment(8)) .scale('C major') sine[](https://strudel.cc/learn/signals/#sine) ----------------------------------------------- A sine signal between 0 and 1. n(sine.segment(16).range(0,15)) .scale("C:minor") cosine[](https://strudel.cc/learn/signals/#cosine) --------------------------------------------------- A cosine signal between 0 and 1. n(stack(sine,cosine).segment(16).range(0,15)) .scale("C:minor") tri[](https://strudel.cc/learn/signals/#tri) --------------------------------------------- A triangle signal between 0 and 1. n(tri.segment(8).range(0,7)).scale("C:minor") square[](https://strudel.cc/learn/signals/#square) --------------------------------------------------- A square signal between 0 and 1. n(square.segment(4).range(0,7)).scale("C:minor") rand[](https://strudel.cc/learn/signals/#rand) ----------------------------------------------- A continuous pattern of random numbers, between 0 and 1. // randomly change the cutoff s("bd\*4,hh\*8").cutoff(rand.range(500,8000)) Ranges from -1 to 1[](https://strudel.cc/learn/signals/#ranges-from--1-to-1) ----------------------------------------------------------------------------- There is also `saw2`, `sine2`, `cosine2`, `tri2`, `square2` and `rand2` which have a range from -1 to 1! perlin[](https://strudel.cc/learn/signals/#perlin) --------------------------------------------------- Generates a continuous pattern of [perlin noise](https://en.wikipedia.org/wiki/Perlin_noise) , in the range 0..1. // randomly change the cutoff s("bd\*4,hh\*8").cutoff(perlin.range(500,8000)) irand[](https://strudel.cc/learn/signals/#irand) ------------------------------------------------- A continuous pattern of random integers, between 0 and n-1. * n (number): max value (exclusive) // randomly select scale notes from 0 - 7 (= C to C) n(irand(8)).struct("x x\*2 x x\*3").scale("C:minor") brand[](https://strudel.cc/learn/signals/#brand) ------------------------------------------------- A continuous pattern of 0 or 1 (binary random) s("hh\*10").pan(brand) brandBy[](https://strudel.cc/learn/signals/#brandby) ----------------------------------------------------- A continuous pattern of 0 or 1 (binary random), with a probability for the value being 1 * probability (number): a number between 0 and 1 s("hh\*10").pan(brandBy(0.2)) mouseX[](https://strudel.cc/learn/signals/#mousex) --------------------------------------------------- The mouse's x position value ranges from 0 to 1. n(mousex.segment(4).range(0,7)).scale("C:minor") mouseY[](https://strudel.cc/learn/signals/#mousey) --------------------------------------------------- The mouse's y position value ranges from 0 to 1. n(mousey.segment(4).range(0,7)).scale("C:minor") Next up: [Random Modifiers](https://strudel.cc/learn/random-modifiers/) --- # Accumulation Modifiers 🌀 Strudel Accumulation Modifiers[](https://strudel.cc/learn/accumulation/#accumulation-modifiers) ======================================================================================== superimpose[](https://strudel.cc/learn/accumulation/#superimpose) ------------------------------------------------------------------ Superimposes the result of the given function(s) on top of the original pattern: "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>\*8" .superimpose(x=>x.add(2)) .scale('C minor').note() layer[](https://strudel.cc/learn/accumulation/#layer) ------------------------------------------------------ Synonyms: `apply` Layers the result of the given function(s). Like `superimpose`, but without the original pattern: "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>\*8" .layer(x=>x.add("0,2")) .scale('C minor').note() off[](https://strudel.cc/learn/accumulation/#off) -------------------------------------------------- Superimposes the function result on top of the original pattern, delayed by the given time. * time (Pattern|number): offset time * func (function): function to apply "c3 eb3 g3".off(1/8, x=>x.add(7)).note() echo[](https://strudel.cc/learn/accumulation/#echo) ---------------------------------------------------- Superimpose and offset multiple times, gradually decreasing the velocity * times (number): how many times to repeat * time (number): cycle offset between iterations * feedback (number): velocity multiplicator for each iteration s("bd sd").echo(3, 1/6, .8) echoWith[](https://strudel.cc/learn/accumulation/#echowith) ------------------------------------------------------------ Synonyms: `echowith, stutWith, stutwith` Superimpose and offset multiple times, applying the given function each time. * times (number): how many times to repeat * time (number): cycle offset between iterations * func (function): function to apply, given the pattern and the iteration index "<0 \[2 4\]>" .echoWith(4, 1/8, (p,n) => p.add(n\*2)) .scale("C:minor").note() stut[](https://strudel.cc/learn/accumulation/#stut) ---------------------------------------------------- Deprecated. Like echo, but the last 2 parameters are flipped. * times (number): how many times to repeat * feedback (number): velocity multiplicator for each iteration * time (number): cycle offset between iterations s("bd sd").stut(3, .8, 1/6) There are also [Tonal Functions](https://strudel.cc/learn/tonal/) . --- # Coding syntax 🌀 Strudel Coding Syntax[](https://strudel.cc/learn/code/#coding-syntax) ============================================================== Let’s take a step back and understand how the syntax in Strudel works. Take a look at this simple example: note("c a f e").s("piano") * We have a word `note` which is followed by some brackets `()` with some words/letters/numbers inside, surrounded by quotes `"c a f e"` * Then we have a dot `.` followed by another similar piece of code `s("piano")`. * We can also see these texts are _highlighted_ using colours: word `note` is purple, the brackets `()` are grey, and the content inside the `""` are green. (The colors could be different if you’ve changed the default theme) What happens if we try to ‘break’ this pattern in different ways? note(c a f e).s(piano) note("c a f e")s("piano") note\["c a f e"\].s{"piano"} Ok, none of these seem to work
 s("piano").note("c a f e") This one does work, but now we only hear the first note
 So what is going on here? Functions, arguments and chaining[](https://strudel.cc/learn/code/#functions-arguments-and-chaining) ===================================================================================================== So far, we’ve seen the following syntax: xxx("foo").yyy("bar") Generally, `xxx` and `yyy` are called [_functions_](https://en.wikipedia.org/wiki/Function_(computer_programming)) , while `foo` and `bar` are called function [_arguments_ or _parameters_](https://en.wikipedia.org/wiki/Parameter_(computer_programming)) . So far, we’ve used the functions to declare which aspect of the sound we want to control, and their arguments for the actual data. The `yyy` function is called a [_chained_ function](https://en.wikipedia.org/wiki/Method_chaining) , because it is preceded with a dot (`.`). Generally, the idea with chaining is that code such as `a("this").b("that").c("other")` allows `a`, `b` and `c` functions to happen in a specified order, without needing to write them as three separate lines of code. You can think of this as being similar to chaining audio effects together using guitar pedals or digital audio effects. Strudel makes heavy use of chained functions. Here is a more sophisticated example: note("a3 c#4 e4 a4") .s("sawtooth") .cutoff(500) //.delay(0.5) .room(0.5) Write your own chained function[](https://strudel.cc/learn/code/#write-your-own-chained-function) -------------------------------------------------------------------------------------------------- You can write your own chained function using `register`. Here’s the above chain but registered as a reusable, chained function. const effectChain = register('effectChain', (pat) => pat .s("sawtooth") .cutoff(500) //.delay(0.5) .room(0.5) ) note("a3 c#4 e4 a4").effectChain() Try adding `.rev()` after `effectChain()` to hear further effects added. Comments[](https://strudel.cc/learn/code/#comments) ==================================================== The `//` in the example above is a line comment, resulting in the `delay` function being ignored. It is a handy way to quickly turn code on and off. Try uncommenting this line by deleting `//` and refreshing the pattern. You can also use the keyboard shortcut `cmd-/` to toggle comments on and off. You might noticed that some comments in the REPL samples include some words starting with a ”@”, like `@by` or `@license`. Those are just a convention to define some information about the music. We will talk about it in the [Music metadata](https://strudel.cc/learn/metadata/) section. Strings[](https://strudel.cc/learn/code/#strings) ================================================== Ok, so what about the content inside the quotes (e.g. `"c a f e"`)? In JavaScript, as in most programming languages, this content is referred to as being a [_string_](https://en.wikipedia.org/wiki/String_(computer_science)) . A string is simply a sequence of individual characters. In TidalCycles, double quoted strings are used to write _patterns_ using the mini-notation, and you may hear the phrase _pattern string_ from time to time. If you want to create a regular string and not a pattern, you can use single quotes, e.g. `'C minor'` will not be parsed as Mini Notation. The good news is, that this covers most of the JavaScript syntax needed for Strudel! --- # Random Modifiers 🌀 Strudel Random Modifiers[](https://strudel.cc/learn/random-modifiers/#random-modifiers) ================================================================================ These methods add random behavior to your Patterns. choose[](https://strudel.cc/learn/random-modifiers/#choose) ------------------------------------------------------------ Chooses randomly from the given list of elements. * xs (any): values / patterns to choose from. note("c2 g2!2 d2 f1").s(choose("sine", "triangle", "bd:6")) wchoose[](https://strudel.cc/learn/random-modifiers/#wchoose) -------------------------------------------------------------- Chooses randomly from the given list of elements by giving a probability to each element * pairs (any): arrays of value and weight note("c2 g2!2 d2 f1").s(wchoose(\["sine",10\], \["triangle",1\], \["bd:6",1\])) chooseCycles[](https://strudel.cc/learn/random-modifiers/#choosecycles) ------------------------------------------------------------------------ Synonyms: `randcat` Picks one of the elements at random each cycle. chooseCycles("bd", "hh", "sd").s().fast(8) s("bd | hh | sd").fast(8) wchooseCycles[](https://strudel.cc/learn/random-modifiers/#wchoosecycles) -------------------------------------------------------------------------- Synonyms: `wrandcat` Picks one of the elements at random each cycle by giving a probability to each element wchooseCycles(\["bd",10\], \["hh",1\], \["sd",1\]).s().fast(8) wchooseCycles(\["c c c",5\], \["a a a",3\], \["f f f",1\]).fast(4).note() // The probability can itself be a pattern wchooseCycles(\["bd(3,8)","<5 0>"\], \["hh hh hh",3\]).fast(4).s() degradeBy[](https://strudel.cc/learn/random-modifiers/#degradeby) ------------------------------------------------------------------ Randomly removes events from the pattern by a given amount. 0 = 0% chance of removal 1 = 100% chance of removal * amount (number): a number between 0 and 1 s("hh\*8").degradeBy(0.2) s("\[hh?0.2\]\*8") //beat generator s("bd").segment(16).degradeBy(.5).ribbon(16,1) degrade[](https://strudel.cc/learn/random-modifiers/#degrade) -------------------------------------------------------------- Randomly removes 50% of events from the pattern. Shorthand for `.degradeBy(0.5)` s("hh\*8").degrade() s("\[hh?\]\*8") undegradeBy[](https://strudel.cc/learn/random-modifiers/#undegradeby) ---------------------------------------------------------------------- Inverse of `degradeBy`: Randomly removes events from the pattern by a given amount. 0 = 100% chance of removal 1 = 0% chance of removal Events that would be removed by degradeBy are let through by undegradeBy and vice versa (see second example). * amount (number): a number between 0 and 1 s("hh\*8").undegradeBy(0.2) s("hh\*10").layer( x => x.degradeBy(0.2).pan(0), x => x.undegradeBy(0.8).pan(1) ) undegrade[](https://strudel.cc/learn/random-modifiers/#undegrade) ------------------------------------------------------------------ Inverse of `degrade`: Randomly removes 50% of events from the pattern. Shorthand for `.undegradeBy(0.5)` Events that would be removed by degrade are let through by undegrade and vice versa (see second example). s("hh\*8").undegrade() s("hh\*10").layer( x => x.degrade().pan(0), x => x.undegrade().pan(1) ) sometimesBy[](https://strudel.cc/learn/random-modifiers/#sometimesby) ---------------------------------------------------------------------- Randomly applies the given function by the given probability. Similar to `someCyclesBy` * probability (number|Pattern): a number between 0 and 1 * function (function): the transformation to apply s("hh\*8").sometimesBy(.4, x=>x.speed("0.5")) sometimes[](https://strudel.cc/learn/random-modifiers/#sometimes) ------------------------------------------------------------------ Applies the given function with a 50% chance * function (function): the transformation to apply s("hh\*8").sometimes(x=>x.speed("0.5")) someCyclesBy[](https://strudel.cc/learn/random-modifiers/#somecyclesby) ------------------------------------------------------------------------ Randomly applies the given function by the given probability on a cycle by cycle basis. Similar to `sometimesBy` * probability (number|Pattern): a number between 0 and 1 * function (function): the transformation to apply s("bd,hh\*8").someCyclesBy(.3, x=>x.speed("0.5")) someCycles[](https://strudel.cc/learn/random-modifiers/#somecycles) -------------------------------------------------------------------- Shorthand for `.someCyclesBy(0.5, fn)` s("bd,hh\*8").someCycles(x=>x.speed("0.5")) often[](https://strudel.cc/learn/random-modifiers/#often) ---------------------------------------------------------- Shorthand for `.sometimesBy(0.75, fn)` s("hh\*8").often(x=>x.speed("0.5")) rarely[](https://strudel.cc/learn/random-modifiers/#rarely) ------------------------------------------------------------ Shorthand for `.sometimesBy(0.25, fn)` s("hh\*8").rarely(x=>x.speed("0.5")) almostNever[](https://strudel.cc/learn/random-modifiers/#almostnever) ---------------------------------------------------------------------- Shorthand for `.sometimesBy(0.1, fn)` s("hh\*8").almostNever(x=>x.speed("0.5")) almostAlways[](https://strudel.cc/learn/random-modifiers/#almostalways) ------------------------------------------------------------------------ Shorthand for `.sometimesBy(0.9, fn)` s("hh\*8").almostAlways(x=>x.speed("0.5")) never[](https://strudel.cc/learn/random-modifiers/#never) ---------------------------------------------------------- Shorthand for `.sometimesBy(0, fn)` (never calls fn) s("hh\*8").never(x=>x.speed("0.5")) always[](https://strudel.cc/learn/random-modifiers/#always) ------------------------------------------------------------ Shorthand for `.sometimesBy(1, fn)` (always calls fn) s("hh\*8").always(x=>x.speed("0.5")) Next up: [Conditional Modifiers](https://strudel.cc/learn/conditional-modifiers/) --- # Conditional Modifiers 🌀 Strudel Conditional Modifiers[](https://strudel.cc/learn/conditional-modifiers/#conditional-modifiers) =============================================================================================== lastOf[](https://strudel.cc/learn/conditional-modifiers/#lastof) ----------------------------------------------------------------- Applies the given function every n cycles, starting from the last cycle. * n (number): how many cycles * func (function): function to apply note("c3 d3 e3 g3").lastOf(4, x=>x.rev()) firstOf[](https://strudel.cc/learn/conditional-modifiers/#firstof) ------------------------------------------------------------------- Applies the given function every n cycles, starting from the first cycle. * n (number): how many cycles * func (function): function to apply note("c3 d3 e3 g3").firstOf(4, x=>x.rev()) when[](https://strudel.cc/learn/conditional-modifiers/#when) ------------------------------------------------------------- Applies the given function whenever the given pattern is in a true state. * binary\_pat (Pattern): * func (function): "c3 eb3 g3".when("<0 1>/2", x=>x.sub("5")).note() chunk[](https://strudel.cc/learn/conditional-modifiers/#chunk) --------------------------------------------------------------- Synonyms: `slowChunk, slowchunk` Divides a pattern into a given number of parts, then cycles through those parts in turn, applying the given function to each part in turn (one part per cycle). "0 1 2 3".chunk(4, x=>x.add(7)) .scale("A:minor").note() ### chunkBack[](https://strudel.cc/learn/conditional-modifiers/#chunkback) Synonyms: `chunkback` Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles "0 1 2 3".chunkBack(4, x=>x.add(7)) .scale("A:minor").note() ### fastChunk[](https://strudel.cc/learn/conditional-modifiers/#fastchunk) Synonyms: `fastchunk` Like `chunk`, but the cycles of the source pattern aren't repeated for each set of chunks. "<0 8> 1 2 3 4 5 6 7" .scale("C2:major").note() .fastChunk(4, x => x.color('red')).slow(2) arp[](https://strudel.cc/learn/conditional-modifiers/#arp) ----------------------------------------------------------- Selects indices in in stacked notes. note("<\[c,eb,g\]!2 \[c,f,ab\] \[d,f,ab\]>") .arp("0 \[0,2\] 1 \[0,2\]") arpWith đŸ§Ș[](https://strudel.cc/learn/conditional-modifiers/#arpwith-) ----------------------------------------------------------------------- Selects indices in in stacked notes. note("<\[c,eb,g\]!2 \[c,f,ab\] \[d,f,ab\]>") .arpWith(haps => haps\[2\]) struct[](https://strudel.cc/learn/conditional-modifiers/#struct) ----------------------------------------------------------------- Applies the given structure to the pattern: note("c,eb,g") .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~") .slow(2) mask[](https://strudel.cc/learn/conditional-modifiers/#mask) ------------------------------------------------------------- Returns silence when mask is 0 or "~" note("c \[eb,g\] d \[eb,g\]").mask("<1 \[0 1\]>") reset[](https://strudel.cc/learn/conditional-modifiers/#reset) --------------------------------------------------------------- Resets the pattern to the start of the cycle for each onset of the reset pattern. s("\[ sd\]\*2, hh\*8").reset("") restart[](https://strudel.cc/learn/conditional-modifiers/#restart) ------------------------------------------------------------------- Restarts the pattern for each onset of the restart pattern. While reset will only reset the current cycle, restart will start from cycle 0. s("\[ sd\]\*2, hh\*8").restart("") hush[](https://strudel.cc/learn/conditional-modifiers/#hush) ------------------------------------------------------------- Silences a pattern. stack( s("bd").hush(), s("hh\*3") ) invert[](https://strudel.cc/learn/conditional-modifiers/#invert) ----------------------------------------------------------------- Synonyms: `inv` Swaps 1s and 0s in a binary pattern. s("bd").struct("1 0 0 1 0 0 1 0".lastOf(4, invert)) pick[](https://strudel.cc/learn/conditional-modifiers/#pick) ------------------------------------------------------------- Picks patterns (or plain values) either from a list (by index) or a lookup table (by name). Similar to `inhabit`, but maintains the structure of the original patterns. * pat (Pattern): * xs (\*): note("<0 1 2!2 3>".pick(\["g a", "e f", "f g f g" , "g c d"\])) sound("<0 1 \[2,0\]>".pick(\["bd sd", "cp cp", "hh hh"\])) sound("<0!2 \[0,1\] 1>".pick(\["bd(3,8)", "sd sd"\])) s("".pick({a: "bd(3,8)", b: "sd sd"})) pickmod[](https://strudel.cc/learn/conditional-modifiers/#pickmod) ------------------------------------------------------------------- The same as `pick`, but if you pick a number greater than the size of the list, it wraps around, rather than sticking at the maximum value. For example, if you pick the fifth pattern of a list of three, you'll get the second one. * pat (Pattern): * xs (\*): pickF[](https://strudel.cc/learn/conditional-modifiers/#pickf) --------------------------------------------------------------- pickF lets you use a pattern of numbers to pick which function to apply to another pattern. * pat (Pattern): * lookup (Pattern): a pattern of indices * funcs (Array.): the array of functions from which to pull s("bd \[rim hh\]").pickF("<0 1 2>", \[rev,jux(rev),fast(2)\]) note("(3,8)").s("square") .pickF("<0 2> 1", \[jux(rev),fast(2),x=>x.lpf(800)\]) pickmodF[](https://strudel.cc/learn/conditional-modifiers/#pickmodf) --------------------------------------------------------------------- The same as `pickF`, but if you pick a number greater than the size of the functions list, it wraps around, rather than sticking at the maximum value. * pat (Pattern): * lookup (Pattern): a pattern of indices * funcs (Array.): the array of functions from which to pull pickRestart[](https://strudel.cc/learn/conditional-modifiers/#pickrestart) --------------------------------------------------------------------------- Similar to `pick`, but the choosen pattern is restarted when its index is triggered. * pat (Pattern): * xs (\*): pickmodRestart[](https://strudel.cc/learn/conditional-modifiers/#pickmodrestart) --------------------------------------------------------------------------------- The same as `pickRestart`, but if you pick a number greater than the size of the list, it wraps around, rather than sticking at the maximum value. * pat (Pattern): * xs (\*): "".pickRestart({ a: n("0 1 2 0"), b: n("2 3 4 ~"), c: n("\[4 5\] \[4 3\] 2 0"), d: n("0 -3 0 ~") }).scale("C:major").s("piano") pickReset[](https://strudel.cc/learn/conditional-modifiers/#pickreset) ----------------------------------------------------------------------- Similar to `pick`, but the choosen pattern is reset when its index is triggered. * pat (Pattern): * xs (\*): pickmodReset[](https://strudel.cc/learn/conditional-modifiers/#pickmodreset) ----------------------------------------------------------------------------- The same as `pickReset`, but if you pick a number greater than the size of the list, it wraps around, rather than sticking at the maximum value. * pat (Pattern): * xs (\*): inhabit[](https://strudel.cc/learn/conditional-modifiers/#inhabit) ------------------------------------------------------------------- Synonyms: `pickSqueeze` Picks patterns (or plain values) either from a list (by index) or a lookup table (by name). Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern. * pat (Pattern): * xs (\*): "".inhabit({a: s("bd(3,8)"), b: s("cp sd") }) s("a@2 \[a b\] a".inhabit({a: "bd(3,8)", b: "sd sd"})).slow(4) inhabitmod[](https://strudel.cc/learn/conditional-modifiers/#inhabitmod) ------------------------------------------------------------------------- Synonyms: `pickmodSqueeze` The same as `inhabit`, but if you pick a number greater than the size of the list, it wraps around, rather than sticking at the maximum value. For example, if you pick the fifth pattern of a list of three, you'll get the second one. * pat (Pattern): * xs (\*): squeeze[](https://strudel.cc/learn/conditional-modifiers/#squeeze) ------------------------------------------------------------------- Pick from the list of values (or patterns of values) via the index using the given pattern of integers. The selected pattern will be compressed to fit the duration of the selecting event * pat (Pattern): * xs (\*): note(squeeze("<0@2 \[1!2\] 2>", \["g a", "f g f g" , "g a c d"\])) After Conditional Modifiers, let’s see what [Accumulation Modifiers](https://strudel.cc/learn/accumulation/) have to offer. --- # Stepwise patterning 🌀 Strudel Stepwise patterning (experimental)[](https://strudel.cc/learn/stepwise/#stepwise-patterning-experimental) ========================================================================================================== This is a developing area of strudel, and behaviour might change or be renamed in future versions. Feedback and ideas are welcome! Introduction[](https://strudel.cc/learn/stepwise/#introduction) ---------------------------------------------------------------- Usually in strudel, the only reference point for most pattern transformations is the _cycle_. Now it is possible to also work with _steps_, via a growing range of functions. For example usually when you `fastcat` two patterns together, the cycles will be squashed into half a cycle each: fastcat("bd hh hh", "bd hh hh cp hh").sound() With the new stepwise `stepcat` function, the steps of the two patterns will be evenly distributed across the cycle: stepcat("bd hh hh", "bd hh hh cp hh").sound() By default, steps are counted according to the ‘top level’ in mini-notation. For example `"a [b c] d e"` has five events in it per cycle, but is counted as four steps, where `[b c]` is counted as a single step. However, you can mark a different metrical level to count steps relative to, using a `^` at the start of a sub-pattern. If we do this to the subpattern in our example: `"a [^b c] d e"`, then the pattern is now counted as having _eight_ steps. This is because ‘b’ and ‘c’ are each counted as single steps, and the events in the pattern are twice as long, and so counted as two steps each. Pacing the steps[](https://strudel.cc/learn/stepwise/#pacing-the-steps) ------------------------------------------------------------------------ Some stepwise functions don’t appear to do very much on their own, for example these two examples of the `expand` function sound exactly the same despite being expanded by different amounts: "c a f e".expand(2).note().sound("folkharp") "c a f e".expand(4).note().sound("folkharp") The number of steps per cycle is being changed behind the scenes, but on its own, that doesn’t do anything. You will hear a difference however, once you use another stepwise function with it, for example `stepcat`: stepcat("c a f e".expand(2), "g d").note() .sound("folkharp") stepcat("c a f e".expand(4), "g d").note() .sound("folkharp") You should be able to hear that `expand` increases the duration of the steps of the first subpattern, proportionally to the second one. You can also change the speed of a pattern to match a given number of steps per cycle, with the `pace` function: stepcat("c a f e".expand(2), "g d").note() .sound("folkharp") .pace(8) stepcat("c a f e".expand(4), "g d").note() .sound("folkharp") .pace(8) The first example has ten steps, and the second example has 18 steps, but are then both played a rate of 8 steps per cycle. The argument to `expand` can also be patterned, and will be treated in a stepwise fashion. This means that the patterns from the changing values in the argument will be `stepcat`ted together: note("c a f e").sound("folkharp").expand("3 2 1 1 2 3") This results in a dense pattern, because the different expanded versions are squashed into a single cycle. `pace` is again handy here for slowing down the pattern to a particular number of steps per cycle: note("c a f e").sound("folkharp").expand("3 2 1 1 2 3").pace(8) Earlier versions of many of these functions had `s_` prefixes, and the `pace` function was previously known as `steps`. These still exist as aliases, but may have changed behaviour and will soon be removed. Please update your patterns! Stepwise functions[](https://strudel.cc/learn/stepwise/#stepwise-functions) ---------------------------------------------------------------------------- ### pace[](https://strudel.cc/learn/stepwise/#pace) _Experimental_ Speeds a pattern up or down, to fit to the given number of steps per cycle. sound("bd sd cp").pace(4) // The same as sound("{bd sd cp}%4") or sound("\*4") ### stepcat[](https://strudel.cc/learn/stepwise/#stepcat) Synonyms: `timeCat, timecat` 'Concatenates' patterns like `fastcat`, but proportional to a number of steps per cycle. The steps can either be inferred from the pattern, or provided as a \[length, pattern\] pair. Has the alias `timecat`. stepcat(\[3,"e3"\],\[1, "g3"\]).note() // the same as "e3@3 g3".note() stepcat("bd sd cp","hh hh").sound() // the same as "bd sd cp hh hh".sound() ### stepalt[](https://strudel.cc/learn/stepwise/#stepalt) _Experimental_ Concatenates patterns stepwise, according to an inferred 'steps per cycle'. Similar to `stepcat`, but if an argument is a list, the whole pattern will alternate between the elements in the list. stepalt(\["bd cp", "mt"\], "bd").sound() // The same as "bd cp bd mt bd".sound() ### expand[](https://strudel.cc/learn/stepwise/#expand) _Experimental_ Expands the step size of the pattern by the given factor. sound("tha dhi thom nam").bank("mridangam").expand("3 2 1 1 2 3").pace(8) ### contract[](https://strudel.cc/learn/stepwise/#contract) _Experimental_ Contracts the step size of the pattern by the given factor. See also `expand`. sound("tha dhi thom nam").bank("mridangam").contract("3 2 1 1 2 3").pace(8) ### extend[](https://strudel.cc/learn/stepwise/#extend) _Experimental_ `extend` is similar to `fast` in that it increases its density, but it also increases the step count accordingly. So `stepcat("a b".extend(2), "c d")` would be the same as `"a b a b c d"`, whereas `stepcat("a b".fast(2), "c d")` would be the same as `"[a b] [a b] c d"`. stepcat( sound("bd bd - cp").extend(2), sound("bd - sd -") ).pace(8) ### take[](https://strudel.cc/learn/stepwise/#take) _Experimental_ Takes the given number of steps from a pattern (dropping the rest). A positive number will take steps from the start of a pattern, and a negative number from the end. "bd cp ht mt".take("2").sound() // The same as "bd cp".sound() "bd cp ht mt".take("1 2 3").sound() // The same as "bd bd cp bd cp ht".sound() "bd cp ht mt".take("-1 -2 -3").sound() // The same as "mt ht mt cp ht mt".sound() ### drop[](https://strudel.cc/learn/stepwise/#drop) _Experimental_ Drops the given number of steps from a pattern. A positive number will drop steps from the start of a pattern, and a negative number from the end. "tha dhi thom nam".drop("1").sound().bank("mridangam") "tha dhi thom nam".drop("-1").sound().bank("mridangam") "tha dhi thom nam".drop("0 1 2 3").sound().bank("mridangam") "tha dhi thom nam".drop("0 -1 -2 -3").sound().bank("mridangam") ### polymeter[](https://strudel.cc/learn/stepwise/#polymeter) Synonyms: `pm` _Experimental_ Aligns the steps of the patterns, creating polymeters. The patterns are repeated until they all fit the cycle. For example, in the below the first pattern is repeated twice, and the second is repeated three times, to fit the lowest common multiple of six steps. // The same as note("{c eb g, c2 g2}%6") polymeter("c eb g", "c2 g2").note() ### shrink[](https://strudel.cc/learn/stepwise/#shrink) _Experimental_ Progressively shrinks the pattern by 'n' steps until there's nothing left, or if a second value is given (using mininotation list syntax with `:`), that number of times. A positive number will progressively drop steps from the start of a pattern, and a negative number from the end. "tha dhi thom nam".shrink("1").sound() .bank("mridangam") "tha dhi thom nam".shrink("-1").sound() .bank("mridangam") "tha dhi thom nam".shrink("1 -1").sound().bank("mridangam").pace(4) note("0 1 2 3 4 5 6 7".scale("C:ritusen")).sound("folkharp") .shrink("1 -1").pace(8) ### grow[](https://strudel.cc/learn/stepwise/#grow) _Experimental_ Progressively grows the pattern by 'n' steps until the full pattern is played, or if a second value is given (using mininotation list syntax with `:`), that number of times. A positive number will progressively grow steps from the start of a pattern, and a negative number from the end. "tha dhi thom nam".grow("1").sound() .bank("mridangam") "tha dhi thom nam".grow("-1").sound() .bank("mridangam") "tha dhi thom nam".grow("1 -1").sound().bank("mridangam").pace(4) note("0 1 2 3 4 5 6 7".scale("C:ritusen")).sound("folkharp") .grow("1 -1").pace(8) ### tour[](https://strudel.cc/learn/stepwise/#tour) _Experimental_ Inserts a pattern into a list of patterns. On the first repetition it will be inserted at the end of the list, then moved backwards through the list on successive repetitions. The patterns are added together stepwise, with all repetitions taking place over a single cycle. Using `pace` to set the number of steps per cycle is therefore usually recommended. "\[c g\]".tour("e f", "e f g", "g f e c").note() .sound("folkharp") .pace(8) ### zip[](https://strudel.cc/learn/stepwise/#zip) _Experimental_ 'zips' together the steps of the provided patterns. This can create a long repetition, taking place over a single, dense cycle. Using `pace` to set the number of steps per cycle is therefore usually recommended. zip("e f", "e f g", "g \[f e\] a f4 c").note() .sound("folkharp") .pace(8) --- # Pattern Aligment 🌀 Strudel Pattern Aligment & Combination[](https://strudel.cc/technical-manual/alignment/#pattern-aligment--combination) =============================================================================================================== One core aspect of Strudel, inherited from Tidal, is the flexible way that patterns can be combined, irrespective of their structure. Its declarative approach means a live coder does not have to think about the details of _how_ this is done, only _what_ is to be done. As a simple example, consider two number patterns `"0 [1 2] 3"`, and `"10 20"`. The first has three contiguous steps of equal lengths, with the second step broken down into two substeps, giving four events in total. There are a very large number of ways in which the structure of these two patterns could be combined, but the default method in both Strudel and Tidal is to line up the cycles of the two patterns, and then take events from the first pattern and match them with those in the second pattern. Therefore, the following two lines are equivalent: '0 [1 2] 3'.add('10 20'); ('10 [11 22] 23'); Where the events only partially overlap, they are treated as fragments of the event in the first pattern. This is a little difficult to conceptualise, but lets start by comparing the two patterns in the following example: '0 1 2'.add('10 20'); ('10 [11 21] 22'); They are similar to the previous example in that the number `1` is split in two, with its two halves added to `10` and `20` respectively. However, the `11` ‘remembers’ that it is a fragment of that original `1` event, and so is treated as having a duration of a third of a cycle, despite only being active for a sixth of a cycle. Likewise, the `21` is also a fragment of that original `1` event, but a fragment of its second half. Because the start of its event is missing, it wouldn’t actually trigger a sound (unless it underwent further pattern transformations/combinations). In practice, the effect of this default, implicit method for combining two patterns is that the second pattern is added _in_ to the first one, and indeed this can be made explicit: '0 1 2'.add.in('10 20'); This makes way for other ways to align the pattern, and several are already defined, in particular: * `in` - as explained above, aligns cycles, and applies values from the pattern on the right _in_ to the pattern on the left. * `out` - as with `in`, but values are applied _out_ of the pattern on the left (i.e. _in_ to the one on the right). * `mix` - structures from both patterns are combined, so that the new events are not fragments but are created at intersections of events from both sides. * `squeeze` - cycles from the pattern on the right are squeezed into events on the left. So that e.g. `"0 1 2".add.squeeze("10 20")` is equivalent to `"[10 20] [11 21] [12 22]"`. * `squeezeout` - as with `squeeze`, but cycles from the left are squeezed into events on the right. So, `"0 1 2".add.squeezeout("10 20")` is equivalent to `[10 11 12] [20 21 22]`. * `reset` is similar to `squeezeout` in that cycles from the right are aligned with events on the left. However those cycles are not ‘squeezed’, rather they are truncated to fit the event. So `"0 1 2 3 4 5 6 7".add.reset("10 [20 30]")` would be equivalent to `10 11 12 13 20 21 30 31`. In effect, events on the right ‘reset’ cycles on the left. * `restart` is similar to `reset`, but the pattern is ‘restarted’ from its very first cycle, rather than from the current cycle. `reset` and `restart` therefore only give different results where the leftmost pattern differs from one cycle to the next. We will save going deeper into the background, design and practicalities of these alignment functions for future publications. However in the next section, we take them as a case study for looking at the different design affordances offered by Haskell to Tidal, and JavaScript to Strudel. Ok, so how do Strudel and Tidal [compare](https://strudel.cc/learn/strudel-vs-tidal/) ? --- # Xen Harmonic Functions 🌀 Strudel Xen Harmonic Functions[](https://strudel.cc/learn/xen/#xen-harmonic-functions) =============================================================================== These functions allow the use of scales other than your typical chromatic 12 based ones. ### tune(scale)[](https://strudel.cc/learn/xen/#tunescale) Here’s an example of how to configure a basic hexany scale: "0 1 2 3 4 5".tune("hexany15").mul("220").freq() Try other scales like `hexany1`, `iraq`, `gumbeng`, `gunkali`, or `tranh3` For a full list of available scales from tunejs, see [http://abbernie.github.io/tune/scales.html](http://abbernie.github.io/tune/scales.html) You can set your root to be a particular note with `getFreq` "4 8 9 10 - - 5 7 9 11 - -".tune("tranh3") .mul(getFreq('c3')) .freq().clip(.5).room(1) Some tunings become more pronounced with a longer reverb decay: "<\[5 6 8 10\] - \[5 7 9 12\] -> -".tune("gumbeng") .mul(getFreq('c3')) .freq().clip(.8).room("3:10").rdim(10000).rfade(5) Additionally, you can combo this with `fmap` so that the base note changes: "9 11 12 10 - - -".tune("gunkali") .mul("".fmap(getFreq)) .freq().legato("2 .7").room("1:15").rdim(8500).rlp(14000).rfade(8) Combining this with various polyrhythm tricks can become very evocative: "<\[0 3 1 -\] \[-1 4 2 8\]> ~ ~,<-4 -5>" .transpose(4) .tune("iraq") .mul("".fmap(getFreq)) .freq().clip(.5).room(1).rfade(9) Another helpful trick when exploring new tunings is to strum them. Many have a much more enchanting sound that was chosen over many generations of musicians for being strummed. Take the `sanza` tuning: "4 5 6 7 8 9".tune("sanza") .mul(getFreq('c3')) .freq() Notes 7 and 9 will clash quite a bit if you arp them normally. Many tunings will have this sort of sound, and it can feel distracting on its own. See how close they are on the pitch wheel? "\[7 9\]!3".tune("sanza").mul(getFreq('c3')).freq().\_pitchwheel() This quality is often due to how the tunings were formed with instruments that were played differently than a piano. As such, some tunings are much better strummed, with the subtle clash of the detuned notes actually making the sound much more magical: "\[0 1 2 3 4 5 6\]@0.3 -" .transpose("<2 5 8 1>") .tune("sanza") .mul(getFreq('c3')).freq() .legato("3").room(1).rfade(5) Note the legato and reverb effects make sure the sound of the strumming gets to wash together. Alternating the direction of the strum can make the tones sound even more alive, too. The `tranh3` tuning has a similar set of notes, with two clashing. You might trying plugging that in above and see if you find a favorite strumming pattern. --- # Understanding Cycles 🌀 Strudel Understanding Cycles[](https://strudel.cc/understand/cycles/#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[](https://strudel.cc/understand/cycles/#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 0.5 CPS: s("bd") Here we can hear the 0.5CPS in action: The kick repeats once every two seconds. Let’s make it 4 kicks: s("bd bd bd bd") Now we have 4 kicks per cycle, but the whole pattern still plays at 0.5CPS. In terms of BPM, most musicians would tell you this is playing at 120bpm. What about this one: s("bd hh bd hh") Because the second sound is now a hihat, the tempo feels slower again. This brings us to an important realization: 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. Setting CPM[](https://strudel.cc/understand/cycles/#setting-cpm) ----------------------------------------------------------------- If you’re familiar with BPM, you can use the `setcpm` method to set the global tempo in cycles per minute: setcpm(110) s("bd hh") If you want to add more beats per cycle, you might want to divide the cpm: setcpm(110/4) s("bd sd bd rim, hh\*8") Or using 2 beats per cycle: setcpm(110/2) s("bd sd, hh\*4") You can use the `setcps` method to set the global tempo in cycles per second. `setcpm(x)` is the same as `setcps(x / 60)`. To set a specific bpm, use `setcpm(bpm/bpc)` * bpm: the target beats per minute * bpc: the number of perceived beats per cycle Cycles and Bars[](https://strudel.cc/understand/cycles/#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: setcpm(110/4) s("bd sd bd rim, hh\*8") This could be interpreted as 4/4 time with a tempo of 110bpm. We could write out multiple bars like this: setcpm(110/4) s(\`< \[bd sd bd rim, hh\*8\] \[bd sd bd rim\*2, hh\*8\] >\`) Instead of writing out each bar separately, we could express this much shorter: setcpm(110/2) s("bd >,hh\*4") 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[](https://strudel.cc/understand/cycles/#time-signatures) ------------------------------------------------------------------------- To get a time signature, just change the number of elements per bar. Here is a rhythm with 7 beats: s("bd ~ rim bd bd rim ~") or with 5: s("bd hh hh bd hh hh bd rim bd hh") We could also write multiple bars with different time signatures: setcpm(110\*2) s(\`< \[bd hh rim\]@3 \[bd hh rim sd\]@4 >\`) 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: setcpm(110/2) s(\`< \[bd hh rim\] \[bd hh rim sd\] >\`) Now the 3 elements get the same time as the 4 elements, which is why the tempo changes. --- # Strudel vs Tidal 🌀 Strudel Comparing Strudel and Tidal[](https://strudel.cc/learn/strudel-vs-tidal/#comparing-strudel-and-tidal) ====================================================================================================== This page is dedicated to exisiting tidal users, giving an overview of all the differences between Strudel and Tidal. Language[](https://strudel.cc/learn/strudel-vs-tidal/#language) ---------------------------------------------------------------- Strudel is written in JavaScript, while Tidal is written in Haskell. ### Example[](https://strudel.cc/learn/strudel-vs-tidal/#example) This difference is most obvious when looking at the syntax: iter 4 $ every 3 (||+ n "10 20") $ (n "0 1 3") # s "triangle" # crush 4 One _could_ express that pattern to Strudel like so: iter(4, every(3, add.squeeze("10 20"), n("0 1 3").s("triangle").crush(4))) * The `$` operator does not exist, so the `iter` function has to wrap everything in parens. * Custom operators like `||+` are explicit function calls, `add.squeeze` in this case * The `#` operator is replaced with a chained function call `# crush 4` => `.crush(4)` Unlike Haskell, JavaScript lacks the ability to define custom infix operators, or change the meaning of existing ones. Before you discard Strudel as an unwieldy paren monster, look at this alternative way to write the above: n("0 1 3").every(3, add.squeeze("10 20")).iter(4).s("triangle").crush(4) By reordering calls, the parens are much less nested. As a general rule by thumb, you could say that everything Tidal does with `$` is reversed in Strudel: `iter 4 $ every 3 (||+ n "10 20") $ (n "0 1 3")` becomes `n("0 1 3").every(3, add.squeeze("10 20")).iter(4)` Simply put, `foo x $ bar x` becomes `bar(x).foo(x)`. ### Operators[](https://strudel.cc/learn/strudel-vs-tidal/#operators) The [custom operators of tidal](https://tidalcycles.org/docs/reference/pattern_structure/#all-the-operators) are normal functions in strudel: | function | tidal | strudel | | --- | --- | --- | | add | \|+ n | .add(n) | | subtract | \|- n | .sub(n) | | multiply | \|\* n | .mul(n) | | divide | \|/ n | .div(n) | | modulo | \|% n | .mod(n) | | left values | \|< n | .set(n) | The above list only displays the operators taking the structure comes from the `left`. For each of those, a `right` and `both` variant also exists. As this directional thinking only works with code, strudel calls these `in` / `out` / `mix`: | direction | tidal | strudel | | --- | --- | --- | | left | \|+ n | .add.in(n) | | right | +\| n | .add.out(n) | | both | \|+\| n | .add.mix(n) | Instead of `+` / `add`, you can use any of the available operators of the first list. Function Compatibility[](https://strudel.cc/learn/strudel-vs-tidal/#function-compatibility) -------------------------------------------------------------------------------------------- [This issue](https://codeberg.org/uzu/strudel/issues/31) tracks which Tidal functions are implemented in Strudel. The list might not be 100% up to date and probably also misses some functions completely.. Feel encouraged to search the source code for a function you’re looking for. If you find a function that’s not on the list, please tell! Control Params[](https://strudel.cc/learn/strudel-vs-tidal/#control-params) ---------------------------------------------------------------------------- As seen in the example, the `#` operator (shorthand for `|>`) is also just a function call in strudel. So `note "c5" # s "gtr"` becomes `note("c5").s('gtr')`. [This file](https://codeberg.org/uzu/strudel/src/branch/main/packages/core/controls.mjs) lists all available control params. Note that not all of those work in the Webaudio Output of Strudel. If you find a tidal control that’s not on the list, please tell! Sound[](https://strudel.cc/learn/strudel-vs-tidal/#sound) ---------------------------------------------------------- Tidal is commonly paired with Superdirt / Supercollider for sound generation. While Strudel also has a way of [communicating with Superdirt](https://strudel.cc/learn/input-output/) , it aims to provide a standalone live coding environment that runs entirely in the browser. ### Audio Effects[](https://strudel.cc/learn/strudel-vs-tidal/#audio-effects) Many of SuperDirt’s effects have been reimplemented in Strudel, using the Web Audio API. You can find a [list of available effects here](https://strudel.cc/learn/effects/) . ### Sampler[](https://strudel.cc/learn/strudel-vs-tidal/#sampler) Strudel’s sampler supports [a subset](https://strudel.cc/learn/samples/) of Superdirt’s sampler. Also, samples are always loaded from a URL rather than from the disk, although [that might be possible in the future](https://codeberg.org/uzu/strudel/issues/118) . Evaluation[](https://strudel.cc/learn/strudel-vs-tidal/#evaluation) -------------------------------------------------------------------- The Strudel REPL does not support [block based evaluation](https://codeberg.org/uzu/strudel/issues/34) yet. You can use labeled statements and `_` to mute: $: n("\[0 .. 8\]\*8/9").scale("C:minor:pentatonic") \_$: s("bd\*4").bank('RolandTR909') Tempo[](https://strudel.cc/learn/strudel-vs-tidal/#tempo) ---------------------------------------------------------- Strudels tempo is 1 cycle per second, while tidal defaults to `0.5625`. You can get the same tempo as tidal with: note("c a f e").fast(.5625); Next up: the [REPL](https://strudel.cc/technical-manual/repl/) --- # Understanding Pitch 🌀 Strudel Understanding Pitch[](https://strudel.cc/understand/pitch/#understanding-pitch) ================================================================================ Let’s learn how pitch works! The slider below controls the frequency of an oscillator, producing a pitch: 220Hz * Drag the slider to hear a pitch * Move the slider to change the pitch * Observe how the Hz number changes * Caution: The higher frequencies could be disturbing for children or animals! The Hz number is the frequency of the pitch you’re hearing. The higher the frequency, the higher the pitch and vice versa. A pitch occurs whenever something is vibrating / oscillating at a frequency, in this case it’s your speaker. The unit **Hz** describes how many times that oscillation happens per second. Our eyes are too slow to actually see the oscillation on the speaker, but we can [see it in slow motion](https://www.youtube.com/watch?v=CDMBWw7OuJQ) . The hearing range of a newborn is said to be between 20Hz and 20000Hz. The upper limit decreases with age. What’s your upper limit? In Strudel, we can play frequencies directly with the `freq` control: freq("<200 \[300,500\] 400 \[500,<600 670 712 670>\]>\*8") Frequency vs Pitch Perception[](https://strudel.cc/understand/pitch/#frequency-vs-pitch-perception) ---------------------------------------------------------------------------------------------------- Maybe you have already noticed that the frequency slider is “lopsided”, meaning the pitch changes more in the left region and less in the right region. To make that more obvious, let’s add a pitch slider that controls the frequency on a different scale: 220Hz = 55Hz \* 22 Frequency SweepPitch Sweep Try out the buttons above to sweep through the frequency range in 2 different ways: * Frequency Sweep: frequency rises linear , pitch rises logarithmic * Pitch Sweep: frequency rises exponential , pitch rises linear Don’t be scared of these mathematical terms: * “logarithmic” is just a fancy way of saying “it starts fast and slows down” * “exponential” is just a fancy way of saying “it starts slow and gets faster” Most of the time, we might want to control pitch in a way that matches our perception, which is what the pitch slider does. From Hz to Semitones[](https://strudel.cc/understand/pitch/#from-hz-to-semitones) ---------------------------------------------------------------------------------- Because Hz does not match our perception, let’s try to find a unit for pitch that matches. To approach that unit of pitch, let’s look at how frequency behaves when it is doubled: 220Hz = 55Hz \* 22 * Use the now stepped pitch slider above * Can you hear how these pitches seem related to each other? In musical terms, a pitch with double the frequency of another is an `octave` higher. Because octaves are pretty far apart, octaves are typically divided into 12 smaller parts: 440Hz = 440Hz \* 20 This step is also called a semitone, which is the most common division of pitched music. For example, the keys on a piano keyboard are also divided into semitones. In Strudel, we could do that with `freq` like this: freq( "0 4 7 12" .fmap(n => 440 \* 2\*\*(n/12)) ) Of course, this can be written shorter with note, as we will see below. From Semitones to MIDI numbers[](https://strudel.cc/understand/pitch/#from-semitones-to-midi-numbers) ------------------------------------------------------------------------------------------------------ Now we know what the distance of a semitone is. Above, we used an arbitrary base frequency of 440Hz, which means the exponent 0 is equal to 440Hz. Typically, 440Hz is standardized to the number 69, which leads to this calculation: 440Hz = 440Hz \* 2(69 - 69)/12 The yellow number is now a MIDI number, covering more than the whole human hearing range with numbers from 0 to 127. In Strudel, we can use MIDI numbers inside `note`: note("69 73 76 81") From MIDI numbers to notes[](https://strudel.cc/understand/pitch/#from-midi-numbers-to-notes) ---------------------------------------------------------------------------------------------- In western music theory, notes are used instead of numbers. For each midi number, there is at least one note label: 440Hz = 440Hz \* 2(69 - 69)/12 \= A4 A4A4 A full note label consists of a letter (A-G), 0 or more accidentals (b | #) and an octave number. This system is also known as [Scientific Pitch Notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation) . In Strudel, these note labels can also be used inside `note` as an alternative to midi numbers: note("A4 C#5 E5 A5").piano() Open Questions[](https://strudel.cc/understand/pitch/#open-questions) ---------------------------------------------------------------------- Now that we have learned about different representations of pitch, there are still open questions: * Why 12 notes? What about different divisions of the octave? * Why are notes labeled as they are? Why only 7 letters? * Are there other labeling systems? * What about Just Intonation Systems? * What about Timbre? All those questions are important to ask and will be answered in another article. Definition[](https://strudel.cc/understand/pitch/#definition) -------------------------------------------------------------- At first, I wanted to start this article with a definition, but then thought it might be a good idea to focus on intuitive exploration. Maybe you now understand this definition much better: From [wikipedia](https://en.wikipedia.org/wiki/Pitch_(music)) : “Pitch is a perceptual property of sounds that allows their ordering on a frequency-related scale, or more commonly, pitch is the quality that makes it possible to judge sounds as “higher” and “lower” in the sense associated with musical melodies.” --- # Using Strudel in your Project 🌀 Strudel Using Strudel in your Project[](https://strudel.cc/technical-manual/project-start/#using-strudel-in-your-project) ================================================================================================================== This Guide shows you the different ways to get started with using Strudel in your own project. Respect the license[](https://strudel.cc/technical-manual/project-start/#respect-the-license) ---------------------------------------------------------------------------------------------- First, please take a moment to understand Strudel’s free/open source license, [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.en.html) . Here is a lay summary, but check the license for legal definitions and responsibilities. * You can distribute modified versions if you keep track of the changes and the date you made them. * You must license derivative work under the same license. * Source code must be distributed along with web publication. Among other things, it means that when you share your work, the whole application must be shared under the same free/open source license, or one compatible with it. This is because we want Strudel to stay free/open source. In other words, you are not permitted to distribute integrations of Strudel with libraries or other code that does not have a compatible free/open source license. This also applies to clones informed by reading Strudel’s source code, as legally speaking, that counts as a ‘derivative work’. Again, please [read the licence](https://www.gnu.org/licenses/agpl-3.0.en.html) for details. Embedding the Strudel REPL[](https://strudel.cc/technical-manual/project-start/#embedding-the-strudel-repl) ------------------------------------------------------------------------------------------------------------ There are 3 quick ways to embed strudel in your website: 1. Embed the strudel website as an iframe directly 2. Embed the strudel website as an iframe using `@strudel/embed` 3. Embed the REPL directly using `@strudel/repl` ### Inside an iframe[](https://strudel.cc/technical-manual/project-start/#inside-an-iframe) Using an iframe is the most easy way to embed a strudel tune. You can embed any pattern of your choice via an iframe and the URL of the pattern of your choice: The URL can be obtained by pressing `share` in the REPL. Note that these share links depend on a database, which is not guaranteed to live forever. To make sure your code is not lost, you can also use the long url: That long URL can just be copy pasted from the URL bar when you’re on the strudel website. It always reflects the latest evaluation of your code. ### @strudel/embed[](https://strudel.cc/technical-manual/project-start/#strudelembed) To simplify the process of emebdding via an iframe, you can use the package `@strudel/embed`: This will load the strudel website in an iframe, using the code provided within the HTML comments ``. The HTML comments are needed to make sure the browser won’t interpret it as HTML. For alternative ways to load this package, see the [@strudel/embed README](https://codeberg.org/uzu/strudel/src/branch/main/packages/embed#strudel-embed) . ### @strudel/repl[](https://strudel.cc/technical-manual/project-start/#strudelrepl) Loading strudel directly in your site, without an iframe, looks similar to the iframe variant: Here, we’re loading `@strudel/repl` instead of `@strudel/embed`, and the component is called `strudel-editor` instead of `strudel-repl`. Yes the naming is a bit confusing.. The upside of using the repl without an iframe is that you can pin the strudel version you’re using: This will guarantee your pattern wont break due to changes to the strudel project in the future. For more info on this package, see the [@strudel/repl README](https://codeberg.org/uzu/strudel/src/branch/main/packages/repl#strudel-repl) . With your own UI[](https://strudel.cc/technical-manual/project-start/#with-your-own-ui) ---------------------------------------------------------------------------------------- The above approach assumes you want to use the builtin [codemirror](https://codemirror.net/) editor. If you’d rather use your own UI, you can use the `@strudel/web` package: For more info on this package, see the [@strudel/web README](https://codeberg.org/uzu/strudel/src/branch/main/packages/web#strudel-web) . Via npm[](https://strudel.cc/technical-manual/project-start/#via-npm) ---------------------------------------------------------------------- [All the packages and many more are available on npm under the @strudel namespace](https://www.npmjs.com/search?q=%40strudel) . There are actually many more packages you can use to have fine grained control over what you use and what not. To use these packages, you have to use a bundler that supports es modules, like [vite](https://vitejs.dev/) . To find out more about the purpose of each package, see [Packages](https://strudel.cc/technical-manual/packages/) --- # Understanding Chord Voicings 🌀 Strudel Understanding Chords and Voicings[](https://strudel.cc/understand/voicings/#understanding-chords-and-voicings) =============================================================================================================== Let’s dig deeper into how chords and voicings work in strudel. I’ll try to keep theory jargon to a minimum, so hopefully this is approachable for anyone interested. What is a chord[](https://strudel.cc/understand/voicings/#what-is-a-chord) --------------------------------------------------------------------------- Playing more than one note at a time is generally called a `chord`. Here’s an example: note("<\[c3,eb3,g3\] \[f3,a3,c4\]>").room(.5) Here’s the same with midi numbers: note("<\[48,51,55\] \[53,57,60\]>").room(.5) Here, we have two 3-note chords played in a loop. You could already stop here and write chords in this style, which is totally fine and gives you control over individual notes. One downside is that it can be difficult to find good sounding chords and maybe you’re yearning for a way to organize chords in some other way. Labeling Chords[](https://strudel.cc/understand/voicings/#labeling-chords) --------------------------------------------------------------------------- Chords are typically given different labels depending on the relationship of the notes within. In the number example above, we have `48,51,55` and `53,57,60`. To analyze the relationship of those notes, they are typically compared to some `root`, which is often the lowest note. In our case, the `roots` would be `48` (= `c3`) and `53` (= `f3`). We can express the same chords relative to those `roots` like this: note("<\[0,3,7\] \[0,4,7\]>".add("<48 53>")).room(.5) Now within each chord, each number represents the distance from the root. A distance between pitches is typically called `interval`, but let’s stick to distance for now. Now we can see that our 2 chords are actually quite similar, as the only difference is the middle note (and the root of course). They are part of a group of chords called `triads` which are chords with 3 notes. ### Triads[](https://strudel.cc/understand/voicings/#triads) These 4 shapes are the most common types of `triads` you will encounter: | shape | label | | --- | --- | | 0,4,7 | major | | 0,3,7 | minor | | 0,3,6 | diminished | | 0,4,8 | augmented | Here they are in succession: note("<\[0,4,7\] \[0,3,7\] \[0,3,6\] \[0,4,8\]>".add("60")) .room(.5).\_pitchwheel() Many types of music often only use minor and major chords, so we already have the knowledge to accompany songs. Here’s one: note(\`< \[0,3,7\] \[0,4,7\] \[0,4,7\] \[0,4,7\] \[0,3,7\] \[0,4,7\] \[0,3,7\] \[0,4,7\] >\`.add(\`< a c d f a e a e >\`)).room(.5) These are the chords for “The House of the Rising Sun” by The Animals. So far, it doesn’t sound too exciting, but at least it’s recognizable. Voicings[](https://strudel.cc/understand/voicings/#voicings) ------------------------------------------------------------- A `voicing` is one of many ways a certain chord shape can be arranged. The term comes from choral music, where chords can be sung in different ways by assigning different notes to each voice. For example we could add 12 semitones to one or more notes in the chord: note("<\[0,3,7\] \[12,3,7\] \[12,15,7\] \[12,15,19\]>".add("48")) .room(.5) Notes that are 12 semitone steps apart (= 1 `octave`) are considered to be equal in a harmonic sense, which is why they get the same note letter. Here’s the same example with note letters: note("<\[c3,eb3,g3\] \[c4,eb3,g3\] \[c4,eb4,g3\] \[c4,eb4,g4\]>") .room(.5) These types of voicings are also called `inversions`. There are many other ways we could `voice` this minor chord: note("<\[0,3,7,12\] \[0,15,24\] \[0,3,12\]>".add("48")) .room(.5) Here we are changing the flavour of the chord slightly by 1. doubling notes 12 steps higher, 2. using very wide distances 3. omitting notes Voice Leading[](https://strudel.cc/understand/voicings/#voice-leading) ----------------------------------------------------------------------- When we want to meaningfully connect chords in a sequence, the chosen voicings affect the way each chord transitions to the next. Let’s revisit “The House of the Rising Sun”, this time using our newly acquired voicing techniques: note(\`< \[0,3,7\] \[7,12,16\] \[0,7,16\] \[4,7,12\] \[0,3,7\] \[4,7,12\] \[0,3,7\] \[4,7,12\] >\`.add(\`< a c d f a e a e >\`)).room(.5) These voicings make the chords sound more connected and less jumpy, compared to the earlier version, which didn’t focus on voicing. The way chords interact is also called `voice leading`, reminiscent of how an individual choir voice would move through a sequence of chords. For example, try singing the top voice in the above example. Then try the same on the example not focusing on voice leading. Which one’s easier? Naturally, there are many ways a progression of chords could be voiced and there is no definitive right or wrong. Chord Symbols[](https://strudel.cc/understand/voicings/#chord-symbols) ----------------------------------------------------------------------- Musicians playing chord-based music often use a `lead sheet`, which is a simplified notation for a piece of music. These sheets condense the essential elements, such as chords, into symbols that make the music easy to read and follow. For example, a lead sheet for “The House of the Rising Sun” might include chords written like this: Am | C | D | F Am | E | Am | E Here, each symbol consists of the `root` of the chord and optionally an `m` to signal it’s a minor chord (just the root note means it’s major). We could mirror that notation in strudel using the `pick` function: "" .pick({ Am: "57,60,64", C: "55,60,64", D: "50,57,66", F: "57,60,65", E: "56,59,64", }) .note().room(.5) The voicing function[](https://strudel.cc/understand/voicings/#the-voicing-function) ------------------------------------------------------------------------------------- Coming up with good sounding voicings that connect well can be a difficult and time consuming process. The `chord` and `voicing` functions can be used to automate that: chord("").voicing().room(.5) Here we’re also using chord symbols but the voicings will be automatically generated with smooth `voice leading`, minimizing jumps. It is inspired by the way a piano or guitar player would pick chords to accompany a song. Voicing Dictionaries[](https://strudel.cc/understand/voicings/#voicing-dictionaries) ------------------------------------------------------------------------------------- The voicing function internally uses so called `voicing dictionaries`, which can also be customized: addVoicings('house', { '': \['7 12 16', '0 7 16', '4 7 12'\], 'm': \['0 3 7'\] }) chord("") .dict('house').anchor(66) .voicing().room(.5) In a `voicing dictionary`, each chord symbol is assigned one or more voicings. The `voicing` function then picks the voicing that is closest to the `anchor` (defaults to `c5`). The handy thing about this approach is that a `voicing dictionary` can be used to play any chord progression with automated voice leading! The default dictionary[](https://strudel.cc/understand/voicings/#the-default-dictionary) ----------------------------------------------------------------------------------------- When using the default dictionary, you can use these chord symbols: 2 5 6 7 9 11 13 69 add9 o h sus ^ - ^7 -7 7sus h7 o7 ^9 ^13 ^7#11 ^9#11 ^7#5 -6 -69 -^7 -^9 -9 -add9 -11 -7b5 h9 -b6 -#5 7b9 7#9 7#11 7b5 7#5 9#11 9b5 9#5 7b13 7#9#5 7#9b5 7#9#11 7b9#11 7b9b5 7b9#5 7b9#9 7b9b13 7alt 13#11 13b9 13#9 7b9sus 7susadd3 9sus 13sus 7b13sus aug M m M7 m7 M9 M13 M7#11 M9#11 M7#5 m6 m69 m^7 -M7 m^9 -M9 m9 madd9 m11 m7b5 mb6 m#5 mM7 mM9 The available chords and the format is very much inspired by [ireal pro chords](https://technimo.helpshift.com/hc/en/3-ireal-pro/faq/88-chord-symbols-used-in-ireal-pro/) . Some symbols are synonymous: * ”-” is the same as “m”, for example C-7 = Cm7 * ”^” is the same as “M”, for example C^7 = CM7 * ”+” is the same as “aug” You can decide which ones you prefer. There is no international standard for these symbols. To get a full chord, the symbols have to be prefixed with a root pitch, e.g. D7#11 is the 7#11 chord relative to the pitch D. Here are all possible chords with root C: chord(\`< C2 C5 C6 C7 C9 C11 C13 C69 Cadd9 Co Ch Csus C^ C- C^7 C-7 C7sus Ch7 Co7 C^9 C^13 C^7#11 C^9#11 C^7#5 C-6 C-69 C-^7 C-^9 C-9 C-add9 C-11 C-7b5 Ch9 C-b6 C-#5 C7b9 C7#9 C7#11 C7b5 C7#5 C9#11 C9b5 C9#5 C7b13 C7#9#5 C7#9b5 C7#9#11 C7b9#11 C7b9b5 C7b9#5 C7b9#9 C7b9b13 C7alt C13#11 C13b9 C13#9 C7b9sus C7susadd3 C9sus C13sus C7b13sus C Caug CM Cm CM7 Cm7 CM9 CM13 CM7#11 CM9#11 CM7#5 Cm6 Cm69 Cm^7 C-M7 Cm^9 C-M9 Cm9 Cmadd9 Cm11 Cm7b5 Cmb6 Cm#5 >\`).voicing().room(.5) Note that the default dictionary contains multiple ways (= `voicings`) to play each chord symbol. By default, the `voicing` function tries to minimize jumps. You can alter the picked voicings in various ways, which are now explained in further detail: anchor[](https://strudel.cc/understand/voicings/#anchor) --------------------------------------------------------- The `anchor` is a note that is used to align the voicings to: anchor("").chord("C").voicing().room(.5) By default, the anchor is the highest possible note the voicing can contain. When deciding which voicing of the dictionary to pick for a certain chord, the voicing with a top note closest to the anchor wins. Note that the anchors in the above example match up with the top notes in the pianoroll. Like `note`, anchor accepts either midi numbers or note names. mode[](https://strudel.cc/understand/voicings/#mode) ----------------------------------------------------- With `mode`, you can change the way the voicing relates to the `anchor`: mode("").chord("C").anchor("c5").voicing().room(.5) The modes are: * `below`: the top note of the voicing is lower than or equal to the anchor (default) * `above`: the bottom note of the voicing is higher than or equal to the anchor * `duck`: the top note of the voicing is lower than the anchor * `root`: the bottom note of the voicing is always the root note closest to the anchor The `anchor` can also be set from within the `mode` function: mode(":c5").chord("C").voicing().room(.5) n[](https://strudel.cc/understand/voicings/#n) ----------------------------------------------- The `n` control can be used with `voicing` to select individual notes: n("0 3 1 2").chord(">").voicing() .clip("4 3 2 1").room(.5) Example[](https://strudel.cc/understand/voicings/#example) ----------------------------------------------------------- Here’s an example of a Jazz Blues in F: let chords = chord(\`< F7 Bb7 F7 \[Cm7 F7\] Bb7 Bo F7 \[Am7 D7\] Gm7 C7 \[F7 D7\] \[Gm7 C7\] >\`) $: n("7 8 \[10 9\] 8").set(chords).voicing().dec(.2) $: chords.struct("- x - x").voicing().room(.5) $: n("0 - 1 -").set(chords).mode("root:g2").voicing() The chords are reused for melody, chords and bassline of the tune. --- # Docs 🌀 Strudel Docs[](https://strudel.cc/technical-manual/docs/#docs) ======================================================= The docs page is built ontop of astro’s [docs site](https://github.com/withastro/astro/tree/main/examples/docs) . Adding a new Docs Page[](https://strudel.cc/technical-manual/docs/#adding-a-new-docs-page) ------------------------------------------------------------------------------------------- 1. add a `.mdx` file in a path under `website/src/pages/`, e.g. [website/src/pages/learn/code.mdx](https://codeberg.org/uzu/strudel/src/branch/main/website/src/pages/learn/code.mdx) will be available under [https://strudel.cc/learn/code/](https://strudel.cc/learn/code/) (or locally under `http://localhost:4321/learn/code/`) 2. make sure to copy the top part of another existing docs page. Adjust the title accordingly 3. To add a link to the sidebar, add a new entry to `SIDEBAR` to [`config.ts`](https://codeberg.org/uzu/strudel/src/branch/main/website/src/config.ts) Using the Mini REPL[](https://strudel.cc/technical-manual/docs/#using-the-mini-repl) ------------------------------------------------------------------------------------- To add a Mini REPL, make sure to import: import { MiniRepl } from '../../docs/MiniRepl'; add a mini repl with * `client:idle` is required to tell astro that the repl should be interactive, see [Client Directive](https://docs.astro.build/en/reference/directives-reference/#client-directives) * `tune`: be any valid pattern code * `punchcard`: if added, a punchcard / pianoroll visualization is renderd * `drawTime`: time window for drawing, defaults to `[0, 4]` * `canvasHeight`: height of the canvas, defaults to 100px See `mini-notation.mdx` for usage examples In-Source Documentation[](https://strudel.cc/technical-manual/docs/#in-source-documentation) --------------------------------------------------------------------------------------------- You can add the in-source documentation for a function by using the `JsDoc` component. Import: import { JsDoc } from '../../docs/JsDoc'; Usage: * `name`: function name, as named with `@name` in jsdoc * `h`: level of heading. `0` will hide the heading. Hiding it allows using a manual heading which results in a nav link being generated in the right sidebar. * `hideDescription`: if set, the description will be hidden ### Writing jsdoc[](https://strudel.cc/technical-manual/docs/#writing-jsdoc) Documentation is written with [jsdoc](https://jsdoc.app/) comments. Example: /** * Select a sound / sample by name. * * @name s * @param {string | Pattern} sound The sound / pattern of sounds to pick * @example * s("bd hh") * */ // implementation of s function * Before each build, these comments will be rendered into `doc.json` using [jsdoc-json](https://www.npmjs.com/package/jsdoc-json) as a template * To regenerate the `doc.json` file manually, run `npm run jsdoc-json` * The file is used by the `JsDoc` component to find the documentation by name * Also, it is used for the `examples.test.mjs` snapshot test How does Strudel do its [Testing](https://strudel.cc/technical-manual/testing/) ? --- # Strudel Packages 🌀 Strudel Strudel Packages[](https://strudel.cc/technical-manual/packages/#strudel-packages) =================================================================================== The [strudel repo](https://codeberg.org/uzu/strudel) is organized as a monorepo, containing multiple npm packages. The purpose of the multiple packages is to * organize the codebase into more modular, encapsulated pieces * be able to opt out of certain functionalities * keep the dependencies of the core packages small Overview[](https://strudel.cc/technical-manual/packages/#overview) ------------------------------------------------------------------- [See the latest published packages on npm](https://www.npmjs.com/search?q=%40strudel) . Here is an overview of all the packages: ### Umbrella Packages[](https://strudel.cc/technical-manual/packages/#umbrella-packages) These packages give you a batteries-included point of getting started, and most likely the thing you’d want to use in your project: * [repl](https://codeberg.org/uzu/strudel/src/branch/main/packages/repl) : The Strudel REPL as a web component. * [web](https://codeberg.org/uzu/strudel/src/branch/main/packages/web) : Strudel library for the browser, without UI. To find out more about these two, read [Using Strudel in Your Project](https://strudel.cc/technical-manual/project-start/) ### Essential Packages[](https://strudel.cc/technical-manual/packages/#essential-packages) These package are the most essential. You might want to use all of those if you’re using strudel in your project: * [core](https://codeberg.org/uzu/strudel/src/branch/main/packages/core#strudelcore) : tidal pattern engine with core primitives * [mini](https://codeberg.org/uzu/strudel/src/branch/main/packages/mini#strudelmini) : mini notation parser + core bindings * [transpiler](https://codeberg.org/uzu/strudel/src/branch/main/packages/transpiler#strudeltranspiler) : user code transpiler. syntax sugar + highlighting ### Language Extensions[](https://strudel.cc/technical-manual/packages/#language-extensions) These packages extend the pattern language by specific functions * [tonal](https://codeberg.org/uzu/strudel/src/branch/main/packages/tonal) : tonal functions for scales and chords * [xen](https://codeberg.org/uzu/strudel/src/branch/main/packages/xen) : microtonal / xenharmonic functions ### Outputs[](https://strudel.cc/technical-manual/packages/#outputs) These packages provide bindings for different ways to output strudel patterns: * [webaudio](https://codeberg.org/uzu/strudel/src/branch/main/packages/webaudio#strudelwebaudio) : the default webaudio output * [osc](https://codeberg.org/uzu/strudel/src/branch/main/packages/osc#strudelosc) : bindings to communicate via OSC * [midi](https://codeberg.org/uzu/strudel/src/branch/main/packages/midi#strudelmidi) : webmidi bindings * [csound](https://codeberg.org/uzu/strudel/src/branch/main/packages/csound#strudelcsound) : csound bindings * [soundfonts](https://codeberg.org/uzu/strudel/src/branch/main/packages/serial#strudelsoundfonts) : Soundfont support * [serial](https://codeberg.org/uzu/strudel/src/branch/main/packages/serial#strudelserial) : webserial bindings ### Others[](https://strudel.cc/technical-manual/packages/#others) * [embed](https://codeberg.org/uzu/strudel/src/branch/main/packages/embed#strudelembed) : embeddable REPL web component ### No Longer Maintained[](https://strudel.cc/technical-manual/packages/#no-longer-maintained) * [react](https://www.npmjs.com/package/@strudel.cycles/react) : react hooks and components for strudel * [eval](https://www.npmjs.com/package/@strudel.cycles/eval) : old code transpiler * [tone](https://www.npmjs.com/package/@strudel.cycles/tone) : bindings for Tone.js instruments and effects * [webdirt](https://www.npmjs.com/package/@strudel.cycles/webdirt) : webdirt bindings, replaced by webaudio package * any `@strudel.cycles/*` packages have been renamed to `@strudel/*` since version 0.10.0. Tools[](https://strudel.cc/technical-manual/packages/#tools) ------------------------------------------------------------- * [pnpm](https://pnpm.io/) for package management, workspaces and publishing * [lerna](https://lerna.js.org/) for bumping versions * see CONTRIBUTING.md for more info --- # Sounds 🌀 Strudel Sounds[](https://strudel.cc/technical-manual/sounds/#sounds) ============================================================= Let’s take a closer look about how sounds are implemented in the webaudio output. Registering a sound[](https://strudel.cc/technical-manual/sounds/#registering-a-sound) --------------------------------------------------------------------------------------- All sounds are registered in the sound map, using the the `registerSound` function: function registerSound( name: string, // The name of the sound that should be given to `s`, e.g. `mysaw` // The function called by the scheduler to trigger the sound: ( time: number, // The audio context time the sound should start value: object, // The value of the `Hap` onended: () => void // A callback that should be fired when the sound has ended ) => { node: AudioNode, // node to connect to rest of the effects chain stop: (time:number) => void // a function that will stop the sound }, data: object // meta data, only for ui logic in sounds tab ); When `registerSound` is called, it registers `{ onTrigger, data }` under the given `name` in a [nanostore map](https://github.com/nanostores/nanostores#maps) . ### Example[](https://strudel.cc/technical-manual/sounds/#example) This might be a bit abstract, so here is a minimal example: registerSound( 'mysaw', (time, value, onended) => { let { freq } = value; // destructure control params const ctx = getAudioContext(); // create oscillator const o = new OscillatorNode(ctx, { type: 'sawtooth', frequency: Number(freq) }); o.start(time); // add gain node to level down osc const g = new GainNode(ctx, { gain: 0.3 }); // connect osc to gain const node = o.connect(g); // this function can be called from outside to stop the sound const stop = (time) => o.stop(time); // ended will be fired when stop has been fired o.addEventListener('ended', () => { o.disconnect(); g.disconnect(); onended(); }); return { node, stop }; }, { type: 'synth' }, ); // use the sound freq(220, 440, 330).s('mysaw'); You can actually use this code in the [REPL](https://strudel.cc/) and it’ll work. After evaluating the code, you should see `mysaw` in listed in the sounds tab. Playing sounds[](https://strudel.cc/technical-manual/sounds/#playing-sounds) ----------------------------------------------------------------------------- Now here is what happens when a sound is played: When the webaudio output plays a `Hap`, it will lookup and call the `onTrigger` function for the given `s`. The returned `node` can then be connected to the rest of the standard effects chain Having the stop function separate allows playing sounds via midi too, where you don’t know how long the noteon will last --- # Testing 🌀 Strudel Testing[](https://strudel.cc/technical-manual/testing/#testing) ================================================================ Strudel uses [vitest](https://vitest.dev/) for testing, with 2 types of testing strategies: * unit tests for fine grained testing * automated snapshot tests for broader testing Unit Tests[](https://strudel.cc/technical-manual/testing/#unit-tests) ---------------------------------------------------------------------- Each package has a `test` folder where tests are written on a file by file basis, e.g. `util.test.mjs` implements all tests for `util.mjs`. Snapshot Tests[](https://strudel.cc/technical-manual/testing/#snapshot-tests) ------------------------------------------------------------------------------ Snapshot tests allow testing larger chunks of data. Strudel uses snapshot tests for: * Example Snippets: `examples.test.mjs`, using snippets under `@example` inside jsdoc comments * Example Tunes: `tunes.test.mjs`, using all patterns in `tunes.mjs` The snapshot (`.snap`) files contain all haps within a certain number of cycles for each tested pattern. They allow testing for breaking changes on a larger scale. If breaking changes are intentional, the snapshots can be updated with `npm run snapshot`. Just make sure to verify that all affected patterns behave as expected. --- # Notes 🌀 Strudel Notes[](https://strudel.cc/learn/notes/#notes) =============================================== Pitches are an important building block in many musical traditions. In Strudel, pitches can be expressed as note names, note numbers or frequencies. Here’s the same pattern written in three different ways: * `note`: letter notation, good for those who are familiar with western music theory: note("a3 c#4 e4 a4") * `note`: number notation, good for those who want to use recognisable pitches, but don’t care about music theory: note("57 61 64 69") * `freq`: frequency notation, good for those who want to go beyond standardised tuning systems: freq("220 275 330 440") Let’s look at those in more detail
 `note` names[](https://strudel.cc/learn/notes/#note-names) ----------------------------------------------------------- Notes names can be notated with the note letter, followed by the octave number. You can notate flats with `b` and sharps with `#`. note("a3 c#4 e4 a4") By the way, you can edit the contents of the player, and press “update” to hear your change! You can also press “play” on the next player without needing to stop the last one. `note` numbers[](https://strudel.cc/learn/notes/#note-numbers) --------------------------------------------------------------- If you prefer, you can also use numbers with `note` instead: note("57 61 64 69") These numbers are interpreted as so called [MIDI numbers](https://www.inspiredacoustics.com/en/MIDI_note_numbers_and_center_frequencies) , where adjacent whole numbers are one ‘semitone’ apart. You could also write decimal numbers to get ‘microtonal’ pitches (in between the black and white piano notes): note("74.5 75 75.5 76") `freq`[](https://strudel.cc/learn/notes/#freq) ----------------------------------------------- To get maximum freedom, you can also use `freq` to directly control the frequency: freq("220 275 330 440") Hearing and frequency[](https://strudel.cc/learn/notes/#hearing-and-frequency) ------------------------------------------------------------------------------- In the above example, we play A3 (220Hz), C#4 natural (275Hz), E4 (330Hz) and A4 (440Hz), mirroring our previous examples. But can you hear the difference between these individual frequencies? freq("220 221 223 224") How about these? freq("2020 2021 2023 2024") The higher we go up
 freq("5020 5021 5023 5024") The less distance we can hear between the frequencies! freq("10020 10021 10023 10024") Why is this? [Human hearing operates logarithmically](https://www.audiocheck.net/soundtests_nonlinear.php) . From notes to sounds[](https://strudel.cc/learn/notes/#from-notes-to-sounds) ----------------------------------------------------------------------------- In this page, when we played a pattern of notes like this: note("a3 c#4 e4 a4") We heard a simple synthesised sound, in fact we heard a [triangle wave oscillator](https://en.wikipedia.org/wiki/Triangle_wave) . This is the default synthesiser used by Strudel, but how do we then make different sounds in Strudel? Let’s find out in the next page on [Sounds](https://strudel.cc/learn/sounds/) . --- # 🌀 Strudel Blog Strudel Blog ============ Welcome to the Strudel Blog, where we will keep you updated with the latest changes and things happening in the strudelsphere. You can subscribe to this blog using [this rss link](https://strudel.cc/rss.xml) Release Notes v1.2.0 ==================== May 2025 by froos What’s Changed[](https://workspace/uzu/strudel/website/src/content/blog/release-1.2.0-kardinalschnitten/#whats-changed) ------------------------------------------------------------------------------------------------------------------------ highlights[](https://workspace/uzu/strudel/website/src/content/blog/release-1.2.0-kardinalschnitten/#highlights) ----------------------------------------------------------------------------------------------------------------- * [stepwise functions](https://strudel.cc/learn/stepwise/) ([PR](https://github.com/tidalcycles/strudel/pull/1262) ) * [midimaps](https://strudel.cc/learn/input-output/#midimaps) ([PR](https://github.com/tidalcycles/strudel/pull/1274) ) * [spectrum](https://strudel.cc/learn/visual-feedback/#spectrum) ([PR](https://github.com/tidalcycles/strudel/pull/1213) ) * [mqtt](https://strudel.cc/learn/input-output/#mqtt) ([PR](https://github.com/tidalcycles/strudel/pull/1224) ) * pulse oscillator (todo: [https://github.com/tidalcycles/strudel/issues/1336](https://github.com/tidalcycles/strudel/issues/1336) ) ([PR](https://github.com/tidalcycles/strudel/pull/1304) ) * theme improvements breaking changes[](https://workspace/uzu/strudel/website/src/content/blog/release-1.2.0-kardinalschnitten/#breaking-changes) ----------------------------------------------------------------------------------------------------------------------------- * \[breaking change\] Sample signals from query onset, rather than midpoint by @yaxu in [https://github.com/tidalcycles/strudel/pull/1278](https://github.com/tidalcycles/strudel/pull/1278) * change behaviour of polymeter, and remove polymeterSteps by @yaxu in [https://github.com/tidalcycles/strudel/pull/1302](https://github.com/tidalcycles/strudel/pull/1302) * Polish, rename, and document stepwise functions by @yaxu in [https://github.com/tidalcycles/strudel/pull/1262](https://github.com/tidalcycles/strudel/pull/1262) ### superdough[](https://workspace/uzu/strudel/website/src/content/blog/release-1.2.0-kardinalschnitten/#superdough) * feat: Create Pulse Oscillator with variable PWM by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1304](https://github.com/tidalcycles/strudel/pull/1304) * add num samples (edited numbers) by @yaxu in [https://github.com/tidalcycles/strudel/pull/1309](https://github.com/tidalcycles/strudel/pull/1309) * Add num samples from 0 up to 20 by @yaxu in [https://github.com/tidalcycles/strudel/pull/1310](https://github.com/tidalcycles/strudel/pull/1310) * feat: add max polyphony feature for superdough by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1317](https://github.com/tidalcycles/strudel/pull/1317) ### docs[](https://workspace/uzu/strudel/website/src/content/blog/release-1.2.0-kardinalschnitten/#docs) * doc: visual functions + refactor onPaint by @felixroos in [https://github.com/tidalcycles/strudel/pull/1125](https://github.com/tidalcycles/strudel/pull/1125) * Labeled statements doc by @felixroos in [https://github.com/tidalcycles/strudel/pull/1126](https://github.com/tidalcycles/strudel/pull/1126) * Correct spelling mistakes by @EdwardBetts in [https://github.com/tidalcycles/strudel/pull/1183](https://github.com/tidalcycles/strudel/pull/1183) * remove redundant example for cat, update snapshot by @kdiab in [https://github.com/tidalcycles/strudel/pull/1189](https://github.com/tidalcycles/strudel/pull/1189) * chore: Edit run locally instructions in README.md by @ChinoUkaegbu in [https://github.com/tidalcycles/strudel/pull/1206](https://github.com/tidalcycles/strudel/pull/1206) * suggested changes to voicings.mdx by @bwagner in [https://github.com/tidalcycles/strudel/pull/1231](https://github.com/tidalcycles/strudel/pull/1231) * Documentation for all/each, and bugfix for each by @yaxu in [https://github.com/tidalcycles/strudel/pull/1233](https://github.com/tidalcycles/strudel/pull/1233) * Update documentation for param value modification by @gillespi314 in [https://github.com/tidalcycles/strudel/pull/1238](https://github.com/tidalcycles/strudel/pull/1238) * fix docs for beat function by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1248](https://github.com/tidalcycles/strudel/pull/1248) * understand voicings page by @felixroos in [https://github.com/tidalcycles/strudel/pull/1230](https://github.com/tidalcycles/strudel/pull/1230) * add reference package by @felixroos in [https://github.com/tidalcycles/strudel/pull/1252](https://github.com/tidalcycles/strudel/pull/1252) * Stepwise documentation tweaks, with mridangam samples by @yaxu in [https://github.com/tidalcycles/strudel/pull/1275](https://github.com/tidalcycles/strudel/pull/1275) * showcase tweaks by @yaxu in [https://github.com/tidalcycles/strudel/pull/1291](https://github.com/tidalcycles/strudel/pull/1291) * Signpost licenses for source code and samples a bit more, ref #1277 by @yaxu in [https://github.com/tidalcycles/strudel/pull/1289](https://github.com/tidalcycles/strudel/pull/1289) * Fix misplaced ending sentence by @makmanalp in [https://github.com/tidalcycles/strudel/pull/1296](https://github.com/tidalcycles/strudel/pull/1296) * Fix typo pattnr by @ReneNyffenegger in [https://github.com/tidalcycles/strudel/pull/1316](https://github.com/tidalcycles/strudel/pull/1316) * update docs to reflect import sounds tab change by @hpunq in [https://github.com/tidalcycles/strudel/pull/1332](https://github.com/tidalcycles/strudel/pull/1332) ### ui improvements[](https://workspace/uzu/strudel/website/src/content/blog/release-1.2.0-kardinalschnitten/#ui-improvements) * Udels (MultiFrame Strudel) Revisited by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1132](https://github.com/tidalcycles/strudel/pull/1132) * Create audio target selector for OSC/Superdirt by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1160](https://github.com/tidalcycles/strudel/pull/1160) * Add a search bar to the REPL Reference tab by @netux in [https://github.com/tidalcycles/strudel/pull/1165](https://github.com/tidalcycles/strudel/pull/1165) * Adding search bar (soundtab.jsx) by @Bubobubobubobubo in [https://github.com/tidalcycles/strudel/pull/1185](https://github.com/tidalcycles/strudel/pull/1185) * add 2 new ui settings by @felixroos in [https://github.com/tidalcycles/strudel/pull/1200](https://github.com/tidalcycles/strudel/pull/1200) * Theme glowup by @felixroos in [https://github.com/tidalcycles/strudel/pull/1268](https://github.com/tidalcycles/strudel/pull/1268) * Create Pattern Page Pagination by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1287](https://github.com/tidalcycles/strudel/pull/1287) * feat: Theme improvements by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1295](https://github.com/tidalcycles/strudel/pull/1295) * feat: new themes + theme improvements by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1326](https://github.com/tidalcycles/strudel/pull/1326) * Add new “import-sounds” tab with explanation on folder import by @hpunq in [https://github.com/tidalcycles/strudel/pull/1329](https://github.com/tidalcycles/strudel/pull/1329) * Add Icon to import sample button by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1331](https://github.com/tidalcycles/strudel/pull/1331) * better spacing in zen mode by @felixroos in [https://github.com/tidalcycles/strudel/pull/1147](https://github.com/tidalcycles/strudel/pull/1147) * Screenreader improvements by @yaxu in [https://github.com/tidalcycles/strudel/pull/1158](https://github.com/tidalcycles/strudel/pull/1158) * colorize console + tweak header by @felixroos in [https://github.com/tidalcycles/strudel/pull/1203](https://github.com/tidalcycles/strudel/pull/1203) * Menu Panel Improvements! by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1193](https://github.com/tidalcycles/strudel/pull/1193) * Make panel hover behavior optional by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1199](https://github.com/tidalcycles/strudel/pull/1199) * REPL: solo and sync configuration by @bthj in [https://github.com/tidalcycles/strudel/pull/1214](https://github.com/tidalcycles/strudel/pull/1214) * enhancement: make error messages easier to read by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1315](https://github.com/tidalcycles/strudel/pull/1315) ### mqtt[](https://workspace/uzu/strudel/website/src/content/blog/release-1.2.0-kardinalschnitten/#mqtt) * MQTT support by @yaxu in [https://github.com/tidalcycles/strudel/pull/1224](https://github.com/tidalcycles/strudel/pull/1224) * MQTT - if password isn’t provided, prompt for one by @yaxu in [https://github.com/tidalcycles/strudel/pull/1249](https://github.com/tidalcycles/strudel/pull/1249) * MQTT - support adding hap duration and cps metadata to JSON messages by @yaxu in [https://github.com/tidalcycles/strudel/pull/1279](https://github.com/tidalcycles/strudel/pull/1279) * make mqtt topic patternable by @yaxu in [https://github.com/tidalcycles/strudel/pull/1280](https://github.com/tidalcycles/strudel/pull/1280) * Bugfix: update mqtt connections dictionary by @yaxu in [https://github.com/tidalcycles/strudel/pull/1281](https://github.com/tidalcycles/strudel/pull/1281) * mqtt bugfix - connection check by @yaxu in [https://github.com/tidalcycles/strudel/pull/1282](https://github.com/tidalcycles/strudel/pull/1282) ### new functions[](https://workspace/uzu/strudel/website/src/content/blog/release-1.2.0-kardinalschnitten/#new-functions) * Add scramble and shuffle by @yaxu in [https://github.com/tidalcycles/strudel/pull/1167](https://github.com/tidalcycles/strudel/pull/1167) * polyJoin by @yaxu in [https://github.com/tidalcycles/strudel/pull/1168](https://github.com/tidalcycles/strudel/pull/1168) * Add seqPLoop from Tidal by @yaxu in [https://github.com/tidalcycles/strudel/pull/1182](https://github.com/tidalcycles/strudel/pull/1182) * add filter + filterWhen + within by @felixroos in [https://github.com/tidalcycles/strudel/pull/1039](https://github.com/tidalcycles/strudel/pull/1039) * Add bite function by @yaxu in [https://github.com/tidalcycles/strudel/pull/1187](https://github.com/tidalcycles/strudel/pull/1187) * markcss by @felixroos in [https://github.com/tidalcycles/strudel/pull/1202](https://github.com/tidalcycles/strudel/pull/1202) * “beat” function for “step sequencer” style rhythm notation by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1237](https://github.com/tidalcycles/strudel/pull/1237) * Add s\_zip for ‘cat’-ing patterns together step-by-step, bugfix `steps` by @yaxu in [https://github.com/tidalcycles/strudel/pull/1208](https://github.com/tidalcycles/strudel/pull/1208) * “stretch” function (phase vocoder) by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1130](https://github.com/tidalcycles/strudel/pull/1130) * add basic spectrum function by @felixroos in [https://github.com/tidalcycles/strudel/pull/1213](https://github.com/tidalcycles/strudel/pull/1213) * Add onKey function for custom key commands for patterns by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1235](https://github.com/tidalcycles/strudel/pull/1235) * Add binary and binaryN by @heerman in [https://github.com/tidalcycles/strudel/pull/1226](https://github.com/tidalcycles/strudel/pull/1226) * midimaps by @felixroos in [https://github.com/tidalcycles/strudel/pull/1274](https://github.com/tidalcycles/strudel/pull/1274) * small feat: Add alias for segment and ribbon by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1314](https://github.com/tidalcycles/strudel/pull/1314) * feat: Create scrub function for scrubbing an audio file by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1321](https://github.com/tidalcycles/strudel/pull/1321) * feat: Improve gain curve by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1318](https://github.com/tidalcycles/strudel/pull/1318) * Chop chop by @yaxu in [https://github.com/tidalcycles/strudel/pull/1078](https://github.com/tidalcycles/strudel/pull/1078) ### more[](https://workspace/uzu/strudel/website/src/content/blog/release-1.2.0-kardinalschnitten/#more) * Make `all()` post-stack again, and add `each()` for pre-stack by @yaxu in [https://github.com/tidalcycles/strudel/pull/1229](https://github.com/tidalcycles/strudel/pull/1229) * Add stepBind, and some toplevel aliases for binds and withValue by @yaxu in [https://github.com/tidalcycles/strudel/pull/1241](https://github.com/tidalcycles/strudel/pull/1241) * Make cps patternable by @eefano in [https://github.com/tidalcycles/strudel/pull/1001](https://github.com/tidalcycles/strudel/pull/1001) * Allow wchooseCycles probabilities to be patterned by @yaxu in [https://github.com/tidalcycles/strudel/pull/1292](https://github.com/tidalcycles/strudel/pull/1292) * @strudel/sampler improvements by @felixroos in [https://github.com/tidalcycles/strudel/pull/1288](https://github.com/tidalcycles/strudel/pull/1288) ### refactor[](https://workspace/uzu/strudel/website/src/content/blog/release-1.2.0-kardinalschnitten/#refactor) * expose comment commands by @felixroos in [https://github.com/tidalcycles/strudel/pull/1136](https://github.com/tidalcycles/strudel/pull/1136) * containerize/seperate out boolean checks for repl types/Repl logic into bespoke components. by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1163](https://github.com/tidalcycles/strudel/pull/1163) * Improve + simplify neocyclist timing by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1164](https://github.com/tidalcycles/strudel/pull/1164) * Make phaser control consistent with superdirt by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1178](https://github.com/tidalcycles/strudel/pull/1178) * Revert “Make phaser control consistent with superdirt” by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1179](https://github.com/tidalcycles/strudel/pull/1179) * make phaser control match superdirt by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1180](https://github.com/tidalcycles/strudel/pull/1180) * refactor sampler by @felixroos in [https://github.com/tidalcycles/strudel/pull/1101](https://github.com/tidalcycles/strudel/pull/1101) * update lockfile + minor versions by @felixroos in [https://github.com/tidalcycles/strudel/pull/1198](https://github.com/tidalcycles/strudel/pull/1198) * Preserve tactus for ‘degrade’ and friends, and tidy up ‘pick’ and friends by @yaxu in [https://github.com/tidalcycles/strudel/pull/1205](https://github.com/tidalcycles/strudel/pull/1205) * Apply `all` function to individual patterns rather than final stack by @yaxu in [https://github.com/tidalcycles/strudel/pull/1209](https://github.com/tidalcycles/strudel/pull/1209) * Revert “Fix sometimes” by @yaxu in [https://github.com/tidalcycles/strudel/pull/1267](https://github.com/tidalcycles/strudel/pull/1267) * patchday by @felixroos in [https://github.com/tidalcycles/strudel/pull/1264](https://github.com/tidalcycles/strudel/pull/1264) * Rename repeat back to extend by @yaxu in [https://github.com/tidalcycles/strudel/pull/1285](https://github.com/tidalcycles/strudel/pull/1285) * Send delta in OSC message in seconds, to match tidal/superdirt by @yaxu in [https://github.com/tidalcycles/strudel/pull/1323](https://github.com/tidalcycles/strudel/pull/1323) ### fixes[](https://workspace/uzu/strudel/website/src/content/blog/release-1.2.0-kardinalschnitten/#fixes) * Fix clock worker dependency path in module builds by @matthewkaney in [https://github.com/tidalcycles/strudel/pull/1129](https://github.com/tidalcycles/strudel/pull/1129) * Fix bug in Fraction.lcm by @yaxu in [https://github.com/tidalcycles/strudel/pull/1133](https://github.com/tidalcycles/strudel/pull/1133) * Fix tactus marking in mininotation by @yaxu in [https://github.com/tidalcycles/strudel/pull/1144](https://github.com/tidalcycles/strudel/pull/1144) * Fix loopAt tactus by @yaxu in [https://github.com/tidalcycles/strudel/pull/1145](https://github.com/tidalcycles/strudel/pull/1145) * Fix OSC clock jitter by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1157](https://github.com/tidalcycles/strudel/pull/1157) * \[CORS HOTFIX\] by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1162](https://github.com/tidalcycles/strudel/pull/1162) * Fixes fit so it works after a chop or slice by @yaxu in [https://github.com/tidalcycles/strudel/pull/1171](https://github.com/tidalcycles/strudel/pull/1171) * fix sample speed when using splice and fit with superdirt by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1172](https://github.com/tidalcycles/strudel/pull/1172) * handle midin device not found error by @felixroos in [https://github.com/tidalcycles/strudel/pull/1146](https://github.com/tidalcycles/strudel/pull/1146) * Fix serial timing by @yaxu in [https://github.com/tidalcycles/strudel/pull/1188](https://github.com/tidalcycles/strudel/pull/1188) * Fix regression for d1, p1, p(n) by @yaxu in [https://github.com/tidalcycles/strudel/pull/1227](https://github.com/tidalcycles/strudel/pull/1227) * Fix sometimes by @yaxu in [https://github.com/tidalcycles/strudel/pull/1243](https://github.com/tidalcycles/strudel/pull/1243) * Fix sf2 timing by @felixroos in [https://github.com/tidalcycles/strudel/pull/1272](https://github.com/tidalcycles/strudel/pull/1272) * Fix “squeezejoin” and functions using it, including “bite” by @yaxu in [https://github.com/tidalcycles/strudel/pull/1286](https://github.com/tidalcycles/strudel/pull/1286) * Fixes inverted triangle wave by renaming it to “itri”, making non-inverted “tri” by @yaxu in [https://github.com/tidalcycles/strudel/pull/1283](https://github.com/tidalcycles/strudel/pull/1283) * Hotfix: prevent undefined pattern code from crashing strudel on load by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1297](https://github.com/tidalcycles/strudel/pull/1297) * Fix test error #1297 by @nkymut in [https://github.com/tidalcycles/strudel/pull/1298](https://github.com/tidalcycles/strudel/pull/1298) * bugfix zoom stepcount by @yaxu in [https://github.com/tidalcycles/strudel/pull/1301](https://github.com/tidalcycles/strudel/pull/1301) * bugfix: Allow single param to be used in the as function by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1312](https://github.com/tidalcycles/strudel/pull/1312) * fix: replace empty spaces in registered sound keys by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1319](https://github.com/tidalcycles/strudel/pull/1319) * FIX: Multichannel Audio by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1322](https://github.com/tidalcycles/strudel/pull/1322) * fix: udels header by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1325](https://github.com/tidalcycles/strudel/pull/1325) * fix: disable astro toolbar by default by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1324](https://github.com/tidalcycles/strudel/pull/1324) * FIX: sound import order by @daslyfe in [https://github.com/tidalcycles/strudel/pull/1333\`](https://github.com/tidalcycles/strudel/pull/1333%60) New Contributors[](https://workspace/uzu/strudel/website/src/content/blog/release-1.2.0-kardinalschnitten/#new-contributors) ----------------------------------------------------------------------------------------------------------------------------- * @EdwardBetts made their first contribution in [https://github.com/tidalcycles/strudel/pull/1183](https://github.com/tidalcycles/strudel/pull/1183) * @netux made their first contribution in [https://github.com/tidalcycles/strudel/pull/1165](https://github.com/tidalcycles/strudel/pull/1165) * @kdiab made their first contribution in [https://github.com/tidalcycles/strudel/pull/1189](https://github.com/tidalcycles/strudel/pull/1189) **Full Changelog**: [https://github.com/tidalcycles/strudel/compare/v1.1.0
v1.1.1](https://github.com/tidalcycles/strudel/compare/v1.1.0...v1.1.1) packages[](https://workspace/uzu/strudel/website/src/content/blog/release-1.2.0-kardinalschnitten/#packages) ------------------------------------------------------------------------------------------------------------- * @strudel/codemirror@1.2.0 * @strudel/core@1.2.0 * @strudel/csound@1.2.0 * @strudel/draw@1.2.0 * @strudel/gamepad@1.2.0 * @strudel/hydra@1.2.0 * @strudel/midi@1.2.0 * @strudel/mini@1.2.0 * @strudel/motion@1.2.0 * @strudel/mqtt@1.2.0 * @strudel/osc@1.2.0 * @strudel/reference@1.2.0 * @strudel/repl@1.2.0 * @strudel/sampler@0.2.0 * @strudel/serial@1.2.0 * @strudel/soundfonts@1.2.0 * superdough@1.2.0 * @strudel/tonal@1.2.0 * @strudel/transpiler@1.2.0 * @strudel/web@1.2.0 * @strudel/webaudio@1.2.0 * @strudel/xen@1.2.0 Release Notes v1.1.0 ==================== June 2024 by froos These are the release notes for Strudel 1.1.0 aka “Bananensplit”. The last release was over 19 weeks ago, so a lot of things have happened! First, here’s a little demo, teasing some of the new features: Let’s write up some of the highlights: New DSP Features[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#new-dsp-features) ------------------------------------------------------------------------------------------------------------------------ ### Stereo Supersaw[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#stereo-supersaw) with spread, unison, and detune parameters note("d f a a# a d3").fast(2) .s("supersaw").spread(".8").detune(.3).unison("2 7") ### Analog “ladder” filter type[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#analog-ladder-filter-type) works great for acid basslines and vibey tones note("{d d d a a# d3 f4}%16".sub(12)).gain(1).s("sawtooth") .lpf(200).lpenv(slider(1.36,0,8)).lpq(7).distort("1.5:.7")` .ftype('ladder') ### stereo distortion effect[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#stereo-distortion-effect) note("{g g a# g g4}%8".add("{0 7 12 0}%8")).lpf(500) .s("supersaw").dist("4:.2") Editor Features[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#editor-features) ---------------------------------------------------------------------------------------------------------------------- ### inline viz[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#inline-viz) The editor now supports multiple visuals within the code, using the `_` prefix for viz functions: ![Screenshot 2024-06-01 at 01 23 51](https://codeberg.org/uzu/strudel/assets/12023032/978fee85-e533-4da6-a245-d20d0083f57e) * `._pianoroll()`: inline pianoroll * `._punchcard()`: inline punchcard * `._scope()`: inline scope * `._pitchwheel()`: inline pitchwheel For more info, check out the new [Visual Feedback Page](https://strudel.cc/learn/visual-feedback/) ### label notation[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#label-notation) This new notation simplifies writing patterns at the top level: d1: s("bd*4") d2: s("[- hh]*4") This is equivalent to: stack( s("bd*4"), s("[- hh]*4") ) The labels you choose are arbitrary, the above `d1` and `d2` are a typical thing you’d write in tidal, for example `d1 $ s "bd*4"`. If the same label is used multiple times, the last one wins: d1: s("bd*4") d1: s("[- hh]*4") // <-- only this plays There is a special label anonymous label `$`, which can appear multiple times without overriding itself: // both of these will play: $: s("bd*4") $: s("[- hh]*4") You can mute a pattern by prefixing `_`: _$: s("bd*4") // <-- this one is muted $: s("[- hh]*4") To run a transformation on all patterns, you can use `all`: $: s("bd*4") $: s("[- hh]*4") all(x=>x.room(.5)) This notation is now the recommended way to [play patterns in parallel](https://strudel.cc/workshop/first-notes/#playing-multiple-patterns) ### Clock sync between multiple instances[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#clock-sync-between-multiple-instances) timing has received a major overhaul, and is now much more accurate on all browsers. Additionally, you can now sync timing across multiple windows. ![Screenshot 2024-06-02 at 11 24 40 PM](https://codeberg.org/uzu/strudel/assets/47068718/840be744-a13e-4d7b-ab09-50d3a70b1f85) ### Better sample upload support[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#better-sample-upload-support) you can now upload large amounts of samples much faster across all browsers including on IOS devices. supported filetypes now include: ogg flac mp3 wav aac m4a ### experimental tidal syntax[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#experimental-tidal-syntax) The new `tidal` function allows you to write strudel patterns in tidal syntax: await initTidal() tidal` d1 $ s "bd*4" d2 $ s "[- hh]*4" ` As we’re looking to improve compatibility with tidal, we’re happy to hear feedback. breaking changes[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#breaking-changes) ------------------------------------------------------------------------------------------------------------------------ This release comes with a bunch of breaking changes. If you find your patterns to sound different, check out the PRs below for guidance on how to update them. Most of these changes shouldn’t affect a lot of patterns. In case of doubt, add the line `// @version 1.0` to your old pattern. If you’re having problems, please let us know! * remove legacy legato + duration implementations by @felixroos in [https://codeberg.org/uzu/strudel/pull/965](https://codeberg.org/uzu/strudel/pull/965) * Velocity in value by @felixroos in [https://codeberg.org/uzu/strudel/pull/974](https://codeberg.org/uzu/strudel/pull/974) * use ireal as default voicing dict by @felixroos [https://codeberg.org/uzu/strudel/pull/967](https://codeberg.org/uzu/strudel/pull/967) * Color in hap value by @felixroos [https://codeberg.org/uzu/strudel/pull/1007](https://codeberg.org/uzu/strudel/pull/1007) * rename trig -> reset, trigzero -> restart by @felixroos [https://codeberg.org/uzu/strudel/pull/1010](https://codeberg.org/uzu/strudel/pull/1010) * remove dangerous arithmetic feature by @felixroos [https://codeberg.org/uzu/strudel/pull/1030](https://codeberg.org/uzu/strudel/pull/1030) * change fanchor to 0 by @daslyfe [https://codeberg.org/uzu/strudel/pull/1107](https://codeberg.org/uzu/strudel/pull/1107) superdough features[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#superdough-features) ------------------------------------------------------------------------------------------------------------------------------ * replace shape with distort in learn doc by @daslyfe [https://codeberg.org/uzu/strudel/pull/982](https://codeberg.org/uzu/strudel/pull/982) * Worklet Improvents / fixes by @daslyfe [https://codeberg.org/uzu/strudel/pull/963](https://codeberg.org/uzu/strudel/pull/963) * supersaw oscillator by @daslyfe [https://codeberg.org/uzu/strudel/pull/978](https://codeberg.org/uzu/strudel/pull/978) * Add analog-style ladder filter by @daslyfe [https://codeberg.org/uzu/strudel/pull/1103](https://codeberg.org/uzu/strudel/pull/1103) * Calculate phaser modulation phase based on time by @daslyfe [https://codeberg.org/uzu/strudel/pull/1110](https://codeberg.org/uzu/strudel/pull/1110) * rollback phaser by @daslyfe [https://codeberg.org/uzu/strudel/pull/1113](https://codeberg.org/uzu/strudel/pull/1113) editor / ui features[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#editor--ui-features) ------------------------------------------------------------------------------------------------------------------------------- * ‘Enable Bracket Matching’ option in Codemirror by @eefano [https://codeberg.org/uzu/strudel/pull/956](https://codeberg.org/uzu/strudel/pull/956) * REPL sync between windows by @daslyfe [https://codeberg.org/uzu/strudel/pull/900](https://codeberg.org/uzu/strudel/pull/900) * inline viz / widgets package by @felixroos [https://codeberg.org/uzu/strudel/pull/989](https://codeberg.org/uzu/strudel/pull/989) * Inline punchcard + spiral by @felixroos [https://codeberg.org/uzu/strudel/pull/1008](https://codeberg.org/uzu/strudel/pull/1008) * More fonts by @felixroos [https://codeberg.org/uzu/strudel/pull/1023](https://codeberg.org/uzu/strudel/pull/1023) * better theme integration for visuals + various fixes by @felixroos [https://codeberg.org/uzu/strudel/pull/1024](https://codeberg.org/uzu/strudel/pull/1024) * add setting for sync flag by @felixroos [https://codeberg.org/uzu/strudel/pull/1025](https://codeberg.org/uzu/strudel/pull/1025) * add closeBrackets setting by @felixroos [https://codeberg.org/uzu/strudel/pull/1031](https://codeberg.org/uzu/strudel/pull/1031) * add font file types to offline cache by @felixroos [https://codeberg.org/uzu/strudel/pull/1032](https://codeberg.org/uzu/strudel/pull/1032) * pitchwheel visual by @felixroos [https://codeberg.org/uzu/strudel/pull/1041](https://codeberg.org/uzu/strudel/pull/1041) * repl: set document.title from @title by @kasparsj [https://codeberg.org/uzu/strudel/pull/1090](https://codeberg.org/uzu/strudel/pull/1090) * Samples tab improvements by @daslyfe [https://codeberg.org/uzu/strudel/pull/1102](https://codeberg.org/uzu/strudel/pull/1102) language features[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#language-features) -------------------------------------------------------------------------------------------------------------------------- * pickOut(), pickRestart(), pickReset() by @eefano [https://codeberg.org/uzu/strudel/pull/950](https://codeberg.org/uzu/strudel/pull/950) * Auto await samples by @felixroos [https://codeberg.org/uzu/strudel/pull/955](https://codeberg.org/uzu/strudel/pull/955) * feat: can now invert euclid pulses with negative numbers by @felixroos [https://codeberg.org/uzu/strudel/pull/959](https://codeberg.org/uzu/strudel/pull/959) * Nested controls by @felixroos [https://codeberg.org/uzu/strudel/pull/973](https://codeberg.org/uzu/strudel/pull/973) * alias - for ~ by @yaxu [https://codeberg.org/uzu/strudel/pull/981](https://codeberg.org/uzu/strudel/pull/981) * Beat-oriented functionality by @yaxu [https://codeberg.org/uzu/strudel/pull/976](https://codeberg.org/uzu/strudel/pull/976) * Labeled statements by @felixroos [https://codeberg.org/uzu/strudel/pull/991](https://codeberg.org/uzu/strudel/pull/991) * accidentals in scale degrees by @eefano [https://codeberg.org/uzu/strudel/pull/1000](https://codeberg.org/uzu/strudel/pull/1000) * Feature: tactus marking by @yaxu [https://codeberg.org/uzu/strudel/pull/1021](https://codeberg.org/uzu/strudel/pull/1021) * Tactus tidy by @yaxu [https://codeberg.org/uzu/strudel/pull/1027](https://codeberg.org/uzu/strudel/pull/1027) * Wax, wane, taper and taperlist by @yaxu [https://codeberg.org/uzu/strudel/pull/1042](https://codeberg.org/uzu/strudel/pull/1042) * transpose: support all combinations of numbers and strings for notes and intervals by @felixroos [https://codeberg.org/uzu/strudel/pull/1048](https://codeberg.org/uzu/strudel/pull/1048) * anonymous patterns + muting by @felixroos [https://codeberg.org/uzu/strudel/pull/1059](https://codeberg.org/uzu/strudel/pull/1059) * add swing + swingBy by @felixroos [https://codeberg.org/uzu/strudel/pull/1038](https://codeberg.org/uzu/strudel/pull/1038) * Stepwise functions from Tidal by @yaxu [https://codeberg.org/uzu/strudel/pull/1060](https://codeberg.org/uzu/strudel/pull/1060) * Tactus tweaks - fixes for maintaining tactus and highlight locations by @yaxu [https://codeberg.org/uzu/strudel/pull/1065](https://codeberg.org/uzu/strudel/pull/1065) * Fix stepjoin by @yaxu [https://codeberg.org/uzu/strudel/pull/1067](https://codeberg.org/uzu/strudel/pull/1067) * More tactus tidying by @yaxu [https://codeberg.org/uzu/strudel/pull/1071](https://codeberg.org/uzu/strudel/pull/1071) * Tactus calculation toggle and breaking change to tactus calculation in fast/slow/hurry by @yaxu [https://codeberg.org/uzu/strudel/pull/1081](https://codeberg.org/uzu/strudel/pull/1081) * hs2js package / tidal parser by @felixroos [https://codeberg.org/uzu/strudel/pull/870](https://codeberg.org/uzu/strudel/pull/870) * Add the mousex and mousey signal by @Enelg52 [https://codeberg.org/uzu/strudel/pull/1112](https://codeberg.org/uzu/strudel/pull/1112) * can now access strudelMirror from repl by @felixroos [https://codeberg.org/uzu/strudel/pull/1117](https://codeberg.org/uzu/strudel/pull/1117) sampler[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#sampler) ------------------------------------------------------------------------------------------------------ If you have nodejs installed on your system, you can now use [@strudel/sampler](https://www.npmjs.com/package/@strudel/sampler) to serve samples from disk to the REPL or flok. * local sample server cli by @felixroos [https://codeberg.org/uzu/strudel/pull/1033](https://codeberg.org/uzu/strudel/pull/1033) * Fix sampler paths by @felixroos [https://codeberg.org/uzu/strudel/pull/1034](https://codeberg.org/uzu/strudel/pull/1034) * Fix sampler windows by @felixroos [https://codeberg.org/uzu/strudel/pull/1108](https://codeberg.org/uzu/strudel/pull/1108) * fix sampler on windows by @geikha [https://codeberg.org/uzu/strudel/pull/1109](https://codeberg.org/uzu/strudel/pull/1109) docs[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#docs) ------------------------------------------------------------------------------------------------ * V1 release notes by @felixroos [https://codeberg.org/uzu/strudel/pull/935](https://codeberg.org/uzu/strudel/pull/935) * Minor documentation error: Update first-sounds.mdx by @mhetrick [https://codeberg.org/uzu/strudel/pull/941](https://codeberg.org/uzu/strudel/pull/941) * Update synths.mdx by @andresgottlieb [https://codeberg.org/uzu/strudel/pull/984](https://codeberg.org/uzu/strudel/pull/984) * using strudel in your project guide + cleanup examples by @felixroos [https://codeberg.org/uzu/strudel/pull/1006](https://codeberg.org/uzu/strudel/pull/1006) * Document signals by @ilesinge [https://codeberg.org/uzu/strudel/pull/1015](https://codeberg.org/uzu/strudel/pull/1015) * improve tutorial + custom samples doc by @felixroos [https://codeberg.org/uzu/strudel/pull/1053](https://codeberg.org/uzu/strudel/pull/1053) * fix cr typo on first-sounds.mdx by @cleary [https://codeberg.org/uzu/strudel/pull/1068](https://codeberg.org/uzu/strudel/pull/1068) * fix first sounds typo by @cleary [https://codeberg.org/uzu/strudel/pull/1069](https://codeberg.org/uzu/strudel/pull/1069) * add `<...>` to first-sounds.mdx recap by @cleary [https://codeberg.org/uzu/strudel/pull/1070](https://codeberg.org/uzu/strudel/pull/1070) * add nesting to `off` example variation in pattern-effects.mdx by @cleary [https://codeberg.org/uzu/strudel/pull/1075](https://codeberg.org/uzu/strudel/pull/1075) * fix translation issue in first-effects.mdx by @cleary [https://codeberg.org/uzu/strudel/pull/1072](https://codeberg.org/uzu/strudel/pull/1072) * add signals to recap in first-effects.mdx by @cleary [https://codeberg.org/uzu/strudel/pull/1073](https://codeberg.org/uzu/strudel/pull/1073) * fix docs on alignment.mdx by @diegodorado [https://codeberg.org/uzu/strudel/pull/1076](https://codeberg.org/uzu/strudel/pull/1076) * fix little dub tune example by @lukad [https://codeberg.org/uzu/strudel/pull/1104](https://codeberg.org/uzu/strudel/pull/1104) * clarify `off` in pattern-effects.mdx by @cleary [https://codeberg.org/uzu/strudel/pull/1074](https://codeberg.org/uzu/strudel/pull/1074) * Fixes drawPianoroll import in codemirror example by @giohappy [https://codeberg.org/uzu/strudel/pull/1116](https://codeberg.org/uzu/strudel/pull/1116) * Migrate tutorial fanchor by @felixroos [https://codeberg.org/uzu/strudel/pull/1122](https://codeberg.org/uzu/strudel/pull/1122) internals[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#internals) ---------------------------------------------------------------------------------------------------------- * remove cjs builds by @felixroos [https://codeberg.org/uzu/strudel/pull/945](https://codeberg.org/uzu/strudel/pull/945) * controls refactoring: simplify exports by @felixroos [https://codeberg.org/uzu/strudel/pull/962](https://codeberg.org/uzu/strudel/pull/962) * move canvas related helpers from core to new draw package by @felixroos [https://codeberg.org/uzu/strudel/pull/971](https://codeberg.org/uzu/strudel/pull/971) * remove canvas, externalize samples, delete junk by @felixroos [https://codeberg.org/uzu/strudel/pull/1003](https://codeberg.org/uzu/strudel/pull/1003) * Improve performance of ! (replicate) by @yaxu [https://codeberg.org/uzu/strudel/pull/1084](https://codeberg.org/uzu/strudel/pull/1084) * Benchmarks by @yaxu [https://codeberg.org/uzu/strudel/pull/1079](https://codeberg.org/uzu/strudel/pull/1079) fixes[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#fixes) -------------------------------------------------------------------------------------------------- * fix midi issue on firefox and added quote error by @Enelg52 [https://codeberg.org/uzu/strudel/pull/936](https://codeberg.org/uzu/strudel/pull/936) * fix: pianoroll sorting by @felixroos [https://codeberg.org/uzu/strudel/pull/938](https://codeberg.org/uzu/strudel/pull/938) * account for cps in midi time duration by @daslyfe [https://codeberg.org/uzu/strudel/pull/954](https://codeberg.org/uzu/strudel/pull/954) * fix script importable packages (web + repl) by @felixroos [https://codeberg.org/uzu/strudel/pull/957](https://codeberg.org/uzu/strudel/pull/957) * fix: reset global fx on pattern change by @felixroos [https://codeberg.org/uzu/strudel/pull/960](https://codeberg.org/uzu/strudel/pull/960) * add debounce to logger by @felixroos [https://codeberg.org/uzu/strudel/pull/968](https://codeberg.org/uzu/strudel/pull/968) * fix for transpose(): preserve hap value object structure by @eefano [https://codeberg.org/uzu/strudel/pull/966](https://codeberg.org/uzu/strudel/pull/966) * fix: clear hydra on reset by @felixroos [https://codeberg.org/uzu/strudel/pull/983](https://codeberg.org/uzu/strudel/pull/983) * little fix for withVal by @eefano [https://codeberg.org/uzu/strudel/pull/980](https://codeberg.org/uzu/strudel/pull/980) * fix: share now shares what’s visible instead of active by @felixroos [https://codeberg.org/uzu/strudel/pull/985](https://codeberg.org/uzu/strudel/pull/985) * Fix pure mini highlight by @yaxu [https://codeberg.org/uzu/strudel/pull/994](https://codeberg.org/uzu/strudel/pull/994) * fix: await injectPatternMethods by @felixroos [https://codeberg.org/uzu/strudel/pull/1012](https://codeberg.org/uzu/strudel/pull/1012) * update undocumented script by @felixroos [https://codeberg.org/uzu/strudel/pull/1013](https://codeberg.org/uzu/strudel/pull/1013) * eliminate chromium clock jitter by @felixroos [https://codeberg.org/uzu/strudel/pull/1004](https://codeberg.org/uzu/strudel/pull/1004) * Repl sync fixes by @daslyfe [https://codeberg.org/uzu/strudel/pull/1014](https://codeberg.org/uzu/strudel/pull/1014) * hotfix for 1017 by @daslyfe [https://codeberg.org/uzu/strudel/pull/1020](https://codeberg.org/uzu/strudel/pull/1020) * fix cyclist fizzling out by @felixroos [https://codeberg.org/uzu/strudel/pull/1046](https://codeberg.org/uzu/strudel/pull/1046) * Midi Time hotfix for scheduler updates by @daslyfe [https://codeberg.org/uzu/strudel/pull/1047](https://codeberg.org/uzu/strudel/pull/1047) * fix: do not reset cc input values on each eval by @felixroos [https://codeberg.org/uzu/strudel/pull/1054](https://codeberg.org/uzu/strudel/pull/1054) * Fix wchooseCycles not picking the whole pattern by @ilesinge [https://codeberg.org/uzu/strudel/pull/1061](https://codeberg.org/uzu/strudel/pull/1061) * fix OSC timing for recent scheduler updates by @daslyfe [https://codeberg.org/uzu/strudel/pull/1062](https://codeberg.org/uzu/strudel/pull/1062) * clarify license by @yaxu [https://codeberg.org/uzu/strudel/pull/1064](https://codeberg.org/uzu/strudel/pull/1064) * fix failing format test by @daslyfe [https://codeberg.org/uzu/strudel/pull/1077](https://codeberg.org/uzu/strudel/pull/1077) * fix: url parsing with extra params by @felixroos [https://codeberg.org/uzu/strudel/pull/1083](https://codeberg.org/uzu/strudel/pull/1083) * fix: csound + dough timing by @felixroos [https://codeberg.org/uzu/strudel/pull/1086](https://codeberg.org/uzu/strudel/pull/1086) * fix: missing events due to premature worklet cleanup by @felixroos [https://codeberg.org/uzu/strudel/pull/1089](https://codeberg.org/uzu/strudel/pull/1089) * Use sessionStorage for viewingPatternData and activePattern by @kasparsj [https://codeberg.org/uzu/strudel/pull/1091](https://codeberg.org/uzu/strudel/pull/1091) * osc: couple of fixes by @kasparsj [https://codeberg.org/uzu/strudel/pull/1093](https://codeberg.org/uzu/strudel/pull/1093) * web package fixes by @felixroos [https://codeberg.org/uzu/strudel/pull/1044](https://codeberg.org/uzu/strudel/pull/1044) * Fix audio worklets by @daslyfe [https://codeberg.org/uzu/strudel/pull/1114](https://codeberg.org/uzu/strudel/pull/1114) * fix: use full repl in web package by @felixroos [https://codeberg.org/uzu/strudel/pull/1119](https://codeberg.org/uzu/strudel/pull/1119) * \[BUG FIX\] Audio worklets sometimes dont load by @daslyfe [https://codeberg.org/uzu/strudel/pull/1121](https://codeberg.org/uzu/strudel/pull/1121) New Contributors[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#new-contributors) ------------------------------------------------------------------------------------------------------------------------ * @mhetrick made their first contribution [https://codeberg.org/uzu/strudel/pull/941](https://codeberg.org/uzu/strudel/pull/941) * @eefano made their first contribution [https://codeberg.org/uzu/strudel/pull/956](https://codeberg.org/uzu/strudel/pull/956) * @Enelg52 made their first contribution [https://codeberg.org/uzu/strudel/pull/936](https://codeberg.org/uzu/strudel/pull/936) * @andresgottlieb made their first contribution [https://codeberg.org/uzu/strudel/pull/984](https://codeberg.org/uzu/strudel/pull/984) * @cleary made their first contribution [https://codeberg.org/uzu/strudel/pull/1068](https://codeberg.org/uzu/strudel/pull/1068) * @diegodorado made their first contribution [https://codeberg.org/uzu/strudel/pull/1076](https://codeberg.org/uzu/strudel/pull/1076) * @lukad made their first contribution [https://codeberg.org/uzu/strudel/pull/1104](https://codeberg.org/uzu/strudel/pull/1104) * @giohappy made their first contribution [https://codeberg.org/uzu/strudel/pull/1116](https://codeberg.org/uzu/strudel/pull/1116) A huge thanks to all contributors!!! Packages[](https://workspace/uzu/strudel/website/src/content/blog/release-1.1.0-bananensplit/#packages) -------------------------------------------------------------------------------------------------------- * @strudel/codemirror@1.1.0 * @strudel/core@1.1.0 * @strudel/csound@1.1.0 * @strudel/draw@1.1.0 * @strudel/embed@1.1.0 * hs2js@0.1.0 * @strudel/hydra@1.1.0 * @strudel/midi@1.1.0 * @strudel/mini@1.1.0 * @strudel/osc@1.1.0 * @strudel/repl@1.1.0 * @strudel/sampler@0.1.0 * @strudel/serial@1.1.0 * @strudel/soundfonts@1.1.0 * superdough@1.1.0 * @strudel/tidal@0.1.0 * @strudel/tonal@1.1.0 * @strudel/transpiler@1.1.0 * @strudel/web@1.1.0 * @strudel/webaudio@1.1.0 * @strudel/xen@1.1.0 **Full Changelog**: [https://codeberg.org/uzu/strudel/compare/v1.0.0
v1.1.0](https://codeberg.org/uzu/strudel/compare/v1.0.0...v1.1.0) Release Notes v1.0.0 ==================== January 2024 by froos These are the release notes for Strudel 1.0.0 aka “Geburtstagskuchen” This release marks the 2 year anniversary of the project, the first commit was on the 22nd January 2022 by Alex McLean. If you generally need a heads up on what happened to Strudel in the last year, read the [2023 recap](https://strudel.cc/blog/#year-2) A lot has happened since then, and also since the last release 16 weeks ago. Let me write up some of the highlights: Breaking Change[](https://workspace/uzu/strudel/website/src/content/blog/release-1.0.0-geburtstagskuchen/#breaking-change) --------------------------------------------------------------------------------------------------------------------------- This version changes the default cps value from 1 to 0.5 to give patterns a little bit more time by default. If you find your existing patterns to be suddenly half the speed, just add a `setcps(1)` to the top and it should sound as it did before! * make 0.5hz cps the default by @yaxu in [https://codeberg.org/uzu/strudel/pulls/931](https://codeberg.org/uzu/strudel/pulls/931) New Domain[](https://workspace/uzu/strudel/website/src/content/blog/release-1.0.0-geburtstagskuchen/#new-domain) ----------------------------------------------------------------------------------------------------------------- Strudel is now available under [strudel.cc](https://strudel.cc/) . The old domain still works but you might not get the most recent version. * replace strudel.tidalcycles.org with strudel.cc by @felixroos in [https://codeberg.org/uzu/strudel/pulls/768](https://codeberg.org/uzu/strudel/pulls/768) Strudel on Mastodon[](https://workspace/uzu/strudel/website/src/content/blog/release-1.0.0-geburtstagskuchen/#strudel-on-mastodon) ----------------------------------------------------------------------------------------------------------------------------------- Strudel now has a mastodon presence: [https://social.toplap.org/@strudel](https://social.toplap.org/@strudel) New Audio Engine Features[](https://workspace/uzu/strudel/website/src/content/blog/release-1.0.0-geburtstagskuchen/#new-audio-engine-features) ----------------------------------------------------------------------------------------------------------------------------------------------- superdough, the audio engine of strudel has gotten some new features: * Create phaser effect by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/798](https://codeberg.org/uzu/strudel/pulls/798) * Multichannel audio by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/820](https://codeberg.org/uzu/strudel/pulls/820) * Audio device selection by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/854](https://codeberg.org/uzu/strudel/pulls/854) * Better convolution reverb by generating impulse responses by @Bubobubobubobubo and @felixroos in [https://codeberg.org/uzu/strudel/pulls/718](https://codeberg.org/uzu/strudel/pulls/718) * Add ‘white’, ‘pink’ and ‘brown’ oscillators + refactor synth by @Bubobubobubobubo and @felixroos in [https://codeberg.org/uzu/strudel/pulls/713](https://codeberg.org/uzu/strudel/pulls/713) * New noise type: “crackle” by @Bubobubobubobubo in [https://codeberg.org/uzu/strudel/pulls/806](https://codeberg.org/uzu/strudel/pulls/806) * Add support for using samples as impulse response buffers for the reverb by @vasilymilovidov in [https://codeberg.org/uzu/strudel/pulls/717](https://codeberg.org/uzu/strudel/pulls/717) * Compressor by @felixroos in [https://codeberg.org/uzu/strudel/pulls/729](https://codeberg.org/uzu/strudel/pulls/729) * Adding vibrato to Superdough sampler by @Bubobubobubobubo and @felixroos in [https://codeberg.org/uzu/strudel/pulls/706](https://codeberg.org/uzu/strudel/pulls/706) * Further Envelope improvements by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/868](https://codeberg.org/uzu/strudel/pulls/868) * Add more vowel qualities for the vowels function by @fnordomat in [https://codeberg.org/uzu/strudel/pulls/907](https://codeberg.org/uzu/strudel/pulls/907) * pitch envelope by @felixroos in [https://codeberg.org/uzu/strudel/pulls/913](https://codeberg.org/uzu/strudel/pulls/913) Slider Controls[](https://workspace/uzu/strudel/website/src/content/blog/release-1.0.0-geburtstagskuchen/#slider-controls) --------------------------------------------------------------------------------------------------------------------------- The new `slider` function inlines a draggable slider element into the code, bridging the gap between code and GUI. * widgets by @felixroos in [https://codeberg.org/uzu/strudel/pulls/714](https://codeberg.org/uzu/strudel/pulls/714) * Slider afterthoughts by @felixroos in [https://codeberg.org/uzu/strudel/pulls/723](https://codeberg.org/uzu/strudel/pulls/723) * add xfade by @felixroos in [https://codeberg.org/uzu/strudel/pulls/780](https://codeberg.org/uzu/strudel/pulls/780) Improved MIDI integration[](https://workspace/uzu/strudel/website/src/content/blog/release-1.0.0-geburtstagskuchen/#improved-midi-integration) ----------------------------------------------------------------------------------------------------------------------------------------------- Pattern params [can now be controlled with cc messages](https://www.youtube.com/watch?v=e2-Sv_jjDQk) + you can now send a MIDI clock to sync your DAW with strudel. * Midi in by @felixroos in [https://codeberg.org/uzu/strudel/pulls/699](https://codeberg.org/uzu/strudel/pulls/699) * add midi clock support by @felixroos in [https://codeberg.org/uzu/strudel/pulls/710](https://codeberg.org/uzu/strudel/pulls/710) hydra[](https://workspace/uzu/strudel/website/src/content/blog/release-1.0.0-geburtstagskuchen/#hydra) ------------------------------------------------------------------------------------------------------- [hydra](https://hydra.ojack.xyz/) , the live coding video synth [can now be used directly inside the strudel REPL](https://strudel.cc/learn/hydra/) . * Hydra integration by @felixroos in [https://codeberg.org/uzu/strudel/pulls/759](https://codeberg.org/uzu/strudel/pulls/759) * add options param to initHydra by @kasparsj in [https://codeberg.org/uzu/strudel/pulls/808](https://codeberg.org/uzu/strudel/pulls/808) * Hydra fixes and improvements by @atfornes in [https://codeberg.org/uzu/strudel/pulls/818](https://codeberg.org/uzu/strudel/pulls/818) Vanilla REPL[](https://workspace/uzu/strudel/website/src/content/blog/release-1.0.0-geburtstagskuchen/#vanilla-repl) --------------------------------------------------------------------------------------------------------------------- The codemirror editor and the repl abstraction have been refactored from react to vanilla JS! This should give some performance improvements and less dependency / maintenance burden: * Vanilla repl 2 by @felixroos in [https://codeberg.org/uzu/strudel/pulls/863](https://codeberg.org/uzu/strudel/pulls/863) * Vanilla repl 3 by @felixroos in [https://codeberg.org/uzu/strudel/pulls/865](https://codeberg.org/uzu/strudel/pulls/865) * more work on vanilla repl: repl web component + package + MicroRepl by @felixroos in [https://codeberg.org/uzu/strudel/pulls/866](https://codeberg.org/uzu/strudel/pulls/866) * main repl vanillification by @felixroos in [https://codeberg.org/uzu/strudel/pulls/873](https://codeberg.org/uzu/strudel/pulls/873) * final vanillification by @felixroos in [https://codeberg.org/uzu/strudel/pulls/876](https://codeberg.org/uzu/strudel/pulls/876) Doc Changes[](https://workspace/uzu/strudel/website/src/content/blog/release-1.0.0-geburtstagskuchen/#doc-changes) ------------------------------------------------------------------------------------------------------------------- Plenty of things have been added to the docs, including a [showcase of what people have been done with strudel](https://strudel.cc/intro/showcase/) and the new [Community Bakery](https://strudel.cc/bakery/) ! show PRs * Showcase by @felixroos in [https://codeberg.org/uzu/strudel/pulls/885](https://codeberg.org/uzu/strudel/pulls/885) * Recipes by @felixroos in [https://codeberg.org/uzu/strudel/pulls/742](https://codeberg.org/uzu/strudel/pulls/742) * Document striate function by @ilesinge in [https://codeberg.org/uzu/strudel/pulls/766](https://codeberg.org/uzu/strudel/pulls/766) * Document adsr function by @ilesinge in [https://codeberg.org/uzu/strudel/pulls/767](https://codeberg.org/uzu/strudel/pulls/767) * Add function params in reference tab by @ilesinge in [https://codeberg.org/uzu/strudel/pulls/785](https://codeberg.org/uzu/strudel/pulls/785) * Update first-sounds.mdx by @bwagner in [https://codeberg.org/uzu/strudel/pulls/794](https://codeberg.org/uzu/strudel/pulls/794) * Update recap.mdx by @bwagner in [https://codeberg.org/uzu/strudel/pulls/797](https://codeberg.org/uzu/strudel/pulls/797) * Update pattern-effects.mdx by @bwagner in [https://codeberg.org/uzu/strudel/pulls/796](https://codeberg.org/uzu/strudel/pulls/796) * Update first-effects.mdx by @bwagner in [https://codeberg.org/uzu/strudel/pulls/795](https://codeberg.org/uzu/strudel/pulls/795) * Document pianoroll by @ilesinge in [https://codeberg.org/uzu/strudel/pulls/784](https://codeberg.org/uzu/strudel/pulls/784) * Add doc for euclidLegatoRot, wordfall and slider by @ilesinge in [https://codeberg.org/uzu/strudel/pulls/801](https://codeberg.org/uzu/strudel/pulls/801) * Improve documentation for synonym functions by @ilesinge in [https://codeberg.org/uzu/strudel/pulls/800](https://codeberg.org/uzu/strudel/pulls/800) * Add and style algolia search by @ilesinge in [https://codeberg.org/uzu/strudel/pulls/827](https://codeberg.org/uzu/strudel/pulls/827) * Fix a typo by @drewgbarnes in [https://codeberg.org/uzu/strudel/pulls/830](https://codeberg.org/uzu/strudel/pulls/830) * add mastodon link by @felixroos in [https://codeberg.org/uzu/strudel/pulls/884](https://codeberg.org/uzu/strudel/pulls/884) * adds a blog by @felixroos in [https://codeberg.org/uzu/strudel/pulls/911](https://codeberg.org/uzu/strudel/pulls/911) * community bakery by @felixroos in [https://codeberg.org/uzu/strudel/pulls/923](https://codeberg.org/uzu/strudel/pulls/923) * Blog improvements by @felixroos in [https://codeberg.org/uzu/strudel/pulls/919](https://codeberg.org/uzu/strudel/pulls/919) * 2 years blog post by @felixroos in [https://codeberg.org/uzu/strudel/pulls/929](https://codeberg.org/uzu/strudel/pulls/929) Other Features[](https://workspace/uzu/strudel/website/src/content/blog/release-1.0.0-geburtstagskuchen/#other-features) ------------------------------------------------------------------------------------------------------------------------- There is a lot more * mini notation: international alphabets support by @ilesinge in [https://codeberg.org/uzu/strudel/pulls/751](https://codeberg.org/uzu/strudel/pulls/751) * Add shabda shortcut by @ilesinge in [https://codeberg.org/uzu/strudel/pulls/740](https://codeberg.org/uzu/strudel/pulls/740) * add play function by @felixroos in [https://codeberg.org/uzu/strudel/pulls/758](https://codeberg.org/uzu/strudel/pulls/758) (superseded by next) * tidal style d1 
 d9 functions + more by @felixroos in [https://codeberg.org/uzu/strudel/pulls/805](https://codeberg.org/uzu/strudel/pulls/805) * add vscode bindings by @Dsm0 in [https://codeberg.org/uzu/strudel/pulls/773](https://codeberg.org/uzu/strudel/pulls/773) * Implement optional hover tooltip with function documentation by @ilesinge in [https://codeberg.org/uzu/strudel/pulls/783](https://codeberg.org/uzu/strudel/pulls/783) * samples loading shortcuts: by @felixroos in [https://codeberg.org/uzu/strudel/pulls/788](https://codeberg.org/uzu/strudel/pulls/788) * add option to disable active line highlighting in Code Settings by @kasparsj in [https://codeberg.org/uzu/strudel/pulls/804](https://codeberg.org/uzu/strudel/pulls/804) * Color hsl by @felixroos in [https://codeberg.org/uzu/strudel/pulls/815](https://codeberg.org/uzu/strudel/pulls/815) * Patterns tab + Refactor Panel by @felixroos in [https://codeberg.org/uzu/strudel/pulls/769](https://codeberg.org/uzu/strudel/pulls/769) * patterns tab: import patterns + style by @felixroos in [https://codeberg.org/uzu/strudel/pulls/852](https://codeberg.org/uzu/strudel/pulls/852) * Export patterns + ui tweaks by @felixroos in [https://codeberg.org/uzu/strudel/pulls/855](https://codeberg.org/uzu/strudel/pulls/855) * Pattern organization by @felixroos in [https://codeberg.org/uzu/strudel/pulls/858](https://codeberg.org/uzu/strudel/pulls/858) * Sound Import from local file system by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/839](https://codeberg.org/uzu/strudel/pulls/839) * bugfix: suspend and close existing audio context when changing interface by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/882](https://codeberg.org/uzu/strudel/pulls/882) * add root mode for voicings by @felixroos in [https://codeberg.org/uzu/strudel/pulls/887](https://codeberg.org/uzu/strudel/pulls/887) * scales can now be anchored by @felixroos in [https://codeberg.org/uzu/strudel/pulls/888](https://codeberg.org/uzu/strudel/pulls/888) * add dough function for raw dsp by @felixroos in [https://codeberg.org/uzu/strudel/pulls/707](https://codeberg.org/uzu/strudel/pulls/707) (experimental) * support mininotation ’..’ range operator, fixes #715 by @yaxu in [https://codeberg.org/uzu/strudel/pulls/716](https://codeberg.org/uzu/strudel/pulls/716) * Add pick and squeeze functions by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/771](https://codeberg.org/uzu/strudel/pulls/771) * support , in < > by @felixroos in [https://codeberg.org/uzu/strudel/pulls/886](https://codeberg.org/uzu/strudel/pulls/886) * public sharing by @felixroos in [https://codeberg.org/uzu/strudel/pulls/910](https://codeberg.org/uzu/strudel/pulls/910) * pick, pickmod, inhabit, inhabitmod by @yaxu in [https://codeberg.org/uzu/strudel/pulls/921](https://codeberg.org/uzu/strudel/pulls/921) * Mini-notation additions towards tidal compatibility by @yaxu in [https://codeberg.org/uzu/strudel/pulls/926](https://codeberg.org/uzu/strudel/pulls/926) * add pickF and pickmodF by @geikha in [https://codeberg.org/uzu/strudel/pulls/924](https://codeberg.org/uzu/strudel/pulls/924) * Make splice cps-aware by @yaxu in [https://codeberg.org/uzu/strudel/pulls/932](https://codeberg.org/uzu/strudel/pulls/932) * Refactor cps functions by @felixroos in [https://codeberg.org/uzu/strudel/pulls/933](https://codeberg.org/uzu/strudel/pulls/933) * Add useful pattern selection behavior for performing. by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/897](https://codeberg.org/uzu/strudel/pulls/897) Other Fixes[](https://workspace/uzu/strudel/website/src/content/blog/release-1.0.0-geburtstagskuchen/#other-fixes) ------------------------------------------------------------------------------------------------------------------- show * fix: finally repair envelopes by @felixroos in [https://codeberg.org/uzu/strudel/pulls/861](https://codeberg.org/uzu/strudel/pulls/861) * fix: reverb regenerate loophole by @felixroos in [https://codeberg.org/uzu/strudel/pulls/726](https://codeberg.org/uzu/strudel/pulls/726) * fix: reverb roomsize not required by @felixroos in [https://codeberg.org/uzu/strudel/pulls/731](https://codeberg.org/uzu/strudel/pulls/731) * fix: reverb sampleRate by @felixroos in [https://codeberg.org/uzu/strudel/pulls/732](https://codeberg.org/uzu/strudel/pulls/732) * consume n with scale by @felixroos in [https://codeberg.org/uzu/strudel/pulls/727](https://codeberg.org/uzu/strudel/pulls/727) * fix: hashes in urls by @felixroos in [https://codeberg.org/uzu/strudel/pulls/728](https://codeberg.org/uzu/strudel/pulls/728) * \[Bug Fix\] chooseWith: prevent pattern from stopping audio when selection is >= 1 or < 0 by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/741](https://codeberg.org/uzu/strudel/pulls/741) * Fix addivite synthesis phases by @felixroos in [https://codeberg.org/uzu/strudel/pulls/762](https://codeberg.org/uzu/strudel/pulls/762) * fix: scale offset by @felixroos in [https://codeberg.org/uzu/strudel/pulls/764](https://codeberg.org/uzu/strudel/pulls/764) * fix zen mode logo overlap by @felixroos in [https://codeberg.org/uzu/strudel/pulls/760](https://codeberg.org/uzu/strudel/pulls/760) * fix: share copy to clipboard + alert by @felixroos in [https://codeberg.org/uzu/strudel/pulls/774](https://codeberg.org/uzu/strudel/pulls/774) * fix: style issues by @felixroos in [https://codeberg.org/uzu/strudel/pulls/781](https://codeberg.org/uzu/strudel/pulls/781) * Fix scope pos + document by @felixroos in [https://codeberg.org/uzu/strudel/pulls/786](https://codeberg.org/uzu/strudel/pulls/786) * don’t use anchor links for reference by @felixroos in [https://codeberg.org/uzu/strudel/pulls/791](https://codeberg.org/uzu/strudel/pulls/791) * remove unwanted cm6 outline for strudelTheme by @kasparsj in [https://codeberg.org/uzu/strudel/pulls/802](https://codeberg.org/uzu/strudel/pulls/802) * FIXES: palindrome abc -> abccba by @bwagner in [https://codeberg.org/uzu/strudel/pulls/831](https://codeberg.org/uzu/strudel/pulls/831) * Bug Fix #119: Clock drift by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/874](https://codeberg.org/uzu/strudel/pulls/874) * bugfix: sound select indexes out of bounds by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/871](https://codeberg.org/uzu/strudel/pulls/871) * Error tolerance by @felixroos in [https://codeberg.org/uzu/strudel/pulls/880](https://codeberg.org/uzu/strudel/pulls/880) * fix: make sure n is never undefined before nanFallback by @felixroos in [https://codeberg.org/uzu/strudel/pulls/881](https://codeberg.org/uzu/strudel/pulls/881) * fix: invisible selection on vim + emacs mode by @felixroos in [https://codeberg.org/uzu/strudel/pulls/889](https://codeberg.org/uzu/strudel/pulls/889) * fix: autocomplete / tooltip code example bug by @felixroos in [https://codeberg.org/uzu/strudel/pulls/898](https://codeberg.org/uzu/strudel/pulls/898) * Fix examples page, piano() and a few workshop imgs by @shiyouganai in [https://codeberg.org/uzu/strudel/pulls/848](https://codeberg.org/uzu/strudel/pulls/848) * fix: trailing slash confusion by @felixroos in [https://codeberg.org/uzu/strudel/pulls/743](https://codeberg.org/uzu/strudel/pulls/743) * fix: try different trailing slash behavior by @felixroos in [https://codeberg.org/uzu/strudel/pulls/744](https://codeberg.org/uzu/strudel/pulls/744) * Fix krill build command in README by @ilesinge in [https://codeberg.org/uzu/strudel/pulls/748](https://codeberg.org/uzu/strudel/pulls/748) * Fix for #1. Enables named instruments for csoundm. by @gogins in [https://codeberg.org/uzu/strudel/pulls/662](https://codeberg.org/uzu/strudel/pulls/662) * fix: missing hash for links starting with / by @felixroos in [https://codeberg.org/uzu/strudel/pulls/845](https://codeberg.org/uzu/strudel/pulls/845) * fix: swatch png src by @felixroos in [https://codeberg.org/uzu/strudel/pulls/846](https://codeberg.org/uzu/strudel/pulls/846) * Fix edge case with rehype-urls and trailing slashes in image file paths by @shiyouganai in [https://codeberg.org/uzu/strudel/pulls/849](https://codeberg.org/uzu/strudel/pulls/849) * fix: multiple repls by @felixroos in [https://codeberg.org/uzu/strudel/pulls/813](https://codeberg.org/uzu/strudel/pulls/813) * Fix chunk, add fastChunk and repeatCycles by @yaxu in [https://codeberg.org/uzu/strudel/pulls/712](https://codeberg.org/uzu/strudel/pulls/712) * Update tauri.yml workflow file by @vasilymilovidov in [https://codeberg.org/uzu/strudel/pulls/705](https://codeberg.org/uzu/strudel/pulls/705) * vite-vanilla-repl readme fix by @felixroos in [https://codeberg.org/uzu/strudel/pulls/737](https://codeberg.org/uzu/strudel/pulls/737) * completely revert config mess by @felixroos in [https://codeberg.org/uzu/strudel/pulls/745](https://codeberg.org/uzu/strudel/pulls/745) * hopefully fix trailing slashes bug by @felixroos in [https://codeberg.org/uzu/strudel/pulls/753](https://codeberg.org/uzu/strudel/pulls/753) * Update vite pwa by @felixroos in [https://codeberg.org/uzu/strudel/pulls/772](https://codeberg.org/uzu/strudel/pulls/772) * Update to Astro 3 by @felixroos in [https://codeberg.org/uzu/strudel/pulls/775](https://codeberg.org/uzu/strudel/pulls/775) * support multiple named serial connections, change default baudrate by @yaxu in [https://codeberg.org/uzu/strudel/pulls/551](https://codeberg.org/uzu/strudel/pulls/551) * CHANGES: github action checkout v2 -> v4 by @bwagner in [https://codeberg.org/uzu/strudel/pulls/837](https://codeberg.org/uzu/strudel/pulls/837) * CHANGES: pin pnpm to version 8.3.1 by @bwagner in [https://codeberg.org/uzu/strudel/pulls/834](https://codeberg.org/uzu/strudel/pulls/834) * CHANGES: github action pnpm version from 7 to 8.3.1 by @bwagner in [https://codeberg.org/uzu/strudel/pulls/835](https://codeberg.org/uzu/strudel/pulls/835) * ADDS: JetBrains IDE files and directories to .gitignore by @bwagner in [https://codeberg.org/uzu/strudel/pulls/840](https://codeberg.org/uzu/strudel/pulls/840) * Prevent 404 on Algolia crawls by @ilesinge in [https://codeberg.org/uzu/strudel/pulls/838](https://codeberg.org/uzu/strudel/pulls/838) * Add in fixes from my fork to slashocalypse branch by @shiyouganai in [https://codeberg.org/uzu/strudel/pulls/843](https://codeberg.org/uzu/strudel/pulls/843) * improve slashing + base href behavior by @felixroos in [https://codeberg.org/uzu/strudel/pulls/842](https://codeberg.org/uzu/strudel/pulls/842) * CHANGES: pnpm 8.1.3 to 8.11.0 by @bwagner in [https://codeberg.org/uzu/strudel/pulls/850](https://codeberg.org/uzu/strudel/pulls/850) * add missing trailing slashes by @felixroos in [https://codeberg.org/uzu/strudel/pulls/860](https://codeberg.org/uzu/strudel/pulls/860) * move all examples to separate examples folder by @felixroos in [https://codeberg.org/uzu/strudel/pulls/878](https://codeberg.org/uzu/strudel/pulls/878) * Dependency update by @felixroos in [https://codeberg.org/uzu/strudel/pulls/879](https://codeberg.org/uzu/strudel/pulls/879) * Update Vite version so hot reload works properly with newest pnpm version by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/892](https://codeberg.org/uzu/strudel/pulls/892) * prevent vite from complaining about additional exports in jsx files by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/891](https://codeberg.org/uzu/strudel/pulls/891) * fix some build warnings by @felixroos in [https://codeberg.org/uzu/strudel/pulls/902](https://codeberg.org/uzu/strudel/pulls/902) * Remove hideHeader for better mobile UI and consistency by @rjulian in [https://codeberg.org/uzu/strudel/pulls/894](https://codeberg.org/uzu/strudel/pulls/894) * Fix: swatch/\[name\].png.js static path by @oscarbyrne in [https://codeberg.org/uzu/strudel/pulls/916](https://codeberg.org/uzu/strudel/pulls/916) * rename @strudel.cycles/\_ packages to @strudel/\_ by @felixroos in [https://codeberg.org/uzu/strudel/pulls/917](https://codeberg.org/uzu/strudel/pulls/917) * `pick` now accepts lookup tables, with alternate cycle squeezing behaviour as new `inhabit` function by @yaxu in [https://codeberg.org/uzu/strudel/pulls/918](https://codeberg.org/uzu/strudel/pulls/918) * Revert “`pick` now accepts lookup tables, with alternate cycle squeezing behaviour as new `inhabit` function” by @yaxu in [https://codeberg.org/uzu/strudel/pulls/920](https://codeberg.org/uzu/strudel/pulls/920) * Fix pattern tab not showing patterns without created date by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/934](https://codeberg.org/uzu/strudel/pulls/934) New Contributors[](https://workspace/uzu/strudel/website/src/content/blog/release-1.0.0-geburtstagskuchen/#new-contributors) ----------------------------------------------------------------------------------------------------------------------------- * @ilesinge made their first contribution in [https://codeberg.org/uzu/strudel/pulls/748](https://codeberg.org/uzu/strudel/pulls/748) * @Dsm0 made their first contribution in [https://codeberg.org/uzu/strudel/pulls/773](https://codeberg.org/uzu/strudel/pulls/773) * @kasparsj made their first contribution in [https://codeberg.org/uzu/strudel/pulls/802](https://codeberg.org/uzu/strudel/pulls/802) * @atfornes made their first contribution in [https://codeberg.org/uzu/strudel/pulls/818](https://codeberg.org/uzu/strudel/pulls/818) * @drewgbarnes made their first contribution in [https://codeberg.org/uzu/strudel/pulls/830](https://codeberg.org/uzu/strudel/pulls/830) * @shiyouganai made their first contribution in [https://codeberg.org/uzu/strudel/pulls/843](https://codeberg.org/uzu/strudel/pulls/843) * @rjulian made their first contribution in [https://codeberg.org/uzu/strudel/pulls/894](https://codeberg.org/uzu/strudel/pulls/894) * @fnordomat made their first contribution in [https://codeberg.org/uzu/strudel/pulls/907](https://codeberg.org/uzu/strudel/pulls/907) * @oscarbyrne made their first contribution in [https://codeberg.org/uzu/strudel/pulls/916](https://codeberg.org/uzu/strudel/pulls/916) * @geikha made their first contribution in [https://codeberg.org/uzu/strudel/pulls/924](https://codeberg.org/uzu/strudel/pulls/924) **Full Changelog**: [https://codeberg.org/uzu/strudel/compare/v0.9.0
v1.0.0](https://codeberg.org/uzu/strudel/compare/v0.9.0...v1.0.0) 2 Years of Strudel ================== January 2024 by froos Strudel is now 2 years old! This was the first commit to the strudel repo: commit 38b5a0d5cdf28685b2b5e18d460772b70246207b Author: Alex McLean Date: Sat Jan 22 20:24:36 2022 +0000 Initial commit I have already written these 2 posts about the first year of strudel: * [Introducing Strudel](https://loophole-letters.vercel.app/strudel) * [1 Year of Strudel](https://loophole-letters.vercel.app/strudel1year) To continue that tradition, let me recap what happened around Strudel in 2023, at least what I have noticed: ICLC 2023[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#iclc-2023) -------------------------------------------------------------------------------------- I presented strudel at the [International Conference on Live Coding (ICLC)](https://iclc.toplap.org/2023/) , which took place in April in Utrecht. Here is the (pretty technical) video of my presentation: For the conference, I wrote a paper with Alex McLean, called [Strudel: Live Coding Patterns on the Web](https://zenodo.org/records/7842142) . For me personally, this was my first major contact with the live coding scene and it was a very memorable experience! Workshops and Courses[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#workshops-and-courses) -------------------------------------------------------------------------------------------------------------- A lot of workshops have been using strudel in the last year, from many parts of the world: [Iceland](http://www.raflost.is/live-coding-for-beginners/) , [UK](https://gutlevel.co.uk/whatson/patternclub8) , [Japan](https://yoppa.org/kanazawa23) , [France](https://www.stereolux.org/agenda/workshop-de-decouverte-de-la-musique-algorithmique-avec-strudel-avec-camille-amet) and the [US](https://www.nycresistor.com/2023/09/25/making-music-with-code-9-30-2023/) (probably more I’ve missed). I’ve also given workshops in [Switzerland](https://basel.codes/2023/index.html) and [Germany](https://www.cityofmediaarts.de/de/events/strudel-live-coding-workshop/) , where we even had real strudel, thanks to [TOPLAP Karlsruhe](https://toplap-ka.de/) ! ![strudel workshop space](https://strudel.cc/img/workshop-space.png) Additionally, there is now a [Creative Coding](https://www.chalmers.se/en/collaborate-with-us/activities-for-schools/creative-coding/) course at Chalmers in Sweden, consisting of “a 10-week series of workshops \[
\] for younger girls to have fun and experiment with code and music”. I was very delighted to see this video of the girls having fun with Strudel: The course was also encompassed by a study on female empowerement through Creative Coding, read more in [Jasmina Maric’s paper “Singing Code”](https://www.academia.edu/106106710/Singing_Code) . Another bigger installment was the [(Algo|Afro) Futures programme](https://algo-afro-futures.lurk.org/) led by Antonio Roberts, which is “a mentoring programme for early career Black artists who want to explore creative coding and live coding”. For the final concert they recorded this amazing live stream: I am so happy to see that the project I’ve co-created has had such an impact already, making live coding workshops more easy and accessible. In my opinion, the reasons why people seem to choose strudel for teaching are: * it does not require an installation, so it even runs on your phone / tablet or a chromebook * in-code and behind-code visualizations help learners to understand what the system is doing * the interactive documentation * the simple javascript syntax, which is very similar to [hydra](https://hydra.ojack.xyz/) * tidal’s mini notation for playful expression of rhythms (based on the [Bol Processor](https://bolprocessor.org/) ’s polymetric expressions) * its range of functions for transforming patterns for those getting deeper into exploring strange rhythms Performances[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#performances) -------------------------------------------------------------------------------------------- ![strudel performance](https://strudel.cc/img/strudel-live-coding-mars-college.jpg) People are starting to use Strudel for performances, many of which can be watched in the new [Showcase](https://strudel.cc/intro/showcase/) ! The above photo comes from the [Mars College Department of Future Music](https://mars.college/gallery/live_coding/) New Features[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#new-features) -------------------------------------------------------------------------------------------- Let’s talk about some of the major features that landed since last year. ### Audio Engine Improved[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#audio-engine-improved) The audio engine has gotten a lot of attention! Once integrated into Strudel itself, the engine has been pulled out and named [superdough](https://www.npmjs.com/package/superdough) , the name being based on [SuperDirt](https://github.com/musikinformatik/SuperDirt) , which is the audio engine of Tidal. Superdough is now also used by [topos](https://topos.live/) , a creation of [RaphaĂ«l Forment](https://github.com/Bubobubobubobubo/) , who helped me implement a bunch of new features. Lately, [Jade Rowland](https://github.com/daslyfe) has also developed many synthesis features into superdough! Here is a non-exhaustive list of new features: * [filter envelopes](https://strudel.cc/learn/effects/#filter-envelope) + [24db filter option](https://strudel.cc/learn/effects/#ftype) * [pitch envelopes](https://strudel.cc/learn/effects/#pitch-envelope) * [fm synthesis](https://strudel.cc/learn/synths/#fm-synthesis) * [wavetable synthesis](https://strudel.cc/recipes/recipes/#wavetable-synthesis) * [phaser](https://strudel.cc/learn/effects/#phaser) * [vibrato](https://strudel.cc/learn/synths/#vibrato) * [noise](https://strudel.cc/learn/synths/#noise) * [an integration of the zzfx synth](https://strudel.cc/learn/synths/#zzfx) * [compressor](https://strudel.cc/learn/effects/#compressor) * multichannel audio / audio device selection Here’s an example that shows off filter envelopes: // "acidic tooth" @by eddyflux setcps(1) stack( note("\[/8\](<3 5>,8)") .clip(perlin.range(.15,1.5)) .release(.1) .s("sawtooth") .lpf(sine.range(400,800).slow(16)) .lpq(cosine.range(6,14).slow(3)) .lpenv(sine.mul(4).slow(4)) .lpd(.2).lpa(.02) .ftype('24db') .rarely(add(note(12))) .room(.2).shape(.3).postgain(.5) .superimpose(x=>x.add(note(12)).delay(.5).bpf(1000)) .gain("\[.2 1@3\]\*2") // fake sidechain , stack( s("bd\*2").mask("<0@4 1@16>"), s("hh\*8").gain(saw.mul(saw.fast(2))).clip(sine) .mask("<0@8 1@16>") ).bank('RolandTR909') ) ### Import Sounds[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#import-sounds) In the “sounds” tab, you can now press “import sounds” to select sounds from your local disk, allowing you to use your own samples without the need to upload them! Keep in mind that if you share a pattern that uses local samples that the samples will fail to load on other people’s machines.. ### Community Bakery / Patterns Tab[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#community-bakery--patterns-tab) You can now share your patterns publicly to the [Community Bakery](https://strudel.cc/bakery/) , via the “Share” button! The Community Patterns are also listed in the new “patterns”, where you can also save your own patterns locally! ### Quality of Life Improvements[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#quality-of-life-improvements) You can now change the theme and font of the editor: ![strudel themes](https://strudel.cc/img/strudel-themes.png) You can now enable autocompletion in the settings: ![autocomplete](https://strudel.cc/img/autocomplete.png) If you enable tooltips, you will get the same info by hovering the mouse over functions while holding the ctrl key! Another handy thing is that Strudel now has offline support! Even samples will work once you have played them once. [More info](https://strudel.cc/learn/pwa/) ### New Visualizations[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#new-visualizations) There is now a new `.spiral()` visualization that renders events in a spiral, elevating the cyclical nature of time (+ swirl also means Strudel in german). ![strudel alien ui](https://strudel.cc/img/strudel-alien-live-coding.png) Ah yes and you see one of the less readable fonts in this screenshot too :) The `.scope()` function renders you an oscilloscope: ![scope](https://strudel.cc/img/strudel-scope.png) The pianoroll also got some more options, including the ability to set labels: ![scope](https://strudel.cc/img/strudel-monkeys.png) ### Sliders[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#sliders) The `slider` is the first of a family of functions that render UI widgets inline: note("\*8") .s("sawtooth").lpenv(2).lpq(8) .lpf(slider(500, 100, 2000, 1)) Sliders are especially useful for gradual changes like fading things in and out by hand, which is not very practical with just code. The fact that the slider is inside the code and also changes the code itself, makes it feel connected to the rest of the system + doesn’t distract or obstruct from the code. Other widgets will come for sure. ### More Voicings[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#more-voicings) One of my personal goals that got me involved with live coding in the first place is to be able to generate good sounding chord voicings on the fly. Strudel now supports most chord symbols: chord("/2") .dict('ireal').voicing() .s("sawtooth") .lpf(400).lpa(.5).lpenv(4) .phaser(4).room(.5) So far, this is only [documented briefly](https://strudel.cc/learn/tonal/#voicing) , a more in-depth guide will follow! ### Multiplayer / Collaborative Coding[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#multiplayer--collaborative-coding) The collaborative live coding editor [flok](https://flok.cc/) had strudel support early on, thanks to [DamiĂĄn Silvani](https://github.com/munshkr) . Later, I’ve added [support for multiple strudel instances](https://github.com/munshkr/flok/pull/181) , which was merged to the official version [just a few days ago](https://social.toplap.org/@munshkr/111748783047032711) . This photo shows a flok jam I did with my workshop participants in basel: ![collaborative coding](https://strudel.cc/img/strudel-collaborative-coding.png) The cool thing about flok is that you can jam with people on the other side of the planet! Here is a recording of the first session we organized over the discord server: ### Better MIDI integration[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#better-midi-integration) The midi integration has gotten a few new features: * [clock out](https://codeberg.org/uzu/strudel/pulls/710) to sync your midi devices / DAW to the strudel clock (better doc coming soon) * [cc output](https://strudel.cc/learn/input-output/#ccn--ccv) to send cc values to your gear * [cc input](https://codeberg.org/uzu/strudel/pulls/699) to control strudel via MIDI (better doc coming soon) Here is a little demo of me fiddling with a midi controller, changing a piano pattern: ### Experimental Features[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#experimental-features) * You can now run [hydra inside strudel](https://strudel.cc/learn/hydra/) + you can even run strudel in [hydra](https://hydra.ojack.xyz/) , thanks to [Olivia Jack](https://ojack.xyz/) and [Ámbar Tenorio FornĂ©s](https://atenor.io/) ! Read more [here](https://alpaca.pubpub.org/pub/b7hwrjfk/release/2?readingCollection=1def0192) * There is now a [VSCode Plugin](https://marketplace.visualstudio.com/items?itemName=roipoussiere.tidal-strudel) thanks to [roipoussiere](https://github.com/roipoussiere) ! It allows you run patterns from a `.strudel` file inside VSCode. * Strudel can now be downloaded as a desktop app, thanks to [vasilymilovidov](https://github.com/vasilymilovidov) who has wrapped the REPL with [tauri](https://tauri.app/) . You can download it [on the releases page](https://codeberg.org/uzu/strudel/releases) (scroll down to Assets). The performance is not optimal on MacOS and Linux, which is why it is still considered experimental Stats[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#stats) ------------------------------------------------------------------------------ Let’s end the recap with some naked numbers: ### Releases[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#releases) * 0.0.2 Schwindlig * 0.1.0 StĂŒrmisch * 0.0.3 Maelstrom - state of last strudel post * 0.0.4 Gischt * 0.3.0 Donauwelle * 0.4.0 Brandung * 0.5.0 Wirbel * 0.6.0 Zimtschnecke - state after 1 year * 0.7.0 Zuckerguss * 0.8.0 Himbeermuffin * 0.9.0 Bananenbrot * 0.10.0 Kuchenboden * 0.11.0 Kuchengabel * 1.0.0 Geburtstagskuchen - now! ### Repo Stats[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#repo-stats) * 3399 commits (+1521) * 427 stars (+243) * 90 forks (+45) * 163 open issues (+110), 186 closed (+100) * 20 open PRs (+18), 515 closed (+292) * 32 contributors (+17) * 12 releases (+4) Thank you 💗[](https://workspace/uzu/strudel/website/src/content/blog/year-2/#thank-you-) ------------------------------------------------------------------------------------------ A big thanks to everyone contributing to this project in one way or another, be it by writing code, fixing typos, asking questions or making sounds! I am very grateful for how all of this came together and I’m looking forward to year 3! If you have some spare cash to support the tidal project, we’d be happy if you can [insert some coins here](https://opencollective.com/tidalcycles) . Release Notes v0.9.0 ==================== September 2023 by froos These are the release notes for Strudel 0.9.0 aka “Bananenbrot”! The last release was over 11 weeks ago, so a lot of things have happened! Let me write up some of the highlights: Improved Synth Engine[](https://workspace/uzu/strudel/website/src/content/blog/release-0.9.0-bananenbrot/#improved-synth-engine) --------------------------------------------------------------------------------------------------------------------------------- The synth engine has gotten a lot of love + a name: [superdough](https://www.npmjs.com/package/superdough) encapsulates the web audio based synth / sampler / fx engine into a reusable package, which is already used by [Topos](https://topos.raphaelforment.fr/) . Main new features include: * [filter envelopes](https://strudel.tidalcycles.org/learn/effects#filter-envelope) * [FM Synthesis](https://strudel.tidalcycles.org/learn/synths#fm-synthesis) * [looping samples](https://strudel.tidalcycles.org/learn/samples#loop) , allowing [wavetable synthesis](https://strudel.tidalcycles.org/?I9myTNQoKKaP) * [vibrato](https://strudel.tidalcycles.org/learn/synths#vibrato) * an integration of [ZZFX](https://strudel.tidalcycles.org/learn/synths#zzfx) Related PRs: * superdough: encapsulates web audio output by @felixroos in [https://codeberg.org/uzu/strudel/pulls/664](https://codeberg.org/uzu/strudel/pulls/664) * basic fm by @felixroos in [https://codeberg.org/uzu/strudel/pulls/669](https://codeberg.org/uzu/strudel/pulls/669) * Wave Selection and Global Envelope on the FM Synth Modulator by @Bubobubobubobubo in [https://codeberg.org/uzu/strudel/pulls/683](https://codeberg.org/uzu/strudel/pulls/683) * control osc partial count with n by @felixroos in [https://codeberg.org/uzu/strudel/pulls/674](https://codeberg.org/uzu/strudel/pulls/674) * ZZFX Synth support by @Bubobubobubobubo in [https://codeberg.org/uzu/strudel/pulls/684](https://codeberg.org/uzu/strudel/pulls/684) * Adding filter envelopes and filter order selection by @Bubobubobubobubo in [https://codeberg.org/uzu/strudel/pulls/692](https://codeberg.org/uzu/strudel/pulls/692) * Adding loop points and thus wavetable synthesis by @Bubobubobubobubo in [https://codeberg.org/uzu/strudel/pulls/698](https://codeberg.org/uzu/strudel/pulls/698) * Adding vibrato to base oscillators by @Bubobubobubobubo in [https://codeberg.org/uzu/strudel/pulls/693](https://codeberg.org/uzu/strudel/pulls/693) Desktop App Improvements[](https://workspace/uzu/strudel/website/src/content/blog/release-0.9.0-bananenbrot/#desktop-app-improvements) --------------------------------------------------------------------------------------------------------------------------------------- Thanks to @daslyfe and @vasilymilovidov , the desktop app now has its own rust based MIDI and OSC integrations, which do not depend on browser APIs! You can see superdough, superdirt via OSC + hardware synths via MIDI all together playing in harmony in this [awesome video](https://www.youtube.com/watch?v=lxQgBeLQBgk) . These are the related PRs: * Create Midi Integration for Tauri Desktop app by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/685](https://codeberg.org/uzu/strudel/pulls/685) * add sleep timer + improve message iterating by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/688](https://codeberg.org/uzu/strudel/pulls/688) * fix MIDI CC messages by @vasilymilovidov in [https://codeberg.org/uzu/strudel/pulls/690](https://codeberg.org/uzu/strudel/pulls/690) * Direct OSC Support in Tauri by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/694](https://codeberg.org/uzu/strudel/pulls/694) * Add logging from tauri by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/697](https://codeberg.org/uzu/strudel/pulls/697) * fix osc bundle timestamp glitches caused by drifting clock by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/666](https://codeberg.org/uzu/strudel/pulls/666) * Midi time fixes by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/668](https://codeberg.org/uzu/strudel/pulls/668) * \[Bug Fix\] Account for numeral notation when converting to midi by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/656](https://codeberg.org/uzu/strudel/pulls/656) * \[Bug Fix\] Midi: Don’t treat note 0 as false by @daslyfe in [https://codeberg.org/uzu/strudel/pulls/657](https://codeberg.org/uzu/strudel/pulls/657) Visuals[](https://workspace/uzu/strudel/website/src/content/blog/release-0.9.0-bananenbrot/#visuals) ----------------------------------------------------------------------------------------------------- * 2 new FFT based vizualisations have now landed: [scope and fscope](https://codeberg.org/uzu/strudel/pulls/677) (featured in the video at the top). * pianoroll has new options, see [PR](https://codeberg.org/uzu/strudel/pulls/679) Related PRs: * Scope by @felixroos in [https://codeberg.org/uzu/strudel/pulls/677](https://codeberg.org/uzu/strudel/pulls/677) ([demo](https://strudel.tidalcycles.org/?hXVQF-KxMI8p) ) * Pianoroll improvements by @felixroos in [https://codeberg.org/uzu/strudel/pulls/679](https://codeberg.org/uzu/strudel/pulls/679) ([demo](https://strudel.tidalcycles.org/?aPMKqXGVMgSM) ) Voicings[](https://workspace/uzu/strudel/website/src/content/blog/release-0.9.0-bananenbrot/#voicings) ------------------------------------------------------------------------------------------------------- There is now a new way to play chord voicings + a huge selection of chord voicings available. Find out more in these PRs: * stateless voicings + tonleiter lib by @felixroos in [https://codeberg.org/uzu/strudel/pulls/647](https://codeberg.org/uzu/strudel/pulls/647) ([demo](https://strudel.tidalcycles.org/?FoILM0Hs9y9f) ) * ireal voicings by @felixroos in [https://codeberg.org/uzu/strudel/pulls/653](https://codeberg.org/uzu/strudel/pulls/653) ([demo](https://strudel.tidalcycles.org/?bv_TjY9hOC28) ) Adaptive Highlighting[](https://workspace/uzu/strudel/website/src/content/blog/release-0.9.0-bananenbrot/#adaptive-highlighting) --------------------------------------------------------------------------------------------------------------------------------- Thanks to @mindofmatthew , the highlighting will adapt to edits instantly! Related PRs: * More work on highlight IDs by @mindofmatthew in [https://codeberg.org/uzu/strudel/pulls/636](https://codeberg.org/uzu/strudel/pulls/636) * Adaptive Highlighting by @felixroos in [https://codeberg.org/uzu/strudel/pulls/634](https://codeberg.org/uzu/strudel/pulls/634) UI Changes[](https://workspace/uzu/strudel/website/src/content/blog/release-0.9.0-bananenbrot/#ui-changes) ----------------------------------------------------------------------------------------------------------- * teletext theme + fonts by @felixroos in [https://codeberg.org/uzu/strudel/pulls/681](https://codeberg.org/uzu/strudel/pulls/681) (featured in video at the top) * togglable panel position by @felixroos in [https://codeberg.org/uzu/strudel/pulls/667](https://codeberg.org/uzu/strudel/pulls/667) Other New Features[](https://workspace/uzu/strudel/website/src/content/blog/release-0.9.0-bananenbrot/#other-new-features) --------------------------------------------------------------------------------------------------------------------------- * slice: list mode by @felixroos in [https://codeberg.org/uzu/strudel/pulls/645](https://codeberg.org/uzu/strudel/pulls/645) ([demo](https://strudel.tidalcycles.org/?bAYIqz5NLjRr) ) * add emoji support by @felixroos in [https://codeberg.org/uzu/strudel/pulls/680](https://codeberg.org/uzu/strudel/pulls/680) ([demo](https://strudel.tidalcycles.org/?a6FgLz475gN9) ) Articles[](https://workspace/uzu/strudel/website/src/content/blog/release-0.9.0-bananenbrot/#articles) ------------------------------------------------------------------------------------------------------- * Understand pitch by @felixroos in [https://codeberg.org/uzu/strudel/pulls/652](https://codeberg.org/uzu/strudel/pulls/652) Other Fixes & Enhancements[](https://workspace/uzu/strudel/website/src/content/blog/release-0.9.0-bananenbrot/#other-fixes--enhancements) ------------------------------------------------------------------------------------------------------------------------------------------ * fix: out of range error by @felixroos in [https://codeberg.org/uzu/strudel/pulls/630](https://codeberg.org/uzu/strudel/pulls/630) * fix: update canvas size on window resize by @felixroos in [https://codeberg.org/uzu/strudel/pulls/631](https://codeberg.org/uzu/strudel/pulls/631) * FIXES: TODO in rotateChroma by @bwagner in [https://codeberg.org/uzu/strudel/pulls/650](https://codeberg.org/uzu/strudel/pulls/650) * snapshot tests: sort haps by part by @felixroos in [https://codeberg.org/uzu/strudel/pulls/637](https://codeberg.org/uzu/strudel/pulls/637) * Delete old packages by @felixroos in [https://codeberg.org/uzu/strudel/pulls/639](https://codeberg.org/uzu/strudel/pulls/639) * update vitest by @felixroos in [https://codeberg.org/uzu/strudel/pulls/651](https://codeberg.org/uzu/strudel/pulls/651) * fix: welcome message for latestCode by @felixroos in [https://codeberg.org/uzu/strudel/pulls/659](https://codeberg.org/uzu/strudel/pulls/659) * fix: always run previous trigger by @felixroos in [https://codeberg.org/uzu/strudel/pulls/660](https://codeberg.org/uzu/strudel/pulls/660) New Contributors[](https://workspace/uzu/strudel/website/src/content/blog/release-0.9.0-bananenbrot/#new-contributors) ----------------------------------------------------------------------------------------------------------------------- * @daslyfe made their first contribution in [https://codeberg.org/uzu/strudel/pulls/656](https://codeberg.org/uzu/strudel/pulls/656) * @Bubobubobubobubo made their first contribution in [https://codeberg.org/uzu/strudel/pulls/683](https://codeberg.org/uzu/strudel/pulls/683) **Full Changelog**: [https://codeberg.org/uzu/strudel/compare/v0.8.0
v0.9.0](https://codeberg.org/uzu/strudel/compare/v0.8.0...v0.9.0) A big thanks to all the contributors! Release Notes v0.8.0 ==================== June 2023 by froos These are the release notes for Strudel 0.8.0 aka “Himbeermuffin”! [Go to Tidal Club Forum for this Release](https://club.tidalcycles.org/t/strudel-0-8-0-released/4769) Let me write up some of the highlights: Desktop App[](https://workspace/uzu/strudel/website/src/content/blog/release-0.8.0-himbeermuffin/#desktop-app) --------------------------------------------------------------------------------------------------------------- Besides the REPL ([https://strudel.tidalcycles.org/](https://strudel.tidalcycles.org/) ), Strudel is now also distributed as a Desktop App via [https://tauri.app/](https://tauri.app/) ! Thanks to [vasilymilovidov](https://github.com/vasilymilovidov) ! * [Linux: Debian based](https://codeberg.org/uzu/strudel/releases/download/v0.8.0/strudel_0.1.0_amd64.deb) * [Linux: AppImage](https://codeberg.org/uzu/strudel/releases/download/v0.8.0/strudel_0.1.0_amd64.AppImage) * [MacOS](https://codeberg.org/uzu/strudel/releases/download/v0.8.0/Strudel_0.1.0_x64.dmg) * [Windows .exe](https://codeberg.org/uzu/strudel/releases/download/v0.8.0/Strudel_0.1.0_x64-setup.exe) * [Windows .msi](https://codeberg.org/uzu/strudel/releases/download/v0.8.0/Strudel_0.1.0_x64_en-US.msi) edit: the desktop app performance on linux is currently not that great.. the web REPL runs much smoother (using firefox or chromium) The desktop App has the same features as the webapp, with the additional ability to load samples from disk. It is currently not documented yet, but you can do something like samples('~/music/xxx'); s('my_sound'); You have to start with `~/music/`, followed by an arbitrary folder path that is expected to be present in the systems [audio directory](https://tauri.app/v1/api/js/path/#audiodir) . When you first run it, the app will create a strudel.json file in that directory to map out the available samples. I would be very happy to collect some feedback on how it works across different platforms & systems! Spiral Visualization[](https://workspace/uzu/strudel/website/src/content/blog/release-0.8.0-himbeermuffin/#spiral-visualization) --------------------------------------------------------------------------------------------------------------------------------- Also still undocumented, but you can now visualize patterns as a spiral via `.spiral()`: This is especially nice because strudel is not only the name of a dessert but also the german word for vortex! The spiral is very fitting to visualize cycles because you can align cycles vertically, while surfing along an infinite twisted timeline. More settings[](https://workspace/uzu/strudel/website/src/content/blog/release-0.8.0-himbeermuffin/#more-settings) ------------------------------------------------------------------------------------------------------------------- In the settings tab, you can now toggle: * line numbers * auto-complete * line wrapping Thanks to [roipoussiere](https://github.com/roipoussiere) ! More[](https://workspace/uzu/strudel/website/src/content/blog/release-0.8.0-himbeermuffin/#more) ------------------------------------------------------------------------------------------------- Scroll down to see the full list of Changes! A big thanks to all the contributors! Package Versions[](https://workspace/uzu/strudel/website/src/content/blog/release-0.8.0-himbeermuffin/#package-versions) ------------------------------------------------------------------------------------------------------------------------- * @strudel.cycles/core: 0.8.2 * @strudel.cycles/mini: 0.8.2 * @strudel.cycles/transpiler: 0.8.2 * @strudel.cycles/webaudio: 0.8.2 * @strudel.cycles/soundfonts: 0.8.2 * @strudel.cycles/react: 0.8.0 * @strudel.cycles/midi: 0.8.0 * @strudel.cycles/osc: 0.8.0 * @strudel.cycles/csound: 0.8.0 * @strudel.cycles/serial: 0.8.0 * @strudel.cycles/tonal: 0.8.2 * @strudel.cycles/xen: 0.8.0 * @strudel/codemirror: 0.8.4 * @strudel/web: 0.8.3 What’s Changed[](https://workspace/uzu/strudel/website/src/content/blog/release-0.8.0-himbeermuffin/#whats-changed) -------------------------------------------------------------------------------------------------------------------- * fix period key for dvorak + remove duplicated code by @felixroos in [https://codeberg.org/uzu/strudel/pulls/537](https://codeberg.org/uzu/strudel/pulls/537) * improve initial loading + wait before eval by @felixroos in [https://codeberg.org/uzu/strudel/pulls/538](https://codeberg.org/uzu/strudel/pulls/538) * do not reset cps before eval #517 by @felixroos in [https://codeberg.org/uzu/strudel/pulls/539](https://codeberg.org/uzu/strudel/pulls/539) * feat: add loader bar to animate loading state by @felixroos in [https://codeberg.org/uzu/strudel/pulls/542](https://codeberg.org/uzu/strudel/pulls/542) * add firacode font by @felixroos in [https://codeberg.org/uzu/strudel/pulls/544](https://codeberg.org/uzu/strudel/pulls/544) * fix: allow whitespace at the end of a mini pattern by @felixroos in [https://codeberg.org/uzu/strudel/pulls/547](https://codeberg.org/uzu/strudel/pulls/547) * fix: reset time on stop by @felixroos in [https://codeberg.org/uzu/strudel/pulls/548](https://codeberg.org/uzu/strudel/pulls/548) * fix: load soundfonts in prebake by @felixroos in [https://codeberg.org/uzu/strudel/pulls/550](https://codeberg.org/uzu/strudel/pulls/550) * fix: colorable highlighting by @felixroos in [https://codeberg.org/uzu/strudel/pulls/553](https://codeberg.org/uzu/strudel/pulls/553) * fix: make soundfonts import dynamic by @felixroos in [https://codeberg.org/uzu/strudel/pulls/556](https://codeberg.org/uzu/strudel/pulls/556) * add basic triads and guidetone voicings by @felixroos in [https://codeberg.org/uzu/strudel/pulls/557](https://codeberg.org/uzu/strudel/pulls/557) * Patchday by @felixroos in [https://codeberg.org/uzu/strudel/pulls/559](https://codeberg.org/uzu/strudel/pulls/559) * Vanilla JS Refactoring by @felixroos in [https://codeberg.org/uzu/strudel/pulls/563](https://codeberg.org/uzu/strudel/pulls/563) * repl: add option to display line numbers by @roipoussiere in [https://codeberg.org/uzu/strudel/pulls/582](https://codeberg.org/uzu/strudel/pulls/582) * learn/tonal: fix typo in “scaleTran\[s\]pose” by @srenatus in [https://codeberg.org/uzu/strudel/pulls/585](https://codeberg.org/uzu/strudel/pulls/585) * Music metadata by @roipoussiere in [https://codeberg.org/uzu/strudel/pulls/580](https://codeberg.org/uzu/strudel/pulls/580) * New Workshop by @felixroos in [https://codeberg.org/uzu/strudel/pulls/587](https://codeberg.org/uzu/strudel/pulls/587) * Fix option dot by @felixroos in [https://codeberg.org/uzu/strudel/pulls/596](https://codeberg.org/uzu/strudel/pulls/596) * fix: allow f for flat notes like tidal by @felixroos in [https://codeberg.org/uzu/strudel/pulls/593](https://codeberg.org/uzu/strudel/pulls/593) * fix: division by zero by @felixroos in [https://codeberg.org/uzu/strudel/pulls/591](https://codeberg.org/uzu/strudel/pulls/591) * Solmization added by @dariacotocu in [https://codeberg.org/uzu/strudel/pulls/570](https://codeberg.org/uzu/strudel/pulls/570) * improve cursor by @felixroos in [https://codeberg.org/uzu/strudel/pulls/597](https://codeberg.org/uzu/strudel/pulls/597) * enable auto-completion by @roipoussiere in [https://codeberg.org/uzu/strudel/pulls/588](https://codeberg.org/uzu/strudel/pulls/588) * add ratio function by @felixroos in [https://codeberg.org/uzu/strudel/pulls/602](https://codeberg.org/uzu/strudel/pulls/602) * editor: enable line wrapping by @roipoussiere in [https://codeberg.org/uzu/strudel/pulls/581](https://codeberg.org/uzu/strudel/pulls/581) * tonal fixes by @felixroos in [https://codeberg.org/uzu/strudel/pulls/607](https://codeberg.org/uzu/strudel/pulls/607) * fix: flatten scale lists by @felixroos in [https://codeberg.org/uzu/strudel/pulls/605](https://codeberg.org/uzu/strudel/pulls/605) * clip now works like legato in tidal by @felixroos in [https://codeberg.org/uzu/strudel/pulls/598](https://codeberg.org/uzu/strudel/pulls/598) * fix: doc links by @felixroos in [https://codeberg.org/uzu/strudel/pulls/612](https://codeberg.org/uzu/strudel/pulls/612) * tauri desktop app by @vasilymilovidov in [https://codeberg.org/uzu/strudel/pulls/613](https://codeberg.org/uzu/strudel/pulls/613) * add spiral viz by @felixroos in [https://codeberg.org/uzu/strudel/pulls/614](https://codeberg.org/uzu/strudel/pulls/614) * patterning ui settings by @felixroos in [https://codeberg.org/uzu/strudel/pulls/606](https://codeberg.org/uzu/strudel/pulls/606) * Fix typo on packages.mdx by @paikwiki in [https://codeberg.org/uzu/strudel/pulls/520](https://codeberg.org/uzu/strudel/pulls/520) * cps dependent functions by @felixroos in [https://codeberg.org/uzu/strudel/pulls/620](https://codeberg.org/uzu/strudel/pulls/620) * desktop: play samples from disk by @felixroos in [https://codeberg.org/uzu/strudel/pulls/621](https://codeberg.org/uzu/strudel/pulls/621) * fix: midi clock drift by @felixroos in [https://codeberg.org/uzu/strudel/pulls/627](https://codeberg.org/uzu/strudel/pulls/627) New Contributors[](https://workspace/uzu/strudel/website/src/content/blog/release-0.8.0-himbeermuffin/#new-contributors) ------------------------------------------------------------------------------------------------------------------------- * @roipoussiere made their first contribution in [https://codeberg.org/uzu/strudel/pulls/582](https://codeberg.org/uzu/strudel/pulls/582) * @srenatus made their first contribution in [https://codeberg.org/uzu/strudel/pulls/585](https://codeberg.org/uzu/strudel/pulls/585) * @dariacotocu made their first contribution in [https://codeberg.org/uzu/strudel/pulls/570](https://codeberg.org/uzu/strudel/pulls/570) * @vasilymilovidov made their first contribution in [https://codeberg.org/uzu/strudel/pulls/613](https://codeberg.org/uzu/strudel/pulls/613) * @paikwiki made their first contribution in [https://codeberg.org/uzu/strudel/pulls/520](https://codeberg.org/uzu/strudel/pulls/520) **Full Changelog**: [https://codeberg.org/uzu/strudel/compare/v0.7.0
v0.8.0](https://codeberg.org/uzu/strudel/compare/v0.7.0...v0.8.0) Release Notes v0.7.0 ==================== March 2023 by froos Package Versions[](https://workspace/uzu/strudel/website/src/content/blog/release-0.7.0-zuckerguss/#package-versions) ---------------------------------------------------------------------------------------------------------------------- * @strudel.cycles/core@0.7.2 * @strudel.cycles/transpiler@0.7.1 * @strudel.cycles/mini@0.7.2 * @strudel.cycles/tonal@0.7.1 * @strudel.cycles/xen@0.7.1 * @strudel.cycles/tone@0.7.1 * @strudel.cycles/webaudio@0.7.1 * @strudel.cycles/react@0.7.1 * @strudel.cycles/osc@0.7.1 * @strudel.cycles/serial@0.7.1 * @strudel.cycles/midi@0.7.1 * @strudel.cycles/csound@0.7.1 What’s Changed[](https://workspace/uzu/strudel/website/src/content/blog/release-0.7.0-zuckerguss/#whats-changed) ----------------------------------------------------------------------------------------------------------------- * pin @csound/browser to 6.18.3 + bump by @felixroos in [https://codeberg.org/uzu/strudel/pulls/403](https://codeberg.org/uzu/strudel/pulls/403) * update csound + fix sound output by @felixroos in [https://codeberg.org/uzu/strudel/pulls/404](https://codeberg.org/uzu/strudel/pulls/404) * fix: share url on subpath by @felixroos in [https://codeberg.org/uzu/strudel/pulls/405](https://codeberg.org/uzu/strudel/pulls/405) * add shabda doc by @felixroos in [https://codeberg.org/uzu/strudel/pulls/407](https://codeberg.org/uzu/strudel/pulls/407) * Update effects.mdx by @bwagner in [https://codeberg.org/uzu/strudel/pulls/410](https://codeberg.org/uzu/strudel/pulls/410) * improve effects doc by @felixroos in [https://codeberg.org/uzu/strudel/pulls/409](https://codeberg.org/uzu/strudel/pulls/409) * google gtfo by @felixroos in [https://codeberg.org/uzu/strudel/pulls/413](https://codeberg.org/uzu/strudel/pulls/413) * improve samples doc by @felixroos in [https://codeberg.org/uzu/strudel/pulls/411](https://codeberg.org/uzu/strudel/pulls/411) * PWA with offline support by @felixroos in [https://codeberg.org/uzu/strudel/pulls/417](https://codeberg.org/uzu/strudel/pulls/417) * add caching strategy for missing file types + cache all samples loaded from github by @felixroos in [https://codeberg.org/uzu/strudel/pulls/419](https://codeberg.org/uzu/strudel/pulls/419) * add more offline caching by @felixroos in [https://codeberg.org/uzu/strudel/pulls/421](https://codeberg.org/uzu/strudel/pulls/421) * add cdn.freesound to cache list by @felixroos in [https://codeberg.org/uzu/strudel/pulls/425](https://codeberg.org/uzu/strudel/pulls/425) * minirepl: add keyboard shortcuts by @felixroos in [https://codeberg.org/uzu/strudel/pulls/429](https://codeberg.org/uzu/strudel/pulls/429) * Themes by @felixroos in [https://codeberg.org/uzu/strudel/pulls/431](https://codeberg.org/uzu/strudel/pulls/431) * autocomplete preparations by @felixroos in [https://codeberg.org/uzu/strudel/pulls/427](https://codeberg.org/uzu/strudel/pulls/427) * Fix anchors by @felixroos in [https://codeberg.org/uzu/strudel/pulls/433](https://codeberg.org/uzu/strudel/pulls/433) * Update code.mdx by @bwagner in [https://codeberg.org/uzu/strudel/pulls/436](https://codeberg.org/uzu/strudel/pulls/436) * Update mini-notation.mdx by @bwagner in [https://codeberg.org/uzu/strudel/pulls/437](https://codeberg.org/uzu/strudel/pulls/437) * Update synths.mdx by @bwagner in [https://codeberg.org/uzu/strudel/pulls/438](https://codeberg.org/uzu/strudel/pulls/438) * FIXES: Warning about jsxBracketSameLine deprecation by @bwagner in [https://codeberg.org/uzu/strudel/pulls/461](https://codeberg.org/uzu/strudel/pulls/461) * Composable functions by @yaxu in [https://codeberg.org/uzu/strudel/pulls/390](https://codeberg.org/uzu/strudel/pulls/390) * weave and weaveWith by @yaxu in [https://codeberg.org/uzu/strudel/pulls/465](https://codeberg.org/uzu/strudel/pulls/465) * slice and splice by @yaxu in [https://codeberg.org/uzu/strudel/pulls/466](https://codeberg.org/uzu/strudel/pulls/466) * fix: osc should not return a promise by @felixroos in [https://codeberg.org/uzu/strudel/pulls/472](https://codeberg.org/uzu/strudel/pulls/472) * FIXES: freqs instead of pitches by @bwagner in [https://codeberg.org/uzu/strudel/pulls/464](https://codeberg.org/uzu/strudel/pulls/464) * Update input-output.mdx by @bwagner in [https://codeberg.org/uzu/strudel/pulls/471](https://codeberg.org/uzu/strudel/pulls/471) * settings tab with vim / emacs modes + additional themes and fonts by @felixroos in [https://codeberg.org/uzu/strudel/pulls/467](https://codeberg.org/uzu/strudel/pulls/467) * fix: hash links by @felixroos in [https://codeberg.org/uzu/strudel/pulls/473](https://codeberg.org/uzu/strudel/pulls/473) * midi cc support by @felixroos in [https://codeberg.org/uzu/strudel/pulls/478](https://codeberg.org/uzu/strudel/pulls/478) * Fix array args by @felixroos in [https://codeberg.org/uzu/strudel/pulls/480](https://codeberg.org/uzu/strudel/pulls/480) * docs: packages + offline by @felixroos in [https://codeberg.org/uzu/strudel/pulls/482](https://codeberg.org/uzu/strudel/pulls/482) * Update mini-notation.mdx by @yaxu in [https://codeberg.org/uzu/strudel/pulls/365](https://codeberg.org/uzu/strudel/pulls/365) * Revert “Another attempt at composable functions - WIP (#390)” by @felixroos in [https://codeberg.org/uzu/strudel/pulls/484](https://codeberg.org/uzu/strudel/pulls/484) * fix app height by @felixroos in [https://codeberg.org/uzu/strudel/pulls/485](https://codeberg.org/uzu/strudel/pulls/485) * add algolia creds + optimize sidebar for crawling by @felixroos in [https://codeberg.org/uzu/strudel/pulls/488](https://codeberg.org/uzu/strudel/pulls/488) * refactor react package by @felixroos in [https://codeberg.org/uzu/strudel/pulls/490](https://codeberg.org/uzu/strudel/pulls/490) * react style fixes by @felixroos in [https://codeberg.org/uzu/strudel/pulls/491](https://codeberg.org/uzu/strudel/pulls/491) * implement cps in scheduler by @felixroos in [https://codeberg.org/uzu/strudel/pulls/493](https://codeberg.org/uzu/strudel/pulls/493) * Add control aliases by @yaxu in [https://codeberg.org/uzu/strudel/pulls/497](https://codeberg.org/uzu/strudel/pulls/497) * fix: nano-repl highlighting by @felixroos in [https://codeberg.org/uzu/strudel/pulls/501](https://codeberg.org/uzu/strudel/pulls/501) * Reinstate slice and splice by @yaxu in [https://codeberg.org/uzu/strudel/pulls/500](https://codeberg.org/uzu/strudel/pulls/500) * can now use : as a replacement for space in scales by @felixroos in [https://codeberg.org/uzu/strudel/pulls/502](https://codeberg.org/uzu/strudel/pulls/502) * Support list syntax in mininotation by @yaxu in [https://codeberg.org/uzu/strudel/pulls/512](https://codeberg.org/uzu/strudel/pulls/512) * update react to 18 by @felixroos in [https://codeberg.org/uzu/strudel/pulls/514](https://codeberg.org/uzu/strudel/pulls/514) * add arrange function by @felixroos in [https://codeberg.org/uzu/strudel/pulls/508](https://codeberg.org/uzu/strudel/pulls/508) * Update README.md by @bwagner in [https://codeberg.org/uzu/strudel/pulls/474](https://codeberg.org/uzu/strudel/pulls/474) * add 2 illegible fonts by @felixroos in [https://codeberg.org/uzu/strudel/pulls/518](https://codeberg.org/uzu/strudel/pulls/518) * registerSound API + improved sounds tab + regroup soundfonts by @felixroos in [https://codeberg.org/uzu/strudel/pulls/516](https://codeberg.org/uzu/strudel/pulls/516) * fix: envelopes in chrome by @felixroos in [https://codeberg.org/uzu/strudel/pulls/521](https://codeberg.org/uzu/strudel/pulls/521) * Update samples.mdx by @bwagner in [https://codeberg.org/uzu/strudel/pulls/524](https://codeberg.org/uzu/strudel/pulls/524) * Update intro.mdx by @bwagner in [https://codeberg.org/uzu/strudel/pulls/525](https://codeberg.org/uzu/strudel/pulls/525) * fix(footer): fix link to tidalcycles by @revolunet in [https://codeberg.org/uzu/strudel/pulls/529](https://codeberg.org/uzu/strudel/pulls/529) * FIXES: alias pm for polymeter by @bwagner in [https://codeberg.org/uzu/strudel/pulls/527](https://codeberg.org/uzu/strudel/pulls/527) * Maintain random seed state in parser, not globally by @ijc8 in [https://codeberg.org/uzu/strudel/pulls/531](https://codeberg.org/uzu/strudel/pulls/531) * feat: add freq support to gm soundfonts by @felixroos in [https://codeberg.org/uzu/strudel/pulls/534](https://codeberg.org/uzu/strudel/pulls/534) * Update lerna by @felixroos in [https://codeberg.org/uzu/strudel/pulls/535](https://codeberg.org/uzu/strudel/pulls/535) New Contributors[](https://workspace/uzu/strudel/website/src/content/blog/release-0.7.0-zuckerguss/#new-contributors) ---------------------------------------------------------------------------------------------------------------------- * @revolunet made their first contribution in [https://codeberg.org/uzu/strudel/pulls/529](https://codeberg.org/uzu/strudel/pulls/529) * @ijc8 made their first contribution in [https://codeberg.org/uzu/strudel/pulls/531](https://codeberg.org/uzu/strudel/pulls/531) **Full Changelog**: [https://codeberg.org/uzu/strudel/compare/v0.6.0
v0.7.0](https://codeberg.org/uzu/strudel/compare/v0.6.0...v0.7.0) Release Notes v0.6.0 ==================== February 2023 by froos Package Versions[](https://workspace/uzu/strudel/website/src/content/blog/release-0.6.0-zimtschnecke/#package-versions) ------------------------------------------------------------------------------------------------------------------------ * @strudel.cycles/core@0.6.8 * @strudel.cycles/eval@0.6.2 * @strudel.cycles/transpiler@0.6.0 * @strudel.cycles/mini@0.6.0 * @strudel.cycles/tonal@0.6.0 * @strudel.cycles/tone@0.6.0 * @strudel.cycles/xen@0.6.0 * @strudel.cycles/webaudio@0.6.1 * @strudel.cycles/react@0.6.0 * @strudel.cycles/osc@0.6.0 * @strudel.cycles/midi@0.6.0 * @strudel.cycles/webdirt@0.6.0 * @strudel.cycles/serial@0.6.0 * @strudel.cycles/soundfonts@0.6.0 * @strudel.cycles/csound@0.6.0 What’s Changed[](https://workspace/uzu/strudel/website/src/content/blog/release-0.6.0-zimtschnecke/#whats-changed) ------------------------------------------------------------------------------------------------------------------- * support freq in pianoroll by @felixroos in [https://codeberg.org/uzu/strudel/pulls/308](https://codeberg.org/uzu/strudel/pulls/308) * ICLC2023 paper WIP by @yaxu in [https://codeberg.org/uzu/strudel/pulls/306](https://codeberg.org/uzu/strudel/pulls/306) * fix: copy share link to clipboard was broken for some browsers by @felixroos in [https://codeberg.org/uzu/strudel/pulls/311](https://codeberg.org/uzu/strudel/pulls/311) * Jsdoc component by @felixroos in [https://codeberg.org/uzu/strudel/pulls/312](https://codeberg.org/uzu/strudel/pulls/312) * object support for .scale by @felixroos in [https://codeberg.org/uzu/strudel/pulls/307](https://codeberg.org/uzu/strudel/pulls/307) * Astro build by @felixroos in [https://codeberg.org/uzu/strudel/pulls/315](https://codeberg.org/uzu/strudel/pulls/315) * Reference tab sort by @felixroos in [https://codeberg.org/uzu/strudel/pulls/318](https://codeberg.org/uzu/strudel/pulls/318) * tutorial updates by @jarmitage in [https://codeberg.org/uzu/strudel/pulls/320](https://codeberg.org/uzu/strudel/pulls/320) * support notes without octave by @felixroos in [https://codeberg.org/uzu/strudel/pulls/323](https://codeberg.org/uzu/strudel/pulls/323) * mini repl improvements by @felixroos in [https://codeberg.org/uzu/strudel/pulls/324](https://codeberg.org/uzu/strudel/pulls/324) * fix: workaround Object.assign globalThis by @felixroos in [https://codeberg.org/uzu/strudel/pulls/326](https://codeberg.org/uzu/strudel/pulls/326) * add examples route by @felixroos in [https://codeberg.org/uzu/strudel/pulls/327](https://codeberg.org/uzu/strudel/pulls/327) * add my-patterns by @felixroos in [https://codeberg.org/uzu/strudel/pulls/328](https://codeberg.org/uzu/strudel/pulls/328) * my-patterns build + deploy by @felixroos in [https://codeberg.org/uzu/strudel/pulls/329](https://codeberg.org/uzu/strudel/pulls/329) * my-patterns: fix paths + update readme by @felixroos in [https://codeberg.org/uzu/strudel/pulls/330](https://codeberg.org/uzu/strudel/pulls/330) * improve displaying ‘s’ in pianoroll by @felixroos in [https://codeberg.org/uzu/strudel/pulls/331](https://codeberg.org/uzu/strudel/pulls/331) * fix: can now multiply floats in mini notation by @felixroos in [https://codeberg.org/uzu/strudel/pulls/332](https://codeberg.org/uzu/strudel/pulls/332) * Embed mode improvements by @felixroos in [https://codeberg.org/uzu/strudel/pulls/333](https://codeberg.org/uzu/strudel/pulls/333) * testing + docs docs by @felixroos in [https://codeberg.org/uzu/strudel/pulls/334](https://codeberg.org/uzu/strudel/pulls/334) * animate mvp by @felixroos in [https://codeberg.org/uzu/strudel/pulls/335](https://codeberg.org/uzu/strudel/pulls/335) * Tidy parser, implement polymeters by @yaxu in [https://codeberg.org/uzu/strudel/pulls/336](https://codeberg.org/uzu/strudel/pulls/336) * animation options by @felixroos in [https://codeberg.org/uzu/strudel/pulls/337](https://codeberg.org/uzu/strudel/pulls/337) * move /my-patterns to /swatch by @yaxu in [https://codeberg.org/uzu/strudel/pulls/338](https://codeberg.org/uzu/strudel/pulls/338) * more animate functions + mini repl fix by @felixroos in [https://codeberg.org/uzu/strudel/pulls/340](https://codeberg.org/uzu/strudel/pulls/340) * Patternify euclid, fast, slow and polymeter step parameters in mininotation by @yaxu in [https://codeberg.org/uzu/strudel/pulls/341](https://codeberg.org/uzu/strudel/pulls/341) * fixes #346 by @felixroos in [https://codeberg.org/uzu/strudel/pulls/347](https://codeberg.org/uzu/strudel/pulls/347) * Fix prebake base path by @felixroos in [https://codeberg.org/uzu/strudel/pulls/345](https://codeberg.org/uzu/strudel/pulls/345) * Fix Bjorklund by @yaxu in [https://codeberg.org/uzu/strudel/pulls/343](https://codeberg.org/uzu/strudel/pulls/343) * docs: tidal comparison + add global fx + add missing sampler fx by @felixroos in [https://codeberg.org/uzu/strudel/pulls/356](https://codeberg.org/uzu/strudel/pulls/356) * Fix .out(), renaming webaudio’s out() to webaudio() by @yaxu in [https://codeberg.org/uzu/strudel/pulls/361](https://codeberg.org/uzu/strudel/pulls/361) * Support for multiple mininotation operators by @yaxu in [https://codeberg.org/uzu/strudel/pulls/350](https://codeberg.org/uzu/strudel/pulls/350) * doc structuring by @felixroos in [https://codeberg.org/uzu/strudel/pulls/360](https://codeberg.org/uzu/strudel/pulls/360) * add https to url by @urswilke in [https://codeberg.org/uzu/strudel/pulls/364](https://codeberg.org/uzu/strudel/pulls/364) * document more functions + change arp join by @felixroos in [https://codeberg.org/uzu/strudel/pulls/369](https://codeberg.org/uzu/strudel/pulls/369) * improve new draw logic by @felixroos in [https://codeberg.org/uzu/strudel/pulls/372](https://codeberg.org/uzu/strudel/pulls/372) * Draw fixes by @felixroos in [https://codeberg.org/uzu/strudel/pulls/377](https://codeberg.org/uzu/strudel/pulls/377) * update my-patterns instructions by @felixroos in [https://codeberg.org/uzu/strudel/pulls/384](https://codeberg.org/uzu/strudel/pulls/384) * docs: use note instead of n to mitigate confusion by @felixroos in [https://codeberg.org/uzu/strudel/pulls/385](https://codeberg.org/uzu/strudel/pulls/385) * add run + test + docs by @felixroos in [https://codeberg.org/uzu/strudel/pulls/386](https://codeberg.org/uzu/strudel/pulls/386) * Rename a to angle by @felixroos in [https://codeberg.org/uzu/strudel/pulls/387](https://codeberg.org/uzu/strudel/pulls/387) * document csound by @felixroos in [https://codeberg.org/uzu/strudel/pulls/391](https://codeberg.org/uzu/strudel/pulls/391) * Notes are not essential :) by @yaxu in [https://codeberg.org/uzu/strudel/pulls/393](https://codeberg.org/uzu/strudel/pulls/393) * add ribbon + test + docs by @felixroos in [https://codeberg.org/uzu/strudel/pulls/388](https://codeberg.org/uzu/strudel/pulls/388) * Add tidal-drum-patterns to examples by @urswilke in [https://codeberg.org/uzu/strudel/pulls/379](https://codeberg.org/uzu/strudel/pulls/379) * add pattern methods hurry, press and pressBy by @yaxu in [https://codeberg.org/uzu/strudel/pulls/397](https://codeberg.org/uzu/strudel/pulls/397) * proper builds + use pnpm workspaces by @felixroos in [https://codeberg.org/uzu/strudel/pulls/396](https://codeberg.org/uzu/strudel/pulls/396) * fix: minirepl styles by @felixroos in [https://codeberg.org/uzu/strudel/pulls/398](https://codeberg.org/uzu/strudel/pulls/398) * can now await initAudio + initAudioOnFirstClick by @felixroos in [https://codeberg.org/uzu/strudel/pulls/399](https://codeberg.org/uzu/strudel/pulls/399) * release webaudio by @felixroos in [https://codeberg.org/uzu/strudel/pulls/400](https://codeberg.org/uzu/strudel/pulls/400) New Contributors[](https://workspace/uzu/strudel/website/src/content/blog/release-0.6.0-zimtschnecke/#new-contributors) ------------------------------------------------------------------------------------------------------------------------ * @jarmitage made their first contribution in [https://codeberg.org/uzu/strudel/pulls/320](https://codeberg.org/uzu/strudel/pulls/320) * @urswilke made their first contribution in [https://codeberg.org/uzu/strudel/pulls/364](https://codeberg.org/uzu/strudel/pulls/364) **Full Changelog**: [https://codeberg.org/uzu/strudel/compare/v0.5.0
v0.6.0](https://codeberg.org/uzu/strudel/compare/v0.5.0...v0.6.0) Release Notes v0.5.0 ==================== December 2022 by froos Package Versions[](https://workspace/uzu/strudel/website/src/content/blog/release-0.5.0-wirbel/#package-versions) ------------------------------------------------------------------------------------------------------------------ * @strudel.cycles/core@0.5.0 * @strudel.cycles/osc@0.4.0 * @strudel.cycles/serial@0.3.0 * @strudel.cycles/csound@0.5.1 * @strudel.cycles/eval@0.5.0 * @strudel.cycles/midi@0.5.0 * @strudel.cycles/mini@0.5.0 * @strudel.cycles/react@0.5.0 * @strudel.cycles/soundfonts@0.5.0 * @strudel.cycles/tonal@0.5.0 * @strudel.cycles/tone@0.5.0 * @strudel.cycles/transpiler@0.5.0 * @strudel.cycles/webaudio@0.5.0 * @strudel.cycles/webdirt@0.5.0 * @strudel.cycles/xen@0.5.0 What’s Changed[](https://workspace/uzu/strudel/website/src/content/blog/release-0.5.0-wirbel/#whats-changed) ------------------------------------------------------------------------------------------------------------- * Binaries by @felixroos in [https://codeberg.org/uzu/strudel/pulls/254](https://codeberg.org/uzu/strudel/pulls/254) * fix tutorial bugs by @felixroos in [https://codeberg.org/uzu/strudel/pulls/263](https://codeberg.org/uzu/strudel/pulls/263) * fix performance bottleneck by @felixroos in [https://codeberg.org/uzu/strudel/pulls/266](https://codeberg.org/uzu/strudel/pulls/266) * Tidying up core by @yaxu in [https://codeberg.org/uzu/strudel/pulls/256](https://codeberg.org/uzu/strudel/pulls/256) * tonal update with fixed memory leak by @felixroos in [https://codeberg.org/uzu/strudel/pulls/272](https://codeberg.org/uzu/strudel/pulls/272) * add eslint by @felixroos in [https://codeberg.org/uzu/strudel/pulls/271](https://codeberg.org/uzu/strudel/pulls/271) * release version bumps by @felixroos in [https://codeberg.org/uzu/strudel/pulls/273](https://codeberg.org/uzu/strudel/pulls/273) * Support sending CRC16 bytes with serial messages by @yaxu in [https://codeberg.org/uzu/strudel/pulls/276](https://codeberg.org/uzu/strudel/pulls/276) * add licenses / credits to all tunes + remove some by @felixroos in [https://codeberg.org/uzu/strudel/pulls/277](https://codeberg.org/uzu/strudel/pulls/277) * add basic csound output by @felixroos in [https://codeberg.org/uzu/strudel/pulls/275](https://codeberg.org/uzu/strudel/pulls/275) * do not recompile orc by @felixroos in [https://codeberg.org/uzu/strudel/pulls/278](https://codeberg.org/uzu/strudel/pulls/278) * implement collect + arp function by @felixroos in [https://codeberg.org/uzu/strudel/pulls/281](https://codeberg.org/uzu/strudel/pulls/281) * Switch ‘operators’ from .whatHow to .what.how by @yaxu in [https://codeberg.org/uzu/strudel/pulls/285](https://codeberg.org/uzu/strudel/pulls/285) * Fancy hap show, include part in snapshots by @yaxu in [https://codeberg.org/uzu/strudel/pulls/291](https://codeberg.org/uzu/strudel/pulls/291) * Reorganise pattern.mjs with a ‘toplevel first’ regime by @yaxu in [https://codeberg.org/uzu/strudel/pulls/286](https://codeberg.org/uzu/strudel/pulls/286) * add prettier task by @felixroos in [https://codeberg.org/uzu/strudel/pulls/296](https://codeberg.org/uzu/strudel/pulls/296) * Move stuff to new register function by @felixroos in [https://codeberg.org/uzu/strudel/pulls/295](https://codeberg.org/uzu/strudel/pulls/295) * can now add bare numbers to numeral object props by @felixroos in [https://codeberg.org/uzu/strudel/pulls/287](https://codeberg.org/uzu/strudel/pulls/287) * update vitest by @felixroos in [https://codeberg.org/uzu/strudel/pulls/297](https://codeberg.org/uzu/strudel/pulls/297) * remove whitespace from highlighted region by @felixroos in [https://codeberg.org/uzu/strudel/pulls/298](https://codeberg.org/uzu/strudel/pulls/298) * .defragmentHaps() for merging touching haps that share a whole and value by @yaxu in [https://codeberg.org/uzu/strudel/pulls/299](https://codeberg.org/uzu/strudel/pulls/299) * fix whitespace trimming by @felixroos in [https://codeberg.org/uzu/strudel/pulls/300](https://codeberg.org/uzu/strudel/pulls/300) * add freq support to sampler by @felixroos in [https://codeberg.org/uzu/strudel/pulls/301](https://codeberg.org/uzu/strudel/pulls/301) * add lint + prettier check before test by @felixroos in [https://codeberg.org/uzu/strudel/pulls/305](https://codeberg.org/uzu/strudel/pulls/305) * Updated csoundm to use the register facility . by @gogins in [https://codeberg.org/uzu/strudel/pulls/303](https://codeberg.org/uzu/strudel/pulls/303) New Contributors[](https://workspace/uzu/strudel/website/src/content/blog/release-0.5.0-wirbel/#new-contributors) ------------------------------------------------------------------------------------------------------------------ * @gogins made their first contribution in [https://codeberg.org/uzu/strudel/pulls/303](https://codeberg.org/uzu/strudel/pulls/303) **Full Changelog**: [https://codeberg.org/uzu/strudel/compare/v0.4.0
v0.5.0](https://codeberg.org/uzu/strudel/compare/v0.4.0...v0.5.0) Release Notes v0.4.0 ==================== November 2022 by froos What’s Changed[](https://workspace/uzu/strudel/website/src/content/blog/release-0.4.0-brandung/#whats-changed) --------------------------------------------------------------------------------------------------------------- * new transpiler based on acorn by @felixroos in [https://codeberg.org/uzu/strudel/pulls/249](https://codeberg.org/uzu/strudel/pulls/249) * Webaudio build by @felixroos in [https://codeberg.org/uzu/strudel/pulls/250](https://codeberg.org/uzu/strudel/pulls/250) * Repl refactoring by @felixroos in [https://codeberg.org/uzu/strudel/pulls/255](https://codeberg.org/uzu/strudel/pulls/255) **Full Changelog**: [https://codeberg.org/uzu/strudel/compare/v0.3.0
v0.4.0](https://codeberg.org/uzu/strudel/compare/v0.3.0...v0.4.0) Release Notes v0.3.0 ==================== November 2022 by froos Package Versions[](https://workspace/uzu/strudel/website/src/content/blog/release-0.3.0-donauwelle/#package-versions) ---------------------------------------------------------------------------------------------------------------------- * @strudel.cycles/core@0.3.1 * @strudel.cycles/eval@0.3.1 * @strudel.cycles/midi@0.3.1 * @strudel.cycles/mini@0.3.1 * @strudel.cycles/react@0.3.1 * @strudel.cycles/soundfonts@0.3.1 * @strudel.cycles/tonal@0.3.1 * @strudel.cycles/tone@0.3.1 * @strudel.cycles/webaudio@0.3.1 * @strudel.cycles/webdirt@0.3.1 * @strudel.cycles/xen@0.3.1 What’s Changed[](https://workspace/uzu/strudel/website/src/content/blog/release-0.3.0-donauwelle/#whats-changed) ----------------------------------------------------------------------------------------------------------------- * Fix numbers in sampler by @felixroos in [https://codeberg.org/uzu/strudel/pulls/196](https://codeberg.org/uzu/strudel/pulls/196) * document random functions by @felixroos in [https://codeberg.org/uzu/strudel/pulls/199](https://codeberg.org/uzu/strudel/pulls/199) * add rollup-plugin-visualizer to build by @felixroos in [https://codeberg.org/uzu/strudel/pulls/200](https://codeberg.org/uzu/strudel/pulls/200) * add vowel to .out by @felixroos in [https://codeberg.org/uzu/strudel/pulls/201](https://codeberg.org/uzu/strudel/pulls/201) * Coarse crush shape by @felixroos in [https://codeberg.org/uzu/strudel/pulls/205](https://codeberg.org/uzu/strudel/pulls/205) * Webaudio guide by @felixroos in [https://codeberg.org/uzu/strudel/pulls/207](https://codeberg.org/uzu/strudel/pulls/207) * Even more docs by @felixroos in [https://codeberg.org/uzu/strudel/pulls/212](https://codeberg.org/uzu/strudel/pulls/212) * Just another docs PR by @felixroos in [https://codeberg.org/uzu/strudel/pulls/215](https://codeberg.org/uzu/strudel/pulls/215) * sampler features + fixes by @felixroos in [https://codeberg.org/uzu/strudel/pulls/217](https://codeberg.org/uzu/strudel/pulls/217) * samples now have envelopes by @felixroos in [https://codeberg.org/uzu/strudel/pulls/218](https://codeberg.org/uzu/strudel/pulls/218) * encapsulate webaudio output by @felixroos in [https://codeberg.org/uzu/strudel/pulls/219](https://codeberg.org/uzu/strudel/pulls/219) * Fix squeeze join by @yaxu in [https://codeberg.org/uzu/strudel/pulls/220](https://codeberg.org/uzu/strudel/pulls/220) * Feedback Delay by @felixroos in [https://codeberg.org/uzu/strudel/pulls/213](https://codeberg.org/uzu/strudel/pulls/213) * support negative speeds by @felixroos in [https://codeberg.org/uzu/strudel/pulls/222](https://codeberg.org/uzu/strudel/pulls/222) * focus tweak for squeezeJoin - another go at fixing #216 by @yaxu in [https://codeberg.org/uzu/strudel/pulls/221](https://codeberg.org/uzu/strudel/pulls/221) * Reverb by @felixroos in [https://codeberg.org/uzu/strudel/pulls/224](https://codeberg.org/uzu/strudel/pulls/224) * fix fastgap for events that go across cycle boundaries by @yaxu in [https://codeberg.org/uzu/strudel/pulls/225](https://codeberg.org/uzu/strudel/pulls/225) * Core util tests by @mystery-house in [https://codeberg.org/uzu/strudel/pulls/226](https://codeberg.org/uzu/strudel/pulls/226) * Refactor tunes away from tone by @felixroos in [https://codeberg.org/uzu/strudel/pulls/230](https://codeberg.org/uzu/strudel/pulls/230) * Just another docs branch by @felixroos in [https://codeberg.org/uzu/strudel/pulls/228](https://codeberg.org/uzu/strudel/pulls/228) * Patternify range by @yaxu in [https://codeberg.org/uzu/strudel/pulls/231](https://codeberg.org/uzu/strudel/pulls/231) * Out by default by @felixroos in [https://codeberg.org/uzu/strudel/pulls/232](https://codeberg.org/uzu/strudel/pulls/232) * Fix zero length queries WIP by @yaxu in [https://codeberg.org/uzu/strudel/pulls/234](https://codeberg.org/uzu/strudel/pulls/234) * add vcsl sample library by @felixroos in [https://codeberg.org/uzu/strudel/pulls/235](https://codeberg.org/uzu/strudel/pulls/235) * fx on stereo speakers by @felixroos in [https://codeberg.org/uzu/strudel/pulls/236](https://codeberg.org/uzu/strudel/pulls/236) * Tidal drum machines by @felixroos in [https://codeberg.org/uzu/strudel/pulls/237](https://codeberg.org/uzu/strudel/pulls/237) * Object arithmetic by @felixroos in [https://codeberg.org/uzu/strudel/pulls/238](https://codeberg.org/uzu/strudel/pulls/238) * Load samples from url by @felixroos in [https://codeberg.org/uzu/strudel/pulls/239](https://codeberg.org/uzu/strudel/pulls/239) * feat: support github: links by @felixroos in [https://codeberg.org/uzu/strudel/pulls/240](https://codeberg.org/uzu/strudel/pulls/240) * in source example tests by @felixroos in [https://codeberg.org/uzu/strudel/pulls/242](https://codeberg.org/uzu/strudel/pulls/242) * Readme + TLC by @felixroos in [https://codeberg.org/uzu/strudel/pulls/244](https://codeberg.org/uzu/strudel/pulls/244) * patchday by @felixroos in [https://codeberg.org/uzu/strudel/pulls/246](https://codeberg.org/uzu/strudel/pulls/246) * Some tunes by @felixroos in [https://codeberg.org/uzu/strudel/pulls/247](https://codeberg.org/uzu/strudel/pulls/247) * snapshot tests on shared snippets by @felixroos in [https://codeberg.org/uzu/strudel/pulls/243](https://codeberg.org/uzu/strudel/pulls/243) * General purpose scheduler by @felixroos in [https://codeberg.org/uzu/strudel/pulls/248](https://codeberg.org/uzu/strudel/pulls/248) New Contributors[](https://workspace/uzu/strudel/website/src/content/blog/release-0.3.0-donauwelle/#new-contributors) ---------------------------------------------------------------------------------------------------------------------- * @mystery-house made their first contribution in [https://codeberg.org/uzu/strudel/pulls/226](https://codeberg.org/uzu/strudel/pulls/226) **Full Changelog**: [https://codeberg.org/uzu/strudel/compare/v0.0.4
v0.3.0](https://codeberg.org/uzu/strudel/compare/v0.0.4...v0.3.0) Release Notes v0.0.4 ==================== August 2022 by froos What’s Changed[](https://workspace/uzu/strudel/website/src/content/blog/release-0.0.4-gischt/#whats-changed) ------------------------------------------------------------------------------------------------------------- * Webaudio rewrite by @felixroos in [https://codeberg.org/uzu/strudel/pulls/138](https://codeberg.org/uzu/strudel/pulls/138) * Fix createParam() by @yaxu in [https://codeberg.org/uzu/strudel/pulls/140](https://codeberg.org/uzu/strudel/pulls/140) * Soundfont Support by @felixroos in [https://codeberg.org/uzu/strudel/pulls/139](https://codeberg.org/uzu/strudel/pulls/139) * Serial twiddles by @yaxu in [https://codeberg.org/uzu/strudel/pulls/141](https://codeberg.org/uzu/strudel/pulls/141) * Pianoroll Object Support by @felixroos in [https://codeberg.org/uzu/strudel/pulls/142](https://codeberg.org/uzu/strudel/pulls/142) * flash effect on ctrl enter by @felixroos in [https://codeberg.org/uzu/strudel/pulls/144](https://codeberg.org/uzu/strudel/pulls/144) * can now generate short link for sharing by @felixroos in [https://codeberg.org/uzu/strudel/pulls/146](https://codeberg.org/uzu/strudel/pulls/146) * Sampler optimizations and more by @felixroos in [https://codeberg.org/uzu/strudel/pulls/148](https://codeberg.org/uzu/strudel/pulls/148) * Final update to demo.pdf by @yaxu in [https://codeberg.org/uzu/strudel/pulls/151](https://codeberg.org/uzu/strudel/pulls/151) * add webdirt drum samples to prebake for general availability by @larkob in [https://codeberg.org/uzu/strudel/pulls/150](https://codeberg.org/uzu/strudel/pulls/150) * update to tutorial documentation by @larkob in [https://codeberg.org/uzu/strudel/pulls/162](https://codeberg.org/uzu/strudel/pulls/162) * add chooseInWith/chooseCycles by @yaxu in [https://codeberg.org/uzu/strudel/pulls/166](https://codeberg.org/uzu/strudel/pulls/166) * fix: jsdoc comments by @felixroos in [https://codeberg.org/uzu/strudel/pulls/169](https://codeberg.org/uzu/strudel/pulls/169) * Pianoroll fixes by @felixroos in [https://codeberg.org/uzu/strudel/pulls/163](https://codeberg.org/uzu/strudel/pulls/163) * Talk fixes by @felixroos in [https://codeberg.org/uzu/strudel/pulls/164](https://codeberg.org/uzu/strudel/pulls/164) * Amend shapeshifter to allow use of dynamic import by @debrisapron in [https://codeberg.org/uzu/strudel/pulls/171](https://codeberg.org/uzu/strudel/pulls/171) * add more shapeshifter flags by @felixroos in [https://codeberg.org/uzu/strudel/pulls/99](https://codeberg.org/uzu/strudel/pulls/99) * Replace react-codemirror6 with @uiw/react-codemirror by @felixroos in [https://codeberg.org/uzu/strudel/pulls/173](https://codeberg.org/uzu/strudel/pulls/173) * fix some annoying bugs by @felixroos in [https://codeberg.org/uzu/strudel/pulls/177](https://codeberg.org/uzu/strudel/pulls/177) * incorporate elements of randomness to the mini notation by @bpow in [https://codeberg.org/uzu/strudel/pulls/165](https://codeberg.org/uzu/strudel/pulls/165) * replace mocha with vitest by @felixroos in [https://codeberg.org/uzu/strudel/pulls/175](https://codeberg.org/uzu/strudel/pulls/175) * scheduler improvements by @felixroos in [https://codeberg.org/uzu/strudel/pulls/181](https://codeberg.org/uzu/strudel/pulls/181) * Fix codemirror bug by @felixroos in [https://codeberg.org/uzu/strudel/pulls/186](https://codeberg.org/uzu/strudel/pulls/186) * wait for prebake to finish before evaluating by @felixroos in [https://codeberg.org/uzu/strudel/pulls/189](https://codeberg.org/uzu/strudel/pulls/189) * fix regression: old way of setting frequencies was broken by @felixroos in [https://codeberg.org/uzu/strudel/pulls/190](https://codeberg.org/uzu/strudel/pulls/190) * Soundfont file support by @felixroos in [https://codeberg.org/uzu/strudel/pulls/183](https://codeberg.org/uzu/strudel/pulls/183) * change “stride”/“offset” of successive degradeBy/chooseIn by @bpow in [https://codeberg.org/uzu/strudel/pulls/185](https://codeberg.org/uzu/strudel/pulls/185) New Contributors[](https://workspace/uzu/strudel/website/src/content/blog/release-0.0.4-gischt/#new-contributors) ------------------------------------------------------------------------------------------------------------------ * @larkob made their first contribution in [https://codeberg.org/uzu/strudel/pulls/150](https://codeberg.org/uzu/strudel/pulls/150) * @debrisapron made their first contribution in [https://codeberg.org/uzu/strudel/pulls/171](https://codeberg.org/uzu/strudel/pulls/171) * @bpow made their first contribution in [https://codeberg.org/uzu/strudel/pulls/165](https://codeberg.org/uzu/strudel/pulls/165) **Full Changelog**: [https://codeberg.org/uzu/strudel/compare/v0.0.3
v0.0.4](https://codeberg.org/uzu/strudel/compare/v0.0.3...v0.0.4) Release Notes v0.0.3 ==================== June 2022 by froos What’s Changed[](https://workspace/uzu/strudel/website/src/content/blog/release-0.0.3-maelstrom/#whats-changed) ---------------------------------------------------------------------------------------------------------------- * Add chunk, chunkBack and iterBack by @yaxu in [https://codeberg.org/uzu/strudel/pulls/25](https://codeberg.org/uzu/strudel/pulls/25) * Update tutorial.mdx by @bwagner in [https://codeberg.org/uzu/strudel/pulls/37](https://codeberg.org/uzu/strudel/pulls/37) * Update tutorial.mdx by @bwagner in [https://codeberg.org/uzu/strudel/pulls/38](https://codeberg.org/uzu/strudel/pulls/38) * Compose by @felixroos in [https://codeberg.org/uzu/strudel/pulls/40](https://codeberg.org/uzu/strudel/pulls/40) * Fix polymeter by @yaxu in [https://codeberg.org/uzu/strudel/pulls/44](https://codeberg.org/uzu/strudel/pulls/44) * First run at squeezeBind, ref #32 by @yaxu in [https://codeberg.org/uzu/strudel/pulls/48](https://codeberg.org/uzu/strudel/pulls/48) * Implement `chop()` by @yaxu in [https://codeberg.org/uzu/strudel/pulls/50](https://codeberg.org/uzu/strudel/pulls/50) * OSC and SuperDirt support by @yaxu in [https://codeberg.org/uzu/strudel/pulls/27](https://codeberg.org/uzu/strudel/pulls/27) * More functions by @yaxu in [https://codeberg.org/uzu/strudel/pulls/56](https://codeberg.org/uzu/strudel/pulls/56) * More functions by @yaxu in [https://codeberg.org/uzu/strudel/pulls/61](https://codeberg.org/uzu/strudel/pulls/61) * Separate out strudel.mjs, make index.mjs aggregate module by @yaxu in [https://codeberg.org/uzu/strudel/pulls/62](https://codeberg.org/uzu/strudel/pulls/62) * Speech output by @felixroos in [https://codeberg.org/uzu/strudel/pulls/67](https://codeberg.org/uzu/strudel/pulls/67) * use new fixed version of osc-js package by @felixroos in [https://codeberg.org/uzu/strudel/pulls/68](https://codeberg.org/uzu/strudel/pulls/68) * First effort at rand() by @yaxu in [https://codeberg.org/uzu/strudel/pulls/69](https://codeberg.org/uzu/strudel/pulls/69) * More randomness, fix `rand`, and add `brand`, `irand` and `choose` by @yaxu in [https://codeberg.org/uzu/strudel/pulls/70](https://codeberg.org/uzu/strudel/pulls/70) * webaudio package by @felixroos in [https://codeberg.org/uzu/strudel/pulls/26](https://codeberg.org/uzu/strudel/pulls/26) * Port `perlin` noise, `rangex`, and `palindrome` by @yaxu in [https://codeberg.org/uzu/strudel/pulls/73](https://codeberg.org/uzu/strudel/pulls/73) * More random functions by @yaxu in [https://codeberg.org/uzu/strudel/pulls/74](https://codeberg.org/uzu/strudel/pulls/74) * Try to fix appLeft / appRight by @yaxu in [https://codeberg.org/uzu/strudel/pulls/75](https://codeberg.org/uzu/strudel/pulls/75) * Basic webserial support by @yaxu in [https://codeberg.org/uzu/strudel/pulls/80](https://codeberg.org/uzu/strudel/pulls/80) * Webaudio in REPL by @felixroos in [https://codeberg.org/uzu/strudel/pulls/77](https://codeberg.org/uzu/strudel/pulls/77) * add `striate()` by @yaxu in [https://codeberg.org/uzu/strudel/pulls/76](https://codeberg.org/uzu/strudel/pulls/76) * Tidy up a couple of old files by @mindofmatthew in [https://codeberg.org/uzu/strudel/pulls/84](https://codeberg.org/uzu/strudel/pulls/84) * Add pattern composers, implements #82 by @yaxu in [https://codeberg.org/uzu/strudel/pulls/83](https://codeberg.org/uzu/strudel/pulls/83) * Fiddles with cat/stack by @yaxu in [https://codeberg.org/uzu/strudel/pulls/90](https://codeberg.org/uzu/strudel/pulls/90) * Paper by @felixroos in [https://codeberg.org/uzu/strudel/pulls/98](https://codeberg.org/uzu/strudel/pulls/98) * Change to Affero GPL by @yaxu in [https://codeberg.org/uzu/strudel/pulls/101](https://codeberg.org/uzu/strudel/pulls/101) * Work on Codemirror 6 highlighting by @mindofmatthew in [https://codeberg.org/uzu/strudel/pulls/102](https://codeberg.org/uzu/strudel/pulls/102) * Codemirror 6 by @felixroos in [https://codeberg.org/uzu/strudel/pulls/97](https://codeberg.org/uzu/strudel/pulls/97) * Tune tests by @felixroos in [https://codeberg.org/uzu/strudel/pulls/104](https://codeberg.org/uzu/strudel/pulls/104) * /embed package: web component for repl by @felixroos in [https://codeberg.org/uzu/strudel/pulls/106](https://codeberg.org/uzu/strudel/pulls/106) * Reset, Restart and other composers by @felixroos in [https://codeberg.org/uzu/strudel/pulls/88](https://codeberg.org/uzu/strudel/pulls/88) * Embed style by @felixroos in [https://codeberg.org/uzu/strudel/pulls/109](https://codeberg.org/uzu/strudel/pulls/109) * In source doc by @yaxu in [https://codeberg.org/uzu/strudel/pulls/105](https://codeberg.org/uzu/strudel/pulls/105) * `.brak()`, `.inside()` and `.outside()` by @yaxu in [https://codeberg.org/uzu/strudel/pulls/112](https://codeberg.org/uzu/strudel/pulls/112) * loopAt by @yaxu in [https://codeberg.org/uzu/strudel/pulls/114](https://codeberg.org/uzu/strudel/pulls/114) * Osc timing improvements by @yaxu in [https://codeberg.org/uzu/strudel/pulls/113](https://codeberg.org/uzu/strudel/pulls/113) * react package + vite build by @felixroos in [https://codeberg.org/uzu/strudel/pulls/116](https://codeberg.org/uzu/strudel/pulls/116) * In source doc by @felixroos in [https://codeberg.org/uzu/strudel/pulls/117](https://codeberg.org/uzu/strudel/pulls/117) * fix: #108 by @felixroos in [https://codeberg.org/uzu/strudel/pulls/123](https://codeberg.org/uzu/strudel/pulls/123) * fix: #122 ctrl enter would add newline by @felixroos in [https://codeberg.org/uzu/strudel/pulls/124](https://codeberg.org/uzu/strudel/pulls/124) * Webdirt by @felixroos in [https://codeberg.org/uzu/strudel/pulls/121](https://codeberg.org/uzu/strudel/pulls/121) * Fix link to contributing to tutorial docs by @stephendwolff in [https://codeberg.org/uzu/strudel/pulls/129](https://codeberg.org/uzu/strudel/pulls/129) * Pianoroll enhancements by @felixroos in [https://codeberg.org/uzu/strudel/pulls/131](https://codeberg.org/uzu/strudel/pulls/131) * add createParam + createParams by @felixroos in [https://codeberg.org/uzu/strudel/pulls/110](https://codeberg.org/uzu/strudel/pulls/110) * remove cycle + delta from onTrigger by @felixroos in [https://codeberg.org/uzu/strudel/pulls/135](https://codeberg.org/uzu/strudel/pulls/135) * Scheduler improvements by @felixroos in [https://codeberg.org/uzu/strudel/pulls/134](https://codeberg.org/uzu/strudel/pulls/134) * add onTrigger helper by @felixroos in [https://codeberg.org/uzu/strudel/pulls/136](https://codeberg.org/uzu/strudel/pulls/136) New Contributors[](https://workspace/uzu/strudel/website/src/content/blog/release-0.0.3-maelstrom/#new-contributors) --------------------------------------------------------------------------------------------------------------------- * @bwagner made their first contribution in [https://codeberg.org/uzu/strudel/pulls/37](https://codeberg.org/uzu/strudel/pulls/37) * @mindofmatthew made their first contribution in [https://codeberg.org/uzu/strudel/pulls/84](https://codeberg.org/uzu/strudel/pulls/84) * @stephendwolff made their first contribution in [https://codeberg.org/uzu/strudel/pulls/129](https://codeberg.org/uzu/strudel/pulls/129) **Full Changelog**: [https://codeberg.org/uzu/strudel/compare/v0.0.2
v0.0.3](https://codeberg.org/uzu/strudel/compare/v0.0.2...v0.0.3) Release Notes v0.0.3 ==================== May 2022 by froos What’s Changed[](https://workspace/uzu/strudel/website/src/content/blog/release-0.0.2.1-stuermisch/#whats-changed) ------------------------------------------------------------------------------------------------------------------- * Add chunk, chunkBack and iterBack by @yaxu in [https://codeberg.org/uzu/strudel/pulls/25](https://codeberg.org/uzu/strudel/pulls/25) * Update tutorial.mdx by @bwagner in [https://codeberg.org/uzu/strudel/pulls/37](https://codeberg.org/uzu/strudel/pulls/37) * Update tutorial.mdx by @bwagner in [https://codeberg.org/uzu/strudel/pulls/38](https://codeberg.org/uzu/strudel/pulls/38) * Compose by @felixroos in [https://codeberg.org/uzu/strudel/pulls/40](https://codeberg.org/uzu/strudel/pulls/40) * Fix polymeter by @yaxu in [https://codeberg.org/uzu/strudel/pulls/44](https://codeberg.org/uzu/strudel/pulls/44) * First run at squeezeBind, ref #32 by @yaxu in [https://codeberg.org/uzu/strudel/pulls/48](https://codeberg.org/uzu/strudel/pulls/48) * Implement `chop()` by @yaxu in [https://codeberg.org/uzu/strudel/pulls/50](https://codeberg.org/uzu/strudel/pulls/50) * OSC and SuperDirt support by @yaxu in [https://codeberg.org/uzu/strudel/pulls/27](https://codeberg.org/uzu/strudel/pulls/27) * More functions by @yaxu in [https://codeberg.org/uzu/strudel/pulls/56](https://codeberg.org/uzu/strudel/pulls/56) * More functions by @yaxu in [https://codeberg.org/uzu/strudel/pulls/61](https://codeberg.org/uzu/strudel/pulls/61) * Separate out strudel.mjs, make index.mjs aggregate module by @yaxu in [https://codeberg.org/uzu/strudel/pulls/62](https://codeberg.org/uzu/strudel/pulls/62) * Speech output by @felixroos in [https://codeberg.org/uzu/strudel/pulls/67](https://codeberg.org/uzu/strudel/pulls/67) * use new fixed version of osc-js package by @felixroos in [https://codeberg.org/uzu/strudel/pulls/68](https://codeberg.org/uzu/strudel/pulls/68) * First effort at rand() by @yaxu in [https://codeberg.org/uzu/strudel/pulls/69](https://codeberg.org/uzu/strudel/pulls/69) * More randomness, fix `rand`, and add `brand`, `irand` and `choose` by @yaxu in [https://codeberg.org/uzu/strudel/pulls/70](https://codeberg.org/uzu/strudel/pulls/70) * webaudio package by @felixroos in [https://codeberg.org/uzu/strudel/pulls/26](https://codeberg.org/uzu/strudel/pulls/26) * Port `perlin` noise, `rangex`, and `palindrome` by @yaxu in [https://codeberg.org/uzu/strudel/pulls/73](https://codeberg.org/uzu/strudel/pulls/73) * More random functions by @yaxu in [https://codeberg.org/uzu/strudel/pulls/74](https://codeberg.org/uzu/strudel/pulls/74) * Try to fix appLeft / appRight by @yaxu in [https://codeberg.org/uzu/strudel/pulls/75](https://codeberg.org/uzu/strudel/pulls/75) * Basic webserial support by @yaxu in [https://codeberg.org/uzu/strudel/pulls/80](https://codeberg.org/uzu/strudel/pulls/80) * Webaudio in REPL by @felixroos in [https://codeberg.org/uzu/strudel/pulls/77](https://codeberg.org/uzu/strudel/pulls/77) * add `striate()` by @yaxu in [https://codeberg.org/uzu/strudel/pulls/76](https://codeberg.org/uzu/strudel/pulls/76) * Tidy up a couple of old files by @mindofmatthew in [https://codeberg.org/uzu/strudel/pulls/84](https://codeberg.org/uzu/strudel/pulls/84) * Add pattern composers, implements #82 by @yaxu in [https://codeberg.org/uzu/strudel/pulls/83](https://codeberg.org/uzu/strudel/pulls/83) * Fiddles with cat/stack by @yaxu in [https://codeberg.org/uzu/strudel/pulls/90](https://codeberg.org/uzu/strudel/pulls/90) * Paper by @felixroos in [https://codeberg.org/uzu/strudel/pulls/98](https://codeberg.org/uzu/strudel/pulls/98) * Change to Affero GPL by @yaxu in [https://codeberg.org/uzu/strudel/pulls/101](https://codeberg.org/uzu/strudel/pulls/101) * Work on Codemirror 6 highlighting by @mindofmatthew in [https://codeberg.org/uzu/strudel/pulls/102](https://codeberg.org/uzu/strudel/pulls/102) * Codemirror 6 by @felixroos in [https://codeberg.org/uzu/strudel/pulls/97](https://codeberg.org/uzu/strudel/pulls/97) * Tune tests by @felixroos in [https://codeberg.org/uzu/strudel/pulls/104](https://codeberg.org/uzu/strudel/pulls/104) * /embed package: web component for repl by @felixroos in [https://codeberg.org/uzu/strudel/pulls/106](https://codeberg.org/uzu/strudel/pulls/106) * Reset, Restart and other composers by @felixroos in [https://codeberg.org/uzu/strudel/pulls/88](https://codeberg.org/uzu/strudel/pulls/88) * Embed style by @felixroos in [https://codeberg.org/uzu/strudel/pulls/109](https://codeberg.org/uzu/strudel/pulls/109) * In source doc by @yaxu in [https://codeberg.org/uzu/strudel/pulls/105](https://codeberg.org/uzu/strudel/pulls/105) * `.brak()`, `.inside()` and `.outside()` by @yaxu in [https://codeberg.org/uzu/strudel/pulls/112](https://codeberg.org/uzu/strudel/pulls/112) * loopAt by @yaxu in [https://codeberg.org/uzu/strudel/pulls/114](https://codeberg.org/uzu/strudel/pulls/114) * Osc timing improvements by @yaxu in [https://codeberg.org/uzu/strudel/pulls/113](https://codeberg.org/uzu/strudel/pulls/113) New Contributors[](https://workspace/uzu/strudel/website/src/content/blog/release-0.0.2.1-stuermisch/#new-contributors) ------------------------------------------------------------------------------------------------------------------------ * @bwagner made their first contribution in [https://codeberg.org/uzu/strudel/pulls/37](https://codeberg.org/uzu/strudel/pulls/37) * @mindofmatthew made their first contribution in [https://codeberg.org/uzu/strudel/pulls/84](https://codeberg.org/uzu/strudel/pulls/84) **Full Changelog**: [https://codeberg.org/uzu/strudel/compare/v0.0.2
@strudel.cycles/core@0.1.0](https://codeberg.org/uzu/strudel/compare/v0.0.2...@strudel.cycles/core@0.1.0) Release Notes v0.0.2 ==================== March 2022 by froos What’s Changed[](https://workspace/uzu/strudel/website/src/content/blog/release-0.0.2-schwindlig/#whats-changed) ----------------------------------------------------------------------------------------------------------------- * Most work done as [commits to main](https://codeberg.org/uzu/strudel/commit/2a0d8c3f77ff7b34e82602e2d02400707f367316) * repl + reify functions by @felixroos in [https://codeberg.org/uzu/strudel/pulls/2](https://codeberg.org/uzu/strudel/pulls/2) * Fix path by @yaxu in [https://codeberg.org/uzu/strudel/pulls/3](https://codeberg.org/uzu/strudel/pulls/3) * update readme for local dev by @kindohm in [https://codeberg.org/uzu/strudel/pulls/4](https://codeberg.org/uzu/strudel/pulls/4) * Patternify all the things by @yaxu in [https://codeberg.org/uzu/strudel/pulls/5](https://codeberg.org/uzu/strudel/pulls/5) * krill parser + improved repl by @felixroos in [https://codeberg.org/uzu/strudel/pulls/6](https://codeberg.org/uzu/strudel/pulls/6) * fixed editor crash by @felixroos in [https://codeberg.org/uzu/strudel/pulls/7](https://codeberg.org/uzu/strudel/pulls/7) * timeCat by @yaxu in [https://codeberg.org/uzu/strudel/pulls/8](https://codeberg.org/uzu/strudel/pulls/8) * Bugfix every, and create more top level functions by @yaxu in [https://codeberg.org/uzu/strudel/pulls/9](https://codeberg.org/uzu/strudel/pulls/9) * Failing test for `when` WIP by @yaxu in [https://codeberg.org/uzu/strudel/pulls/10](https://codeberg.org/uzu/strudel/pulls/10) * Added mask() and struct() by @yaxu in [https://codeberg.org/uzu/strudel/pulls/11](https://codeberg.org/uzu/strudel/pulls/11) * Add continuous signals (sine, cosine, saw, etc) by @yaxu in [https://codeberg.org/uzu/strudel/pulls/13](https://codeberg.org/uzu/strudel/pulls/13) * add apply and layer, and missing div/mul methods by @yaxu in [https://codeberg.org/uzu/strudel/pulls/15](https://codeberg.org/uzu/strudel/pulls/15) * higher latencyHint by @felixroos in [https://codeberg.org/uzu/strudel/pulls/16](https://codeberg.org/uzu/strudel/pulls/16) * test: 📩 Add missing dependency and a CI check, to prevent oversights ;p by @puria in [https://codeberg.org/uzu/strudel/pulls/17](https://codeberg.org/uzu/strudel/pulls/17) * fix: 💄 Enhance visualisation of the Tutorial on mobile by @puria in [https://codeberg.org/uzu/strudel/pulls/19](https://codeberg.org/uzu/strudel/pulls/19) * Stateful queries and events (WIP) by @yaxu in [https://codeberg.org/uzu/strudel/pulls/14](https://codeberg.org/uzu/strudel/pulls/14) * Fix resolveState by @yaxu in [https://codeberg.org/uzu/strudel/pulls/22](https://codeberg.org/uzu/strudel/pulls/22) * added \_asNumber + interpret numbers as midi by @felixroos in [https://codeberg.org/uzu/strudel/pulls/21](https://codeberg.org/uzu/strudel/pulls/21) * Update package.json by @ChiakiUehira in [https://codeberg.org/uzu/strudel/pulls/23](https://codeberg.org/uzu/strudel/pulls/23) * packaging by @felixroos in [https://codeberg.org/uzu/strudel/pulls/24](https://codeberg.org/uzu/strudel/pulls/24) New Contributors[](https://workspace/uzu/strudel/website/src/content/blog/release-0.0.2-schwindlig/#new-contributors) ---------------------------------------------------------------------------------------------------------------------- * @felixroos made their first contribution in [https://codeberg.org/uzu/strudel/pulls/2](https://codeberg.org/uzu/strudel/pulls/2) * @kindohm made their first contribution in [https://codeberg.org/uzu/strudel/pulls/4](https://codeberg.org/uzu/strudel/pulls/4) * @puria made their first contribution in [https://codeberg.org/uzu/strudel/pulls/17](https://codeberg.org/uzu/strudel/pulls/17) * @ChiakiUehira made their first contribution in [https://codeberg.org/uzu/strudel/pulls/23](https://codeberg.org/uzu/strudel/pulls/23) **Full Changelog**: [https://codeberg.org/uzu/strudel/commit/2a0d8c3f77ff7b34e82602e2d02400707f367316](https://codeberg.org/uzu/strudel/commit/2a0d8c3f77ff7b34e82602e2d02400707f367316) --- # REPL 🌀 Strudel REPL[](https://strudel.cc/technical-manual/repl/#repl) ======================================================= While Strudel can be used as a library in any JavaScript codebase, its main, reference user interface is the Strudel REPL[1](https://strudel.cc/technical-manual/repl/#user-content-fn-1) , which is a browser-based live coding environment. This live code editor is dedicated to manipulating Strudel patterns while they play. The REPL features built-in visual feedback, highlighting which elements in the patterned (mini-notation) sequences are influencing the event that is currently being played. This feedback is designed to support both learning and live use of Strudel. Besides a UI for playback control and meta information, the main part of the REPL interface is the code editor powered by CodeMirror. In it, the user can edit and evaluate pattern code live, using one of the available synthesis outputs to create music and/or sound art. The control flow of the REPL follows 3 basic steps: 1. The user writes and updates code. Each update transpiles and evaluates it to create a `Pattern` instance 2. While the REPL is running, the `Scheduler` queries the active `Pattern` by a regular interval, generating `Events` (also known as `Haps` in Strudel) for the next time span. 3. For each scheduling tick, all generated `Events` are triggered by calling their `onTrigger` method, which is set by the output. ![](https://codeberg.org/uzu/strudel/raw/branch/talk/talk/public/strudelflow.png) User Code[](https://strudel.cc/technical-manual/repl/#user-code) ----------------------------------------------------------------- To create a `Pattern` from the user code, two steps are needed: 1. Transpile the JS input code to make it functional 2. Evaluate the transpiled code ### Transpilation & Evaluation[](https://strudel.cc/technical-manual/repl/#transpilation--evaluation) In the JavaScript world, using transpilation is a common practise to be able to use language features that are not supported by the base language. Tools like `babel` will transpile code that contains unsupported language features into a version of the code without those features. In the same tradition, Strudel can add a transpilation step to simplify the user code in the context of live coding. For example, the Strudel REPL lets the user create mini-notation patterns using just double quoted strings, while single quoted strings remain what they are: note("c3 [e3 g3]*2") is transpiled to: note(m('c3 [e3 g3]', 5)) Here, the string is wrapped in `m`, which will create a pattern from a mini-notation string. As the second parameter, it gets passed source code location of the string, which enables highlighting active events later. After the transpilation, the code is ready to be evaluated into a `Pattern`. Behind the scenes, the user code string is parsed with `acorn`, turning it into an Abstract Syntax Tree (AST). The AST allows changing the structure of the code before generating the transpiled version using `escodegen`. ### Mini-notation[](https://strudel.cc/technical-manual/repl/#mini-notation) While the transpilation allows JavaScript to express Patterns in a less verbose way, it is still preferable to use the mini-notation as a more compact way to express rhythm. Strudel aims to provide the same mini-notation features and syntax as used in Tidal. The mini-notation parser is implemented using `peggy`, which allows generating performant parsers for Domain Specific Languages (DSLs) using a concise grammar notation. The generated parser turns the mini-notation string into an AST which is used to call the respective Strudel functions with the given structure. For example, `"c3 [e3 g3]*2"` will result in the following calls: seq( reify('c3').withLoc(6, 9), seq(reify('e3').withLoc(10, 12), reify('g3',).withLoc(13, 15)) ) ### Highlighting Locations[](https://strudel.cc/technical-manual/repl/#highlighting-locations) As seen in the examples above, both the mini-notation parser adds the source code locations using `withLoc`. This location is calculated inside the `m` function, as the sum of 2 locations: 1. the location where the mini notation string begins, as obtained from the JS parser 2. the location of the substring inside the mini notation, as obtained from the mini notation parser The sum of both is passed to `withLoc` to tell each element its location, which can be later used for highlighting when it’s active. ### Mini Notation[](https://strudel.cc/technical-manual/repl/#mini-notation-1) Another important part of the user code is the mini notation, which allows to express rhythms in a short manner. * the mini notation is [implemented as a PEG grammar](https://codeberg.org/uzu/strudel/src/branch/talk/packages/mini/krill.pegjs) , living in the [mini package](https://codeberg.org/uzu/strudel/src/branch/main/packages/mini) * it is based on [krill](https://github.com/Mdashdotdashn/krill) by Mdashdotdashn * the peg grammar is used to generate a parser with [peggyjs](https://peggyjs.org/) * the generated parser takes a mini notation string and outputs an AST * the AST can then be used to construct a pattern using the regular Strudel API Here’s an example AST for `c3 [e3 g3]` { "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 } }\ }\ ]\ },\ }\ ] } which translates to `seq(c3, seq(e3, g3))` Vim Keybindings[](https://strudel.cc/technical-manual/repl/#vim-keybindings) ----------------------------------------------------------------------------- See the separate page on Vim shortcuts for a quick reference: [/technical-manual/vim](https://strudel.cc/technical-manual/vim/) Scheduling Events[](https://strudel.cc/technical-manual/repl/#scheduling-events) --------------------------------------------------------------------------------- After an instance of `Pattern` is obtained from the user code, it is used by the scheduler to get queried for events. Once started, the scheduler runs at a fixed interval to query the active pattern for events within the current interval’s time span. A simplified implementation looks like this: let pattern = seq('c3', ['e3', 'g3']); // pattern from user let interval = 0.5; // query interval in seconds let time = 0; // beginning of current time span let minLatency = 0.1; // min time before a hap should trigger setInterval(() => { const haps = pattern.queryArc(time, time + interval); time += interval; // increment time haps.forEach((hap) => { const deadline = hap.whole.begin - time + minLatency; onTrigger(hap, deadline, duration); }); }, interval * 1000); // query each "interval" seconds Note that the above code is simplified for illustrative purposes. The actual implementation has to work around imprecise callbacks of `setInterval`. More about the implementation details can be read in [this blog post](https://loophole-letters.vercel.app/web-audio-scheduling) . The fact that `Pattern.queryArc` is a pure function that maps a time span to a set of events allows us to choose any interval we like without changing the resulting output. It also means that when the pattern is changed from outside, the next scheduling callback will work with the new pattern, keeping its clock running. The latency between the time the pattern is evaluated and the change is heard is between `minLatency` and `interval + minLatency`, in our example between 100ms and 600ms. In Strudel, the current query interval is 50ms with a minLatency of 100ms, meaning the latency is between 50ms and 150ms. Output[](https://strudel.cc/technical-manual/repl/#output) ----------------------------------------------------------- The last step is to trigger each event in the chosen output. This is where the given time and value of each event is used to generate audio or any other form of time based output. The default output of the Strudel REPL is the WebAudio output. To understand what an output does, we first have to understand what control parameters are. ### Control Parameters[](https://strudel.cc/technical-manual/repl/#control-parameters) To be able to manipulate multiple aspects of sound in parallel, so called control parameters are used to shape the value of each event. Example: note('c3 e3') .cutoff(1000) .s('sawtooth') .queryArc(0, 1) .map((hap) => hap.value); /* [\ { note: 'c3', cutoff: 1000, s: 'sawtooth' }\ { note: 'e3', cutoff: 1000, s: 'sawtooth' }\ ] */ Here, the control parameter functions `note`, `cutoff` and `s` are used, where each controls a different property in the value object. Each control parameter function accepts a primitive value, a list of values to be sequenced into a `Pattern`, or a `Pattern`. In the example, `note` gets a `Pattern` from a mini-notation expression (double quoted), while `cutoff` and `s` are given a `Number` and a (single quoted) `String` respectively. Strudel comes with a large default set of control parameter functions that are based on the ones used by Tidal and SuperDirt, focusing on music and audio terminology. It is however possible to create custom control parameters for any purpose: const { x, y } = createParams('x', 'y'); x(sine.range(0, 200)).y(cosine.range(0, 200)); This example creates the custom control parameters `x` and `y` which are then used to form a pattern that descibes the coordinates of a circle. ### Outputs[](https://strudel.cc/technical-manual/repl/#outputs) Now that we know how the value of an event is manipulated using control parameters, we can look at how outputs can use that value to generate anything. The scheduler above was calling the `onTrigger` function which is used to implement the output. A very simple version of the web audio output could look like this: function onTrigger(hap, deadline, duration) { const { note } = hap.value; const time = getAudioContext().currentTime + deadline; const o = getAudioContext().createOscillator(); o.frequency.value = getFreq(note); o.start(time); o.stop(time + event.duration); o.connect(getAudioContext().destination); } The above example will create an `OscillatorNode` for each event, where the frequency is controlled by the `note` param. In essence, this is how the WebAudio API output of Strudel works, only with many more parameters to control synths, samples and effects. I want to help, how do I contribute to the [Docs](https://strudel.cc/technical-manual/docs/) ? Footnotes[](https://strudel.cc/technical-manual/repl/#footnote-label) ---------------------------------------------------------------------- 1. REPL stands for read, evaluate, print/play, loop. It is friendly jargon for an interactive programming interface from computing heritage, usually for a commandline interface but also applied to live coding editors. [↩](https://strudel.cc/technical-manual/repl/#user-content-fnref-1) --- # Erste Sounds 🌀 Strudel Erste Sounds[](https://strudel.cc/de/workshop/first-sounds/#erste-sounds) ========================================================================== Dies ist das erste Kapitel im Strudel Workshop, schön dich an Bord zu haben! Textfelder[](https://strudel.cc/de/workshop/first-sounds/#textfelder) ---------------------------------------------------------------------- Der Workshop ist voller interaktiver Textfelder. Lass uns lernen wie sie funktionieren. Hier ist eins: sound("casio") 1. âŹ†ïž Klicke in das obige Textfeld âŹ†ïž 2. DrĂŒcke `Strg`+`Enter` zum Abspielen 3. Ändere `casio` in `metal` 4. DrĂŒcke `Strg`+`Enter` zum Aktualisieren 5. DrĂŒcke `Strg`+`Punkt` zum Stoppen Mac: `Strg` = `control` oder auch `option` GlĂŒckwunsch, du kannst jetzt live coden! Sounds[](https://strudel.cc/de/workshop/first-sounds/#sounds) -------------------------------------------------------------- Gerade haben wir schon den ersten sound mit `sound` abgespielt: sound("casio") `casio` ist einer von vielen Standard Sounds. Probier ein paar andere Sounds aus: insect wind jazz metal east crow casio space numbers Es kann sein, dass du kurz nichts hörst wĂ€hrend ein neuer Sound lĂ€dt. **Sample Nummer Ă€ndern mit :** Ein Sound kann mehrere Samples (Audio Dateien) enthalten. Du kannst ein anderes Sample wĂ€hlen, indem du `:` und eine Zahl an den Sound-Namen anhĂ€ngst: sound("casio:1") Probiere verschiedene Sound / Zahlen Kombinationen. Ohne Zahl ist gleichbedeutend mit `:0` Jetzt weißt du wie man Sounds abspielt und ihre Sample Nummer einstellt. Vorerst bleiben wir bei den voreingestellten Sounds, spĂ€ter erfahren wir noch wie man eigene benutzt. Drum Sounds[](https://strudel.cc/de/workshop/first-sounds/#drum-sounds) ------------------------------------------------------------------------ Strudel kommt von Haus aus mit einer breiten Auswahl an Drum Sounds: sound("bd hh sd oh") Diese Kombinationen von Buchstaben stehen fĂŒr verschiedene Teile eines Schlagzeugs: ![](https://strudel.cc/img/drumset.png)[original von Pbroks13](https://de.wikipedia.org/wiki/Schlagzeug#/media/Datei:Drum_set.svg) * `bd` = **b**ass **d**rum - Basstrommel * `sd` = **s**nare **d**rum - Schnarrtrommel * `rim` = **rim**shot - Rahmenschlag * `hh` = **h**i**h**at - Hallo Hut * `oh` = **o**pen **h**ihat - Offener Hallo Hut * `lt` = **l**ow tom * `mt` = **m**iddle tom * `ht` = **h**igh tom * `rd` = **r**i**d**e cymbal * `cr` = **cr**ash cymbal Probier verschiedene Sounds aus! Wir können den Charakter des Drum Sounds verĂ€ndern, indem wir mit `bank` die Drum Machine auswĂ€hlen: sound("bd hh sd oh").bank("RolandTR909") In diesem Beispiel ist `RolandTR909` der Name der Drum Machine, die eine prĂ€gende Rolle fĂŒr House und Techno Musik spielte. Ändere `RolandTR909` in * `AkaiLinn` * `RhythmAce` * `RolandTR808` * `RolandTR707` * `ViscoSpaceDrum` Es gibt noch viel mehr, aber das sollte fĂŒrs Erste reichen.. đŸŠ„ Tipp fĂŒr faule: Mach Doppel-Klick auf einen Namen um ihn zu markieren. Dann kannst du ihn mit `Strg`+`C` kopieren und mit `Strg`+`V` einfĂŒgen. Sequenzen / Sequences[](https://strudel.cc/de/workshop/first-sounds/#sequenzen--sequences) ------------------------------------------------------------------------------------------- Im letzten Beispiel haben wir schon gesehen dass man mehrere Sounds hintereinander abspielen kann wenn man sie durch Leerzeichen trennt: sound("bd hh sd hh") Beachte wie der aktuell gespielte Sound im Code markiert und auch darunter visualisiert wird. Versuch noch mehr Sounds hinzuzfĂŒgen! **Je lĂ€nger die Sequence, desto schneller** sound("bd bd hh bd rim bd hh bd") Der Inhalt einer Sequence wird in einen sogenannten Cycle (=Zyklus) zusammengequetscht. Ein Cycle ist standardmĂ€ĂŸig 2 Sekunden lang. **Eins pro Cycle mit `< .. >`** Hier ist die gleiche Sequence, aber dieses mal umgeben von `< .. >` (angle brackets): sound("") Jetzt spielt nur ein Sound pro Cycle. Mit diesen Klammern bleibt das Tempo immer gleich, ganz egal wieviele Elemente enhalten sind! Das ist jetzt aber etwas langsam, machen wir es schneller mit `*`: sound("\*8") Die `*8` macht die ganze Sequenz 8 mal so schnell. Warte mal, ist das jetzt nicht das gleiche Ergebnis wie ohne `< ... >*8`? WofĂŒr ist das dann gut? Korrekt, der echte Vorteil dieser Schreibweise zeigt sich wenn du Elemente entfernst oder hinzufĂŒgst. Versuch es mal! Ändere auch mal die Zahl am Ende um das Tempo zu verĂ€ndern. **Tempo Ă€ndern mit `setcpm`** setcpm(90/4) sound("\*8") cpm = **c**ycles per **m**inute = Cycles pro Minute Das Tempo is standardmĂ€ĂŸig is 30 Cycles pro Minute = 120/4 = 1 Cycle alle 2 Sekunden In taditioneller Musik-Terminologie könnte man sagen, das Pattern oben besteht aus 8tel Noten auf 90bpm im 4/4 Takt. Kein Sorge wenn dir diese Begriffe nichts sagen, das ist nicht notwendig um mit Strudel Musik zu machen. Wir werden spĂ€ter noch mehr Möglichkeiten kennen lernen das Tempo zu verĂ€ndern. **Pausen mit ’-’ oder ’~’** sound("bd hh - rim") Du siehst wahrscheinlich auch Patterns von anderen Leuten mit ’~’ als Pausensymbol. Besonders fĂŒr deutsche Tastaturen ist `-` eine Alternative zum schwer tippbaren `~`. **Unter-Sequenzen mit \[Klammern\]** sound("bd \[hh hh\] rim \[hh hh\]") Der Inhalt der Klammer wird ebenfalls zusammengequetscht! FĂŒge noch mehr Sounds in die Klammern ein! Genau wie bei der ganzen Sequence wird eine Unter-Sequence schneller je mehr drin ist. **Multiplikation: Dinge schneller machen** sound("bd hh\*2 sd hh\*3") **Multiplikation: Vieeeeeeel schneller** sound("bd hh\*16 sd hh\*8") Tonhöhe = sehr schneller Rhythmus **Multiplikation: Ganze Unter-Sequences schneller machen** sound("bd \[sd hh\]\*2") Bolero: setcpm(10) sound("sd sd\*3 sd sd\*3 sd sd sd sd\*3 sd sd\*3 sd\*3 sd\*3") **Unter-Unter-Sequenzen mit \[\[Klammern\]\]** sound("bd \[\[rim rim\] hh\]") Du kannst so tief verschachteln wie du willst! **Parallele Sequenzen mit Komma** sound("hh hh hh, bd casio") Du kannst so viele Kommas benutzen wie du möchtest: sound("hh hh hh, bd bd, - casio") Kommas können auch in Unter-Sequenzen verwendet werden: sound("hh hh hh, bd \[bd,casio\]") Ist dir aufgefallen dass sich die letzten beiden Beispiele gleich anhören? Es kommt öfter vor, dass man die gleiche Idee auf verschiedene Arten ausdrĂŒcken kann. **Mehrere Zeilen schreiben mit \` (backtick)** sound(\`bd\*2, - cp, - - - oh, hh\*4, \[- casio\]\*2\`) Ob man ” oder \` benutzt ist nur eine Frage der Übersichtlichkeit. **Sample Nummer separat auswĂ€hlen** Benutzt man nur einen Sound mit unterschiedlichen Sample Nummer sieht das so aus: sound("jazz:0 jazz:1 \[jazz:4 jazz:2\] jazz:3\*2") Das gleiche kann man auch so schreiben: n("0 1 \[4 2\] 3\*2").sound("jazz") RĂŒckblick[](https://strudel.cc/de/workshop/first-sounds/#r%C3%BCckblick) ------------------------------------------------------------------------- Wir haben jetzt die Grundlagen der sogenannten Mini-Notation gelernt, der Rhythmus-Sprache von Tidal. Das haben wir bisher gelernt: | Concept | Syntax | Example | | --- | --- | --- | | Sequenz | Leerzeichen | sound("bd bd sd hh") | | Sound Nummer | :x | sound("hh:0 hh:1 hh:2 hh:3") | | Pausen | \- | sound("metal - jazz jazz:1") | | Unter-Sequenzen | \[\] | sound("bd wind \[metal jazz\] hh") | | Unter-Unter-Sequenzen | \[\[\]\] | sound("bd \[metal \[jazz sd\]\]") | | Schneller | \* | sound("bd sd\*2 cp\*3") | | Parallel | , | sound("bd\*2, hh\*2 \[hh oh\]") | Die mit Apostrophen umgebene Mini-Notation benutzt man normalerweise in einer sogenannten Funktion. Die folgenden Funktionen haben wir bereits gesehen: | Name | Description | Example | | --- | --- | --- | | sound | Spielt den Sound mit dem Namen | sound("bd sd") | | bank | WĂ€hlt die Soundbank / Drum Machine | sound("bd sd").bank("RolandTR909") | | setcpm | Tempo in **C**ycles **p**ro **M**inute | setcpm(90); sound("bd sd") | | n | Sample Nummer | n("0 1 4 2").sound("jazz") | Beispiele[](https://strudel.cc/de/workshop/first-sounds/#beispiele) -------------------------------------------------------------------- **Einfacher Rock Beat** setcpm(100/2) sound("bd sd, hh\*4").bank("RolandTR505") **Klassischer House** sound("bd\*2, - cp, \[- hh\]\*2").bank("RolandTR909") Ist die aufgefallen dass die letzten 2 Patterns extrem Ă€hnlich sind? Bestimmte Drum Patterns werden oft genreĂŒbergreifend wiederverwendet. We Will Rock you setcpm(81/2) sound("bd\*2 cp").bank("RolandTR707") **Yellow Magic Orchestra - Firecracker** sound("bd sd, - - - hh - hh - -, - perc - perc:1\*2") .bank("RolandCompurhythm1000") **Nachahmung eines 16 step sequencers** setcpm(90/4) sound(\` \[- - oh - \] \[- - - - \] \[- - - - \] \[- - - - \], \[hh hh - - \] \[hh - hh - \] \[hh - hh - \] \[hh - hh - \], \[- - - - \] \[cp - - - \] \[- - - - \] \[cp - - - \], \[bd - - - \] \[- - - bd\] \[- - bd - \] \[- - - bd\] \`) **Noch eins** setcpm(88/4) sound(\` \[- - - - \] \[- - - - \] \[- - - - \] \[- - oh:1 - \], \[hh hh hh hh\] \[hh hh hh hh\] \[hh hh hh hh\] \[hh hh - - \], \[- - - - \] \[cp - - - \] \[- - - - \] \[- cp - - \], \[bd bd - - \] \[- - bd - \] \[bd bd - bd \] \[- - - - \] \`).bank("RolandTR808") **Nicht so typische Drums** setcpm(100/2) s(\`jazz\*2, insect \[crow metal\] - -, - space:4 - space:1, - wind\`) Jetzt haben wir eine grundlegende Ahnung davon wie man mit Strudel Beats baut! Im nĂ€chsten Kapitel werden wir ein paar [Töne spielen](https://strudel.cc/de/workshop/first-notes/) . --- # Erste Töne 🌀 Strudel Erste Töne[](https://strudel.cc/de/workshop/first-notes/#erste-t%C3%B6ne) ========================================================================== Jetzt schauen wir uns an wie man mit Tönen mit der `note` Funktion spielt. Zahlen und Noten[](https://strudel.cc/de/workshop/first-notes/#zahlen-und-noten) --------------------------------------------------------------------------------- **Töne mit Zahlen** note("48 52 55 59").sound("piano") Probiere verschiedene Zahlen aus! Versuch auch mal Kommazahlen, z.B. 55.5 (beachte die englische Schreibweise von Kommazahlen mit ”.” anstatt ”,”) **Töne mit Buchstaben** note("c e g b").sound("piano") Versuch verschiedene Buchstaben aus (a - g). Findest du Melodien die auch gleichzeitig ein Wort sind? Tipp: ☕ 🙈 🧚 **Vorzeichen** note("db eb gb ab bb").sound("piano") note("c# d# f# g# a#").sound("piano") **Andere Oktaven** note("c2 e3 g4 b5").sound("piano") Probiere verschiedene Oktaven aus (1-8) Normalerweise kommen Leute die keine Noten besser mit Zahlen anstatt mit Buchstaben zurecht. Daher benutzen die folgenden Beispiele meistens Zahlen. SpĂ€ter sehen wir auch noch ein paar Tricks die es uns erleichtern Töne zu spielen die zueinander passen. Den Sound verĂ€ndern[](https://strudel.cc/de/workshop/first-notes/#den-sound-ver%C3%A4ndern) -------------------------------------------------------------------------------------------- Genau wie bei gerĂ€uschhaften Sounds können wir den Klang unserer Töne mit `sound` verĂ€ndern: note("36 43, 52 59 62 64").sound("piano") Probier ein paar sounds aus: * gm\_electric\_guitar\_muted - E-Gitarre * gm\_acoustic\_bass - Kontrabass * gm\_voice\_oohs - Chords * gm\_blown\_bottle - Flasche * sawtooth - SĂ€gezahn-Welle * square - Rechteck-Welle * triangle - Dreieck-Welle * Was ist mit bd, sd oder hh? * Entferne `.sound('...')` komplett **Zwischen Sounds hin und her wechseln** note("48 67 63 \[62, 58\]") .sound("piano gm\_electric\_guitar\_muted") **Gleichzeitige Sounds** note("48 67 63 \[62, 58\]") .sound("piano, gm\_electric\_guitar\_muted") Die patterns in `note` und `sound` werden kombiniert! Wir schauen uns spĂ€ter noch mehr Möglichkeiten an wie man patterns kombiniert. LĂ€ngere Sequenzen[](https://strudel.cc/de/workshop/first-notes/#l%C3%A4ngere-sequenzen) ---------------------------------------------------------------------------------------- **Sequenzen verlangsamen mit `/`** note("\[36 34 41 39\]/4").sound("gm\_acoustic\_bass") Das `/4` spielt die Sequenz 4 mal so langsam, also insgesamt 4 cycles = 8s. Jede Note ist nun also 2s lang. Schreib noch mehr Töne in die Klammern und achte darauf dass es schneller wird. Wenn eine Sequenz unabhĂ€ngig von ihrem Inhalt immer gleich schnell bleiben soll, gibt es noch eine andere Art Klammern: **Eins pro Cycle per < >** Im letzten Kapitel haben wir schon gelernt dass `< ... >` (angle brackets) nur ein Element pro Cycle spielt. Das ist fĂŒr Melodien auch sehr nĂŒtzlich: note("<36 34 41 39>").sound("gm\_acoustic\_bass") FĂŒg noch mehr Töne hinzu und achte darauf wie das Tempo gleich bleibt! TatsĂ€chlich sind diese Klammern nur eine AbkĂŒrzung: `` = `[a b c]/3` `` = `[a b c d]/4` usw.. **Eine Sequenz pro Cycle** note("<\[36 48\] \[34 46\] \[41 53\] \[39 51\]>") .sound("gm\_acoustic\_bass") oder auch
 note("<\[36 48\]\*4 \[34 46\]\*4 \[41 53\]\*4 \[39 51\]\*4>/2") .sound("gm\_acoustic\_bass") **Alternativen** Ähnlich wie Unter-Sequenzen, kann auch `<...>` innerhalb einer Sequenz verwendet werden: note("60 <63 62 65 63>") .sound("gm\_xylophone") Das ist auch praktisch fĂŒr atonale Sounds: sound("bd\*2, ~ , \[~ hh\]\*2") .bank("RolandTR909") Skalen[](https://strudel.cc/de/workshop/first-notes/#skalen) ------------------------------------------------------------- Es kann mĂŒhsam sein die richtigen Noten zu finden wenn man alle zur VerfĂŒgung hat. Mit Skalen ist das einfacher: n("0 2 4 <\[6,8\] \[7,9\]>") .scale("C:minor").sound("piano") Probier verschiedene Zahlen aus. Jede klingt gut! Probier verschiedene Skalen: * C:major * A2:minor * D:dorian * G:mixolydian * A2:minor:pentatonic * F:major:pentatonic **Automatisierte Skalen** Wie alle Funktionen können auch Skalen mit einem Pattern automatisiert werden: n("<0 -3>, 2 4 <\[6,8\] \[7,9\]>") .scale("/4") .sound("piano") Wenn du keine Ahnung hast was die Skalen bedeuten, keine Sorge. Es sind einfach nur Namen fĂŒr verschiedene Gruppen von Tönen die gut zusammenpassen. Nimm dir Zeit um herauszufinden welche Skalen du magst. Wiederholen und VerlĂ€ngern[](https://strudel.cc/de/workshop/first-notes/#wiederholen-und-verl%C3%A4ngern) ---------------------------------------------------------------------------------------------------------- **VerlĂ€ngern mit @** note("c@3 eb").sound("gm\_acoustic\_bass") Ein Element ohne `@` ist gleichbedeutend mit `@1`. Im Beispiel ist `c` drei Einheiten lang, `eb` nur eine. Spiel mit der LĂ€nge! **Unter-Sequenzen verlĂ€ngern** n("<\[4@2 4\] \[5@2 5\] \[6@2 6\] \[5@2 5\]>\*2") .scale("/4") .sound("gm\_acoustic\_bass") Dieser Groove wird auch `shuffle` genannt. Jeder Schlag enthĂ€lt 2 Töne, wobei der erste doppelt so lang wie der zweite ist. Das nennt man auch manchmal `triolen swing`. Es ist ein typischer Rhythmus im Blues und Jazz. **Wiederholen** note("c!2 \[eb,\]").sound("piano") Wechsel zwischen `!`, `*` und `@` hin und her. Was ist der Unterschied? RĂŒckblick[](https://strudel.cc/de/workshop/first-notes/#r%C3%BCckblick) ------------------------------------------------------------------------ Das haben wir in diesem Kapitel gelernt: | Concept | Syntax | Example | | --- | --- | --- | | Verlangsamen | / | note("\[c a f e\]/2") | | Alternativen | <> | note("c ") | | VerlĂ€ngern | @ | note("c@3 e") | | Wiederholen | ! | note("c!3 e") | Neue Funktionen: | Name | Description | Example | | --- | --- | --- | | note | Tonhöhe als Buchstabe oder Zahl | note("b g e c").sound("piano") | | scale | Interpretiert `n` als Skalenstufe | n("6 4 2 0").scale("C:minor").sound("piano") | | stack | Spiele mehrere Patterns parallel (s.u.) | stack(s("bd sd"),note("c eb g")) | Beispiele[](https://strudel.cc/de/workshop/first-notes/#beispiele) ------------------------------------------------------------------- **Bassline** note("<\[c2 c3\]\*4 \[bb1 bb2\]\*4 \[f2 f3\]\*4 \[eb2 eb3\]\*4>/2") .sound("gm\_synth\_bass\_1") .lpf(800) // <-- we'll learn about this soon **Melodie** n(\`< \[~ 0\] 2 \[0 2\] \[~ 2\] \[~ 0\] 1 \[0 1\] \[~ 1\] \[~ 0\] 3 \[0 3\] \[~ 3\] \[~ 0\] 2 \[0 2\] \[~ 2\] >\*2\`).scale("C4:minor") .sound("gm\_synth\_strings\_1") **Drums** sound("bd\*2, ~ , \[~ hh\]\*2") .bank("RolandTR909") **Wenn es doch nur einen Weg gĂ€be das alles gleichzeitig zu spielen
** Das geht mit `stack` 😙 stack( note("<\[c2 c3\]\*4 \[bb1 bb2\]\*4 \[f2 f3\]\*4 \[eb2 eb3\]\*4>/2") .sound("gm\_synth\_bass\_1").lpf(800), n(\`< \[~ 0\] 2 \[0 2\] \[~ 2\] \[~ 0\] 1 \[0 1\] \[~ 1\] \[~ 0\] 3 \[0 3\] \[~ 3\] \[~ 0\] 2 \[0 2\] \[~ 2\] >\*2\`).scale("C4:minor") .sound("gm\_synth\_strings\_1"), sound("bd\*2, ~ , \[~ hh\]\*2") .bank("RolandTR909") ) Das hört sich doch langsam nach echter Musik an! Wir haben Sounds, wir haben Töne.. noch ein Puzzleteil fehlt: [Effekte](https://strudel.cc/de/workshop/first-effects/) --- # Erste Effekte 🌀 Strudel Erste Effekte[](https://strudel.cc/de/workshop/first-effects/#erste-effekte) ============================================================================= Ein paar grundlegende Effekte[](https://strudel.cc/de/workshop/first-effects/#ein-paar-grundlegende-effekte) ------------------------------------------------------------------------------------------------------------- **low-pass filter** note("<\[c2 c3\]\*4 \[bb1 bb2\]\*4 \[f2 f3\]\*4 \[eb2 eb3\]\*4>/2") .sound("sawtooth").lpf(800) lpf = **l**ow **p**ass **f**ilter * Ändere `lpf` in 200. Hörst du, wie der Bass dumpfer klingt? Es klingt so, als wĂŒrde die Musik hinter einer geschlossenen TĂŒr spielen đŸšȘ * Lass uns nun die TĂŒr öffnen: Ändere `lpf` in 5000. Der Klang wird dadurch viel heller und schĂ€rfer ✹đŸȘ© **filter automatisieren** note("<\[c2 c3\]\*4 \[bb1 bb2\]\*4 \[f2 f3\]\*4 \[eb2 eb3\]\*4>/2") .sound("sawtooth").lpf("200 1000") * FĂŒg noch mehr `lpf` Werte hinzu * Das Pattern in `lpf` Ă€ndert nicht den Rhythmus der Basslinie SpĂ€ter sehen wir, wie man mit Wellenformen Dinge automatisieren kann. **vowel = Vokal** note("<\[c3,g3,e4\] \[bb2,f3,d4\] \[a2,f3,c4\] \[bb2,g3,eb4\]>/2") .sound("sawtooth").vowel("/2") **gain = VerstĂ€rkung** stack( sound("hh\*8").gain("\[.25 1\]\*2"), sound("bd\*2,~ sd:1") ) Bei Rhythmen ist die Dynamik (= VerĂ€nderungen der LautstĂ€rke) sehr wichtig. * Entferne `.gain(...)` und achte darauf, wie es viel flacher klingt. * Mach es rĂŒckgĂ€ngig (strg+z dann strg+enter) **stacks in stacks** Lass uns die obigen Beispiele kombinieren: stack( stack( sound("hh\*8").gain("\[.25 1\]\*2"), sound("bd\*2,~ sd:1") ), note("<\[c2 c3\]\*4 \[bb1 bb2\]\*4 \[f2 f3\]\*4 \[eb2 eb3\]\*4>/2") .sound("sawtooth").lpf("200 1000"), note("<\[c3,g3,e4\] \[bb2,f3,d4\] \[a2,f3,c4\] \[bb2,g3,eb4\]>/2") .sound("sawtooth").vowel("/2") ) Versuche die einzelnen Teile innerhalb von `stack` zu erkennen. Schau dir an wie die Kommas gesetzt sind. Die 3 Teile (Drums, Bass, Akkorde) sind genau wie vorher, nur in einem `stack`, getrennt durch Kommas. **Den Sound formen mit ADSR-HĂŒllkurve** note("") .sound("sawtooth").lpf(600) .attack(.1) .decay(.1) .sustain(.25) .release(.2) Versuche herauszufinden, was die Zahlen machen. Probier folgendes: * attack: `.5` vs `0` * decay: `.5` vs `0` * sustain: `1` vs `.25` vs `0` * release: `0` vs `.5` vs `1` Kannst du erraten, was die einzelnen Werte machen? Lösung anzeigen **adsr-Kurznotation** note("") .sound("sawtooth").lpf(600) .adsr(".1:.1:.5:.2") **delay = Verzögerung** stack( note("~ \[<\[d3,a3,f4\]!2 \[d3,bb3,g4\]!2> ~\]") .sound("gm\_electric\_guitar\_muted"), sound("").bank("RolandTR707") ).delay(".5") Probier verschiedene `delay` Werte zwischen 0 und 1. Übrigens: `.5` ist kurz fĂŒr `0.5`. Was passiert, wenn du `.delay(".8:.125")` schreibst? Kannst du erraten, was die zweite Zahl macht? Was passiert, wenn du `.delay(".8:.06:.8")` schreibst? Kannst du erraten, was die dritte Zahl macht? Lösung anzeigen **room aka reverb = Hall** n("<4 \[3@3 4\] \[<2 0> ~@16\] ~>/2") .scale("D4:minor").sound("gm\_accordion:2") .room(2) Spiel mit verschiedenen Werten. FĂŒg auch ein Delay hinzu! **kleiner Dub-Tune** stack( note("~ \[<\[d3,a3,f4\]!2 \[d3,bb3,g4\]!2> ~\]") .sound("gm\_electric\_guitar\_muted").delay(.5), sound("").bank("RolandTR707").delay(.5), n("<4 \[3@3 4\] \[<2 0> ~@16\] ~>/2") .scale("D4:minor").sound("gm\_accordion:2") .room(2).gain(.5) ) FĂŒr echten Dub fehlt noch der Bass: stack( note("~ \[<\[d3,a3,f4\]!2 \[d3,bb3,g4\]!2> ~\]") .sound("gm\_electric\_guitar\_muted").delay(.5), sound("").bank("RolandTR707").delay(.5), n("<4 \[3@3 4\] \[<2 0> ~@16\] ~>/2") .scale("D4:minor").sound("gm\_accordion:2") .room(2).gain(.4), n("<0 \[~ 0\] 4 \[3 2\] \[0 ~\] \[0 ~\] <0 2> ~>\*2") .scale("D2:minor") .sound("sawtooth,triangle").lpf(800) ) FĂŒg `.hush()` ans Ende eines Patterns im stack
 **pan = Panorama** sound("numbers:1 numbers:2 numbers:3 numbers:4") .pan("0 0.3 .6 1") .slow(2) **speed = Geschwindigkeit** sound("bd rim").speed("<1 2 -1 -2>").room(.2) **fast and slow = schnell und langsam** Mit `fast` und `slow` kann man das Tempo eines Patterns außerhalb der Mini-Notation Ă€ndern: sound("bd\*2,~ rim").slow(2) Ändere den `slow`\-Wert. Ersetze `slow` durch `fast`. Was passiert, wenn du den Wert automatisierst? z.b. `.fast("<1 [2 4]>")` ? Übrigens, innerhalb der Mini-Notation: `fast` ist `*` und `slow` ist `/`. sound("\[bd\*2,~ rim\]\*<1 \[2 4\]>") Automation mit Signalen[](https://strudel.cc/de/workshop/first-effects/#automation-mit-signalen) ------------------------------------------------------------------------------------------------- Anstatt Werte schrittweise zu automatisieren, können wir auch sogenannte Signale benutzen: sound("hh\*16").gain(sine) Die grundlegenden Wellenformen sind `sine`, `saw`, `square`, `tri` 🌊 Probiere auch die zufĂ€lligen Signale `rand` und `perlin`! Der `gain`\-Wert (VerstĂ€rkung) wird in der Visualisierung als Transparenz dargestellt. **Bereich Ă€ndern mit `range`** Signale bewegen sich standardmĂ€ĂŸig zwischen 0 und 1. Wir können das mit `range` Ă€ndern: sound("hh\*8").lpf(saw.range(500, 2000)) `range` ist nĂŒtzlich wenn wir Funktionen mit einem anderen Wertebereich als 0 und 1 automatisieren wollen (z.b. `lpf`) Was passiert wenn du die beiden Werte vertauschst? Wir können die Geschwindigkeit der Automation mit slow / fast Ă€ndern: note("<\[c2 c3\]\*4 \[bb1 bb2\]\*4 \[f2 f3\]\*4 \[eb2 eb3\]\*4>/2") .sound("sawtooth") .lpf(sine.range(100, 2000).slow(8)) Die ganze Automation braucht nun 8 cycle bis sie sich wiederholt. RĂŒckblick[](https://strudel.cc/de/workshop/first-effects/#r%C3%BCckblick) -------------------------------------------------------------------------- | Name | Beispiel | | --- | --- | | lpf | note("c2 c3").s("sawtooth").lpf("<400 2000>") | | vowel | note("c3 eb3 g3").s("sawtooth").vowel("") | | gain | s("hh\*8").gain("\[.25 1\]\*2") | | delay | s("bd rim").delay(.5) | | room | s("bd rim").room(.5) | | pan | s("bd rim").pan("0 1") | | speed | s("bd rim").speed("<1 2 -1 -2>") | | range | s("hh\*16").lpf(saw.range(200,4000)) | Lass uns nun die fĂŒr Tidal typischen [Pattern-Effekte anschauen](https://strudel.cc/de/workshop/pattern-effects/) . --- # Pattern-Effekte 🌀 Strudel Pattern-Effekte[](https://strudel.cc/de/workshop/pattern-effects/#pattern-effekte) =================================================================================== Bis jetzt sind die meisten Funktionen, die wir kennengelernt haben, Ă€hnlich wie Funktionen in anderen Musik Programmen: Sequencing von Sounds, Noten und Effekten. In diesem Kapitel beschĂ€ftigen wir uns mit Funktionen die weniger herkömmlich oder auch einzigartig sind. **rev = rĂŒckwĂ€rts abspielen** n("0 1 \[4 3\] 2").sound("jazz").rev() **jux = einen stereo kanal modifizieren** n("0 1 \[4 3\] 2").sound("jazz").jux(rev) So wĂŒrde man das ohne `jux` schreiben: stack( n("0 1 \[4 3\] 2").sound("jazz").pan(0), n("0 1 \[4 3\] 2").sound("jazz").pan(1).rev() ) Lass uns visualisieren, was hier passiert: stack( n("0 1 \[4 3\] 2").sound("jazz").pan(0).color("cyan"), n("0 1 \[4 3\] 2").sound("jazz").pan(1).color("magenta").rev() ) Schreibe `//` vor eine der beiden Zeilen im `stack`! **mehrere tempos** note("c2, eb3 g3 \[bb3 c4\]").sound("piano").slow("1,2,3") Das hat den gleichen Effekt, wie: stack( note("c2, eb3 g3 \[bb3 c4\]").s("piano").slow(1).color('cyan'), note("c2, eb3 g3 \[bb3 c4\]").s("piano").slow(2).color('magenta'), note("c2, eb3 g3 \[bb3 c4\]").s("piano").slow(3).color('yellow') ) Schreibe wieder `//` vor eine oder mehrere Zeilen im `stack`. **add = addieren** note("c2 \[eb3,g3\]".add("<0 <1 -1>>")) .color(">").adsr("\[.1 0\]:.2:\[1 0\]") .sound("gm\_acoustic\_bass").room(.5) Addiert man eine Zahl zu einer Note, verhĂ€lt sich diese wie eine Zahl. z.B. c4 = 60, also ist c4 + 2 = 62 Man kann auch mehrmals addieren: note("c2 \[eb3,g3\]".add("<0 <1 -1>>").add("0,7")) .color(">").adsr("\[.1 0\]:.2:\[1 0\]") .sound("gm\_acoustic\_bass").room(.5) **add + scale** n("<0 \[2 4\] <3 5> \[~ <4 1>\]>\*2".add("<0 \[0,2,4\]>/4")) .scale("C5:minor").release(.5) .sound("gm\_xylophone").room(.5) **Alles zusammen** stack( n("<0 \[2 4\] <3 5> \[~ <4 1>\]>\*2".add("<0 \[0,2,4\]>/4")) .scale("C5:minor") .sound("gm\_xylophone") .room(.4).delay(.125), note("c2 \[eb3,g3\]".add("<0 <1 -1>>")) .adsr("\[.1 0\]:.2:\[1 0\]") .sound("gm\_acoustic\_bass") .room(.5), n("0 1 \[2 3\] 2").sound("jazz").jux(rev).slow(2) ) **ply** sound("hh, bd rim").bank("RolandTR707").ply(2) das ist wie: sound("hh\*2, bd\*2 rim\*2").bank("RolandTR707") Probier `ply` mit einem pattern zu automatisieren, z.b. `"<1 2 1 3>"` **off** n("<0 \[4 <3 2>\] <2 3> \[~ 1\]>" .off(1/8, x=>x.add(4)) //.off(1/4, x=>x.add(7)) ).scale("/4") .s("triangle").room(.5).dec(.1).delay(.5) In der Notation `x=>x.`, ist `x` das Pattern, das wir bearbeiten. `off` ist auch nĂŒtzlich fĂŒr Sounds: s("bd sd,\[~ hh\]\*2").bank("CasioRZ1") .off(1/8, x=>x.speed(1.5).gain(.25)) | Name | Beschreibung | Beispiel | | --- | --- | --- | | rev | rĂŒckwĂ€rts | n("0 2 4 6").scale("C:minor").rev() | | jux | einen Stereo-Kanal modifizieren | n("0 2 4 6").scale("C:minor").jux(rev) | | add | addiert Zahlen oder Noten | n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor") | | ply | multipliziert jedes Element x mal | s("bd sd").ply("<1 2 3>") | | off | verzögert eine modifizierte Kopie | s("bd sd, hh\*4").off(1/8, x=>x.speed(2)) | --- # Recap 🌀 Strudel Workshop RĂŒckblick[](https://strudel.cc/de/workshop/recap/#workshop-r%C3%BCckblick) ==================================================================================== Diese Seite ist eine Auflistung aller im Workshop vorgestellten Funktionen. Mini Notation[](https://strudel.cc/de/workshop/recap/#mini-notation) --------------------------------------------------------------------- | Konzept | Syntax | Beispiel | | --- | --- | --- | | Sequenz | space | sound("bd bd sn hh") | | Sample-Nummer | :x | sound("hh:0 hh:1 hh:2 hh:3") | | Pausen | ~ | sound("metal ~ jazz jazz:1") | | Unter-Sequenzen | \[\] | sound("bd wind \[metal jazz\] hh") | | Unter-Unter-Sequenzen | \[\[\]\] | sound("bd \[metal \[jazz sn\]\]") | | Schneller | \* | sound("bd sn\*2 cp\*3") | | Verlangsamen | / | note("\[c a f e\]/2") | | Parallel | , | sound("bd\*2, hh\*2 \[hh oh\]") | | Alternieren | <> | note("c ") | | VerlĂ€ngern | @ | note("c@3 e") | | Wiederholen | ! | note("c!3 e") | Sounds[](https://strudel.cc/de/workshop/recap/#sounds) ------------------------------------------------------- | Name | Beschreibung | Beispiel | | --- | --- | --- | | sound | spielt den Sound mit Namen | sound("bd sd") | | bank | wĂ€hlt die Soundbank | sound("bd sd").bank("RolandTR909") | | n | wĂ€hlt Sample mit Nummer | n("0 1 4 2").sound("jazz") | Noten[](https://strudel.cc/de/workshop/recap/#noten) ----------------------------------------------------- | Name | Beschreibung | Beispiel | | --- | --- | --- | | note | wĂ€hlt Note per Zahl oder Buchstabe | note("b g e c").sound("piano") | | n + scale | wĂ€hlt Note n in Skala | n("6 4 2 0").scale("C:minor").sound("piano") | | stack | spielt mehrere Patterns parallel | stack(s("bd sd"),note("c eb g")) | Audio-Effekte[](https://strudel.cc/de/workshop/recap/#audio-effekte) --------------------------------------------------------------------- | Name | Beispiele | | --- | --- | | lpf | note("c2 c3").s("sawtooth").lpf("<400 2000>") | | vowel | note("c3 eb3 g3").s("sawtooth").vowel("") | | gain | s("hh\*8").gain("\[.25 1\]\*2") | | delay | s("bd rim").delay(.5) | | room | s("bd rim").room(.5) | | pan | s("bd rim").pan("0 1") | | speed | s("bd rim").speed("<1 2 -1 -2>") | | range | s("hh\*16").lpf(saw.range(200,4000)) | Pattern-Effekte[](https://strudel.cc/de/workshop/recap/#pattern-effekte) ------------------------------------------------------------------------- | Name | Beschreibung | Beispiel | | --- | --- | --- | | setcpm | Tempo in Cycles pro Minute | setcpm(90); sound("bd sd") | | fast | schneller | sound("bd sd").fast(2) | | slow | langsamer | sound("bd sd").slow(2) | | rev | rĂŒckwĂ€rts | n("0 2 4 6").scale("C:minor").rev() | | jux | einen Stereo-Kanal modifizieren | n("0 2 4 6").scale("C:minor").jux(rev) | | add | addiert Zahlen oder Noten | n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor") | | ply | jedes Element schneller machen | s("bd sd").ply("<1 2 3>") | | off | verzögert eine modifizierte Kopie | s("bd sd, hh\*4").off(1/8, x=>x.speed(2)) | --- # Strudel REPL 9 1 $: s("\[bd \]\*2").bank("tr909").dec(.4) [mastodon](https://social.toplap.org/@strudel) --- # Sounds 🌀 Strudel Sounds[](https://strudel.cc/learn/sounds/#sounds) ================================================== We can play sounds with `s`, in two different ways: * `s` can trigger audio samples, where a sound file is loaded in the background and played back: s("bd hh sd hh") * `s` can trigger audio synthesisers, which are synthesised in real-time using code also running in the background: s("sawtooth square triangle sine") You can learn more about both of these approaches in the pages [Synths](https://strudel.cc/learn/synths/) and [Samples](https://strudel.cc/learn/samples/) . Combining notes and sounds[](https://strudel.cc/learn/sounds/#combining-notes-and-sounds) ========================================================================================== In both of the above cases, we are no longer directly controlling the `note`/`freq` of the sound heard via `s`, as we were in the [Notes](https://strudel.cc/workshop/first-notes/) page. So how can we both control the sound and the pitch? We can _combine_ `note`/`freq` with `s` to change the sound of our pitches: note("a3 c#4 e4 a4").s("sawtooth") note("57 61 64 69").s("sine") freq("220 275 330 440").s("triangle") The last example will actually sound the same with or without `s`, because `triangle` is the default value for `s`. What about combining different notes with different sounds at the same time? freq("220 275 330 440 550").s("triangle sawtooth sine") Hmm, something interesting is going on there, related to there being five notes and three sounds. Let’s now take a step back and think about the Strudel [Code](https://strudel.cc/learn/code/) we’ve been hearing so far. --- # 🌀 Strudel Community Bakery Community Bakery ================ This page contains all the strudel patterns baked by the community. Add your own by clicking the "Share" button in the REPL. Have fun, and please share some of what you create with the community. Featured Patterns ----------------- Last Creations -------------- --- # Vim Shortcuts 🌀 Strudel Vim Shortcuts in the REPL[](https://strudel.cc/technical-manual/vim/#vim-shortcuts-in-the-repl) ================================================================================================ When the REPL editor (CodeMirror) is configured to use Vim keybindings, the following commands are available: * :w — Evaluate the current code * Triggers the same evaluation as Ctrl+Enter / Alt+Enter * You’ll see messages in the Console panel such as: * \[vim\] :w — evaluating code * \[repl\] evaluate via event * \[eval\] code updated * :q — Stop/pause playback * Triggers the same stop action as Alt+. * Useful to quickly stop scheduling without leaving Vim mode * gc — Toggle line comments for the current selection(s) * Works in normal and visual mode * If there’s a selection, all selected lines are toggled Notes * Behavior respects the current language mode in the editor for comment syntax. * If multiple REPL editors are open, commands target the active editor. The implementation dispatches custom events handled by the editor. * If you don’t see the Console panel, open the right panel in the REPL UI. Troubleshooting * If :w logs but evaluation doesn’t apply, ensure Vim keybindings are active and try again. You can also use Ctrl+Enter as a fallback. * For :q / gc, ensure focus is inside the editor. If an error occurs, reload the page to reset editor state and try again. ---