{- |
If you have the Haskore package installed,
then you can use this module for playing Haskore songs in realtime via ALSA.
Example:

> Prelude> Sound.ALSA.Sequencer.Play.run (Sound.ALSA.Sequencer.numAddress 128 0) (Haskore.Interface.MIDI.Render.generalMidi Haskore.Example.ChildSong6.song)
-}
module Sound.ALSA.Sequencer.Play (run) where


import qualified Sound.ALSA.Sequencer     as SndSeq
import qualified Sound.ALSA.Sequencer.FFI as SndSeqFFI

import qualified Sound.MIDI.File            as MIDIFile
import qualified Sound.MIDI.File.Event      as MIDIFileEvent
import qualified Sound.MIDI.File.Event.Meta as MetaEvent

import qualified Data.EventList.Relative.TimeBody as EventList
import qualified Numeric.NonNegative.Wrapper as NonNeg

import Control.Concurrent (threadDelay)
import Control.Exception (bracket)
-- import Control.Monad.Error ()  -- Monad instance for Either


mkEvent ::
   SndSeqFFI.Queue ->
   SndSeqFFI.Address -> SndSeqFFI.Address ->
   MIDIFileEvent.T -> SndSeqFFI.Event
mkEvent queue srcAddress dstAddress ev =
   let (typ, dat) =
          case ev of
             MIDIFileEvent.MIDIEvent msg -> SndSeq.eventFromChannelMsg msg
             MIDIFileEvent.MetaEvent msg -> SndSeq.eventFromMetaEvent queue msg
             MIDIFileEvent.SystemExclusive _ -> error "SystemExclusive not implemented"
   in  SndSeqFFI.Event {
          SndSeqFFI.typ = typ,
          SndSeqFFI.tag = 0,
          SndSeqFFI.queue = SndSeqFFI.queueDirect,
          SndSeqFFI.time  = SndSeqFFI.TimeStampTick (SndSeqFFI.TickTime 0),
          SndSeqFFI.timeMode = SndSeqFFI.TimeModeRelative,
          SndSeqFFI.eventLength = SndSeqFFI.EventLengthFixed,
          SndSeqFFI.priority = SndSeqFFI.PriorityNormal,
          SndSeqFFI.source = srcAddress,
          SndSeqFFI.dest   = dstAddress,
          SndSeqFFI.eventData = dat}


run :: SndSeqFFI.Address -> MIDIFile.T -> IO ()
run dstAddress (MIDIFile.Cons typ _div tracks) =
   do -- print midi

      let track =
             case typ of
                MIDIFile.Parallel -> foldl EventList.merge EventList.empty tracks
                MIDIFile.Serial   -> EventList.concat tracks
                MIDIFile.Mixed    -> EventList.concat tracks

      bracket
         (SndSeq.createClient SndSeqFFI.openOutput "midi player")
         SndSeq.deleteClient $
         \ client ->
         SndSeq.withNamedQueue client "playmidi out queue" $
         \ queue ->
             do -- putStrLn "playing"
                port <- SndSeq.createOutputPort client "player output"
                let srcAddress = SndSeq.portAddress client port
                EventList.mapM_
                   (threadDelay . (5000*) . fromInteger . NonNeg.toNumber)
                   (\ev ->
                       (SndSeq.sendPlainEvent client $
                          let seqEv = mkEvent queue srcAddress dstAddress ev
                          in  case ev of
                                 MIDIFileEvent.MetaEvent (MetaEvent.SetTempo _) ->
                                      seqEv{SndSeqFFI.dest = SndSeqFFI.addressTimer}
                                 _ -> seqEv)
                         >> SndSeq.drainOutput client)
                   track