zmidi-score-0.3.0.0: src/ZMidi/Score/ToMidiFile.hs
{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- |
-- Module : ZMidi.Score.Datatypes
-- Copyright : (c) 2012--2014, Utrecht University
-- License : LGPL-3
--
-- Maintainer : W. Bas de Haas <w.b.dehaas@uu.nl>
-- Stability : experimental
-- Portability : non-portable
--
-- Summary: converting a 'MidiScore' into a ZMidi.Core.MidiFile
-- (see: <https://hackage.haskell.org/package/zmidi-core> ).
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 )
--------------------------------------------------------------------------------
-- Converting a MidiScore into a MidiFile
--------------------------------------------------------------------------------
-- | Transforms a 'MidiFile' into a 'MidiScore'
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 -- the MidiTracks
-- Takes the Key and TimeSig fields and tranforms them into
-- a MidiTrack containing only MetaEvents
metaToMidiEvent :: MidiTrack
metaToMidiEvent = mkMidiTrack MetaEvent ( mapMaybe keyToMidiEvent ks
++ (mapMaybe tsToMidiEvent ts)
++ map tempoToMidiEvent tp)
-- transforms a Key into a MetaEvent
keyToMidiEvent :: Timed Key -> Maybe (Timed MidiMetaEvent)
keyToMidiEvent (Timed _ NoKey ) = Nothing
keyToMidiEvent (Timed o (Key r s)) = Just $ Timed o (KeySignature r s)
-- transforms a TimeSig into a MetaEvent
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)
-- transforms a tempo into a MetaEvent
tempoToMidiEvent :: Timed Time -> Timed MidiMetaEvent
tempoToMidiEvent = fmap (SetTempo . fromIntegral)
-- transforms a Voice into a MidiTrack
voiceToTrack :: Voice -> MidiTrack
voiceToTrack = mkMidiTrack (VoiceEvent RS_OFF) . concatMap toMidiNote
-- transforms a NoteEvent into a MidiVoiceEvent
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."
-- this is where the magic happens. A list of timed events is made relative
-- such that timestamps denote the time between elements. We close
-- the track by appending a EndOfTrack marker with final time stamp.
-- The track is sorted by midi tick
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)