Patterns in Haskell revisited

A while back I came up with this way of representing musical patterns as pure functions in Haskell:

data Pattern a = Pattern {at :: Int -> [a], period :: Int}
These patterns can be composed nicely with pattern combinators, creating strange polyrhythmic structures, see my earlier post for info.
This turned out just great for representing acid techno, see for example this video of people dancing to Dave and I.  I was using Tidal which uses a representation similar to the above (and Dave was using his lovely SchemeBricks software).
However lately I’ve been wanting to make music other than acid techno, in particular in preparation for a performance with Hester Reeve, a Live Artist.

After a lot of fiddling about, I seem to be settling on this:

data Pattern a = Atom {event :: a}
                 | Arc {pattern :: Pattern a,
                        onset :: Double,
                        duration :: Maybe Double
                       }
                 | Cycle {patterns :: [Pattern a]}
                 | Signal {at :: Double -> Pattern a}

I’ve got rid of periods, now patterns always have a relative period of 1. However they can be scaled down by being enclosed in an Arc pattern, and given a floating point duration and time phase offset (which in music parlance is called an onset), which should be less than 1.  A Cycle pattern consists of a number of Arcs, which may overlap in time.

The end result is a nice representation of cyclic patterns within patterns, with floating point time so that events don’t have to occur within the fixed time grids of the acid techno I’ve been making.

It is also still possible to represent a pattern as a function, which is what a Signal is in the above.

The Functor definition is straightforward:

instance Functor Pattern where
    fmap f p@(Atom {event = a}) = p {event = f a}
    fmap f p@(Arc {pattern = p'}) = p {pattern = fmap f p'}
    fmap f p@(Cycle {patterns = ps}) = p {patterns = fmap (fmap f) ps}
    fmap f p@(Signal _) = p {at = (fmap f) . (at p)}

The Applicative functor definition isn’t so bad either:

instance Applicative Pattern where
    pure = Atom
    Atom f <*> xs = f <$> xs
    fs <*> (Atom x) = fmap (\f -> f x) fs
    (Cycle fs) <*> xs = Cycle $ map (<*> xs) fs
    fs <*> (Cycle xs) = Cycle $ map (fs <*>) xs
    fs@(Arc {onset = o}) <*> s@(Signal {}) = fs <*> (at s o)
    fs@(Arc {}) <*> xs@(Arc {}) | isIn fs xs = fs {pattern = (pattern fs) <*> (pattern xs)}
                                | otherwise = Cycle []
    fs@(Signal {}) <*> xs = Signal $ (<*> xs) . (at fs)
    fs <*> xs@(Signal {}) = Signal $ (fs <*>) . (at xs)

Here’s how to turn a list into a pattern:

class Patternable p where
    toPattern :: p a -> Pattern a
instance Patternable [] where
    toPattern xs = Cycle ps
      where
        ps = map (\x -> Arc {pattern = Atom $ xs !! x,
                             onset = (fromIntegral x) / (fromIntegral $ length xs),
                             duration = Nothing
                            }
                 ) [0 .. (length xs) - 1]

And here’s how to make a Signal pattern of a sinewave:

-- sinewave from -1 to 1
sinewave :: Pattern Double
sinewave = Signal {at = f}
    where f x = Arc {pattern = Atom $ (sin . (pi * 2 *)) x,
                     onset = mod' x 1,
                     duration = Nothing
                    }
-- sinewave from 0 to 1
sinewave1 :: Pattern Double
sinewave1 = fmap ((/ 2) . (+ 1)) sinewave

Finally, here’s how to multiply a Cycle of discrete events by a Signal, thanks to our Applicative definition:

(*) <$> toPattern [1 .. 16] <*> sinewave

Well this may all be rather trivial, but somehow I find this really exciting, that continuous functions can be multipled by (potentially) complex discrete, hierarchical patterns with such tersity.  Furthermore that time can be manipulated outside of fixed grids.  I’ve been putting off making sounds from this, to try not to prejudice possibilities, but am really looking forward to experimenting with it live in performance.

It’s probably not of much use to anyone else at the moment, but the code is over here.
 

2 Comments

  1. Cool stuff. I’d note that both `length xs` and `xs !! x` are O(n), so that makes `toPattern :: [a] -> Pattern a` O(n^2), but it doesn’t have to be. You can look the length up once, and use `zipWith` to iterate through your elements

        instance Patternable [] where
            toPattern xs = Cycle ps
              where
                n = length xs
                ps = zipWith mkArc xs [0..]
                mkArc x i = Arc  x ((fromIntegral i) / (fromIntegral n)) Nothing
    

Leave a Reply

Your email address will not be published. Required fields are marked *