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}
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 = AtomAtom 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 <*>) xsfs@(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 ainstance 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.
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
Great tip, thanks Noah!