packages feed

hmt-0.20: Music/Theory/Time/Bel1990/R.hs

{- | /Bel(R)/ is a simplified form of the /Bel/ notation described in:

- Bernard Bel.
  \"Time and musical structures\".
  /Interface (Journal of New Music Research)/
  Volume 19, Issue 2-3, 1990.
  (<http://hal.archives-ouvertes.fr/hal-00134160>)

- Bernard Bel.
  \"Two algorithms for the instantiation of structures of musical objects\".
  Centre National de la Recherche Scientifique, 1992. /GRTC 458/
  (<http://www.lpl.univ-aix.fr/~belbernard/music/2algorithms.pdf>)

For details see <http://rohandrape.net/?t=hmt-texts>.
-}

module Music.Theory.Time.Bel1990.R where

import Control.Monad {- base -}
import Data.Function {- base -}
import Data.List {- base -}
import Data.Ratio {- base -}

import qualified Text.Parsec as P {- parsec -}

import qualified Music.Theory.List as T
import qualified Music.Theory.Parse as T
import qualified Music.Theory.Show as T

-- * Bel

-- | Types of 'Par' nodes.
data Par_Mode = Par_Left | Par_Right | Par_Min | Par_Max | Par_None
  deriving (Eq, Show)

-- | The different 'Par' modes are indicated by bracket types.
par_mode_brackets :: Par_Mode -> (String,String)
par_mode_brackets m =
    case m of
      Par_Left -> ("(",")")
      Par_Right -> ("~(",")")
      Par_Min -> ("~{","}")
      Par_Max -> ("{","}")
      Par_None -> ("[","]")

-- | Inverse of par_mode_brackets
par_mode_kind :: (String, String) -> Par_Mode
par_mode_kind brk =
  case brk of
    ("{","}") -> Par_Max
    ("~{","}") -> Par_Min
    ("(",")") -> Par_Left
    ("~(",")") -> Par_Right
    ("[","]") -> Par_None
    _ -> error "par_mode_kind: incoherent par"

bel_brackets_match :: (Char,Char) -> Bool
bel_brackets_match (open,close) =
    case (open,close) of
      ('{','}') -> True
      ('(',')') -> True
      ('[',']') -> True
      _ -> False

{- | Tempo is rational.
The duration of a 'Term' is the reciprocal of the 'Tempo' that is in place at the 'Term'.
-}
type Tempo = Rational

-- | Terms are the leaf nodes of the temporal structure.
data Term a = Value a | Rest | Continue
  deriving (Eq,Show)

-- | Value of Term, else Nothing
term_value :: Term t -> Maybe t
term_value t =
  case t of
    Value x -> Just x
    _ -> Nothing

-- | Recursive temporal structure.
data Bel a =
  Node (Term a) -- ^ Leaf node
  | Iso (Bel a) -- ^ Isolate
  | Seq (Bel a) (Bel a) -- ^ Sequence
  | Par Par_Mode (Bel a) (Bel a) -- ^ Parallel
  | Mul Tempo -- ^ Tempo multiplier
  deriving (Eq,Show)

-- | Given a Par mode, generate either: 1. an Iso, 2. a Par, 3. a series of nested Par.
par_of :: Par_Mode -> [Bel a] -> Bel a
par_of m l =
  case l of
    [] -> error "par_of: null"
    [e] -> Iso e
    lhs : rhs : [] -> Par m lhs rhs
    e : l' -> Par m e (par_of m l')

{- | Pretty printer for 'Bel', given pretty printer for the term type.
Note this does not write nested Par nodes in their simplified form.
-}
bel_pp :: (a -> String) -> Bel a -> String
bel_pp f b =
    case b of
      Node Rest -> "-"
      Node Continue -> "_"
      Node (Value c) -> f c
      Iso b' -> T.bracket_l ("{","}") (bel_pp f b')
      Seq p q -> concat [bel_pp f p,bel_pp f q]
      Par m p q ->
          let pq = concat [bel_pp f p,",",bel_pp f q]
          in T.bracket_l (par_mode_brackets m) pq
      Mul n -> concat ["*",T.rational_pp n]

