Concatenation functions breaking randomness?

Hey everyone! I'm new to this software and it's been a blast using it so far, but I've just now come across an issue that I can't figure out with the available documentation.

I've found that using a cat function on a set of sequences containing randomness will sort of 'set' the random variables for the duration of the concatenation. For example, I have a concatenation of four sequences with note probabilities set by the ? notation, and when playing this, all four sequences end up having the same notes trigger each time. The result is four repeats of the same pattern before it is randomised, instead of the pattern randomly varying each cycle. I was wondering if there's another way to combine these patterns together that won't break the randomness of them (and also if there's a way to do what randcat does also without this same issue).

Here's the example that alerted me to this problem. It's a generative bassline where the rhythms are weightedly random but the notes change in a preset pattern. (forgive me if the notation is clumsy in any unrelated ways)

d3 $ cat[
    n "[bf2?0.4 bf2?0.8 bf2?0.99 bf2?0.92 bf2?0.98 bf2?0.7 bf2?0.99 bf2?0.92]",
    n "[bf2?0.4 bf2?0.8 bf2?0.99 bf2?0.92 bf2?0.98 bf2?0.7 bf2?0.99 bf2?0.92]",
    n "[df3?0.4 df3?0.8 df3?0.99 df3?0.92 df3?0.98 df3?0.7 df3?0.99 df3?0.92]",
    n "[gf2?0.4 gf2?0.8 gf2?0.99 gf2?0.92 gf2?0.98 gf2?0.7 af2?0.99 af2?0.92]"
] # s "soskick"

(note: I've found this issue persists also when the probability values in each sequence aren't the same)

any help is appreciated!

What you're observing is the result of (i)rand being deterministic: a one-way function is applied to the time interval (Tidal/UI.hs at main · tidalcycles/Tidal · GitHub )

With cat, each argument (in a round-robin fashion) gets asked for events in the same interval.
This property can be used to repeat a random sequence (see my other message on random-not-random)

Then why is mininotation different? (for each ?, the random events appears to be independent) Because the parser silently inserts some magic (a small local time-shift to get different arguments for the pseudo-random function
Tidal/ParseBP.hs at main · tidalcycles/Tidal · GitHub)
This trick only works inside one mininotation string (in your example: for each line).

You could also do this trick in cat, cf. the ~> in https://gitlab.imn.htwk-leipzig.de/waldmann/computer-mu/-/blob/master/tidal/code/random-not-random.tidal#L13. The trick is to apply the time-shift to irand only, and not the whole pattern, because you want to keep that synchronous to the rest.

[EDIT] "the trick is" .. you can time-shift from the outside, , as in (your example)

d1 $ cat
   [  n "[gf2?0.4 gf2?0.8 gf2?0.99 gf2?0.92 gf2?0.98 gf2?0.7 af2?0.99 af2?0.92]"
   # pan 0
   , 0.001 ~> n "[gf2?0.4 gf2?0.8 gf2?0.99 gf2?0.92 gf2?0.98 gf2?0.7 af2?0.99 af2?0.92]"
   # pan 1
   ] # s "soskick"

the shift is so small that it does not change perception of synchronicity. I now think that this is what the mininotion parser actually does.

Thank you so much for your reply! I've tried implementing that 'trick' into the line in question, and it doesn't seem to have changed how the pattern is randomised.

d3 $ cat
    [n "[bf2?0.4 bf2?0.8 bf2?0.99 bf2?0.92 bf2?0.98 bf2?0.7 bf2?0.99 bf2?0.92]",
    0.001 ~> n "[gf2?0.4 gf2?0.8 gf2?0.99 gf2?0.92 gf2?0.98 gf2?0.7 af2?0.99 af2?0.92]"
] # s "soskick"

This is how i've done it; I'll keep tinkering because it's likely i've missed something, but if you notice something glaringly wrong with that please let me know! Once again seriously thanks so much for helping with such an oddly specific issue

[edit] so i've confirmed that it is definitely delaying the pattern, it's just not changing the randomisation

Huh. You are right (not changing the randomisation), and I am confused.

  stack
   [ n ( segment 1 $ irand 8)
   -- , n ( 0.001 ~> segment 1 (irand 8 - 12))   -- same "random" sequence
   , n ( segment 1 (0.001 ~> (irand 8 - 12)))  -- different sequence
  ]

"oddly specific" - not at all. some form of randomisation is present in a lot of algorithmic composition, so I'd like to understand how it's realized here.

