module Sound.MIDI.File (
T(..), Division(..), Track, Type(..),
empty,
ElapsedTime, fromElapsedTime, toElapsedTime,
Tempo, fromTempo, toTempo,
explicitNoteOff, implicitNoteOff,
getTracks, mergeTracks, mapTrack,
secondsFromTicks, ticksPerQuarterNote,
showLines, changeVelocity, resampleTime,
showEvent, showTime,
sortEvents, progChangeBeforeSetTempo,
) where
import qualified Sound.MIDI.Message.Channel.Voice as VoiceMsg
import qualified Sound.MIDI.Message.Channel as ChannelMsg
import qualified Sound.MIDI.File.Event.Meta as MetaEvent
import qualified Sound.MIDI.File.Event as Event
import Sound.MIDI.File.Event.Meta (
ElapsedTime, fromElapsedTime, toElapsedTime,
Tempo, fromTempo, toTempo,
)
import qualified Data.EventList.Relative.TimeBody as EventList
import qualified Numeric.NonNegative.Wrapper as NonNegW
import qualified Numeric.NonNegative.Class as NonNeg
import Test.QuickCheck (Arbitrary(arbitrary, shrink), )
import qualified Test.QuickCheck as QC
import qualified Control.Monad.Trans.State as MS
import Control.Monad (liftM, liftM2, )
import Sound.MIDI.String (rightS, )
import Data.Ratio((%))
import Data.Ix(Ix)
import Data.List(groupBy, sort)
import Data.Maybe(fromMaybe)
data T = Cons Type Division [Track] deriving (Show, Eq)
data Type = Mixed | Parallel | Serial
deriving (Show, Eq, Ord, Ix, Enum, Bounded)
data Division = Ticks Tempo | SMPTE Int Int
deriving (Show, Eq)
type Track = EventList.T ElapsedTime Event.T
empty :: T
empty = Cons Mixed (Ticks 1) [EventList.empty]
instance Arbitrary T where
arbitrary =
do (typ, content) <-
QC.oneof $
fmap (\track -> (Mixed, [track])) arbitrary :
fmap (\tracks -> (Parallel, tracks)) arbitrary :
fmap (\tracks -> (Serial, tracks)) arbitrary :
[]
division <- arbitrary
return (Cons typ division content)
shrink (Cons typ division tracks) =
map (Cons typ division) $ shrink tracks
instance Arbitrary Division where
arbitrary =
QC.oneof $
liftM (Ticks . (1+) . flip mod 32767) arbitrary :
liftM2 (\x y -> SMPTE (1 + mod x 127) (1 + mod y 255)) arbitrary arbitrary :
[]
mapTrack :: (Track -> Track) -> T -> T
mapTrack f (Cons mfType division tracks) =
Cons mfType division (map f tracks)
explicitNoteOff :: T -> T
explicitNoteOff =
mapTrack (EventList.mapBody (Event.mapVoice VoiceMsg.explicitNoteOff))
implicitNoteOff :: T -> T
implicitNoteOff =
mapTrack (EventList.mapBody (Event.mapVoice VoiceMsg.implicitNoteOff))
getTracks :: T -> [Track]
getTracks (Cons _ _ trks) = trks
mergeTracks ::
(NonNeg.C time) =>
Type ->
[EventList.T time event] ->
EventList.T time event
mergeTracks typ tracks =
case typ of
Mixed -> foldr (EventList.mergeBy (\_ _ -> True)) EventList.empty tracks
Parallel -> foldr (EventList.mergeBy (\_ _ -> True)) EventList.empty tracks
Serial -> EventList.concat tracks
secondsFromTicks ::
Division ->
EventList.T ElapsedTime Event.T ->
EventList.T NonNegW.Rational Event.T
secondsFromTicks division =
EventList.catMaybes .
flip MS.evalState MetaEvent.defltTempo .
EventList.mapM
(\ticks -> do
microsPerQN <- MS.get
return $
NonNegW.fromNumberMsg "MIDI.File.processTempo" $
fromElapsedTime ticks * fromIntegral (NonNegW.toNumber microsPerQN)
% (1000000 * fromIntegral (NonNegW.toNumber (ticksPerQuarterNote division))))
(\ev ->
case ev of
Event.MetaEvent (MetaEvent.SetTempo microsPerQN) ->
MS.put microsPerQN >> return Nothing
_ -> return $ Just ev)
ticksPerQuarterNote :: Division -> Tempo
ticksPerQuarterNote division =
case division of
Ticks ticksPerQN -> ticksPerQN
SMPTE framesPerSecond ticksPerFrames ->
NonNegW.fromNumberMsg "MIDI.File.ticksPerQuarterNote" $
framesPerSecond * ticksPerFrames
showLines :: T -> String
showLines (Cons mfType division tracks) =
let showTrack track =
unlines
(" (" :
map
(\event -> " " ++ show event ++ " :")
(EventList.toPairList track) ++
" []) :" :
[])
in "MIDIFile.Cons " ++ show mfType ++ " (" ++ show division ++ ") (\n" ++
concatMap showTrack tracks ++
" [])"
showTime :: ElapsedTime -> ShowS
showTime t =
rightS 10 (shows t) . showString " : "
showEvent :: Event.T -> ShowS
showEvent (Event.MIDIEvent e) =
showString "Event.MIDIEvent " . shows e
showEvent (Event.MetaEvent e) =
showString "Event.MetaEvent " . shows e
showEvent (Event.SystemExclusive s) =
showString "SystemExclusive " . shows s
changeVelocity :: Double -> T -> T
changeVelocity r =
let multVel vel =
VoiceMsg.toVelocity $
round (r * fromIntegral (VoiceMsg.fromVelocity vel))
procVoice (VoiceMsg.NoteOn pitch vel) = VoiceMsg.NoteOn pitch (multVel vel)
procVoice (VoiceMsg.NoteOff pitch vel) = VoiceMsg.NoteOff pitch (multVel vel)
procVoice me = me
in mapTrack (EventList.mapBody (Event.mapVoice procVoice))
resampleTime :: Double -> T -> T
resampleTime r =
let divTime time = round (fromIntegral time / r)
newTempo tmp = round (fromIntegral tmp * r)
procEvent ev =
case ev of
Event.MetaEvent (MetaEvent.SetTempo tmp) ->
Event.MetaEvent (MetaEvent.SetTempo (newTempo tmp))
_ -> ev
in mapTrack (EventList.mapBody procEvent . EventList.mapTime divTime)
sortEvents :: T -> T
sortEvents =
let coincideNote ev0 ev1 =
fromMaybe False $
do (_,x0) <- Event.maybeVoice ev0
(_,x1) <- Event.maybeVoice ev1
return (VoiceMsg.isNote x0 && VoiceMsg.isNote x1)
sortTrack =
EventList.flatten . EventList.mapBody sort .
EventList.mapCoincident (groupBy coincideNote)
in mapTrack sortTrack
progChangeBeforeSetTempo :: T -> T
progChangeBeforeSetTempo =
let sortTrack evs =
do ((t0,st@(Event.MetaEvent (MetaEvent.SetTempo _))), rest0)
<- EventList.viewL evs
((t1,pc@(Event.MIDIEvent (ChannelMsg.Cons _
(ChannelMsg.Voice (VoiceMsg.ProgramChange _))))), rest1)
<- EventList.viewL rest0
return $
EventList.cons t0 pc $
EventList.cons 0 st $
EventList.delay t1 rest1
in mapTrack (\track -> fromMaybe track (sortTrack track))