Tidal
Tidal copied to clipboard
Preplace update
Updating and reinstating the preplace family of functions, including prrw protate preplace <~> ~>> etc.
(The Travis fail seems unrelated.)
I'm a bit unsure about how valuePattern is treated as a list rather than a pattern. Wouldn't it be better to just pass a list there, maybe using varargs for nicer syntax? https://www.youtube.com/watch?v=u_5Ldi2YtGM
Would the varargs usage make it transparent for the user? I'll have to look into that. One of the nice things about the preplace functions is that even though they bake in some list-like behavior, they mostly feel natural and easy to use live. I think people work with Patterns a lot more than lists.
Coming back to this...
So the convenience of the Pattern notation means it's easy to do something like this with preplace:
t> rotL 0 $ preplace (1,1) "1 0 1" (s "a:0 b:0 c:1")
(0>⅓)|n: 0.0f, s: "a"
(⅔>1)|n: 0.0f, s: "b"
t> rotL 1 $ preplace (1,1) "1 0 1" (s "a:0 b:0 c:1")
(0>⅓)|n: 1.0f, s: "c"
(⅔>1)|n: 0.0f, s: "a"
Your objection is absolutely correct - we're not really using s "a:0 b:0 c:1" as a Pattern here, we really are using it as a list. The tricky part is that it's a list of type [ControlMap], and Tidal doesn't really have a concise syntax for building such a thing (the best I can come up with is [singleton "s" (VS "a" Nothing) `union` singleton "n" (VF 1 Nothing), ... ] which is a bit lengthy). TIdal of course does have plenty of nice syntax for building ControlPatterns. So while it's kind of an abuse to use that only to then coerce it into a list, the only alternatives I see are:
- have the
preplacefamily just coerce Patterns into lists (i.e. the current functionality) - like the above, but force it to be explicit:
preplace (1,1) "1 0 1" (toList $ s "a:0 b:0 c:1") - build an entire new set of functions so lists of ControlMaps can be built concisely, even though most of these would presumably be duplicating functionality that already exists for Patterns (ugh)
- not have
preplaceat all (but it seems like it fills a niche that's otherwise hard to duplicate) - ???
Maybe having an explicit toList is the best? There's already deconstruct but it seems to be doing something a little different.
OK I see the problem with making lists of controlmaps now, but don't really understand preplace itself. preplace (1,1) "1 0 1" (s "a:0 b:0 c:1") takes two patterns, both with three elements in per cycle but returns a pattern with two elements per cycle.. how so?
Also what's the significance of the values in "1 0 1" - are they ignored?
Agreed that stepwise combination of patterns would be really amazing to have back in tidal, but would like to make sure that there isn't some tweaking of the underlying model that could make it a lot more flexible.
The first parameter (1,1) has to be there because the patterns don't know their own period right?
It works somewhat like struct, in that the first Pattern (the "1 0 1") is a Pattern Bool. The difference is that the second pattern is treated as a list, and values from that list are put into the True values of the Pattern Bool in order.
And yes, the (1,1) is the number of cycles of the Bool and "list-like" Pattern to use.
This particular example is also similar to something fit does, it's almost
fit 2 ["a", "b", "c"] "0 ~ 1"
But since fit explicitly takes lists, it's difficult to work the ControlMap stuff in there.
Ok so how about we imagine we had mini-notation syntax to produce a pattern of lists, would that help? We kind of have this with :.
place :: Pattern Bool -> Pattern [a] -> Pattern a
fast 2 $ place "t f t" $ s "a:b:c"
|a bc a|b ca b|
fast 2 $ place "t f t" $ s "a:b:c x:y:z"
|a bc a|y zx y|
The above would require the params being able to produce both Pattern Control and Pattern [Control], that might be possible.. It also needs place to know how many items per cycle are in the boolean pattern.. It'd need to calculate all the elements in a cycle for that, but I don't think that'd be too heavy.
I'm not sure I understand what place is doing, wouldn't it make more sense for the second to evaluate to
|a yc x|b za y|
And what happens if you use lists of differing lengths, would it be
fast 2 $ place "t f t" $ s "a:b:c x:y:z:w"
|a yc w|b ya w|
Yes true, I guess I meant this:
fast 2 $ place "t f t" $ s "<a:b:c x:y:z>"
|a bc a|y zx y|
Do you think this sort of approach would cover what preplace does now?
It's a bit more complicated without the <>, but I'd expect:
place "t f t" $ s "<a:b:c x:y:z:w>"
|a bc a|z wx y|c ab c|z wx y|b ca b|z wx y|
This seems related: https://github.com/tidalcycles/Tidal/issues/750
OK, coming back to this...
So if I'm thinking about this correctly, if a mini-notation were to produce a pattern of lists with ":", then that would have to happen in the pattern parser. Right now ":" is kind of special in that it actually gets handled at a later stage, with the grp function. And grp itself would have to be redone, as I think it would have a different type
grp :: [String -> ControlMap] -> Pattern [String] -> ControlPattern
But otherwise I think it's quite possible?
Meant to comment earlier - I did take a look at modifying the parser but got a bit stuck on the seeming need for a new "atomic" type to handle Patterns of lists and how to implement it without tearing everything apart. Meanwhile, preplace might still be nice to have back again... (the Travis fails seem to be unrelated cabal issues)
I'm keen to find a way forward that avoids treating patterns as lists though, it seems like there's a nice opportunity to make things more flexible. For example as you point to, the current grp function isn't ideal, as you can't do things like sound "bd:<0 3>".
I'I think it might be surprisingly easy to generalise some bits of tidal so that the same functions works on both [ControlMap] and Pattern ControlMap. E.g. the type of speed :: Pattern Double -> ControlPattern could be generalised to speed :: Functor a => a Double -> a ControlMap without changing any definitions - just the types. It uses pF :: String -> Pattern Double -> ControlPattern which is really pF :: Functor f => k -> f Double -> f (Map k Value).
Looking at |+|, it should already work on lists of controlmaps: (|+|) :: (Applicative a, Num b) => a b -> a b -> a b
The new stateful events from #750 offers up a different approach to isorhythms too.
OK, that's interesting... listToPat already exists, and patToList is fairly straightforward:
patToList p = value <$> (flip queryArc) (Arc 0 1)
(could be generalized for longer Arcs)
which means you can do something sort of preplace-like with
let a = patToList (s "bd sn cp")
b = patToList (speed "1 2")
n = lcm (length a) (length b)
in
slow (fromIntegral $ n `div` length a) $ listToPat $ take n $ zipWith Map.union (cycle a) (cycle b)
Certainly could be wrapped in something easier to use.
One issue is "rests" and how to represent them in lists, patToList (s "a ~ b") evaluates to the same thing as patToList (s "a b"), but that can probably be fixed with a fancier patToList.... I'll keep working on it
patToList is straightforward to make but it's also something I'd like to avoid basing Tidal functions on. I think patterns and lists are different worlds, really. You can convert a list to a pattern with contiguous events, but can't convert a pattern to a list without losing information, as the example of rests shows.
Here's a different implementation though:
patToList pat = value <$> concatMap (\i -> queryArc pat (Arc i (i+1))) [0 ..]
It returns an infinite lazy list so you don't have to don't have to specify a number of cycles. Then you can just take the number of values you want:
take 8 $ patToList ("1 2 3?" :: Pattern Int)
> [1,2,1,2,1,2,3,1]
Then this works:
d1 $ sound "numbers(3,8)" # nTake "foo" (patToList "1 2 3?")
OK, I definitely need to dive into the new stateful stuff; I don't quite understand nTake
The stateful stuff is a WIP really! But promising.. You have to name the state explicitly, which is what "foo" is. Then the 'take' functions read from the given list, consuming a value for each event. Essentially you're creating a pattern of functions that manipulate state, which means that reversing a stateful pattern, e.g.
d1 $ rev $ sound "numbers(3,8)" # nTake "foo" (patToList "1 2 3?")
... will reverse the structure, but not the values.
Infinite lists seem to clog up pStateList so that the state can't be further updated, might need a lazy map?
Ah yes, forgot about that bug.. :/