I’ve been livecoding in hydra and Tidal Cycles since about 2020, and want to share some patterns that I never get tired of.
Visuals
Feedback is my bread and butter in hydra. When VJing I like to set up a patch with feedback and audioreactivity, a source video/camera feed, and just walk away and chill out while my patch does its thing and lives its own life. This is a technique visual artists have been using for ages, but I picked up how to do in in hydra from watching Flor de Fuega‘s videos.
The basic premise is that we grab the video buffer’s last frame, apply some transformations, and slap some new content on top of it. We want the new content to act as a new layer, with the feedback happening under it. To do the layering, we use hydra’s layer
, mask
, and thresh
functions to mask the input buffer based on thresholding its intensity like so:
src(o0)
.modulateScale(src(s0), ()=>a.fft[1]*0.1) //
.modulateRotate(src(s0), ()=>a.fft[3]*0.1) // Transformations go here
.hue(0.01) // Unleash your imagination!
.layer(
src(s0).mask(src(s0).thresh(()=>0.3+a.fft[2]) // s0 is the buffer containing our 'new content'
)
)
.out()
Here’s the example patch in the hydra editor - open it up, give it access to your webcam/microphone, and make some sounds at your computer!
Sounds
Since my main instrument is the tabla, most of my sounds are written to accompany a live tabla performance. In Indian classical music, a tabla is typically accompanied by a ‘lehra’, a repeating melody with a constant tempo, played on an instrument like the harmonium or sarangi that acts as a scaffolding for the tabla player’s rhythmic explorations. This melody starts at a root note, then ascends and descends before arriving back at the root. The lehra doesn’t change, except for its expression being modulated by the accompanying artist in the context of the performance.
Livecoding works great for generative lehras! I like to pre-define a ‘root sequence’ based on the time cycle I’m playing in, and have different instruments playing that sequence and adding variations on top of it - so we get the ascending and descending structure with some improvisation to spice things up. I mostly use Tidal Cycles for generating midi sequences that I send to my DAW or VCV rack, but I’ll use strudel to illustrate my point here.
In this example I’ve hardcoded the variations which get picked randomly, but in Tidal Cycles I like to use Markov chains to try and make the sequences feel more alive.
Markov chains in Tidal Cycles
Markov chains are a great way to get some structured randomness into your sounds. Tidal Cycles has an implementation of Markov chains built in, here’s a post on the Tidal blog. I’ll start from the example at the end of the post and share some hacky tricks. Let’s start by defining a transition probability matrix for a 3-state Markov chain:
let tpm = [[0.3, 0.4, 0.3]
,[0.3, 0.5, 0.2]
,[0.2, 0.3, 0.5]]
tpm[i,j]
represents the probability of transitioning from state i
to j
. The rows don’t have to add up to 1, Tidal normalizes them for us. We’ll then feed tpm
to markovPat
to start getting some sounds:
d1
$ n (fromIntegral <$> (markovPat 8 0 tpm))
# s "arpy"
The first argument of markovPat
is the length of the chain, the second one is the initial state, and the final one is the transition probability matrix. So in this case, markovPat
is spitting out chains that start at state 0 (note 0), and ride the Markov train for 8 steps. What if we want to define our own notes for each state? We can index into a list of notes using fmap
like so:
d1
$ n (fmap ([0,3,5]!!) $ (markovPat 8 0 tpm))
# s "arpy"
We can also use the Markov patterns to index into samples or slices:
d1 $ loopAt 4 $ splice 4 (markovPat 8 0 tpm) $ s "breaks152"
Okay, now the fuckery begins - Tidal/Haskell purists, turn back now (or feel free to tell me more elegant ways to achieve this). I wanted to have the states of the Markov chain be patterns instead of notes or indices, but I couldn’t figure out how to do this with a markovPat
, so I resorted to using runMarkov
to generate a list of indices (long enough to not notice repetiton), use them to index into a list of patterns, glue it all into one big string with foldr (++) ""
and evaluate it with parseBP_E
. I know evaluating strings is bad programming practice, forgive me for I have sinned. But hey, this is what we end up with when we run two chains in parallel:
list1 = (fmap (["[-2 0]",
"[0 2 0 -2]",
"[3 2 1 0]"]!!) (runMarkov 128 tpm 0 2))
list2 = (fmap (["[0 2]",
"[0 1 0 2]",
"[0 1 2]"]!!) (runMarkov 128 tpm 1 0))
markovSeq1 = (slow 32 (parseBP_E (foldr (++) "" list1)))
markovSeq2 = (slow 32 (parseBP_E (foldr (++) "" list2)))
do
d1 $ n (scale "minor" markovSeq1) # s "arpy" # pan 0.25
d2 $ n (scale "minor" markovSeq2) # s "arpy" # pan 0.75
Combining it with the above technique of using a common ‘root sequence’, using the markovs for variation:
let rootSeq = (slow 4 "0 -1 1 2 4 3 2 1")
do
d1 $ degradeBy 0.3 $ n (scale "minor" (rootSeq +| markovSeq1)) # s "supersaw" # pan 0.25
d2 $ degradeBy 0.3 $ n (scale "minor" (rootSeq +| markovSeq2)) # s "supersaw" # pan 0.75
d3 $ n (scale "minor" (rootSeq +| "0!16")) # s "supersaw" # amp 0.3