Week 4 lesson 2 - random marathon: rand, irand, mininotation randomness, scramble, shuffle, choose + more

Continuing from our look at waveforms including random ones, here's the first of a two-parter looking at a wide range of random functions.. Starting with a bit of armchair philosophising about the nature of randomness in algorithmic music.

I made these videos before the worksheet. I've decided that I should really do this the other way around, for a more organised video, so might reshoot it at some point. As ever, let me know what you think! I think I go through things a bit too fast, and at this point am starting to freely mix in techniques we've looked at in earlier lessons which you might have already forgotten about, so please (virtually) stick your hand up if you'd like me to go through anything again, from this or in any other lesson. You'd be doing everyone a service!

Ok.. The video:

Video index:

  • 0:00 - Introduction
  • 0:25 - Thoughts on random numbers in algorithmic music
  • 3:51 - "rand" waveform - recap
  • 5:20 - Determinism - using "resetCycles" to re-listen to the same random stream from the start
  • 6:32 - Using "resetCycles" to repeat each cycle multiple times
  • 8:30 - "irand" - randomly generated whole numbers (integers)
  • 10:25 - preview of "scale" function, used here to pick notes randomly from a major scale
  • 11:18 - randomness in the mininotation - random choices with [ | ]
  • 12:52 - randomness in the mininotation - randomly dropping events with ?
  • 14:48 - "scramble" for picking randomly from parts of a pattern
  • 16:35 - "shuffle" for randomly shifting around the order of parts of a pattern
  • 17:40 - "choose" to pick from a list of values
  • 20:21 - "wchoose" to pick from a list of values, but with some more likely to be chosen than others
  • 23:17 - giving different patterns different (parts of a) random stream, so they don't 'interact'

And the worksheet:

-- Let's start with a look at the 'rand' waveform that we
-- met in the last lesson:

d1 $ n "1*8" # sound "drum"
  # speed (range 1 8 rand)

-- The 'resetCycles' resets the cycle count to '0', as
-- though you'd just started Tidal:
resetCycles

-- If you run resetCycles while the above pattern is running,
-- you'll notice that you also reset the random stream. You
-- will always get the same 'random' numbers every time you
-- start or reset Tidal.

-- You can apply rand to any numerical effect, but might have
-- to adjust the range. For example with the low pass filter
-- that cuts out frequencies higher than the given amount:
d1 $ sound "drum:5(5,8,<0 4>)"
   # lpf (range 200 8000 rand)
   # lpq 0.2

-- 'irand' is similar to 'rand', but creates integers, or
-- whole numbers, from 0 up to (and not including) the given
-- number. This is particularly useful for the 'n' and
-- 'note' controls:

d1 $ sound "rash(5,8)" # n (irand 32)
   # room 0.3 # sz 0.5

-- There are a couple of ways of doing random things in the
-- mininotation too. To randomly choose between subsequences,
-- put a | (vertical bar) between them

-- The second step in this sequence is a randomly pick from
-- four subsequences:
d1 $ n "0 [0|1*3|2*8|3 4 5] 2 3" # sound "cpu"
   # speed 1.5

-- Also, ? randomly 'drops' an event. In the following the
-- second step has a 50-50 chance of being played.
d1 $ sound "kick clap? kick snare"
  # delay 0.3 # delaytime (1/3) # delayfb 0.8 # speed 1.5

-- (I've added some echo delay to make it sound cool. Delay is the
-- amount of sound to be delayed, delaytime is the length of the
-- echo, delayfb is the feedback of the delay into itself)

-- You can adjust the probability of ? working with a decimal
-- (floating point) number. For example, to have an 80% chance
-- of dropping that clap (and therefore 20% chance of playing
-- it)
d1 $ sound "kick clap?0.8 kick snare"
  # speed 1.5

-- If you apply ? to a subsequence, it'll work individually
-- on each value in the subsequence
d1 $ sound "kick [clap:4 off clap:5]? kick snare"
  # speed 1.5

d1 $ sound "bd*8? clap:4"

-- Ok, onward to functions, starting with scramble. scramble
-- takes a number, which is the number of parts to equally
-- divide a pattern into. It'll then play those parts at
-- random.
d1 $ scramble 4 $ n "0 1 2 3 4 5 6 7" # sound "arpy"
   # room 0.3 # sz 0.8

-- The above is divided into four parts, and there are
-- eight events in them, so they are played in pairs. This
-- means that 0 is always followed by 1, 2 is always followed
-- by 3, and so on.

