Thales Grilo


Open Stage Control + TidalCycles = ❤️

(watch it on lbry)

I decided to make a tutorial on how to set this toolchain up because it’s been very helpful to me, and I definitely want to better explore its possibilities. Maybe other people will enjoy it too. All files and snippets mentioned below are available on my dotfiles repo (give me a ★ maybe?)


Live-coding music is awesome, but there are some caveats compared to other music-making tools. One of the biggest pain points is tweaking values. In a DAW, you often want to nudge the volume/pan/gain or effect of a channel/bus a bit to get it to sound better. In code, you have to actually find the variable (given you took the time to create a variable )

Open Stage Control avoids this altogether by providing you with faders and knobs you can bind to whatever values you wish to control, making it very powerful in combination with a tool like TidalCycles, by covering some of its biggest caveats.


Tidalcycles is the Haskell-based musical pattern engine I’ve been using to compose algorithmic music. The reason live-coding is so interesting (personally) is because it allows for the creation of musical patterns which are difficult if not impossible to note through the standard DAW Piano Rolls and MIDI Clips, and sure enough, Tidal is a great tool to define these patterns in terse and relatively short notation.

Open Stage Control

Open Stage Control is a web-ready OSC/MIDI FOSS fully customizable widget-based mixer app. It’s very simple to set up and allows you to quickly create a mixer tailored to your own workflow.


As described in the tutorial, there are three places you must configure:

1. Open Stage Control Mixer

2. SuperCollider Boot File

OSCdef(\controltidal, { arg msg;"localhost", 6010).sendMsg("/ctrl", *msg); }, '/ctrl', NetAddr.localAddr );

3. Tidalcycles Boot File

tidal <- startTidal (superdirtTarget { oLatency = 0.1, oAddress = "", oPort = 5720}) (defaultConfig { cFrameTimespan = 1/20 , cCtrlAddr = "" , cCtrlPort = 6010 })

4. Optional: Wrappers

This is completely optional but I find it very convenient to have a couple of functions for short-hand value access:

-- boot.tidal
let fx n = cF 0 $ "f" ++ (show n) -- Maps ID to FX Fader
    vx n = cF 0 $ "v" ++ (show n) -- Maps ID to Volume Fader
    px n = cF 0 $ "p" ++ (show n) -- Maps ID to Pan Knob
    bus = room (cF 0.1 "room") # size (cF 0.1 "size") # orbit 0

Bear in mind these rely on my mixer’s personal configuration: I use a naming standard whereby all volume faders are titled v1, v2, v3…, effects faders are f1, f2, f3… and pan knobs are p1, p2, p3…. Similarly, the bus function expects widgets named room and size to exist - these are the names of OSC messages it’ll capture.

If there are no widgets with these preArgs, it’s pointless to use these defs (fx, vx, px and bus). That said, this is how you’ll likely call these functions during a set:

d1 $ sound "bd" # gain (vx 1)      -- or (gain . vx) 1
d2 $ sound "sd" # distort (fx 2)   -- or (distort . fx) 2
d3 $ sound "hh" # pan (px 3)       -- or (p . px) 3

d4 $ sound "crow" # bus

I hope this is helpful to someone, and if that’s the case, please reach out through one of the platforms below ✨ (while I don’t have a proper inbox for this website 🤦🏻)

To the reader

I plan on releasing content more steadily in the future (and I owe a couple of updates), but frankly it’s a challenge. I’m still learning how to create content without sinking too many hours into it (the video alone took me about two days) so bear with me, and thanks a lot, honestly, for those who read ❤️