-- | 'bel_pp' of 'return'.
bel_char_pp :: Bel Char -> String
bel_char_pp = bel_pp return

{- | Analyse a Par node giving (duration,LHS-tempo-*,RHS-tempo-*).

> par_analyse 1 Par_Left (nseq "cd") (nseq "efg") == (2,1,3/2)
> par_analyse 1 Par_Right (nseq "cd") (nseq "efg") == (3,2/3,1)
> par_analyse 1 Par_Min (nseq "cd") (nseq "efg") == (2,1,3/2)
> par_analyse 1 Par_Max (nseq "cd") (nseq "efg") == (3,2/3,1)
> par_analyse 1 Par_None (nseq "cd") (nseq "efg") == (3,1,1)
-}
par_analyse :: Tempo -> Par_Mode -> Bel a -> Bel a -> (Rational,Rational,Rational)
par_analyse t m p q =
    let (_,d_p) = bel_tdur t p
        (_,d_q) = bel_tdur t q
    in case m of
         Par_Left -> (d_p,1,d_q / d_p)
         Par_Right -> (d_q,d_p / d_q,1)
         Par_Min -> let r = min d_p d_q in (r,d_p / r,d_q / r)
         Par_Max -> let r = max d_p d_q in (r,d_p / r,d_q / r)
         Par_None -> (max d_p d_q,1,1)

-- | Duration element of 'par_analyse'.
par_dur :: Tempo -> Par_Mode -> Bel a -> Bel a -> Rational
par_dur t m p q =
    let (d,_,_) = par_analyse t m p q
    in d

-- | Calculate final tempo and duration of 'Bel'.
bel_tdur :: Tempo -> Bel a -> (Tempo,Rational)
bel_tdur t b =
    case b of
      Node _ -> (t,1 / t)
      Iso b' -> (t,snd (bel_tdur t b'))
      Seq p q ->
          let (t_p,d_p) = bel_tdur t p
              (t_q,d_q) = bel_tdur t_p q
          in (t_q,d_p + d_q)
      Par m p q -> (t,par_dur t m p q)
      Mul n -> (t * n,0)

-- | 'snd' of 'bel_tdur'.
bel_dur :: Tempo -> Bel a -> Rational
bel_dur t = snd . bel_tdur t

-- * Linearisation

-- | Time point.
type Time = Rational

{- | Voices are named as a sequence of left and right directions within nested 'Par' structures.
l is left and r is right.
-}
type Voice = [Char]

{- | Linear state.
'Time' is the start time of the term.
'Tempo' is the active tempo & therefore the reciprocal of the duration.
'Voice' is the part label.
-}
type L_St = (Time, Tempo, Voice)

-- | Linear term.
type L_Term a = (L_St,Term a)

-- | Start time of 'L_Term'.
lterm_time :: L_Term a -> Time
lterm_time ((st,_,_),_) = st

-- | Duration of 'L_Term' (reciprocal of tempo).
lterm_duration :: L_Term a -> Time
lterm_duration ((_,tm,_),_) = 1 / tm

-- | End time of 'L_Term'.
lterm_end_time :: L_Term a -> Time
lterm_end_time e = lterm_time e + lterm_duration e

-- | Voice of 'L_Term'.
lterm_voice :: L_Term t -> Voice
lterm_voice ((_,_,vc),_) = vc

-- | Term of L_Term
lterm_term :: L_Term t -> Term t
lterm_term (_,t) = t

-- | Value of Term of L_Term
lterm_value :: L_Term t -> Maybe t
lterm_value = term_value . lterm_term

-- | Linear form of 'Bel', an ascending sequence of 'L_Term'.
type L_Bel a = [L_Term a]