-- shuffle takes the same parameters as scramble, and sounds
-- very similar. Can you hear the difference?
d1 $ shuffle 4 $ n "0 1 2 3 4 5 6 7" # sound "arpy"
  # room 0.3 # sz 0.8

-- Whereas scramble picks part at random, shuffle plays the
-- parts in random order. The difference is that with shuffle,
-- every cycle, you'll hear each part exactly once. With
-- scramble, there's a (small) chance that you'll hear only
-- one part, played four times.


-- You can maybe hear this better if you play a clap at the
-- same time, to mark the start of the cycle. Then you can
-- hear that parts aren't repeating within the cycle.
d1 $ shuffle 4 $ n "0 1 2 3 4 5 6 7" # sound "arpy"
  # room 0.3 # sz 0.8

d2 $ sound "clap"

-- The "choose" function is for when you want to pick between
-- single values. It produces a continuous stream, with no
-- structure, so the following won't produce any events:
d1 $ sound (choose ["bd", "arpy", "snare"])

-- You'll need to provide some structure, with a function like
-- 'segment', which in this case picks 8 values per cycle:
d1 $ sound (segment 8 $ choose ["bd", "arpy", "snare"])

-- Or 'struct', which picks values according to a binary pattern:
d1 $ sound (struct "t t ~ t" $ choose ["bd", "arpy", "kick"])

d1 $ sound (struct "t(5,8)" $ choose ["bd", "arpy", "kick"])

-- Or by combining it with a pattern that *does* have structure:
d1 $ squiz "0*2 4 2 5 0 6*2 4 7"
  # sound (choose ["bd", "arpy", "kick"])

-- Another 'gotcha' - the parameters to choose are a list of values,
-- *not*, patterns, so you can't normally use mininotation there.

-- This *won't* work.
d1 $ squiz "0*2 4 2 5 0 6*2 4 7"
  # sound (choose ["bd*5", "arpy*2", "kick clap"])

-- I'll try to fix this in a future version of tidal! There is a
-- workaround, which is to use the 'innerJoin' function. Then you
-- can choose between patterns:
d1 $ squiz "0*2 4 2 5 0 6*2 4 7"
  # sound (innerJoin $ choose ["bd*5", "arpy*2", "kick clap"])

-- You can use choose with any parameter.

-- For example:
d1 $ sound "clap:4(3,8)"
  # speed (choose [2,5,0.5])

-- The following example is a bit different to the above, because
-- a new value is chosen only once per cycle:
d1 $ sound "clap:4(3,8)"
  # speed "[2|5|0.5]"

-- You could get the same behaviour from choose with 'segment'ing it
-- by a cycle:
d1 $ sound "clap:4(3,8)"
  # speed (segment 1 $ choose [2,5,0.5])

-- The 'wchoose' function is like 'choose', but you can give
-- a 'weighting' for each possibility. So something with a weighting
-- of '4' would be twice as likely to be chosen as one with a weighting
-- of '2', for example:
d1 $ sound "clap*4" # speed (wchoose [(2, 4), (-2, 2)])

-- The above claps will play either with a speed of '2' , or '-2'.
-- You can hear that negative speeds cause sounds to play backwards!
-- '2' has a weighting of '4', and '-2' has a weighting of
-- '2', so is half as likely to play.

-- Here I've weighted things so you get a lot of kicks, occasional
-- claps, and rarer snares:
d1 $ squiz "1 4*8 8*2 0*3"
  # sound (wchoose [("bd", 8), ("snare", 0.5), ("clap", 1)])

-- Ok one more thing! In Tidal, randomness is "deterministic". At
-- a certain cycle time, you will always get the same number. We
-- saw this at the start of the lesson, with resetCycles. That
-- resets the cycle count, as if you just started Tidal up. You
-- can then hear that the 'random' numbers are the same.

-- This can result in unexpected results.
-- Listen to this:
d1 $ sound "clap*2" # speed (range 0.1 2 rand) # pan rand

-- You can hear that on the left speaker, the 'speed' of the
-- sound is always low, and when it pans to the right, it's
-- always high. Strange! This is because the same 'random'
-- number stream is used for both the speed and the pan, so
-- they get the same numbers, and seem to interact.

-- This can be nice! But if you don't want this effect, you can
-- avoid it by manipulating the timeline of one of the random
-- patterns. For example:
d1 $ sound "clap*2" # speed (range 0.1 2 rand)
  # pan (slow 1.001 rand)

