%% -*- mode: literate-tidal; mode: iimage; mode: visual-line -*- [To run examples, install tidal (from http://yaxu.org/tidal/) and tidal-vis (from hackage), and evaluate the code at the end.] Making Programming Languages to Dance to: Live Coding with Tidal Alex McLean University of Leeds Principles * Programmer as human * Code as artistic expression * Against autonomy * Connecting with people through code Live coding http://toplap.org/ "Black slate" live coding as a design challenge * Focused, creative flow, working in environment * Just in time * Immediate interaction with others through code changes * Highly expressive; terse, and with close domain mapping between code and music * Ability to change more important than ability to understand * Little time for TDD, etc.. Live coding and Functional programming * Overtone * LiveCodeLab * Fluxus * Extempore * Conductive * Live-Sequencer * Tidal Vague timeline to Tidal * 2000 - London - Slub formed with Ade Ward, a lot of Perl scripts, RealBasic and C * 2001 - Paradiso - First room dancing to our code (Paradiso, Amsterdam) * 2001/2 - Berlin - Transmediale software art award * 2004 - Hamburg - First live coding workshop, TOPLAP is born (and then feedback.pl) * 2005 - Barcelona - Dave Griffiths joined, first good live coded slub performance (Sonar festival) * 2006 - started learning Haskell (during MSc) * 2009 - First Tidal-esque pattern language appeared * 2012 - London - First ``Algorave'' * 2012 - Goldsmiths - Completed PhD ``Artist-Programmers and Programming Languages for the Arts'' * 2013 - Barcelona - Tidal became installable by others Tidal: Developed through performance * 'Fully' improvised * Hundreds of diverse performances * Mainly with other live coders, percussionists, plus singers, dancers, noise artists, a roots band, a punk banjo player.. * Broken techno and free Jazz Slub SC <> BC Canute (+ Shelly Knotts) Hair of the horse with Hester Reeve Corlab Algorave Algoravers Tidal and Time Tidal's Pattern Datatype * Time is Rational * Time is cyclic (with a period of 1) * Polyrhythm works fine * An Arc is a time range * An Event is a value that is active within its own arc * A Pattern is a function giving all the events occuring within a given arc * May represent both discrete and continuous events > type Time = Rational > type Arc = (Time, Time) > type Event a = (Arc, Arc, a) > data Pattern a = Pattern (Arc -> [Event a]) Building and combining patterns Starting with nothing: > silence :: Pattern a > silence = Pattern $ const [] A 'pure' value, one event every cycle: > pure x = > Pattern $ \(s, e) -> > map (\t -> ((t%1, (t+1)%1), > (t%1, (t+1)%1), > x > ) > ) > [floor s .. ((ceiling e) - 1)] > d1 $ sound (pure "bd") Manipulating time > mapArc :: (Time -> Time) -> Arc -> Arc > mapArc f (s,e) = (f s, f e) > withQueryArc :: (Arc -> Arc) -> Pattern a -> Pattern a > withQueryArc f p = Pattern $ \a -> arc p (f a) > withQueryTime :: (Time -> Time) -> Pattern a -> Pattern a > withQueryTime = withQueryArc . mapArc > withResultArc :: (Arc -> Arc) -> Pattern a -> Pattern a > withResultArc f p = Pattern $ \a -> mapArcs f $ arc p a > withResultTime :: (Time -> Time) -> Pattern a -> Pattern a > withResultTime = withResultArc . mapArc Shifting time > (<~) :: Time -> Pattern a -> Pattern a > (<~) t p = withResultTime (subtract t) $ withQueryTime (+ t) p > (~>) = (<~) . (0-) Compressing time > density :: Time -> Pattern a -> Pattern a > density r p = withResultTime (/ r) $ withQueryTime (* r) p > vis $ density 3 (cat [pure red, pure blue]) > densityGap :: Time -> Pattern a -> Pattern a > densityGap r p = splitQueries $ withResultArc (\(s,e) -> (sam s + ((s - sam s)/r), (sam s + ((e - sam s)/r)))) $ Pattern (\a -> arc p $ mapArc (\t -> sam t + (min 1 (r * cyclePos t))) a) > vis $ density 2 $ densityGap 2 (cat [pure red, pure blue]) > vis $ density 4 $ densityGap 2 (cat [pure red, pure blue]) > compress :: Arc -> Pattern a -> Pattern a > compress a@(s,e) p = s ~> densityGap (1/(e-s)) p > vis $ compress (1%4, 1%2) (cat [pure red, pure blue]) Merging patterns > overlay :: Pattern a -> Pattern a -> Pattern a > overlay p p' = Pattern $ \a -> (arc p a) ++ (arc p' a) > vis $ overlay (pure red) (pure green) > stack :: [Pattern a] -> Pattern a > stack ps = foldr overlay silence ps > cat :: [Pattern a] -> Pattern a > cat ps = stack $ map (\(n, p) -> > compress ((fromIntegral n) % (fromIntegral $ length ps), (fromIntegral n+1) % (fromIntegral $ length ps)) p) (zip [0..] ps) > vis $ cat [pure red, pure blue] > vis $ cat ["red blue", "green orange purple"] Polyrhythmic DSL - Parsing strings > vis $ density 4 $ p "red [blue, green purple] orange" > vis $ p "[blue, ~ [green [yellow tomato]], orange]*16" Overloading strings saves a couple of characters > vis $ "[blue, ~ [green yellow], orange]*16" Different brackets for different kinds of polyrhythm: > vis $ density 6 $ "[red black, blue orange green]" > vis $ density 6 $ "{red black, blue orange green}" > vis $ "white*128?" > vis $ "[[black white]*32, [[yellow ~ pink]*3 purple]*5, [white black]*16]]*16" Pattern as a functor > vis $ fmap (blend 0.5 red) "blue black" > vis $ blend 0.5 > <$> "[blue orange, yellow grey]*16" > <*> "white blue black red" TRANSFORMATIONS Reversal How to reverse an infinite pattern? > vis $ density 16 $ every 3 rev "blue grey orange" every > vis $ density 16 $ every 3 rev "blue grey orange" > vis $ density 4 $ every 4 ((1/3) <~) "blue grey purple" > grid $ every 4 ((1/3) <~) "blue grey purple" whenmod > vis $ density 16 $ whenmod 6 3 rev "blue grey orange" iter > vis $ density 4 $ iter 4 $ "blue green purple orange" superimpose > vis $ density 4 $ superimpose (iter 4) $ "blue green purple orange" Combining transformations > vis $ density 8 $ whenmod 8 4 (slow 4) $ every 2 ((1/2) <~) $ > every 3 (density 4) $ iter 4 "grey darkgrey green black" Dirt > d1 $ jux (iter 4) $ sound "bd sn:2" > |+| (slow 3 $ speed "1 2") > d1 $ (jux (|+| speed "2") $ every 3 rev $ slow 8 $ striate 128 $ sound "bev") |+| vowel "a e i o" > d1 $ every 3 (density 2) $ every 4 (density 2) $ (slow 2 $ spread' (chop) (every 3 rev "8 16 32 64") $ sound "[bass3 [~ bass3:8*2]]") > |+| speed "[4 2]/5" > d4 $ slowspread ($) [id, rev, iter 4, density 2, (|+| speed "4")] $ sound (pick <$> "bd*2 lighter*4" <*> (slow 3 $ run 12)) |+| vowel "a e i o u" > d3 $ every 4 (within (0, 0.25) (density 4)) $ every 3 (0.25 <~) $ jux (iter 4) $ slow 4 $ chop 16 $ sound "shackup" > d1 $ every 3 rev $ slow 2 $ every 2 (density 2) $ sound (samples "amencutup*4 sd8*4" (slow 1.5 $ run 12)) > |+| shape "0.4" > |+| speed (scale 1 2 (slow 8 sine1) ) > d2 $ rev $ every 2 (inside 2 rev) $ chop 8 $ sound "breaks165" > d5 $ slow 3 $ stack [(stut 8 0.9 1.5 $ sound "latibro:4 [[latibro:2*2 latibro:0*2] latibro:5*4]/8") |+| cutoff "[0.02 0.02 0.03 0.02 0.04 0.02 0.06 0.03]/8" |+| resonance "[0.7 0.4]/2" |+| gain "1.2" |+| pan "0.7" > ,(density 1.5 $ stut 8 0.9 1.5 $ sound "latibro:4 [[latibro:2*2 latibro:0*2] latibro:5*4]/8") > |+| cutoff (0.25 <~ "[0.02 0.025 0.03 0.03 0.04 0.03 0.06 0.03]/8") |+| resonance "[0.7 0.4]/2" |+| gain "1.2" |+| pan "0.3" > ] > d7 $ slow 4 $ jux (|+| speed "8.08") $ spread' (stut 4 0.95) "[1/8 1/8 1/8 1/8, 1/3 5 3 2]/4" $ sound "~ [tok tok:1 tok:2 tok:3]" |+| speed "8" > d1 $ slow 4 $ spread ($) [id, trunc (1/4), (0.25 <~), (|+| begin "0") . (|+| end "1") . chop 4] $ every 2 (0.25 ~>) $ jux (|+| speed "1.64 ! ! 4") $ sound "[bass3:6 ~ ~ bass3:6] [~ bass3:6]" |+| speed ((*1) <$> "[1.66 ! ! 4.02]") > d1 $ slow 2 $ jux ((stut 2 0.5 3) . (|+| speed "1")) $ slow 4 $ striate 32 $ sound "mef*2" |+| speed "2" > d1 $ jux (iter 4) $ every 4 (0.5 <~) $ every 3 (0.25 <~) $ chop 8 $ sound "rave:6 rave:7?" |+| shape "0.3" THANKS FOR LISTENING Grab it here: http://yaxu.org/tidal/ import Sound.Tidal.Vis import qualified Graphics.Rendering.Cairo as C import Data.Colour import Data.Colour.Names import Data.Colour.SRGB import System.Cmd let vis pat = do vLines (C.withSVGSurface) "vis.svg" (400,100) pat 1 1 rawSystem "/home/alex/Dropbox/bin/fixsvg.pl" ["vis.svg"] rawSystem "convert" ["vis.svg", "vis.pdf"] return () grid pat = do vLines (C.withSVGSurface) "vis.svg" (400,400) pat 10 10 rawSystem "/home/alex/Dropbox/bin/fixsvg.pl" ["vis.svg"] rawSystem "convert" ["vis.svg", "vis.pdf"] return ()