{- |
Very simple MIDI file renderer.
It uses an arbitrary set of simple instruments,
that is in no way related to General MIDI or something else,
ignores tempo changes and respects only MIDI channel 0.
-}
module Main where

import qualified Synthesizer.MIDI.Example.Instrument as Instr
import qualified Synthesizer.MIDI.Storable as MidiSt

import qualified Synthesizer.Frame.Stereo as Stereo

import qualified Data.StorableVector.Lazy as SVL

import qualified Sound.Sox.Write as SoxWrite
import qualified Sound.Sox.Play  as SoxPlay

import qualified Sound.MIDI.File as MidiFile
import qualified Sound.MIDI.File.Event as FileEvent
import qualified Sound.MIDI.File.Load as Load
import qualified Sound.MIDI.Message.Channel.Voice as VoiceMsg
import qualified Sound.MIDI.Message.Channel       as ChannelMsg
import Sound.MIDI.Message.Channel (Channel, )

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

import Control.Monad.Trans.State (evalState, )

import Data.Monoid (mempty, )

import qualified System.Environment as Env
import qualified System.Exit as Exit
import qualified System.IO as IO

import NumericPrelude.Numeric ((*>), )
import Prelude hiding (Real, )


type Real = Float

channel :: Channel
channel = ChannelMsg.toChannel 0

sampleRate :: Num a => a
-- sampleRate = 24000
-- sampleRate = 48000
sampleRate = 44100

chunkSize :: SVL.ChunkSize
chunkSize = SVL.defaultChunkSize


render ::
   MidiFile.T -> SVL.Vector (Stereo.T Real)
render =
   SVL.map ((0.2::Real)*>) .
   evalState
      (MidiSt.sequenceMultiProgram chunkSize channel
         (VoiceMsg.toProgram 0) $
            Instr.pingStereoRelease :
            Instr.tineStereo :
            Instr.softString :
            []) .
   EventList.collectCoincident .
   EventList.mapMaybe (\ev ->
      case ev of
         FileEvent.MIDIEvent mev -> Just mev
         _ -> Nothing) .
   EventList.mapTime
      (NonNeg.fromNumberMsg "MIDI.render" . fromInteger . NonNeg.toNumber) .
   EventList.resample sampleRate .
   (\(MidiFile.Cons typ division tracks) ->
      MidiFile.mergeTracks typ $
      map (MidiFile.secondsFromTicks division) tracks)

handleSoxExit :: IO Exit.ExitCode -> IO ()
handleSoxExit sox = do
   soxResult <- sox
   case soxResult of
      Exit.ExitSuccess -> return ()
      Exit.ExitFailure n -> do
         IO.hPutStrLn IO.stderr $
            "'sox' aborted with exit code " ++ show n
         Exit.exitFailure

main :: IO ()
main = do
   args <- Env.getArgs
   case args of
      [midiPath] ->
         handleSoxExit .
            SoxPlay.simple SVL.hPut mempty sampleRate .
            render =<<
            Load.fromFile midiPath
      [midiPath, wavePath] ->
         handleSoxExit .
            SoxWrite.simple SVL.hPut mempty wavePath sampleRate .
            render =<<
            Load.fromFile midiPath
      _ -> do
         IO.hPutStrLn IO.stderr
            "need arguments: infile.mid [outfile.wav]"
         Exit.exitFailure