-- I only slowed that 'rand' down by a tiny amount, but that's
-- enough to end up with totally different numbers.. So now
-- you're as likely to get lower speeds on the left as on the right.

Next lesson:

11 Likes

Yay, love this stuff. I'm not very far through it, but I wanted to maybe throw in something for thought re randomness (or, let's call it indeterminacy to engage that tradition more). It is a bit of an aesthetic debate I suppose, about leaving things to chance in music-making, in contrast to improvising (and that is in itself a big discussion). But, at least for me, I use randomness a lot, not to avoid making decisions - the decision to add or not add randomness is the decision - but rather the reach outcomes that I would not otherwise reach. So thank you for including randomness into Tidal! <3

Edit: http://aaroncassidy.com/experimental-composer/

This article is one of my favourite succinct ones on some of these ideas.

3 Likes

I broke my brain while riffing on this video. Switching the commented stuff on and off = my brain. My poor brain. Alex, this is so cool. I can't wait to get really good at understanding efficient ways to structure the code, and building fairly hefty compositions, installations, etc.

d1 $ degradeBy (slow 4(range 0.1 1.0 $ rand)) $ snowball 2 (+) (slow 4) $ shuffle (irand 5) $ n (struct "[t(5,7,3)|f(5,7,3)]" $ (irand 5) - (irand 24)) # sound "supersaw"
  -- # sustain 0.4
  -- # decay 0.8
  -- # speed (rangex 0.1 0.2 $ perlin)
  -- # room 0.3
  -- # sz 0.2
  -- # lfo 0.8
  -- # resonance 0.8
  # lpf (slow 2 (range 100 400 $ saw))
  -- # lpq 0.1
  -- # voice 0.8
  -- # lpq (slow 3 (range 0.1 0.3 $ perlin))
  # gain 0.1
  -- # binshift (slow 5 (range 0.2 0.8 $ perlin))

d2 $ repeatCycles 3 $ sometimes (fast 2) $ scramble (irand 6) $ n (struct "[t(5,7,3)|t(5,4,2)]" $ (irand 3))
    # sound (choose ["cpu", "bskick"])
    # gain 0.8
    # speed (slow 4 (range 0.02 0.4 $ sine))
    # cut 1
    -- # waveloss (slow 4 (range 20 50 $ perlin))
    -- # smear (slow 3 (range 0.2 0.8 $ perlin))
4 Likes

Also, quick question, with stuff like repeatCycles, is it possible to have that fed a random number? I tried to give it repeatCycles (irand 5) for example but it returns an error. I tried using choose as well, but that similarly didn't work as it seems the integer that xCycles is expecting is of a different data type or something?

The thoughts on randomness in this video were very interesting, thanks. I have to confess I was slighly dreading watching it, as I also have strong ideas about this area. To me, being able to ask the machine to make arbitrary decisions – agreed, let's not call them random – is fundamental to the business of algorithmic composition. I like to be able to create a high level structure and let the computer fill in the details: I see that as a fascinating way to discover 'what works' in music. James McCartney talks this somewhere, sorry I can't find the exact reference, as being able to create a class of compositions and then listen to various instances of the class.

To take an rather wonderful example from your worksheet:

d1 $ sound "bd*8? clap:4"

To me, this is asks and answers an interesting musical question, something like: 'What about… half a bar of arbitrarily syncopated kick drum hits followed by an offbeat clap? Would that work?' And yes, it does.

1 Like

Thanks @tedthetrumpet and @vin for taking on my polemic. I started out with some wariness about random number generators, then spent the rest of the video having fun using them, so take what I said with a pinch of salt!

For me the emergence of live coding was in part a reaction against "generative art", in particular the idea that by using random numbers, you can create infinite variation, and gasp at the creative system you've made which holds a mirror to the beauty of the universe. The problem is that the more you listen to infinite variation, the more it sounds the same.

I like the idea of creativity as search and discovery. You define a space of possibilities (e.g., all the possible 8 step kick-snare rhythms), and look for things in it. You can use a random number generator for picking rhythms from that space, and listen out for good ones to use.. This is the approach taken by algorithmic composers such as David Cope. But there is a slight of hand here. The value comes not from the random number generator part (which comes up with all kinds of nonsense), but the act of listening out for good ones. I.e., this isn't algorithmic composition, but simply composition lead by the ear. That's be completely fine of course, but all the posturing around chance operations, creative systems, automatic composers etc is missing the point, when the whole process still revolves around human perception.

