Tidal Side

-- -- house intro

setcps(135/60/4)

-- percussion and bass

d1 $ s "808bd:5" * slow "<0.03 0.0625 0.125 0.25>" 1
  # note ("<fs d a e>" - 1)

xfadeIn 1 2 $ slow 2 $ s "[808bd:5]*8" # note ("<fs d a e>" - 1)  
   
d1 silence



xfadeIn 2  8 $ stack [
  s "bd:6(4,4)" # shape 0.4,
  s "~ db:13 ~ jazz:4",
  s "[~ db]*4" # gain 0.8 # pan 0.6
]
  # gain 0.8 # gain 0.8 # pan 0.6

hush

p "housemidi" $ stack [
  ccv "127 90 110 90" # ccn 0 # s "midi",
  ccv "0 100 0 120" # ccn 1 # s "midi",
  ccv "[0 80 0 100]*4" # ccn 2 # s "midi"
]

p "housemidi" $ stack [
  ccv "80 30 50 30" # ccn 0 # s "midi"
]

-- tempo change to 145

hush

d6 silence

xfadeIn 6 8 $ note "<fs fs d d a a e e>"
  # s "supersaw*4"
  # legato 0.5
  # lpf 3000
  # resonance 0.2
  # gain 0.8
  # octave 2

d6 silence

do
  p "drop" $ qtrigger $ seqP [
      (0, 8, s (cat [
          "808bd:12(4,4) ~ ~ ~",
          "808bd:12(4,4) ~ ~ ~",
          "808bd:12(4,4) ~ [~ jazz:6] ~",
          "808bd:12(4,4) ~ [~ jazz:6] ~",
          "808bd:12(4,4) ~ [~ jazz:6] ~",
          "808bd:12*4", 
          "808bd:12*8",
          "808bd:12*16"
        ]) # gain (range 0.9 1.2 $ slow 8 saw)
      )
    ]
  ct_ (145/60/4) 8
  


p "drop" silence

d7 silence

--melody and chords
-- keep throughout
xfadeIn 7 8 $ jux rev $ slow 2
  $ struct " t ~ t ~ ~ t ~ t"
  $ n "<fs'min7 d'maj7 a'maj7 e'maj7>"
  # s "superpiano"
  # sustain 4
  # velocity (range 0.4 0.75 $ slow 8 sine)
  # lpf (range 1500 8000 $ slow 4 saw) 
  # lpq 0.1
  # delay 0.7 # delayt (3/10) # delayfeedback 0.6
  # room 0.9 # size 0.95
  # gain 0.7
  # legato 2
  # orbit 6

xfadeIn 8 2 $ jux rev $ n "[~ fs'min7] [~ d'dom7] [a'maj7 ~] [~ e'maj7]"
    # s "superfork"
    # shape 0.2
    # room 0.5
    # lpf 2000
    # gain 0.7

d8 silence

d10 silence

hush

xfade 1    $ s "808bd:12*4 "
    # gain 1


xfadeIn 10 2 $ n (cat [
    "[~ fs'min9] [~ d'maj9] [a'maj9 ~] [~ e'dom9]",
    "[~ fs'min9] [~ d'maj9] [a'maj9 ~] [d'maj9 e'dom9]",
    "[~ fs'min9] d'maj9 [~ a'maj9] [~ e'dom9]",
    "[~ fs'min9] [d'maj9 d'maj9] [~ a'maj9] ~",
    "[~ fs'min9] [~ d'maj9] [a'maj9 ~] [~ e'dom9]",
    "[~ fs'min9] [~ d'maj9] [a'maj9 ~] [fs'min9 e'dom9]",
    "[~ fs'min9] [~ d'maj9] [~ a'maj9] [~ e'dom9]",
    "[fs'min9*2] [d'maj9 ~] [~ a'maj9] ~"
  ])
  # s "supervibe"
  # octave 4
  # sustain 0.25
  # amp (range 0.1 1.2 $ fast 4 saw)
  # djf 0.65
  # room 0.5 # sz 0.4
  # delay 0.5 # delayt 0.25 # delayfeedback 0.4
  # gain 0.5

hush

-- change tempo 

ct_ (175/60/4) 8

-- break beats -- 

