{- |
System Common messages
-}
module Sound.MIDI.Message.System.Common (
   T(..), TimeNibbleType(..), get, put,
   ) where

import           Sound.MIDI.Parser.Primitive
import qualified Sound.MIDI.Parser.Class as Parser

import Control.Monad.Trans (lift, )
import Control.Monad (liftM, liftM2, )

import qualified Sound.MIDI.Writer.Basic as Writer
import Sound.MIDI.Monoid ((+#+))

import qualified Sound.MIDI.Bit as Bit

import Data.Ix(Ix)


data T =
     TimeCodeQuarterFrame TimeNibbleType Int
   | SongPositionPointer Int
   | SongSelect Int
   | TuneRequest
--   | EndOfSystemExclusive

data TimeNibbleType =
     FrameLS
   | FrameMS
   | SecondsLS
   | SecondsMS
   | MinutesLS
   | MinutesMS
   | HoursLS
   | HoursMS -- ^ also includes SMPTE type
   deriving (Eq, Ord, Show, Enum, Ix)


-- * serialization

get :: Parser.C parser => Int -> Parser.Fallible parser T
get code =
   case code of
      0xF1 ->
         do dat <- get1
            let (nib, value)  = Bit.splitAt 4 dat
            let (msb, nibble) = Bit.splitAt 3 nib
            lift $ Parser.warnIf (msb/=0)
               "TimeCodeQuarterFrame: most significant bit must 0"
            return $ TimeCodeQuarterFrame (toEnum nibble) value
      0xF2 -> liftM2 (\lsb msb -> SongPositionPointer (lsb + Bit.shiftL 7 msb)) get1 get1
      0xF3 -> liftM SongSelect get1
      0xF6 -> return TuneRequest
      _    -> Parser.giveUp ("invalid System Common code:" ++ show code)

put :: Writer.C writer => T -> writer
put msg =
   case msg of
      TimeCodeQuarterFrame nibble value ->
         Writer.putByte 0xF1 +#+
         Writer.putIntAsByte (Bit.shiftL 4 (fromEnum nibble) + value)
      SongPositionPointer pos ->
         Writer.putByte 0xF2 +#+
            let (msb,lsb) = Bit.splitAt 7 pos
            in  Writer.putIntAsByte lsb +#+
                Writer.putIntAsByte msb
      SongSelect song ->
         Writer.putByte 0xF3 +#+
         Writer.putIntAsByte song
      TuneRequest ->
         Writer.putByte 0xF6