So that's where the chip on my shoulder comes from.. But really, I do use random number generation a lot, and a point I was trying to make in the video is that using Tidal often feels a bit like hand-making a random number generator.. Really all a random number generator does is make an interference pattern, and by making your own you are finding that sweet spot between noise and mundane predictability, which is where you could say is where all the music lies!

6 Likes

Yes the first parameter is an Int, rather than a Pattern Int, so you can't put a pattern there:

repeatCycles :: Int -> Pattern a -> Pattern a

There are ways around this, e.g. using 'do' notation, and <- to take values out of patterns:

d1 $ do n <- "<2 3 4>"
        repeatCycles n $ sound "<bd(3,8) cp(3,8)>"

However this probably won't do what you expect. The above switches between different versions of the pattern every cycle, and they will be repeating at different rates, so you won't hear an orderly repetition.

(n.b., rather than taking values out of patterns, I'd say the <- there is really doing something more like taking the computation into the pattern, but this gets hard to think about.. This is the 'monadic' stuff that Haskell people waggle their eyebrows around a lot)

Anyway a version of repeatCycles' with the patternable repeat:

repeatCycles' pn pat = innerJoin $ (\n -> repeatCycles n pat) <$> pn

It's just a bit unruly..

d1 $ repeatCycles' "<2 2 3 3 3>" $ n (struct "t(5,8)" $ scale "ritusen" (irand 8)) # sound "rash"
  |+ n 24

d2 $ sound "cp"

I could put this in the next version if you think it's interesting though!

3 Likes

Hey @yaxu. Didn't you mention that this was a 'bug' with repeatCycles in the other thread over here?

Looking at the repeatCycles type:

repeatCycles :: Int -> Pattern a -> Pattern a

That first parameter is an 'int', and not a pattern of ints. Actually it's only in the last couple of years that you've been able to use patterns as parameters in this way in Tidal.. (the inner workings of this are quite something to behold, it's amazing that it works so well in general..) So this is kind of a bug in Tidal that this parameter isn't 'patternable'. I've made a note to fix it https://github.com/tidalcycles/Tidal/issues/645

1 Like

Stumbled upon something that gives a syntax error that I would have thought would have worked:
d1 $ s "bd!8?"
This is ok:
d1 $ s "[bd!8]?"

Erp, so I did!

1 Like

hm yes.. maybe worth a bug report

Done, did the bug report.

1 Like

Thanks @yaxu, that is going to take some mental gymnastics (and trial and error) to understand the structures. Am I right in understanding that you can use haskell variables across TC more generally? In particular can you declare global variables that can be used in multiple OSC sends?

To borrow the SuperCollider global variable vernacular a bit. Sorry, this is probably for a different topic.

do
   ~globalVar1 = "some pattern"
   ~globalVar2 = "some other pattern"

d1 $ n ~globalVar1|~globalVar2 # s "bd -- mininotation pipe

d2 $ n ~globalVar1 # s "synth"

d3 $ n ~globalVar2 # s "bass"
2 Likes

Rather than do a long discussion about this (it's one of my favs), I started a new thread!

2 Likes

If I do this:

d1 $ scramble 8 $ n "0 1 2 3" # s "arpy"

is that a structure of 8 events from the scramble or a structure of 4 events from the n?

1 Like

I got the following error when trying to run:

d1 $ n "0 [0|1*3|2*8|3 4 5] 2 3" # sound "cpu"
   # speed 1.5
Error in pattern: Syntax error in sequence:
  "0 [0|1*3|2*8|3 4 5] 2 3"
       ^  
unexpected "|"
expecting float, "'", rest, "[", "{", "<", ".", "_", "," or "]"

Any ideas?

Seems like it's the " | " (straight line) in between that it's not liking?

Hi @tomh, I think you'll just have an older version of tidal installed, the | syntax is fairly new

Btw I took the liberty of editing your post to put

```

above and below the code blocks, as the forum was messing up the formatting otherwise.

2 Likes

Hey @vin would you mind explaining a bit what the "do" line does? Is this necessary for declaring variables?

1 Like

Hey @julianmrn5, in the above it is pseudocode. But in reality it encapsulates everything below it that is indented by one tab (or more) into a single execution. I’m not sure how variable scopes work in Haskell/Tidal but to my mind if in the above pseudocode the subsequent Dirt connections were indebted correctly then those variables would be local in scope. When I get in front of a computer I can paste in some actual code if that would be helpful? I can’t answer about the necessity for variables but I reckon @yaxu or @eris (I think) can give better answers around variable creation and scope!

1 Like