chopBy?

I'm trying to write an implementation for a chopBy function that would be similar to striateBy, that accepts a second parameter like striateBy for sample length, but playing bits sequentially like chop .

But I can't quite get the right behaviour ..

The first version I wrote:

_chopBy :: Int -> Double -> ControlPattern -> ControlPattern
_chopBy n f pat = squeezeJoin $ fmap (chopPart n f) pat


chopPart :: Int -> Double -> ValueMap -> ControlPattern
chopPart n f v = fastcat $ map (pure . rangemap v) slices
  where
    rangemap v (b, e) = Map.union (fromMaybe (makeMap (b,e)) $ merge v (b,e)) v
    merge :: ValueMap -> (Double, Double) -> Maybe ValueMap
    merge v (b, e) = do
      b' <- Map.lookup "begin" v >>= getF
      e' <- Map.lookup "end" v >>= getF
      let d = e' - b'
      return $ makeMap (b' + b*d, b' + e*d)
    makeMap (b,e) = Map.fromList [("begin", VF b), ("end", VF e)]
    slices = map (\i -> (slot * fromIntegral i, slot * fromIntegral i + f)) [0 .. n-1]
    slot = (1 - f) / fromIntegral n

Distributes the events amongst the cycle like so:

(0>⅛)|begin: 0.0f, end: 0.16666666666666666f, s: "a"
(⅛>¼)|begin: 0.4166666666666667f, end: 0.5833333333333334f, s: "a"
(¼>⅜)|begin: 0.0f, end: 0.16666666666666666f, s: "b"
(⅜>½)|begin: 0.4166666666666667f, end: 0.5833333333333334f, s: "b"
(½>⅝)|begin: 0.0f, end: 0.16666666666666666f, s: "c"
(⅝>¾)|begin: 0.4166666666666667f, end: 0.5833333333333334f, s: "c"
(¾>⅞)|begin: 0.0f, end: 0.16666666666666666f, s: "d"
(⅞>1)|begin: 0.4166666666666667f, end: 0.5833333333333334f, s: "d"

But the expected output should evenly divide each part by the specified length, but it looks like the calculation for b and e in rangemap is not handling the positions correctly

I rewrote to try and correct the slicing logic so that each part starts where the previous one ended


chopPart :: Int -> Double -> ValueMap -> ControlPattern
chopPart n f v = fastcat $ map (pure . rangemap v) slices
  where
    rangemap v (b, e) = Map.union (fromMaybe (makeMap (b, e)) $ merge v (b, e)) v
    merge :: ValueMap -> (Double, Double) -> Maybe ValueMap
    merge v (b, e) = do
      b' <- Map.lookup "begin" v >>= getF
      e' <- Map.lookup "end" v >>= getF
      let d = e' - b'
      return $ makeMap (b' + b * d, b' + e * d)
    makeMap (b, e) = Map.fromList [("begin", VF b), ("end", VF e)]
    slices = map (\i -> (i * slot, i * slot + f)) [0 .. fromIntegral (n - 1)]
    slot = f

which is outputting:

(0>⅛)|begin: 0.0f, end: 0.16666666666666666f, s: "a"
(⅛>¼)|begin: 0.16666666666666666f, end: 0.3333333333333333f, s: "a"
(¼>⅜)|begin: 0.0f, end: 0.16666666666666666f, s: "b"
(⅜>½)|begin: 0.16666666666666666f, end: 0.3333333333333333f, s: "b"
(½>⅝)|begin: 0.0f, end: 0.16666666666666666f, s: "c"
(⅝>¾)|begin: 0.16666666666666666f, end: 0.3333333333333333f, s: "c"
(¾>⅞)|begin: 0.0f, end: 0.16666666666666666f, s: "d"
(⅞>1)|begin: 0.16666666666666666f, end: 0.3333333333333333f, s: "d"

still not quite the expected behaviour.. and not quite sure where to go next?

1 Like

Oh somehow I missed this back in May! Looks useful, did you ever get the behaviour you wanted?

Noo but this reminds me to revisit this!

Hmm, well looking at the behaviour of the existing striateBy..

_striateBy 2 0.5 $ s "a"
(0>½)|begin: 0.0f, end: 0.5f, s: "a"
(½>1)|begin: 0.25f, end: 0.75f, s: "

That seems wrong as well?

Looks like an off-by-one error.. I just changed n to n-1 on the last line:

_striateBy :: Int -> Double -> ControlPattern -> ControlPattern
_striateBy n f p = fastcat $ map (offset . fromIntegral) [0 .. n-1]
  where offset i = p # P.begin (pure (slot * i) :: Pattern Double) # P.end (pure ((slot * i) + f) :: Pattern Double)
        slot = (1 - f) / fromIntegral (n-1)

Now it works better, e.g.:

_striateBy 4 0.5 $ s "a"
(0>¼)|begin: 0.0f, end: 0.5f, s: "a"
(¼>½)|begin: 0.16666666666666666f, end: 0.6666666666666666f, s: "a"
(½>¾)|begin: 0.3333333333333333f, end: 0.8333333333333333f, s: "a"
(¾>1)|begin: 0.5f, end: 1.0f, s: "a"

Here's a PR which also uses the same mergeplayrange function as striate, so that it works within any existing begin/end controls.

I've run out of time now but hopefully this fix helps with fixing chopBy!

not exactly the same but when I want to do something like that I use my custom chopAt function and change the legato of the note:

e.g. d1 $ chopAt' 8 8 "someloopable" # legato 0.5

Looks like I failed to add the link to the striateBy PR: Fix off-by-one error in striateBy by yaxu · Pull Request #1106 · tidalcycles/Tidal · GitHub