d2 $ every 4 (slice 8 "0 1 [2 3] 4 5 [6 7 6 7] 3 1")
   $ sometimesBy 0.3 (# speed 2)
   $ slice 8 "0 3 1 [6 7] 4 2 5 3"
   $ s "breaks125:1 808bd"
   # cut 1 # krush 4 # lpf 6000 # speed " 1.36 1.07" # gain 0.9


xfade 2 $ every 4 (slice 8 "0 1 [2 3] 4 5 [6 7 6 7] 3 1")
   $ sometimesBy 0.3 (# speed 2)
   $ slice 8 "0 3 1 [6 7] 4 2 5 3"
   $ s "<[breaks165 breaks125:1] [breaks125:1] [breaks125:1 breaks165] [breaks165]>"
   # cut 1 # krush 4 # speed "<[1.07 1.36] [1.36] [1.36 1.07] [1.07]>" # gain 1
    #djf 0.5 # orbit 1 # lpf (range 1000 3000 $ slow 8 rand)

p "breakmidi" $ stack [
  ccv (segment 16 $ "127 80 100 [90 110] 127 80 100 90") # ccn 1 # s "midi",
  ccv (segment 16 $ sometimesBy 0.3 (const 127) $ "60 60 60 60 60 60 60 60") # ccn 2 # s "midi",  
  ccv (segment 16 $ range 30 127 $ slow 8 rand) # ccn 3 # s "midi",
  ccv (every 4 (const "127 127 [127 127] 127 127 [127 127 127 127] 127 127") $ "40 40 40 40 40 40 40 40") # ccn 6 # s "midi"
]



--breakbeats melody
xfadeIn 10 8 $ n ( cat ["[~ fs'min9] [~ d'maj9] [a'maj9 ~] [~ e'dom9]",
    "[~ fs'min9] [~ d'maj9] [a'maj9 ~] [d'maj9 e'dom9]",
    "[~ fs'min9] d'maj9 [~ a'maj9] [~ e'dom9]",
    "[~ fs'min9] [d'maj9 d'maj9] [~ a'maj9] ~",
    "[~ fs'min9] [~ d'maj9] [a'maj9 ~] [~ e'dom9]",
    "[~ fs'min9] [~ d'maj9] [a'maj9 ~] [fs'min9 e'dom9]",
    "[~ fs'min9] [~ d'maj9] [~ a'maj9] [~ e'dom9]",
    "[fs'min9*2] [d'maj9 ~] [~ a'maj9] ~"])
    # s "superfm"
    # octave 4
    # sustain 0.25
    # crush 3
    # shape "0 0.2 0.4 0.6"
    # amp (range 0.1 1.2 $ fast 4 saw)
    # djf 0.65
    # room 0.9 # sz 0.6
    # cut 1
    # gain 0.5

p "birds" $ jux (rev . (# crush 3)) $ every 4 (fast 2) $ striate 16 $ s "birds"
    # n (irand 10)
    # speed (range 0.5 4 $ fast 4 sine)
    # gain 0.6
    -- # squiz "2 4 1 5"
    # cut 2
    # legato 0.3
    # pan rand
    # room 0.2 # size 0.85
    

do 
  xfadeIn 10 12 silence
  xfadeIn 2 12 silence 
  


hush

-- you wanna try a quick run through with what we have now and we can note down what we shoul dfix


do 
  xfadeIn 1 16 silence
  xfadeIn 6 16 silence
  xfadeIn 8 16 silence
  xfadeIn 10 16 silence
  xfadeIn 2 16 silence 
-- for the end part DONT FORGET
  xfadeIn 7 16 $ jux rev $ slow 2
    $ struct " t ~ t ~ ~ t ~ t"
    $ n "<fs'min7 d'maj7 a'maj7 e'maj7>"
    # s "superfm"
    # crush 3
    # sustain 8
    # lpf (range 1000 8000 $ slow 4 saw) 
    # lpq 0.1
    # delay 0.7 # delayt (3/10) # delayfeedback 0.6
    # room 0.9 # size 0.95
    # gain (range 0.5 0.7 $ slow 16 sine)
    # legato 2
    # cut 1

p "root" $ slow 2 $ n "<fs'min d'maj a'maj e'maj>"
    # s "superfm"
    # octave 4
    # sustain 4
    # gain 0.6
    # room 0.5 # sz 0.9
    # lpf 300
    # legato 2

do
  d7 silence
  p "root" silence


hush


ct_ finishTempo numCycles = do
      start <- getcps
      p "change_tempo" $ qtrigger $ filterWhen (>=0) $ seqP
        [ (0, numCycles,
            slow (pure numCycles) $
              cps $
                segment 128 $
                  range (pure $ realToFrac start) finishTempo saw
          )
        ]
-- i like this as our "transition" thing and i can just keep slowly increasing bpm here
Hydra Side

//  trigger next() to advance scenes


s0.initCam()


window.scenes = [

  //[0]ENTRY
  () => {
    osc(8, 0.5, 0.3)
      .color(1.5, 0.5, 0.15)
      .scale(0.35, 1.6)
      .scrollX(0.3)
      .modulate(noise(3, 0.4), 0.2)
      .thresh(() => 0.4 + Math.sin(time * 2.08) * 0.25, 0.05)
      .out(o3)

    noise(3, 0.1)
      .modulate(osc(2, 0.3), () => 0.3 + Math.sin(time * 1.04) * 0.2)
      .colorama(0.06)
      .luma(0.4, 0.4)
      .color(0.08, 0.1, 0.18)
      .hue(() => time * 0.05)
      .out(o2)

    src(s0)
      .scale(1.2)
      .saturate(0.3)
      .thresh(() => 0.52 + Math.sin(time * 2.5) * 0.12, 0.03)
      .invert()
      .color(0.08, 0.1, 0.2)
      .add(src(s0)
        .scale(1.22)
        .thresh(0.5, 0.04)
        .invert()
        .color(0.3, 0.15, 0.25)
        .scrollX(() => Math.sin(time * 0.7) * 0.01), 0.3)
      .out(o1)

    src(o2)
      .add(src(o3), 0.9)
      .layer(src(o1).luma(0.18, 0.3))
      .modulate(noise(1.5, 0.2), 0.04)
      .out(o0)

    render(o0)
  },

//[1]THRESHOLD
() => {
  osc(6, 0.3, 0.8)
    .diff(osc(6.4, 0.3, 0.8))
    .color(0.6, 0.3, 0.9)
    .scrollX(() => Math.sin(time * 0.5) * 0.08 + ccActual[0] * 0.15)
    .out(o3)
  noise(2, 0.15)
    .mult(osc(4, 0.2))
    .colorama(() => 0.05 + Math.sin(time * 0.6) * 0.04 + cc[0] * 0.08)
    .color(0.05, 0.08, 0.15)
    .out(o2)
  src(s0)
    .scale(() => 1.1 + cc[0] * 0.3)
    .saturate(0.5)
    .thresh(() => 0.5 + Math.sin(time * 1.5) * 0.1 - cc[0] * 0.2, 0.04)
    .color(0.2, 0.25, 0.4)
    .diff(src(o3).scale(0.98))
    .out(o1)
  src(o2)
    .add(src(o3), 0.4)
    .layer(src(o1).luma(0.2, 0.3))
    .blend(o0, () => 0.07 + cc[0] * 0.1)
    .modulate(noise(1, 0.1), () => 0.02 + cc[0] * 0.05)
    .out(o0)
  render(o0)
}

  //[2]BUILD
  () => {
    osc(15, 0.4, 1.2)
      .colorama(() => 0.4 + Math.sin(time * 0.5) * 0.3)
      .colorama(0.3)
      .scrollX(() => time * 0.04)
      .scrollY(() => Math.sin(time * 0.3) * 0.05)
      .rotate(() => time * 0.2)
      .modulate(noise(2, 0.4), 0.3)
      .out(o2)

    osc(50, 0.5, 1.5)
      .color(0.4, 1.2, 1.6)
      .thresh(() => 0.7 - Math.abs(Math.sin(time * 4.16)) * 0.3, 0.04)
      .rotate(() => time * 0.4)
      .out(o3)

    src(s0)
      .scale(() => 1.15 + Math.sin(time * 2.08) * 0.1)
      .colorama(() => 0.3 + Math.sin(time * 0.6) * 0.2)
      .colorama(0.2)
      .kaleid(() => 2 + Math.floor(Math.abs(Math.sin(time * 0.25)) * 3))
      .modulate(osc(3, 0.6), 0.06)
      .out(o1)

    src(o2)
      .add(src(o3), 0.5)
      .modulate(noise(1.5, 0.3), 0.15)
      .layer(src(o1).luma(0.3, 0.35))
      .blend(o0, 0.05)
      .out(o0)

    render(o0)
  },

  //[3]PEAK
  () => {
    osc(30, 0.5, 1.4)
      .colorama(0.5)
      .colorama(0.3)
      .rotate(() => time * 0.4)
      .modulate(noise(4, 0.5), 0.15)
      .out(o3)

    solid(1.8, 0.15, 0.2)
      .mult(osc(50, 0, 0)
        .thresh(() => {
          const burst = Math.floor(time * 4.66) % 7 === 0 ? 0.1 : 0.95
          return burst - Math.abs(Math.sin(time * 4.66)) * 0.85
        }, 0.02))
      .out(o2)

    src(s0)
      .scale(() => 1.2 + Math.sin(time * 2.33) * 0.15)
      .kaleid(() => 4 + Math.floor(Math.abs(Math.sin(time * 0.6)) * 4))
      .modulate(noise(3, 0.4), 0.08)
      .luma(0.3, 0.3)
      .mult(src(o3))
      .out(o1)

    src(o0)
      .scale(() => 0.988 + Math.sin(time * 0.8) * 0.006)
      .rotate(() => Math.sin(time * 0.6) * 0.008)
      .modulate(noise(4, 0.5), 0.05)
      .layer(src(o1))
      .add(src(o2), () => 0.6 + Math.abs(Math.sin(time * 4.66)) * 0.3)
      .modulatePixelate(noise(5, 0.1).pixelate(20, 20), 240)
      .out(o0)

    render(o0)
  },

  //[4]PLATEAU
  () => {
    voronoi(5, 0.4, 0.95)
      .modulate(osc(2, 0.2), () => 0.3 + Math.sin(time * 0.4) * 0.2)
      .kaleid(() => 8 + Math.floor(Math.abs(Math.sin(time * 0.2)) * 4))
      .rotate(() => time * 0.12)
      .colorama(0.3)
      .colorama(0.15)
      .hue(() => time * 0.1)
      .color(0.8, 0.4, 1.2)
      .out(o3)

    shape(6, 0.5, 0.6)
      .scale(() => 1 + Math.sin(time * 0.7) * 0.4)
      .rotate(() => time * 0.1)
      .out(o2)

    src(s0)
      .scale(() => 1.2 + Math.sin(time * 0.9) * 0.08)
      .kaleid(() => 5 + Math.floor(Math.sin(time * 0.3) * 2))
      .rotate(() => time * 0.1)
      .hue(() => time * 0.15)
      .colorama(0.15)
      .diff(src(o0).scale(0.98))
      .out(o1)

    src(o0)
      .modulateRotate(src(o2), 0.4)
      .modulateScale(src(o2), -0.02, -0.02)
      .scale(0.994)
      .blend(src(o3), 0.1)
      .layer(src(o1).luma(0.25, 0.35))
      .modulate(noise(2, 0.1), 0.02)
      .out(o0)

    render(o0)
  },

  //[5]SECOND SURGE tried it againn
  () => {
    osc(80, 0.6, 2.0)
      .colorama(() => 0.3 + Math.sin(time * 2) * 0.2)
      .thresh(() => 0.5 + Math.sin(time * 8) * 0.4, 0.01)
      .pixelate(
        () => 3 + Math.floor(Math.abs(Math.sin(time * 3)) * 12),
        () => 3 + Math.floor(Math.abs(Math.sin(time * 3)) * 12)
      )
      .color(0.6, 0.3, 1.0)
      .out(o3)

    src(s0)
      .scale(() => 1.3 + Math.sin(time * 4) * 0.15)
      .kaleid(() => 6 + Math.floor(Math.abs(Math.sin(time * 1.5)) * 6))
      .modulate(noise(6, 0.8), 0.12)
      .color(0.9, 0.5, 1.2)
      .out(o1)

    src(o0)
      .scale(0.985)
      .modulate(noise(5, 0.6), 0.06)
      .add(src(o3), () => 0.4 + Math.abs(Math.sin(time * 3)) * 0.5)
      .layer(src(o1).luma(0.2, 0.3))
      .out(o0)

    render(o0)
  },
  
  //[6] COMEDOWN — you at the bar. your face in the dark. alone but electric.
  () => {
    // you, thresholded into a ghost — features emerge and dissolve
    src(s0)
      .scale(1.08)
      .thresh(() => 0.45 + Math.sin(time * 0.3) * 0.12, 0.04)
      .color(0.15, 0.1, 0.28)
      .diff(src(s0)
        .scale(1.1)
        .thresh(() => 0.52 + Math.sin(time * 0.45) * 0.08, 0.03)
        .color(0.4, 0.22, 0.5))
      .out(o1)
  
    // slow interference — the haunting tune made visible
    osc(4, 0.08, 1.2)
      .diff(osc(4.3, 0.08, 1.2))
      .rotate(() => time * 0.02)
      .color(0.3, 0.18, 0.5)
      .modulate(noise(1, 0.06), 0.08)
      .out(o2)
  
    // feedback that breathes — the room holding its breath
    src(o0)
      .scale(0.994)
      .modulate(noise(1.2, 0.08), 0.018)
      .mult(src(o2), 0.85)
      .layer(src(o1).luma(0.12, 0.42))
      .out(o0)
  
    render(o0)
  },



  //[7] RESTING
  () => {

src(s0)
  .hue(0.05)
  .saturate(0.6)
  .out(o1)

gradient(0.3)
  .color(1.2, 0.4, 0.6)
  .hue(() => time * 0.02)
  .out(o2)

shape(200, () => 0.2 + Math.sin(time * 0.6) * 0.04, 0.9)
  .color(1.8, 1.4, 0.2)
  .brightness(0.5)
  .out(o3)

src(o2)
  .add(src(o3), 0.8)
  .add(src(o1), 0.6)
  .out(o0)

render(o0)

    
  },

  


  
]

window.scene = 0
window.next = () => { scenes[scene](); scene = (scene + 1) % scenes.length }

window.transition = (toScene, frames = 60) => {
  let f = 0
  const tick = setInterval(() => {     
    if (f++ >= frames) { clearInterval(tick); return }
    scenes[toScene]()
  }, 16)
}


//tried to make it look like birds
scenes[7]()


next()




hush()

Last Call A live audiovisual performance — TidalCycles + Hydra

Concept

The piece is built around a single, mundane Friday night, the kind that becomes something else entirely once you’re inside it. You’re outside a club at 11 pm. The line moves. You get in. What follows is less a narrative than a physiological arc: the disorientation of the crowd, the moment the music takes over your body before your brain consents, the peak where individual people stop being people and become just movement and light, and then the long slow return to yourself at the bar.

The theme is about losing and finding your own interiority in a public space. Clubs are interesting because they’re designed to dissolve the self and most people go willingly. The piece tries to make that process felt rather than described. The visuals fragment the webcam feed in parallel with that arc: the face starts legible and gradually becomes unrecognizable, kaleidoscoped and consumed by feedback, before reassembling into something quieter at the end. The music does the same thing rhythmically by stacking complexity until the breakbeats hit and the tempo feels almost hostile, then unwinding back to the same four chords it started on.

The ending is deliberately small. After everything, someone just asks for water. That felt true.


Structure

The audio is built around a four-chord progression in F♯ minor (F♯min7 → Dmaj7 → Amaj7 → Emaj7) that runs through the whole piece. Tempo climbs across three breakpoints — 135, 145, then 175 bpm — using a custom interpolation function (ct_) that ramps the clock gradually rather than cutting hard. Percussion layers stack progressively via xfadeIn, so the density of the mix builds in parallel with the narrative.

The visuals use the live webcam feed (s0) as their spine throughout. It gets thresholded, kaleidoscoped, color-shifted, and eventually fed back into itself depending on the scene. The core Hydra technique is a self-referencing feedback buffer: src(o0) reads the previous frame back into the pipeline, scaled fractionally below 1 and modulated by noise, so the image accumulates history rather than refreshing clean. At the peak scenes, this creates a sensation of the visuals consuming themselves.

MIDI CC signals bridge the two environments. TidalCycles sends continuous controller values via p “housemidi” and p “breakmidi”, which Hydra reads as cc[] arrays — so in several scenes, musical parameters are directly steering visual ones (camera zoom, threshold cutoff, blend amounts).

Biggest hurdle: Recording

The hardest part of the process had nothing to do with the code. We were recording across four machines and spent a significant amount of time trying to sync the video outputs with the webcam feeds in post. We tried manual timecode alignment, clap-sync markers, and various export-timing approaches — nothing held reliably. Frame rates drifted, audio latency was inconsistent across machines, and the camera feeds introduced their own additional delay. We eventually abandoned the sync attempt and submitted without it. The live performance is the real artifact; the recording is a best-effort document of it.


Roles:

Ahmed and Linus – Tidal-side, recording and concept

Uditi and Alwin – Write-up and Hydra-side