AlgoRhythm-0.1.0.0: src/Grammar/Types.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE StandaloneDeriving #-}
module Grammar.Types
( Weight
, Grammar (..), Rule (..), Head, Activation, Body
, Term (..), Expand (..), Grammarly
, runGrammar, always, (/\), (\/)
, (|:), (-|), (-||), ($:), (|$:), (|->)
) where
import System.Random
import Text.Show.Functions ()
import Generate (Weight)
import Music
{- Operators' precedence. -}
infix 6 :%:
infix 5 $:
infix 5 |$:
infixr 4 :-:
infix 3 :->
infix 3 |->
{- Grammar datatypes. -}
data Grammar meta a = Grammar { initial :: a, rules :: [Rule meta a] }
infix 2 |:
(|:) :: a -> [Rule meta a] -> Grammar meta a
initA |: rs = Grammar initA rs
data Rule meta a = Head a :-> Body meta a
type Head a = (a, Weight, Activation)
type Activation = Duration -> Bool
type Body meta a = Duration -> Term meta a
-- type Terminal a = (a, Duration)
data Term meta a = -- primitive
a :%: Duration
-- sequence
| Term meta a :-: Term meta a
-- auxiliary modifications
| Aux Bool meta (Term meta a)
-- let (enables repetition)
| Let (Term meta a) (Term meta a -> Term meta a)
deriving instance (Show a, Show meta) => Show (Term meta a)
instance (Eq a, Eq meta) => Eq (Term meta a) where
(a :%: d) == (a' :%: d') = a == a' && d == d'
(x :-: y) == (x' :-: y') = x == x' && y == y'
(Aux b meta t) == (Aux b' meta' t') = b == b' && meta == meta' && t == t'
(Let t _) == (Let t' _) = t == t'
_ == _ = False
instance Functor (Term meta) where
fmap f m = case m of
a :%: t -> f a :%: t
m1 :-: m2 -> (f <$> m1) :-: (f <$> m2)
Aux frozen meta m1 -> Aux frozen meta (f <$> m1)
_ -> error "fmap: let-expressions exist"
type Grammarly input a meta b =
(Show a, Show meta, Eq a, Eq meta, Expand input a meta b)
-- | Any metadata-carrying grammar term must be expanded to a stripped-down
-- grammar term with no metadata (i.e. `Term a ()`), possibly producing terms of
-- a different type `b`.
class Expand input a meta b | input a meta -> b where
-- | Expand meta-information.
expand :: input -> Term meta a -> IO (Term () b)
-- | Convert to music (after expansion).
toMusic :: (Expand input a meta b) => input -> Term meta a -> IO (Music b)
toMusic input term = do
expanded <- expand input (unlet term)
go expanded
where go (a :%: t) = return $ Note t a
go (t :-: t') = (:+:) <$> toMusic () t <*> toMusic () t'
go _ = error "toMusic: lets/aux after expansion"
unlet (Let x k) = unlet (k x)
unlet (t :-: t') = unlet t :-: unlet t'
unlet (Aux b meta t) = Aux b meta (unlet t)
unlet t = t
-- | A term with no auxiliary wrappers can be trivially expanded.
instance Expand input a () a where
expand = const return
-- | Run a grammar with the given initial symbol.
runGrammar :: Grammarly input a meta b
=> Grammar meta a -> Duration -> input -> IO (Music b)
runGrammar grammar initT input = do
rewritten <- fixpoint (go grammar) (initial grammar :%: initT)
toMusic input rewritten
where
-- | Run one term of grammar rewriting.
go :: (Eq meta, Eq a) => Grammar meta a -> Term meta a -> IO (Term meta a)
-- go _ (Var x) = return $ Var x
go gram (Let x k) = do
x' <- go gram x
return $ Let x' k
go gram (t :-: t') =
(:-:) <$> go gram t <*> go gram t'
go _ a@(Aux True _ _) =
return a
go gram (Aux False meta term) =
Aux False meta <$> go gram term
go (Grammar _ rs) (a :%: t) = do
let rs' = filter (\((a', _, activ) :-> _) -> a' == a && activ t) rs
(_ :-> rewrite) <- pickRule a rs'
return $ rewrite t
{- Grammar-specific operators. -}
-- | Rule which always activates.
always :: Activation
always = const True
-- | Conjunction of activation functions.
(/\) :: Activation -> Activation -> Activation
(f /\ g) x = f x && g x
-- | Disjunction of activation functions.
(\/) :: Activation -> Activation -> Activation
(f \/ g) x = f x || g x
-- | Rule with duration-independent body.
(|->) :: Head a -> Term meta a -> Rule meta a
a |-> b = a :-> const b
-- | Identity rule.
(-|) :: a -> Weight -> Rule meta a
a -| w = (a, w, always) :-> \t -> a :%: t
-- | Identity rule with activation function.
(-||) :: (a, Weight) -> Activation -> Rule meta a
(a, w) -|| f = (a, w, f) :-> \t -> a :%: t
-- | Operators for auxiliary terms.
($:), (|$:) :: meta -> Term meta a -> Term meta a
($:) = Aux False -- auxiliary symbol that allows internal rewriting
(|$:) = Aux True -- frozen auxiliary symbol
{- Helpers. -}
-- | Randomly pick a rule to rewrite given terminal.
pickRule :: a -> [Rule meta a] -> IO (Rule meta a)
pickRule a [] = return $ a -| 1
pickRule _ rs = do
let totalWeight = sum ((\((_, w, _) :-> _) -> w) <$> rs)
index <- getStdRandom $ randomR (0, totalWeight)
return $ pick' index rs
where pick' :: Double -> [Rule meta a] -> Rule meta a
pick' n (r@((_, w, _) :-> _):rest) =
if n <= w then r else pick' (n-w) rest
pick' _ _ = error "pick: empty list"
-- | Converge to fixpoint with given initial value.
fixpoint :: Eq a => (a -> IO a) -> a -> IO a
fixpoint k l = do
l' <- k l
if l == l' then return l else fixpoint k l'