--------------------------------------------------------------------------------
-- |
-- Module      :  Billboard.BillboardParser
-- Copyright   :  (c) 2012--2013 Utrecht University
-- License     :  LGPL-3
--
-- Maintainer  :  W. Bas de Haas <bash@cs.uu.nl>
-- Stability   :  experimental
-- Portability :  non-portable
--
-- Summary: Modelling musical time (in a minimalistic way) with beats, bars
-- and time signatures.
--------------------------------------------------------------------------------

module Billboard.BeatBar ( TimeSig (..)
                         , BeatWeight (..)
                         , beatWeight
                         , tatumsPerBar
                         , chordsPerDot ) where

--------------------------------------------------------------------------------
-- Modelling Beat and Bar structures (for chord sequences)
--------------------------------------------------------------------------------

-- TODO explain Change: perhaps this should be separated
-- | Barlines can have different weights. Among other applications, this is used
-- in the printing of chord sequences.
data BeatWeight = UnAligned | Beat | Change | Bar | Bar4 | Bar8 
                | Bar16 | LineStart
       deriving (Eq, Ord, Enum) 

instance Show BeatWeight where
  show UnAligned = "" 
  show Change    = "" 
  show Beat      = "." 
  show Bar       = "|" 
  show Bar4      = "|\n|" 
  show Bar8      = "|\n|" 
  show Bar16     = "|\n|" 
  show LineStart = "|\n|" 

-- | Model a time signature as a fraction
newtype TimeSig = TimeSig {timeSig :: (Int, Int)} deriving (Eq)

instance Show TimeSig where
  show (TimeSig (num, denom)) = show num ++ '/' : show denom

-- | Defines the "metrical weight of a bar". A regular beat has strength 0, 
-- a bar has strength 1, a bar after 4 bars 2, a bar after 8 bars 3, and a bar 
-- after 16 bars 4.
beatWeight :: TimeSig -> Int -> BeatWeight
beatWeight ts pos = let tatum = tatumsPerBar ts in
  case (mod pos tatum, mod pos (4*tatum), mod pos (8*tatum), mod pos (16*tatum)) of
    (0,0,0,0) -> Bar16 -- a bar @ sixteen measures, weight 4
    (0,0,0,_) -> Bar8  -- a bar @ eight measures
    (0,0,_,_) -> Bar4  -- a bar @ four measures
    (0,_,_,_) -> Bar   -- a bar position
    _         -> Beat  -- a regular beat position, weight 0 
    


-- | Returns the number of 'tatums' in a bar which is different for time
-- signatures. For example:
-- 
-- >>> tatumsPerBar (TimeSig (3 ,4))
-- 6
--
-- >>> tatumsPerBar (TimeSig (6 ,8))
-- 6
--
-- >>> tatumsPerBar (TimeSig (12,8))
-- 12
--
-- N.B. This function is not strictly correct music-theoretically, but
-- it reflects how Billboard annotators used time signatures.
tatumsPerBar :: TimeSig -> Int
tatumsPerBar (TimeSig (beats , 4)) = 2 * beats
tatumsPerBar (TimeSig (beats , 8)) = beats
tatumsPerBar ts = irregularMeterError ts "illegal denominator"

-- | Returns the number of 'BBChord' that are inserted for one ".", based on
-- a 'TimeSig'nature.
chordsPerDot :: TimeSig -> Int
chordsPerDot (TimeSig (_ , 4)) = 2
chordsPerDot (TimeSig (_ , 8)) = 3
chordsPerDot ts = irregularMeterError ts "illegal denominator"

irregularMeterError :: TimeSig -> String -> a
irregularMeterError ts msg = error ("Irregular meter: " ++ show ts ++ ' ' : msg)