Challenge: non-linear time transform

most (?) library functions that modify time, do this by applying a linear transformation.

Example: fast https://hackage-content.haskell.org/package/tidal-core-1.10.2/docs/src/Sound.Tidal.Pattern.html#_fast

... withResultTime (/ rate) $ withQueryTime (* rate) pat

Example: rev is piece-wise linear. implementation https://hackage-content.haskell.org/package/tidal-core-1.10.2/docs/src/Sound.Tidal.Pattern.html#rev looks a bit funny but the transformation really (?) is

r :: Time -> Time 
r t = let (p,f) = properFraction t in fromIntegral p + 1 - f

(the 1 - f is in mirrorArc, I think)

NOW: Can we build something interesting with a transformation of time that is not piece-wise linear?

Interesting question! I'll approach from a math theory angle:

I think that type Time = Rational limits usable functions to (piecewise) rational functions, which are a ratio of two polynomials with rational coefficients.

As shown by fast and rev the time transformation needs to be (piecewise) invertible to get coherent results (resultTime needs to match up with queryTime). This restricts things further: not only does the function need to be a rational function, but so does its inverse.

I think this rules out everything quadratic and above (square roots aren't always rational, wept Pythagoras), so one is left with only (piecewise) Moebius transformations: \t -> (a*t+b)/(c*t+d)(see Wikipedia for lots of properties including inverse).

Haps don't have a notion of speed of time changing inside them, only start+end, so maybe everything becomes piecewise linear eventually anyway?

module Moebius where

import Sound.Tidal.Core
import Sound.Tidal.Time
import Sound.Tidal.Pattern
import Sound.Tidal.Control
import Sound.Tidal.UI
import Sound.Tidal.Params
import Sound.Tidal.Show

type Bi a = (a -> a, a -> a)

timeFunc :: Bi Time -> Pattern a -> Pattern a
timeFunc (f1, f) = withResultTime f1 . withQueryTime f

moebius :: Rational -> Rational -> Rational -> Rational -> Bi Time
moebius a b c d
  = ( \t -> (d * t - b) / (-c * t + a)
    , \t -> (a * t + b) / ( c * t + d)
    )

apply :: Pattern (Pattern a -> Pattern a) -> Pattern a -> Pattern a
apply fs p = fs >>= \f -> f p

piecewise :: Pattern a -> [Pattern a] -> Pattern a -> Pattern a
piecewise before during after =
  when (< 0) (const before) $
  go 1 during
  where
    go n (p:ps) = when (< n) (const p) $ go (n + 1) ps
    go _ [ ] = after

doubleEspresso :: Pattern a -> Pattern a
doubleEspresso = apply . fmap timeFunc $ piecewise
  ( pure $ moebius 1 0 0 1 )
  [ pure $ moebius 2 0 (-1) 2
  , pure $ moebius 6 (-4) 1 0
  ]
  ( pure $ moebius 1 2 0 1 )

example :: Pattern Char
example = doubleEspresso "<[a b c d] [e f g h] [i j k l] [m n o p]>"

this looks like it does something, haven't listened yet...

I was thinking about a similar thing when making a sketch in strudel. I hope that I am not going too far on a tangent by not being versed in tidal itself (I'll try to translate from strudel to tidal where adequate). Please bear with me.

The question I had was: how to produce a struct which follows the sound of a bouncing ball. So the kind of transformations/functions I am interested are mostly "specific structures" like euclid 3 8 (I am not sure if something like euclid even is withing the scope of the original question)

Not a bouncing ball, but similar is the mini-noation "[x [x [x [x [x [x -]]]]]]" which is the equivalent of a (terminated) geometric series in duration: 0.5 0.25 0.125 0.625 ... the exponent is whole, the duration is halved after every step. the duration of the single step is 1/2^i, i.e. 2^(-i)

The times for a bouncing ball are described by a terminated geometric series with a non-whole exponent. there's some bouncyness/elasticity involved, but it boils down to a duration which is the square root of the elasticity (say 1/2) and thus 1/sqrt(2^î) or 2^(-i/2). (the 2 is arbitrary the 1/2 in the exponent is particular to an actual, physical bounce).

To get back to claude's piecewise functions: Indeed, if time is rational, we can only approximate this kind of pattern by rounding the bounce times with some given precision to a rational number. Being a struct, it's easily inverted (take only the first hap).

I'm not sure if this kind of behaviour (rounding algebraic numbers to rational ones) already fits into the Möbius transform setting (but maybe I am missing the point by wanting a structure as a result).

Moebius: nice. I needed to resetCycles in order to hear the non-trivial part of the transform.

("haven't listened yet" - that's a version of Donald Knuth's "beware I have only proved this code correct, but have not actually run it")

Transforming with the function r t = let (p,f) = properFraction t in fromIntegral p + 1 - f does not work since it has a dis-continuity, and is not self-inverse, at each integer, and this confuses tidal (sc shows "late event" messagaes and does skip several events). It seems that with(Result/Query)Time have an implicit (un-documented) requirement that functions should be monotonic? In order to keep the (also implicit) requirement that for each pattern, queryArc p i must return events inside the interval i. The implementation does not care: withResultArc f pat = pat { query = map (\(Event c w p e) -> Event c (f <$> w) (f p) e) . query pat } . That seems to be the reason for the complexity of the actual rev implementation.

I found that tidal/sc is getting nicely confused by the following (where the transforms are not inverse to each other)

d1  $ withinArc (Arc (1/4) (3/4))
   (withResultTime (/  2) . withQueryTime (/ 2))
  $ s "superpiano"
     + note (scale "major" $ run 16 - 12)

we can build a non-linear time transform (that is still monotonic, rational to rational, and invertible) by repeated application of piece-wise linear transforms (mathematically, we want infinite repeation, but with the naive implementation, we have to stop somewhere)

imagine this process: play a pattern from 0 to 1/2 (outer time) with speed 2/3, so the pattern gets to 1/3 in its local time. Then from 1/2 to 1 outer time, play the rest of the pattern (1/3 to 1 local time) with speed 3/4. Then - apply this (recursively) on the two parts. The following code does this (?), but tidal introduces extra events (?), and even sometimes misses note-offs (?)

let f g p = fastcat [ g $ zoom (0, 1/3) p, g $ zoom (1/3, 1) p ]
in d1 $ (\ p -> stack [  outside 8 (iterate f id !! 8) p , p - note 12 ]) -- could use jux?
      $  s "superpiano"
       + note (scale "major" $ fast 2 $ slow 8 ( run 8) + fastcat [-7,0])

[EDIT] this transformation (in the limit) is https://mathworld.wolfram.com/deRhamsFunction.html first described by Salem 1943 https://www.ams.org/journals/tran/1943-053-03/S0002-9947-1943-0007929-6/S0002-9947-1943-0007929-6.pdf . It does map rationals to rationals. It is clear by construction that each iteration does, but the limit also has this property. So the remaining challenge is - to implement this limit function exactly.

1 Like