tidal-0.1: Pattern.lhs
> {-# LANGUAGE OverloadedStrings #-}
> module Pattern where
> import Data.List
> import Data.Maybe
> import Control.Applicative
> import Data.Fixed
> type Period = Maybe Int
> type Behaviour a = Int -> [Maybe a]
> data Pattern a = Pattern {at :: Behaviour a, period :: Period}
> lcd :: Period -> Period -> Period
> lcd Nothing _ = Nothing
> lcd _ Nothing = Nothing
> lcd (Just n) (Just n') = Just $ lcm n n'
> justPeriod :: Pattern a -> Int
> justPeriod = fromJust . period
> instance (Show a) => Show (Pattern a) where
> show (Pattern _ (Just 0)) = ""
>
> show p@(Pattern f (Just l))
> = show2D $ map (map show . at p) range
> where range = [0 .. (l - 1)]
>
> show p@(Pattern f Nothing)
> = show2D (map (map show . at p) range) ++ "\n..."
> where range = [0 .. 15]
> show2D = intercalate "\n" . map (intercalate " ")
Thanks to Ryan Ingram for this elegant functor implementation.
http://ryani.livejournal.com/19471.html
> instance Functor Pattern where
> fmap f (Pattern xs p) = Pattern (fmap (fmap (fmap f)) xs) p
> instance Applicative Pattern where
> pure x = Pattern (pure (pure (pure x))) (Just 1)
> Pattern fs pf <*> Pattern xs px = Pattern (liftA2 (zipCycleA2 (<*>)) fs xs) (lcd pf px)
Halway between the applicative definition of a list and a ziplist. If
lists aren't the same length, the smallest one is cycled to the same
length of the largest before zipping.
> zipCycleA2 f a b = zipWith id (f <$> takeCycle n a) (takeCycle n b)
> where n = max (length a) (length b)
> takeCycle :: Int -> [a] -> [a]
> takeCycle n = take n . cycle
The null pattern is a zero period of undefinedness.
> nullPattern :: Pattern a
> nullPattern = Pattern {at = const undefined, period = Just 0}
Silence is one empty period.
> silence :: Pattern a
> silence = Pattern {at = const [Nothing], period = Just 1}
Turn a single thing into a pattern of things.
> atom :: a -> Pattern a
> atom = pure
> lToP :: [Maybe a] -> Pattern a
> lToP [] = silence
> lToP xs = Pattern (\n -> [xs !! (n `mod` len)]) (Just len)
> where len = length xs
Add one pattern on the end of another.
> append :: Pattern a -> Pattern a -> Pattern a
> append a@(Pattern f Nothing) _ = a
> append a@(Pattern _ (Just l)) b@(Pattern _ Nothing) = Pattern newF Nothing
> where newF n | n < l = at a n
> | otherwise = at b (n - l)
> append a@(Pattern f (Just l)) b@(Pattern f' (Just l')) = Pattern newF (Just newL)
> where newL = l + l'
> newF n | cycleP < l = f ((loopN * l) + cycleP)
> | otherwise = f' ((loopN * l') + (cycleP - l))
> where cycleP = n `mod` newL
> loopN = n `div` newL
> toInfinity (Pattern f _) = Pattern f Nothing
> isInf :: Pattern a -> Bool
> isInf (Pattern _ Nothing) = True
> isInf _ = False
Concatenate a list of patterns
> cat :: [Pattern a] -> Pattern a
> cat = foldr append nullPattern
> catMap :: (Pattern a -> Pattern a) -> [Pattern a] -> Pattern a
> catMap f = cat . map f
Find lowest common period (lcm but zeros are ignored)
> lcp :: [Pattern a] -> Period
> lcp [] = Just 0
> lcp ps = lcp' $ filter (/= Just 0) (map (\(Pattern _ l) -> l) ps)
> where lcp' [] = Just 0
> lcp' ds = foldl lcd (Just 1) ds
Combine patterns, with where period is the lcm of all the periods.
> combine :: [Pattern a] -> Pattern a
> combine ps = Pattern (\n -> concatMap (\p -> at p n) ps) (lcp ps)
> combineMap :: (Pattern a -> Pattern a) -> [Pattern a] -> Pattern a
> combineMap f = combine . map f
-- As above but patterns are padded out to be the same length (the lcm,
-- so two patterns with periods of 2 and 3 will be padded out to have
-- period of 6).
> combinePad :: [Pattern a] -> Pattern a
> combinePad ps = combine $ map (pad newP) ps
> where newP = lcp ps
> combinePadMap :: (Pattern a -> Pattern a) -> [Pattern a] -> Pattern a
> combinePadMap f = combinePad . map f
-- Zips two patterns together with the given function
-- > combineWith :: (a -> b -> c) -> Pattern a -> Pattern b -> Pattern c
-- > combineWith f a b = Pattern (\n -> zipWith f (at a n) (at b n)) (lcm (period a) (period b))
-- Pads pattern out to given duration. Old period must be divisible
-- by new period.
> pad :: Period -> Pattern a -> Pattern a
> pad Nothing p = Pattern newF Nothing
> where newF 0 = at p 0
> newF _ = [Nothing]
> pad _ (Pattern _ Nothing) = error "can't pad an infinite pattern"
> pad newD@(Just newL) p@(Pattern f d@(Just l))
> | newD == d = p
> | newL `mod` l /= 0 = error "old period must be divisible by new"
> | otherwise = Pattern newF newD
> where pos = newL `div` l
> newF n | n `mod` pos == 0 = f $ n `div` pos
> | otherwise = [Nothing]
> padUp :: Int -> Pattern a -> Pattern a
> padUp n p = pad (Just (n * justPeriod p)) p
-- Inline operators for above.
> (>+<) a b = combine [a, b]
> (<+>) a b = combinePad [a, b]
-- Make a pattern representing a sine wave with a given period.
> sine :: Int -> Pattern Double
> sine l = Pattern f (Just l)
> where f n = [Just $ sin $ fromIntegral n * (pi / fromIntegral l * 2)]
> sine1 :: Int -> Pattern Double
> sine1 l = ((/ 2.0) . (+ 1.0)) <$> sine l
square :: Int -> Pattern Int
square l = Pattern f (Just l)
where f n | (n `mod` l) > (l `div` 2) = 1
| otherwise = -1
square1 :: Int -> Pattern Int
square1 l = ((`div` 2) . (+ 1)) <$> square l
-- Multiply a pattern's period by n.
> (~*) :: Pattern a -> Int -> Pattern a
> (~*) p n = Pattern (at p) (fmap (* n) (period p))
-- Apply a function to a pattern every nth period.
> every :: Int -> (Pattern a -> Pattern a) -> Pattern a -> Pattern a
> every 0 _ p = p
> every n f p = (p ~* (n - 1)) `append` f p
-- Shift (rotate) a pattern n steps to the left.
> rotL :: Int -> Pattern a -> Pattern a
> rotL n p = Pattern (\t -> at p (t + n)) (period p)
-- Shift (rotate) a pattern n steps to the right.
> rotR :: Int -> Pattern a -> Pattern a
> rotR = rotL . negate
> (<~) = rotL
> (~>) = rotR
-- Reverse a pattern.
> rev :: Pattern a -> Pattern a
> rev p | isNothing (period p) = error "Can't reverse infinity"
> rev p |otherwise = Pattern (\n -> at p $ fromJust d - n - 1) d
> where d = period p
> (<<~) = rev
-- Make a pattern into a palindrome by playing forward then back.
> palindrome :: Pattern a -> Pattern a
> palindrome p@(Pattern _ Nothing) = p
> palindrome p@(Pattern _ (Just 1)) = p
> palindrome p@(Pattern _ (Just l)) = cat [p `loopAt` (l - 1),
> (<<~) p `loopAt` (l - 1)
> ]
> patternToList :: Pattern a -> [[a]]
> patternToList p = map (catMaybes . at p) (range p)
> range p | period p == Nothing = [0 ..]
> | otherwise = [0 .. justPeriod p - 1]
> maxPolyphony = foldr (max . length) 0 . patternToList
> loopAt :: Pattern a -> Int -> Pattern a
> loopAt p l = Pattern (\n -> at p $ n `mod` l) (Just l)
> modify :: ((Int -> [Maybe a]) -> (Int -> [Maybe b])) -> Pattern a -> Pattern b
> modify f p = Pattern (f (at p)) (period p)
Replaces empty beats with the previous beat in the pattern plus 1
Needs redoing with nothings
incNulls :: Pattern Int -> Pattern Int
incNulls p = Pattern (f 0) (period p)
where f i n | and [isLooped p i, isSam p i] = [Just 0]
| null (at p n) = [(head (f (i+1) (n-1))) + 1]
| otherwise = [head $ at p n]
breakbeat :: Pattern a -> Pattern Int -> Pattern a
breakbeat p breakPattern = Pattern f (lcd (period p) (period breakPattern))
where f n = at p (head $ at (incNulls breakPattern) n)
> periodPos :: Pattern a -> Int -> Int
> periodPos p n | isInf p = n
> | otherwise = n `mod` justPeriod p
> isSam :: Pattern a -> Int -> Bool
> isSam p n = periodPos p n == 0
> isLooped :: Pattern a -> Int -> Bool
> isLooped p n | isInf p = False
> | otherwise = n > justPeriod p
> onsets :: Pattern a -> Pattern a
> onsets p = modify f p
> where f l n = if and [not $ null $ l n, null $ l (n-1)]
> then l n
> else []
> tween :: Double -> Double -> Int -> Pattern Double
> tween from to steps = Pattern f (Just steps)
> where f n = [Just (from + fromIntegral (n `mod` steps) * (diff / fromIntegral steps))]
> diff = to - from
> soundSet :: String -> Int -> Pattern String
> soundSet s p = Pattern (\n -> [Just $ s ++ "/" ++ show (n `mod` p)]) (Just p)
> enumerate :: Eq a => Pattern a -> Pattern Int
> enumerate p = (\x -> fromJust $ elemIndex x l) <$> p
> where l = nub $ concat $ patternToList p
> headP :: Pattern a -> [Maybe a]
> headP p = at p 0
> tailP :: Pattern a -> Pattern a
> tailP p | isInf p = error "tailP of infinite pattern"
> | l <= 0 = error "tailP of pattern with zero period"
> | l == 1 = nullPattern
> | otherwise = Pattern (at p . offset) (fmap (subtract 1) (period p))
> where offset n = 1 + n + (n `div` (l- 1))
> l = fromJust $ period p
> extrapolate :: Int -> Pattern Int -> Pattern Int
> extrapolate by p | isInf p = p
> | otherwise = Pattern newF newP
> where newF n = map (fmap (+ ((by * (n `div` justPeriod p)) `mod` 12))) (at p n)
> newP = (* ((lcm 12 by) `div` by)) `fmap` period p
> extrapolateF :: Double -> Pattern Double -> Pattern Double
> extrapolateF by p | isInf p = p
> | otherwise = Pattern newF (period p)
> where newF n = map (fmap (+ ((by * fromIntegral (n `div` justPeriod p)) `mod'` 12))) (at p n)