{
"$type": "site.standard.document",
"content": "---\ntitle: \"A short list of Extempore livecoding tricks\"\ndescription: \"Handy Extempore techniques: cosr, markov chains, weighted random, relative\n pitches, nof vs repeat, quasiquote, clock sync and more.\"\ntags:\n - livecoding\n - extempore\n - lens\n---\n\nYesterday's [LENS](https://comp.anu.edu.au/courses/laptop-ensemble/) class\ndiscussion turned into an [AMA](https://www.reddit.com/r/AMA/) of how I do\ncertain things when I'm livecoding in Extempore. As promised, here's a blog post\nwhere I've put together all of the things we've discussed (with a bit more\nexplanation). If you're in the LENS '21 class this'll hopefully be a helpful\ncomplement to yesterday's class discussion. If you're not in the class, then\nmaybe you've always been curious about certain things I (over)use in my\nlivecoding sets? This is maybe a bit too niche to go in the general Extempore\ndocumentation, but if you've got any questions then you can hit me up on Teams\n(for LENS students) or the\n[Extempore mailing list](mailto:extemporelang@googlegroups.com) (everyone else).\n\n[[toc]]\n\n## the `cosr` macro\n\n`cosr` is just a convenient way of sinusoidally modulating something with a\nperiod synced to the tempo.\n\nWhen it came to writing up this part of yesterday's discussion I had _déjà\nvu_---I was sure I'd written this stuff up elsewhere. Turns out I have---it's in\nthe main Extempore docs as part of the\n[pattern](https://extemporelang.github.io/docs/guides/pattern-language/#what-is-cosr)\nguide.\n\nOne other tip which I mentioned yesterday: if you use fractions for the period\n(final argument) which have a co-prime numerator & denominator (e.g. 7/3 or\n15/8) then you'll get interesting accenting patterns, but which will still\nrepeat (so it gives a bit more structure than just using `random`).\n\nOne other related trick that I use is doing \"mod\" calculations on the `beat`\nvariable (which is an explicit argument in most temporal recursions), but is\nalso implicitly bound in the pattern expression if you're using the pattern\nlanguage:\n\n```xtlang\n(if (= (modulo beat 2) 0)\n (println 'downbeat))\n\n(if (= (modulo beat 2) 1)\n (println 'upbeat))\n```\n\n## adding instruments to the sharedsystem setup\n\nThe default sharedsystem instruments are defined near the top of the\n`examples/sharedsystem/audiosetup.xtm` file:\n\n```xtlang\n(make-instrument syn1 analogue)\n(make-instrument syn2 analogue)\n(make-instrument syn3 analogue)\n(make-instrument kit dlogue)\n(make-instrument samp1 sampler)\n```\n\nIf you want to use different instruments, you can add them to the various DSP\ncallbacks `dsp1` to `dsp5` (this multi-DSP-function setup is to allow the audio\nengine's work to be distributed across multiple cores).\n\nIn each of the DSP functions (e.g. `dsp1`), the actual work of getting the\n\"signal\" out of the relevant instrument happens in a line like this:\n\n```xtlang\n(set! out (syn1 in time chan dat))\n```\n\nTo add e.g. an `fmsynth` to the sharedsystem setup, there are two steps:\n\n1. define the `fmsynth` instrument somewhere with\n `(make-instrument fmsynth fmsynth)`\n\n2. call the `fmsynth` function in one of your DSP functions (doesn't matter\n which one---they all get summed in the end) callbacks and make sure the\n return value is added to one of the `out` variables\n\nHere's an example (in `dsp1` as per the previous example):\n\n```xtlang\n(set! out (+ (syn1 in time chan dat)\n (fmsynth in time chan dat)))\n```\n\nThen, any notes you play (using `play`) on the `fmsynth` instrument will make\ntheir way into the audio output.\n\n## markov chains\n\nAs discussed yesterday, Scheme (most lisps, really) makes it pretty easy to\nrepresent markov chains.\n\nConsider the following temporal recursion, which (in addition to the usual\n`beat` and `dur` arguments) also takes a `pitch` argument. This is the exact\ncode I wrote yesterday:\n\n```xtlang\n(define (obi-lead beat dur) pitch\n (play samp1 pitch (cosr 75 20 5/3) dur 2)\n (callback (*metro* (+ beat (* .5 dur))) 'obi-lead (+ beat dur) dur\n (random (cdr (assoc pitch '((60 60 63 67)\n (63 67 60)\n (67 58)\n (58 60)))))))\n\n(obi-lead (*metro* 'get-beat 4) 1/4 60)\n```\n\nThe markov chain happens in that `(random (cdr (assoc ...)))` line at the end of\nthe definition of the `obi-lead` function. A couple of things to note:\n\n- the `cdr` is only necessary to make sure that a \"self-transition\" (i.e. the\n pitch staying the same) only happens if you explicitly add the same pitch as\n an element _other than_ the first element of the list. Otherwise, you wouldn't\n be able to make a markof chain that always went to a _different_ pitch from\n the current one.\n\n- the `random` call isn't a special \"markov\" random, it's just the normal\n `random` picking from a list of pitches (so you can use the same \"use the same\n element multiple times for weighted sampling\" trick mentioned below)\n\n- you can do whatever you want with the `pitch` argument inside the body of the\n `obi-lead` function---the fact that we `play` it here is a common pattern, but\n you could e.g. fire out an OSC message, etc.\n\n- this is just a specific example of the more general class of algorithms which\n use a temporal recursion to pass variables to subsequent callbacks, and\n potentially do tests/operations on said variables to change them for future\n callbacks\n\n- the onus is on the livecoder to make sure that each possible state (i.e. each\n value that `pitch` can take) is represented as the head (`cdr`) of one of the\n lists. But as long as you satisfy that invariant then the `obi-lead` markov\n process will just keep on playing the `pitch` as it travels through time and\n space[^mb].\n\n[^mb]:\n to the\n [world of the Mighty Boosh](https://www.youtube.com/watch?v=uf723IeStzc)\n\n## weighted random selections from a list\n\nAs promised (Caleb!) here's the syntax for doing a weighted random sample: the\nkey is that the argument to `random` isn't a list of `cons` pairs, it's multiple\n`cons` pair arguments, each one of the form `(cons WEIGHTING VALUE)`.\n\n```xtlang\n;; here's an example: note that it's in a \"do 10 times\" loop to show that the\n;; values are indeed sampled using the appropriate weighting\n(dotimes (i 10)\n (println 'i: i (random (cons 0.1 1) (cons 0.9 -1))))\n\n;; printed output:\n;;\n;; i: 0 -1\n;; i: 1 -1\n;; i: 2 -1\n;; i: 3 -1\n;; i: 4 1\n;; i: 5 1\n;; i: 6 -1\n;; i: 7 -1\n;; i: 8 -1\n;; i: 9 -1\n```\n\nNote that if you're happy with just rough \"this value is twice/three times as\nlikely as the others\" then it's usually simpler to just randomly sample\n(equally-weighted) from a list, including duplicate values for the things you\nwant to turn up more often in the output:\n\n```xtlang\n;; 0 will be twice as likely as 3 or 7\n(random (list 0 0 3 7))\n```\n\n## `rel` for relative pitches\n\n:::tip\n\nFrom here on, all the following code snippets assume you've loaded the pattern\nlanguage with\n\n```xtlang\n(sys:load \"libs/core/pattern-language.xtm\")\n```\n\nNote that the pattern language library is loaded when you load the sharedsystem\nas well.\n\n:::\n\nBased on the (global) `*scale*` variable, you can use `rel` to calculate pitches\n\"relative to\" a starting pitch.\n\nSo, if you're starting with a middle C (midi note `60`) and you want to go `2`\nnotes up the scale (and you haven't changed value of the `*scale*` variable from\nthe default \"C natural minor\" scale) then you can use:\n\n```xtlang\n(rel 60 2)\n```\n\nThis can be handy when paired with the `range` function (which just generates\nlists of integers) for running your scales:\n\n```xtlang\n(:> scale-runner 4 0 (play samp1 (rel 60 @1) 80 dur) (range 8))\n```\n\nNote that this is just a slightly terser version of `pc:relative` (in\n`libs/core/pc_ivl.xtm`) function, which doesn't use the `*scale*` variable by\ndefault. Note _further_ that `rel` takes an optional third argument for\nproviding a different scale, if e.g. you want to use a different scale for your\n\"relative pitch\" calculation than you're currently using elsewhere in the piece.\n\nHere's an example:\n\n```xtlang\n;; set scale to F natural minor\n(set! *scale* (pc:scale 5 'aeolian))\n\n;; use a Fm7 chord (a subset of F natural minor) for the relative pitch calculation\n(rel 65 (random 4) (pc:chord 5 '-7))\n```\n\nIf you `println` the output of that `(pc:chord 5 '-7)` (_Fm7_) function, you'll\nget the result `(5 8 0 3)`, which corresponds to the following pitch classes:\n\n- `5`: F\n- `8`: A♭\n- `0`: C\n- `3`: E♭\n\nwhich are the pitches from an Fm7 chord, so it all checks out.\n\nAs one final tip, you can just skip the call to `pc:chord` altogether and do\nsomething like:\n\n```xtlang\n(rel 65 (random 4) '(5 8 0 3))\n```\n\nThis makes it super-easy to make quick edits, e.g. if you want to flatten the\nfifth (C). But it does make it a little less readable for the audience (and\nlet's face it, reading `(pc:chord 5 '-7)` was already pretty tough going for\nmost folks outside the _music theory_ ∩ _computer programmer_ intersection).\n\n## `nof` and the macros vs functions distinction\n\n`nof` (think \"give me _n_ of these\") is a Scheme macro for creating a list by\nrepeatedly evaluating a form.\n\nSo, one way to get a list of 10 `0`s is:\n\n```xtlang\n(nof 10 0)\n```\n\nTo get a list of 4 random integers between `0` and `9` (inclusive) you could\nuse:\n\n```xtlang\n(nof 4 (random 10))\n```\n\nI just evaluated the above form on my machine; the result was `(0 9 9 8)`. Note\nthat the numbers are different; so the `nof` macro is obviously not just taking\nthe result of a single call to `(random 10)` and repeating it `4` times to\ncreate a list.\n\nThis is where the fact that it's a _macro_---not a function---comes into play.\nOne trick for looking at what a macro form \"macroexpands\" out to is calling\n`macro-expand` (note that the `nof` form has been quoted using `'`):\n\n```xtlang\n(println (macro-expand '(nof 4 (random 10))))\n\n;; prints:\n\n;; (make-list-with-proc 4 (lambda (idx) (random 10)))\n```\n\nSo, the list is actually formed by four repeated calls to a `lambda` (anonymous)\nfunction, and that's why the four random numbers in the above list are\ndifferent.\n\nIf this isn't actually the behaviour you want---if you want the same random\nnumber repeated four times, there's a `repeat` function which you probably want\nto use instead. Notice the difference:\n\n```xtlang\n(println (nof 4 (random 10)))\n(println (repeat 4 (random 10)))\n\n;; prints:\n\n;; nof: (1 3 3 6)\n;; repeat: (5 5 5 5)\n```\n\nSo keep that in mind when you're using `nof` in your pattern language. It'll\nprobably just work, but this subtle macro vs functino thing may be the cause of\nerrors or weird behaviour that you see.\n\n## quasiquote (`` ` ``) vs regular quote (`'`)\n\nThe quasiquote (`` ` ``) symbol (which is also called the _tilde_) is like the\nnormal quote operator (`'`), except that you can \"undo\" the quoting (i.e. eval)\ninner forms as necessary using the unquote operator (`,`).\n\nThat's not easy to get your head around when explained in words, but here's an\nexample:\n\n```xtlang\n;; this is kindof tedious to write\n'(c3 | | | | | d3 e3)\n\n;; so instead we write\n`(c3 ,@(nof 5 '|) d3 e3)\n\n;; don't forget the @ (splicing) part; this is probably not what you want...\n`(c3 ,(nof 5 '|) d3 e3)\n\n;; result: (c3 (| | | | |) d3 e3)\n```\n\n## modulating filter (or other) params over time\n\nOne way to do it is show in the example file\n`examples/sharedsystem/analogue_synth_basics.xtm`. Have a look around line 39,\nwhere it says:\n\n```xtlang\n;; and now add a second pattern to 'sweep' the filter\n(:> B 4 0 (set_filter_env syn1 40.0 100.0 (trir 0.0 1.0 1/32) 100.0) (nof 16 0))\n```\n\n## playing a loop which then stops\n\nThe pattern language isn't really designed for playing one (or two, or three, or\n_n_ for _n < ∞_) shot loops. It's really designed for things which will keep on\nlooping until you stop the pattern.\n\nIf you want to play a sequence of events which runs for a while and then stops,\nthen using a standard temporal recursion is probably best.\n\nHere are a couple of examples. First, this recursion will keep playing the notes\nuntil the `pitch` argument gets to `72`.\n\n```xtlang\n(define (ascending-chromatic beat dur pitch)\n (play syn1 pitch 80 dur)\n (if (< pitch 72)\n ;; note the `callback` is inside the `if`\n (callback (*metro* (+ beat (* .5 dur))) 'ascending-chromatic (+ beat dur) dur\n ;; pitch gets incremented by 1 in each subsequent callback\n (+ pitch 1))))\n\n;; kick it off - note that we're passing the initial pitch argument\n(ascending-chromatic (*metro* 'get-beat 4) 1/4 60)\n```\n\nNow, both the \"increment\" part `(+ pitch 1)` and the \"stopping criteria\" part\n(i.e. the `(if (< pitch 72) ...`) are both just Scheme code, so it's very\nflexible. You can handle those things however you like.\n\nOne other approach to doing this is a recursion approach straight out of the\nfunctional programming handbook[^little-schemer], just with a temporal twist.\nThe key idea: start with a list, recurring on the `cdr` (i.e. the tail of the\nlist) each time until it's empty, then stopping.\n\nSo, here's a way of playing a scale---ascending, then descending---then\nstopping.\n\n```xtlang\n(define (ascending-descending-scale beat dur plist)\n ;; note the (car plist) since plist is a list, not a number\n (play syn1 (car plist) 80 dur)\n (if (not (null? plist))\n (callback (*metro* (+ beat (* .5 dur))) 'ascending-descending-scale (+ beat dur) dur\n (cdr plist))))\n\n(ascending-descending-scale (*metro* 'get-beat 4) 1/4\n '(60 62 64 65 67 69 71 72 71 69 67 65 64 62 60))\n```\n\nThere are a couple of nice variations on this one:\n\n- replace the `(cdr plist)` with `(rotate plist -1)` to make it cycle through\n the pitches, but then go back to the start (have a think about what `rotate`\n does to convince yourself that this works)\n\n- you can re-trigger this temporal recursion several times, either with the same\n `plist`, or even with different `plist`s---since all the temporal callback\n chains will be independent they'll all just run nicely over the top of one\n another, which can lead to some interesting musical \"layerings\"\n\n[^little-schemer]:\n that's not just an idiom, I'm actually thinking of a\n [specific book](https://mitpress.mit.edu/books/little-schemer-fourth-edition)\n\n## multi-laptop Extempore clock sync\n\nIf you're jamming with another Extempore laptop musician, you often want to make\nsure your tempos & metronomes sync up. One way to do this is to use the topclock\nprotocol (the name comes from [TOPLAP](https://toplap.org)). You'll need a\nnetwork connection between all the machines---wired is best if you can manage\nit, or at least on a private-ish wifi LAN (you'll probably have a bad time\ntrying to do it over _ANU Secure_).\n\nThere's an example in `examples/core/topclock_metro.xtm` which will show you the\ndetails.\n\nThe alternative (manual) way to sync up two laptops is to:\n\n1. make sure you're both using the same tempo:\n\n ```xtlang\n (*metro* 'set-tempo 140) ;; or whatever tempo you like\n ```\n\n2. both starting \"playing time\", i.e. something simple which is easy to listen\n to and feel the tempo\n\n3. listening carefully, try and figure out if you're\n [rushing or dragging](https://www.youtube.com/watch?v=ZQ_6VUs2VCk), and then\n executing the appropriate `*metro*` function call:\n\n ```xtlang\n ;; if you're rushing\n (*metro* 'pull)\n\n ;; if you're dragggin\n (*metro* 'push)\n ```\n\nPros with this approach: no network connection required, and it's usually not\ntoo tricky to get the timing close enough to jam together. Cons: it's pretty\nmanual (and requires some careful listening, which is a skill that takes time to\nmaster), and it also doesn't help with getting the exact beat clock synced up\n(so that you'll still have to be careful that e.g. your 4-beat bars line up, and\nyou may have to keep fiddling with some offsets to make it all work).\n\n## Selecting the correct audio device\n\nWe discussed this during the demo day class, but you should visit the\n[doco website](https://extemporelang.github.io/docs/overview/quickstart/#no-sound-check-your-audio-device)\nto see how to do it. The tip about using `--device-name` instead of `--device`\nis particularly good advice when you're trying to bump in quickly in a gig\nsituation.\n\n## Audio underflow: are you pushing extempore too hard?\n\nThe sharedsystem is actually kindof heavyweight[^cpu-requirements] (or at least\nmedium-weight) from a CPU use perspective. It loads up 4 analogue synths and a\nsampler, plus some FX (e.g. global convolution reverb) and tries to distribute\nthem across multiple cores on your machine.\n\n[^cpu-requirements]:\n I mean, it's still supposed to work on a half-decent laptop---it shouldn't\n require a real beast---but if you're on a particularly old/wheezy machine\n then even with the below tricks the sharedsystem might not be a good choice.\n The other examples (e.g. `examples/core/fmsynth.xtm`) show how to create a\n lighter-weight DSP chain, and from there you could add only the instruments\n & effects that you need.\n\nIf you're getting lots of \"audio underflow\" messages, you've got a few options,\nin order of easiest fixes to most difficult:\n\n1. don't run any other software on your machine that you don't absolutely need\n for the gig\n\n2. try starting Extempore with a larger frame size (e.g.\n `./extempore --frames 8192`)\n\n3. if you don't need MIDI I/O, just load\n `(sys:load \"examples/sharedsystem/audiosetup.xtm\")` instead of\n `(sys:load \"examples/sharedsystem/setup.xtm\")`\n\n4. remove some of the `dspN` (for `N` = 1..5) functions from the signal chain in\n `examples/sharedsystem/audiosetup.xtm` (e.g. if you're only using `syn1` and\n `syn2` you could remove `dsp3` from the signal chain, which uses `dspmt` as\n the final output sink)\n\n5. get your hands on a beefier laptop (not an option for many people, obviously)\n\nIf you go with option #4, remember that you can create a new copy of\n`audiosetup.xtm` and modify it to your heart's content. Even if you've just been\nmessing with the original `audiosetup.xtm` file directly, remember that you can\nget a \"pristine\" version at any time from\n[GitHub](https://github.com/digego/extempore/blob/master/examples/sharedsystem/audiosetup.xtm).\n\n## Creating a \"startup\" file\n\n`sys:load` can load any file on your computer (even files outside your\n`extempore` directory if you provide a full path). So it's a good idea to put\n_everything_ required to set up your Extempore session into one file, then you\ncan just load that file with e.g.\n\n```xtlang\n(sys:load \"/Users/ben/Documents/research/extemporelang/xtm/sessions/lens-2021-demo/setup.xtm\")\n```\n\nand then once that's loaded you're good to go.\n\nExtra tip: also memorise a line of code that you can quickly type & execute to\ncheck if there's sound coming out.\n",
"createdAt": "2026-05-13T23:14:51.324Z",
"description": "Handy Extempore techniques: cosr, markov chains, weighted random, relative pitches, nof vs repeat, quasiquote, clock sync and more.",
"path": "/blog/2021/04/23/a-short-list-of-extempore-livecoding-tricks",
"publishedAt": "2021-04-23T00:00:00.000Z",
"site": "at://did:plc:tevykrhi4kibtsipzci76d76/site.standard.publication/self",
"tags": [
"livecoding",
"extempore",
"lens"
],
"textContent": "Handy Extempore techniques: cosr, markov chains, weighted random, relative pitches, nof vs repeat, quasiquote, clock sync and more.",
"title": "A short list of Extempore livecoding tricks"
}