tidalcycles / Tidal

Pattern language
http://tidalcycles.org/
GNU General Public License v3.0
2.22k stars 254 forks source link

a more flexible (slow) cat, specifying timespan for each argument #654

Open jwaldmann opened 4 years ago

jwaldmann commented 4 years ago

Is your feature request related to a problem? Please describe.

I found it surprisingly hard to implement "play x cycles of pattern a, then y cycles of pattern b", in fact, impossible with abstract operators (that don't manipulate queries or arcs).

Describe the function/feature you'd like

I thought this variant of (slow)cat would help:

tcat :: [(Time, Pattern a)] -> Pattern a

tcat [.. (xi,pi) .. ]  plays  x1 cycles of p1, then x2 cycles of p2, ...

proxy implementation below.

Describe alternatives you've considered

combinations of "surface" functions (fast, slow, sew) that don't manipulate queries

Additional context

I made this proof-of-concept implementation, starting from the code in Sound.Tidal.Core.cat. Note that we could then cat = tcat . zip (repeat 1).

import Data.Traversable (mapAccumL)

tcat :: [(Time, Pattern a)] -> Pattern a
tcat [] = silence
tcat dps = Pattern q
  where (total, segs) = make_segments dps
        q st = -- concatMap (f st) $ arcCyclesZW (arc st)  -- ?? not sure about this
           f st (arc st)
        f st a = query (withResultTime (+offset) p)
          $ st {arc = Arc (start a - offset) (stop a - offset)}
          where t = start a
                (cyc,f) = properFraction $ t / total
                   :: (Int,Time)
                local_t = f * total
                (arc ,p) = find_segment segs local_t
                offset = start arc + total * fromIntegral cyc

make_segments :: [(Time,a)] -> (Time,[(Arc,a)])
make_segments dps = 
  mapAccumL (\ t (d,p) -> (t + d, (Arc t $ t + d ,p))) 0 dps

find_segment :: [(Arc,a)] -> Time -> (Arc,a)
find_segment (seg@(arc,p) : later) t =
  if isIn arc t then seg else find_segment later t

This seems to work for point-like events (play synth), probably doesn't work right for events with span (play sample).

It could be improved by more efficient lookup in find_segment (lookup table if timespans are nicely commensurable, balanced decision tree for the general case).

There is no way to make timespans patternable in this approach.

yaxu commented 4 years ago

Have you looked at timecat, seqPLoop and ur?

jwaldmann commented 4 years ago

I did now.

timeCat speeds up argument patterns individually, which I don't want. (I find it impossible to undo this later.)

ur is hard to discover (the name tells nothing, implementation has no haddock). The example in https://tidalcycles.org/index.php/ur uses ".. play four cycles each" so I assume the timing is uniform (too uniform for my use case).

seqPLoop comes close but it's not what I want:

flip queryArc (Arc 0 4) $ seqPLoop [(0,1,s "<a1 a2>"),(1,3,s "b")]
[[((2,1),(4,1))](0>1)|s: "a1",
[((1,1),(2,1))](1>2)|s: "b",
[((1,1),(2,1))](2>3)|s: "b",
[((2,1),(4,1))](3>4)|s: "a1"]      -- this should be "a2"

My implementation above wasn't doing this correctly, it should really be

tcat dps = Pattern q
  where (total, segs) = make_segments dps
        q st = concatMap (f st) $ arcCyclesZW (arc st) -- still don't know why exactly
        f st a = query (withResultTime (+offset) p)
          $ st {arc = Arc (start a - offset) (stop a - offset)}
          where t = start a
                (cyc,f) = properFraction $ t / total
                   :: (Int,Time)
                local_t = f * total
                (arc ,p) = find_segment segs local_t
                dur = stop arc - start arc
                offset = start arc
                  + (total - dur) * fromIntegral cyc   -- must substract duration of segment
yaxu commented 4 years ago

Ah, I thought ur would mean something like 'higher' in German, so thought it would make sense as a short name for a function that patterned patterns. It's always felt like a work-in-progress, but it's been around a long time now so does need properly documenting really!

So your implementation is like seqPLoop, but it doesn't play the patterns from cycle 0 each time?

Fractions cause trouble:

d1 $ tcat [(0.5, sound "bd sn"),
           (0.25, sound "<arpy arpy:1 arpy:2>")
          ]

Here's a version built around compress which seems to cope with that:

tcat :: [(Time, Pattern a)] -> Pattern a
tcat pats = _slow s $ stack $ zoomin 0 pats
  where zoomin t [] = []
        zoomin t (pat:pats) = (compress (t, t + (fst pat/s)) $ snd pat) : (zoomin (t + (fst pat/s)) pats)
        s = sum $ map fst pats
jwaldmann commented 4 years ago

Re: "like seqPLoop, but it doesn't play the patterns from cycle 0 each time?" - Yes.

Re: your implementation: that's much better, using compress instead of manipulating queries explicitly. compress(Arc) should be documented.

Re: ur. I thought you meant this as an abbreviation that I couldn't figure out. As a word (prefix) in German, it means "primordial", e.g., "Ur-schrei" = "primal scream", "Ur-suppe" = "primordial soup" (in biology). Even "Ur-sache" (reaon) is literally "first thing". Yes, I would accept "Ur-combinator" (of patterns) as a name for a function that is, well, fundamental in the sense that you could implement all (or lots) others with it.

jwaldmann commented 4 years ago

Examples for "ur-" in art and music: https://en.wikipedia.org/wiki/Kurt_Schwitters#Ursonate and

Ursatz (deep structure) in https://en.wikipedia.org/wiki/Schenkerian_analysis (also "Urlinie", fundamental line)

So, future haddock for ur should start with In Schenkerian analysis, ... - Not!

yaxu commented 4 years ago

From discussion on club.tidalcycles.org, it seems with ur too, sometimes people want inner patterns to restart from 0 for each outer event, and sometimes they don't.. just as with this tcat vs timecat. What should these be called? They could be timecat and timecat', but I don't really like doing that - I renamed a lot of 'primed' functions to be more descriptive in Tidal version 1.0.. There were a lot of them and it was hard to remember what they did. Also it is feasible that someone would want one pattern-as-event to start at cycle 0, and another to align with the 'outer' cycle, in the same pattern. Is there a reasonable way of expressing this?

That's good to know on ur, and I agree we should keep Schenker out of this!

One of the very first things I did was prototypical Tidal was model Kurt Schwitters Ursonate :) https://tidalcycles.org/index.php/History_of_Tidal The kurt default samples might be familiar..

jwaldmann commented 4 years ago

keep Schenker out ..

On the other hand, I am reading references given in https://en.wikipedia.org/wiki/Peter_Westergaard%27s_tonal_theory and it'd definitely be interesting to compare these operators (segmentation, anticipation, ...) with Tidal's.

Kurt

I had no idea! - I have very little knowledge of the superdirt samples. I'm looking forward to the results of https://github.com/tidalcycles/Dirt-Samples/issues/15#issuecomment-630147427 to get this organised.