module ZMidi.Score.ToMidiFile ( midiScoreToMidiFile ) where
import Data.Maybe ( mapMaybe )
import Data.List ( genericLength, sort )
import Control.Monad.State ( State, get, evalState, put )
import GHC.Float ( integerLogBase )
import ZMidi.Core ( MidiFile (..), MidiEvent (..), DeltaTime
, MidiVoiceEvent (..), MidiMetaEvent (..)
, MidiMessage, MidiTrack (..), MidiHeader (..)
, MidiTimeDivision (..), MidiRunningStatus (..)
)
import ZMidi.Score.Datatypes hiding ( TPB (..) )
import ZMidi.Score.Utilities ( toMidiNr )
midiScoreToMidiFile :: MidiScore -> MidiFile
midiScoreToMidiFile (MidiScore ks ts dv mf tp _ vs) = MidiFile hdr trks where
hdr = MidiHeader mf (genericLength trks) (TPB . fromIntegral $ dv)
trks = metaToMidiEvent : map voiceToTrack vs
metaToMidiEvent :: MidiTrack
metaToMidiEvent = mkMidiTrack MetaEvent ( mapMaybe keyToMidiEvent ks
++ (mapMaybe tsToMidiEvent ts)
++ map tempoToMidiEvent tp)
keyToMidiEvent :: Timed Key -> Maybe (Timed MidiMetaEvent)
keyToMidiEvent (Timed _ NoKey ) = Nothing
keyToMidiEvent (Timed o (Key r s)) = Just $ Timed o (KeySignature r s)
tsToMidiEvent :: Timed TimeSig -> Maybe (Timed MidiMetaEvent)
tsToMidiEvent (Timed _ NoTimeSig ) = Nothing
tsToMidiEvent (Timed o (TimeSig n d m n32)) =
Just $ Timed o (TimeSignature (fromIntegral n)
(fromIntegral . integerLogBase 2 . fromIntegral $ d) m n32)
tempoToMidiEvent :: Timed Time -> Timed MidiMetaEvent
tempoToMidiEvent = fmap (SetTempo . fromIntegral)
voiceToTrack :: Voice -> MidiTrack
voiceToTrack = mkMidiTrack (VoiceEvent RS_OFF) . concatMap toMidiNote
toMidiNote :: Timed ScoreEvent -> [Timed MidiVoiceEvent]
toMidiNote (Timed o (NoteEvent c p v d)) =
let p' = toMidiNr p
c' = channel c
v' = velocity v
in [Timed o (NoteOn c' p' v'), Timed (o + d) (NoteOff c' p' 0)]
toMidiNote _ = error "noteEventToMidiNote: not a NoteEvent."
mkMidiTrack :: forall a. Ord a => (a -> MidiEvent) -> [Timed a] -> MidiTrack
mkMidiTrack f e = MidiTrack $ (trk ++ [(0, MetaEvent EndOfTrack)])
where trk = evalState (mapM mkRelative . sort $ e) 0
mkRelative :: Timed a -> State DeltaTime MidiMessage
mkRelative (Timed o me) = do let o' = fromIntegral o
t <- get ; put o'
return (o' t, f me)