How do I lift a (potentially infinite) lazy list to a pattern?

Hey y'all,
So I was wondering if there was any way in the visible API to do what the title says: I want to take lazy lists, which are super easy to generate recursively in Haskell, and turn them into (continuous?) patterns that I can chunk by taking a set amount of events per cycle.

Like I was inspired by this by a challenge I saw on Twitter about generating music from the digits of pi, and I figured if I had a method to generate the digits as an infinite lazy list and then turn that into a pattern it'd be trivial in Tidal.

I was looking into something similar to this earlier in the course where I was thinking that I could create patterns from an infinite list as a function from the current cycle number to the value at the index of that number. It turns out you can do it like this:

    let mylist = [0, 2 ..]
        pat = segment 1 $ (mylist !!) <$> sig floor

There might be other ways to do it, but this works. In my use case, it worked kind of awkwardly since I was constructing lists from iterative functions.

2 Likes

well doing something like

let fib = (1::Int):1:zipWith (+) fib (tail fib)

d1 $ s "superpiano*4" # n (fmap (fromIntegral . mod 24) $ (segment 4 $ (\x -> fib !! (floor $ 4*x)) <$> sig id))

is not giving me different notes a cycle but just either a very low tone or a very high one. Maybe because sig is actually giving me a total count of cycles since the start of Tidal so it's slamming into some kind of overflow on the fib function? There doesn't seem to be any documentation of sig.

Your example fed into

d1 $ s "superpiano*4" # n pat

only gave me a single low tone

1 Like

And of course I was being silly and didn't think about the fact that it's easy to reboot Tidal and check and, well, things work as expected but that means this isn't suitable for improvisation where you want things to start "at 0" when you first create the pattern.

I feel like the problem is that these kinds of things aren't really continuous patterns. We have a discrete pattern with infinite period.

Basically what I want is for this to work but cat really doesn't play well with lazy evaluation

let fib = (1::Int):1:zipWith (+) fib (tail fib)
chunkList c [] = []
chunkList c ls = (take c ls) : (chunkList c $ drop c ls)
patterner c ls = cat $ map (fastCat . map return) $ chunkList c ls  
mylist = [0, 1..]
pat = patterner 8 mylist
1 Like

I think I've spent some time banging my head against the same wall as you.

Fwiw, you might want to check out this thread on resetting the cycles count. resetCycles is what I used
The way I see it is that Tidal is really good at lots of things (and in particular, things that no other languages can do), but iterative functions isn't one of them. . . and that's okay.

1 Like

Yeah but from my perspective this is a place where Tidal's implementation of patterns seems against the grain of Haskell's lazy evaluation. Working with infinite datastructures should be absolutely fine as long as you're never using more than a finite piece in a calculation.

It's possible to hand-code Patterns that evaluate from some iteratively defined thing, but IME this tends to be fragile - it's hard to guarantee that some weird pattern transformation won't trigger arbitrary long/infinite calculations. Patterns will work for a bit but then something will cause 100% CPU load as Tidal grinds to a halt.

For example, you can do something like

flipflop = Pattern $ \(State (Arc s e) cm) -> if s < 1 then [zeroE s e] else map modE (queryArc flipflop (Arc (s-1) (e-1)))
      where modE (Event c w p v) = Event c w p (1-v)
            zeroE s e = Event (Context []) (Just $ Arc s e) (Arc s e) 0

which I think works OK... until it doesn't.

1 Like

Some observations:

  1. we want fromList :: [a] -> Pattern a - which we have (https://github.com/tidalcycles/Tidal/blob/04b889742fca9923ee5bc871572b34db6e6010a6/src/Sound/Tidal/Core.hs#L175) but it uses cat and that's not lazy: it will evaluate the length of its argument
    (https://github.com/tidalcycles/Tidal/blob/04b889742fca9923ee5bc871572b34db6e6010a6/src/Sound/Tidal/Core.hs#L219)

  2. with ifp (https://tidalcycles.org/index.php/ifp ) we can access the current time (cycle count) and use it as the index into a lazy list. This gives something like fromList for [Bool]:

let fib = 0 : 1 : (tail fib >>= \ case 0 -> [0,1] ; 1 -> [0])
d1 $ ifp (\ i -> fib !! i == 0) (const $ s "bd") (const $ s "sn") silence
  1. In this example, fib is the Fibonacci word, see http://www-igm.univ-mlv.fr/~berstel/Lothaire/AlgCWContents.html . It has nice combinatorial properties, aviods repetitions, etc. Fascinating mathematics but utterly meaningless for music IMHO - I claim that any such "magic" sequence can, in a composition, be replaced with any (pseudo) random thing (of similar density function) and the result would sound just the same... But that's for the artist, or the audience, to decide. Let's keep to the technical discussion:

  2. We can indeed build a more general lazy fromList along what @bgold posted above (shifting the time inside queries - this is already in the implementation of cat). But !! i takes time linear in i. We can do better (somewhat): there are data structures for infinite sequences that are non-strict in the tail and allow logarithmic indexed access (for the part that's evaluated). Basically, a lazy sequence of balanced trees (of increasing size) but I don't have the exact reference right now. Might be a fun exercise to program. I wrote down the idea at https://github.com/tidalcycles/Tidal/issues/695

4 Likes