{- |
Parser which handles the running state
that is used in MIDI messages in realtime and files.
The running state consists of a message code and the message channel.
-}
module Sound.MIDI.Parser.Status
   (T, Status, set, get, run, lift,
    Channel, fromChannel,   toChannel, ) where

import qualified Sound.MIDI.Parser.Class as Parser

import qualified Control.Monad.Exception.Synchronous  as Sync
import Control.Monad.Trans.State (StateT, evalStateT, )
import qualified Control.Monad.Trans.State as State
import qualified Control.Monad.Trans.Class as Trans

import Sound.MIDI.Utility (checkRange, )
import Data.Ix (Ix)


{- |
The 'T' monad parses a track of a MIDI File.
In MIDI, a shortcut is used for long strings of similar MIDI events:
If a stream of consecutive events all have the same type and channel,
the type and channel can be omitted for all but the first event.
To implement this /feature/,
the parser must keep track of the type and channel of the most recent MIDI Event.
This is done by managing a 'Status' in the parser.
-}
type T parser = StateT Status parser

type Status = Maybe (Int,Channel)


set :: Monad parser => Status -> Parser.Fallible (T parser) ()
set = Trans.lift . State.put

get :: Monad parser => Parser.Fallible (T parser) Status
get = Trans.lift State.get

run :: Monad parser => T parser a -> parser a
run = flip evalStateT Nothing


lift :: Monad parser => Parser.Fallible parser a -> Parser.Fallible (T parser) a
lift = Sync.mapExceptionalT Trans.lift


-- * Channel definition

{- |
This definition should be in Message.Channel,
but this results in a cyclic import.
-}
newtype Channel     = Channel  {fromChannel  :: Int} deriving (Show, Eq, Ord, Ix)

toChannel :: Int -> Channel
toChannel = checkRange "Channel" Channel

instance Enum Channel where
   toEnum   = toChannel
   fromEnum = fromChannel

instance Bounded Channel where
   minBound = Channel  0
   maxBound = Channel 15