[EDIT] I think what I'm missing is the exact connection between continuous pattern (rand) and discrete pattern, realized by segment.

i don't think it does, maybe you could try it again? the problem in my opinion really is that you are using the same degration values for all of the patterns and since randomness in tidal is deterministic, it always selects the same notes. I tried your example with different degration values and it produces different results (maybe try using lower numbers, to make the occurence more likely?)

you can only apply this trick to shift the random functions themselves to produce different values for the same span of time, this not possible in the mini-notation

now comes the interesting thing with mini notation:

within one and the same mini-notation string the random operators | and ? apply this shift is done implicitly, but if you have more than one mini-notation strings with the same structure, their randomness will be synchronised.

this means that in the pattern "[0 | 1] [2 | 3]" it can happen that it produces "0 3" for one cycle, selecting the first for the first half and the second for the second half - independed choices, similarly with "0?0.2 1?0.2".

but on the other hand "[0 | 1]" and "[2 | 3]" will always select the exact same events for every cycle.

now to be a bit more technical for everyone who is interested:
this magic in the mini-notation happens when transforming the mini AST to patterns in the following two lines:

TPat_DegradeBy seed amt x -> _degradeByUsing (rotL (0.0001 * fromIntegral seed) rand) amt $ toPat x
TPat_CycleChoose seed xs -> unwrap $ segment 1 $ chooseBy (rotL (0.0001 * fromIntegral seed) rand) $ map toPat xs

where seed is dependend on the location of the random operators in the mini-notation string, whenever such a operator is encountered the parsers job is to select a new seed like so:

newSeed :: MyParser Int
newSeed = do seed <- Text.Parsec.Prim.getState
             Text.Parsec.Prim.modifyState (+1)
             return seed

hope this was insightful!

p.s.: i think the way tidal handles randomness is one of the most interesting and powerful features once you wrap your head around how it behaves.. to be able to have "synchronised randomness" can be incredibly useful in my opinion

3 Likes

Hm. This is fair. When i said that, what I had tried was altering the degradation values ever so slightly. I'll try changing them by a larger amount and see if that changes anything. Though ideally I'd like to find a way to not have the probabilities be significantly different and still create a completely random pattern longer than a single cycle.

Hm, I've tried changing them by more now, and it doesn't seem to change anything. I'll keep tinkering

I wanted to get to the bottom of this, so I made this experiment


import Debug.Trace (trace)

let traced s (Pattern f) = Pattern $ \ st -> trace (s <> show (arc st) <> "\n") (f st)
in 
  d1 $ stack
     [ n (segment 2 $ traced "A" $ irand 12) # s "superpiano"
     , 0.001 ~> n (segment 2 $ traced "B" $ irand 12) # s "superpiano"
     , n (segment 2 $ 0.001 ~> (traced "C" $ irand 12)) # s "superpiano"
     ]

and I get

A112>112½
A112½>113
B112>112½
C111999/1000>112499/1000
C112499/1000>112999/1000
A112½>113
B112>112½
B112½>113
C112499/1000>112999/1000

this shows that indeed, for pattern B, irand gets asked for the same time interval as pattern A, while C is different.

strange.. can you make a minimal example (say 4 beats per cycle, two cycles) that produces this behaviour for you? and just to make sure: are you on the latest tidal version?

well you could for example use slow or the corresponding mini-notation / for making sequences longer than a cycle

Fantastic question. And I think you're right, because the short answer is kinda no?
I think what I've found is that when the degradation values (from one line of the cat to another) are within about 0.1 of each other, that's when this occurs.

d3 $ cat
    [n "[bf2?0.1 bf2?0.5 bf2?0.3 bf2?0.5]",
    n "[bf2?0.6 bf2?0.2 bf2?0.8 bf2?0.1]",
    n "[df3?0.1 df3?0.7 df3?0.9 df3?0.8]",
    n "[gf2?0.4 gf2?0.1 gf2?0.7 gf2?0.6]"
] # s "soskick" 

This is simplified test I made. It definitely randomises as it goes, but I have found that steps with high degradation values play suspiciously often. Mostly I think it just comes down to me getting a feel for how randomness works in this program. I think I've learnt enough from this thread to get things as random as I need, so I should probably just get back to writing music haha

Thanks so much, and let me know if you guys find any more interesting or relevant stuff

Actually let me elaborate in case anyone else comes across a similar thing. What I've found is that I get more the results I'm after if i treat the degradation values not just like probability values, but also like random seeds; varying them from each other by significant amounts seems to be better at getting them to randomise independently.