-- | Linearise 'Bel' given initial 'L_St', ascending by construction.
bel_linearise :: L_St -> Bel a -> (L_Bel a,L_St)
bel_linearise l_st b =
    let (st,tm,vc) = l_st
    in case b of
         Node e -> ([(l_st,e)],(st + 1/tm,tm,vc))
         Iso p ->
             let (p',(st',_,_)) = bel_linearise l_st p
             in (p',(st',tm,vc))
         Seq p q ->
             let (p',l_st') = bel_linearise l_st p
                 (q',l_st'') = bel_linearise l_st' q
             in (p' ++ q',l_st'')
         Par m p q ->
             let (du,p_m,q_m) = par_analyse tm m p q
                 (p',_) = bel_linearise (st,tm * p_m,'l':vc) p
                 (q',_) = bel_linearise (st,tm * q_m,'r':vc) q
             in (p' `lbel_merge` q',(st + du,tm,vc))
         Mul n -> ([],(st,tm * n,vc))

-- | Merge two ascending 'L_Bel'.
lbel_merge :: L_Bel a -> L_Bel a -> L_Bel a
lbel_merge = T.merge_on lterm_time

-- | Set of unique 'Tempo' at 'L_Bel'.
lbel_tempi :: L_Bel a -> [Tempo]
lbel_tempi = nub . sort . map (\((_,t,_),_) -> t)

-- | Multiply 'Tempo' by /n/, and divide 'Time' by /n/.
lbel_tempo_mul :: Rational -> L_Bel a -> L_Bel a
lbel_tempo_mul n = map (\((st,tm,vc),e) -> ((st / n,tm * n,vc),e))

{- | The multiplier that will normalise an L_Bel value.
     After normalisation all start times and durations are integral.
-}
lbel_normalise_multiplier :: L_Bel t -> Rational
lbel_normalise_multiplier b =
  let t = lbel_tempi b
      n = foldl1 lcm (map denominator t) % 1
      m = foldl1 lcm (map (numerator . (* n)) t) % 1
  in n / m

-- | Calculate and apply L_Bel normalisation multiplier.
lbel_normalise :: L_Bel a -> L_Bel a
lbel_normalise b = lbel_tempo_mul (lbel_normalise_multiplier b) b

{- | All leftmost voices are re-written to the last non-left turning point.

> map voice_normalise ["","l","ll","lll"] == replicate 4 ""
> voice_normalise "lllrlrl" == "rlrl"
-}
voice_normalise :: Voice -> Voice
voice_normalise = dropWhile (== 'l')

-- | '==' 'on' 'voice_normalise'
voice_eq :: Voice -> Voice -> Bool
voice_eq = (==) `on` voice_normalise

-- | Unique 'Voice's at 'L_Bel'.
lbel_voices :: L_Bel a -> [Voice]
lbel_voices =
    sortOn reverse .
    nub .
    map (\((_,_,v),_) -> voice_normalise v)

-- | The duration of 'L_Bel'.
lbel_duration :: L_Bel a -> Time
lbel_duration b =
    let l = last (T.group_on lterm_time b)
    in maximum (map (\((st,tm,_),_) -> st + recip tm) l)

-- | Locate an 'L_Term' that is active at the indicated 'Time' and in
-- the indicated 'Voice'.
lbel_lookup :: (Time,Voice) -> L_Bel a -> Maybe (L_Term a)
lbel_lookup (st,vc) =
    let f ((st',tm,vc'),_) = (st >= st' && st < st' + (1 / tm)) &&
                             vc `voice_eq` vc'
    in find f

-- | Calculate grid (phase diagram) for 'L_Bel'.
lbel_grid :: L_Bel a -> [[Maybe (Term a)]]
lbel_grid l =
    let n = lbel_normalise l
        v = lbel_voices n
        d = lbel_duration n
        trs st ((st',_,_),e) = if st == st' then e else Continue
        get vc st = fmap (trs st) (lbel_lookup (st,vc) n)
        f vc = map (get vc) [0 .. d - 1]
    in map f v

-- | 'lbel_grid' of 'bel_linearise'.
bel_grid :: Bel a -> [[Maybe (Term a)]]
bel_grid b =
    let (l,_) = bel_linearise (0,1,[]) b
    in lbel_grid l

-- | /Bel/ type phase diagram for 'Bel' of 'Char'.  Optionally print
-- whitespace between columns.
bel_ascii :: Bool -> Bel Char -> String
bel_ascii opt =
    let f e = case e of
                Nothing -> ' '
                Just Rest -> '-'
                Just Continue -> '_'
                Just (Value c) -> c
        g = if opt then intersperse ' ' else id
    in unlines . map (g . map f) . bel_grid

-- | 'putStrLn' of 'bel_ascii'.
bel_ascii_pr :: Bel Char -> IO ()
bel_ascii_pr = putStrLn . ('\n' :) . bel_ascii True

-- * Combinators

-- | Infix form for 'Seq'.
(~>) :: Bel a -> Bel a -> Bel a
p ~> q = Seq p q

{- | 'foldl1' of 'Seq'.

> lseq [Node Rest] == Node Rest
> lseq [Node Rest,Node Continue] == Seq (Node Rest) (Node Continue)
-}
lseq :: [Bel a] -> Bel a
lseq = foldl1 Seq

-- | 'Node' of 'Value'.
node :: a -> Bel a
node = Node . Value

-- | 'lseq' of 'Node'
nseq :: [a] -> Bel a
nseq = lseq . map node

-- | Variant of 'nseq' where @_@ is read as 'Continue' and @-@ as 'Rest'.
cseq :: String -> Bel Char
cseq =
    let f c = case c of
                '_' -> Continue
                '-' -> Rest
                _ -> Value c
    in foldl1 Seq . map (Node . f)

-- | 'Par' of 'Par_Max', this is the default 'Par_Mode'.
par :: Bel a -> Bel a -> Bel a
par = Par Par_Max

-- | 'Node' of 'Rest'.
rest :: Bel a
rest = Node Rest

-- | 'lseq' of 'replicate' of 'rest'.
nrests :: Integral n => n -> Bel a
nrests n = lseq (genericReplicate n rest)

-- | Verify that 'bel_char_pp' of 'bel_char_parse' is 'id'.
bel_parse_pp_ident :: String -> Bool
bel_parse_pp_ident s = bel_char_pp (bel_char_parse s) == s

{- | Run 'bel_char_parse', and print both 'bel_char_pp' and 'bel_ascii'.

> bel_ascii_pp "{i{ab,c[d,oh]e,sr{p,qr}},{jk,ghjkj}}"
-}
bel_ascii_pp :: String -> IO ()
bel_ascii_pp s = do
  let p = bel_char_parse s
  putStrLn (concat ["\nBel(R): \"",bel_char_pp p,"\", Dur: ",T.rational_pp (bel_dur 1 p),""])
  bel_ascii_pr p

-- * Parsing

-- | Parse 'Rest' 'Term'.
--
-- > P.parse p_rest "" "-"
p_rest :: T.P (Term a)
p_rest = fmap (const Rest) (P.char '-')

-- | Parse 'Rest' 'Term'.
--
-- > P.parse p_nrests "" "3"
p_nrests :: T.P (Bel a)
p_nrests = fmap nrests p_non_negative_integer

-- | Parse 'Continue' 'Term'.
--
-- > P.parse p_continue "" "_"
p_continue :: T.P (Term a)
p_continue = fmap (const Continue) (P.char '_')

-- | Parse 'Char' 'Value' 'Term'.
--
-- > P.parse p_char_value "" "a"
p_char_value :: T.P (Term Char)
p_char_value = fmap Value P.lower

-- | Parse 'Char' 'Term'.
--
-- > P.parse (P.many1 p_char_term) "" "-_a"
p_char_term :: T.P (Term Char)
p_char_term = P.choice [p_rest,p_continue,p_char_value]

-- | Parse 'Char' 'Node'.
--
-- > P.parse (P.many1 p_char_node) "" "-_a"
p_char_node :: T.P (Bel Char)
p_char_node = fmap Node p_char_term

-- | Parse non-negative 'Integer'.
--
-- > P.parse p_non_negative_integer "" "3"
p_non_negative_integer :: T.P Integer
p_non_negative_integer = fmap read (P.many1 P.digit)

-- | Parse non-negative 'Rational'.
--
-- > P.parse (p_non_negative_rational `P.sepBy` (P.char ',')) "" "3%5,2/3"
p_non_negative_rational :: T.P Rational
p_non_negative_rational = do
  n <- p_non_negative_integer
  _ <- P.oneOf "%/"
  d <- p_non_negative_integer
  return (n % d)

-- | Parse non-negative 'Double'.
--
-- > P.parse p_non_negative_double "" "3.5"
-- > P.parse (p_non_negative_double `P.sepBy` (P.char ',')) "" "3.5,7.2,1.0"
p_non_negative_double :: T.P Double
p_non_negative_double = do
  a <- P.many1 P.digit
  _ <- P.char '.'
  b <- P.many1 P.digit
  return (read (a ++ "." ++ b))

-- | Parse non-negative number as 'Rational'.
--
-- > P.parse (p_non_negative_number `P.sepBy` (P.char ',')) "" "7%2,3.5,3"
p_non_negative_number :: T.P Rational
p_non_negative_number =
    P.choice [P.try p_non_negative_rational
             ,P.try (fmap toRational p_non_negative_double)
             ,P.try (fmap toRational p_non_negative_integer)]

-- | Parse 'Mul'.
--
-- > P.parse (P.many1 p_mul) "" "/3*3/2"
p_mul :: T.P (Bel a)
p_mul = do
  op <- P.oneOf "*/"
  n <- p_non_negative_number
  let n' = case op of
             '*' -> n
             '/' -> recip n
             _ -> error "p_mul"
  return (Mul n')

-- | Given parser for 'Bel' /a/, generate 'Iso' parser.
p_iso :: T.P (Bel a) -> T.P (Bel a)
p_iso f = do
  open <- P.oneOf "{(["
  iso <- P.many1 f
  close <- P.oneOf "})]"
  when (not (bel_brackets_match (open,close))) (error "p_iso: open/close mismatch")
  return (Iso (lseq iso))

-- | 'p_iso' of 'p_char_bel'.
--
-- > P.parse p_char_iso "" "{abcde}"
p_char_iso :: T.P (Bel Char)
p_char_iso = p_iso p_char_bel

-- | Given parser for 'Bel' /a/, generate 'Par' parser.
p_par :: T.P (Bel a) -> T.P (Bel a)
p_par f = do
  tilde <- P.optionMaybe (P.char '~')
  open <- P.oneOf "{(["
  items <- P.sepBy (P.many1 f) (P.char ',')
  close <- P.oneOf "})]"
  let m = par_mode_kind (T.mcons tilde [open], [close])
  return (par_of m (map lseq items))

{- | 'p_par' of 'p_char_bel'.

> p = P.parse p_char_par ""
> p "{ab,{c,de}}" == p "{ab,c,de}"
> p "{ab,~(c,de)}"
-}
p_char_par :: T.P (Bel Char)
p_char_par = p_par p_char_bel

-- | Parse 'Bel' 'Char'.
--
-- > P.parse (P.many1 p_char_bel) "" "-_a*3"
p_char_bel :: T.P (Bel Char)
p_char_bel = P.choice [P.try p_char_par,p_char_iso,p_mul,p_nrests,p_char_node]

-- | Run parser for 'Bel' of 'Char'.
bel_char_parse :: String -> Bel Char
bel_char_parse s =
    either
    (\e -> error ("bel_parse failed\n" ++ show e))
    lseq
    (P.parse (P.many1 p_char_bel) "" s)