{-# LANGUAGE TypeInType, GADTs #-} ----------------------------------------------------------------------------- -- | -- Module : Mezzo.Render.MIDI -- Description : MIDI exporting -- Copyright : (c) Dima Szamozvancev -- License : MIT -- -- Maintainer : ds709@cam.ac.uk -- Stability : experimental -- Portability : portable -- -- Functions for exporting Mezzo compositions into MIDI files. -- Skeleton code by Stephen Lavelle. -- ----------------------------------------------------------------------------- module Mezzo.Render.MIDI ( renderMusic, musicToMidi ) where import Mezzo.Model import Codec.Midi ------------------------------------------------------------------------------- -- Types ------------------------------------------------------------------------------- -- | A MIDI representation of a musical note. data MidiNote = MidiNote { noteNum :: Int -- ^ MIDI number of a note (middle C is 60). , vel :: Velocity -- ^ Performance velocity of the note. , start :: Ticks -- ^ Relative start time of the note. , noteDur :: Ticks -- ^ Duration of the note. } deriving Show -- | A MIDI event: a MIDI message at a specific timestamp. type MidiEvent = (Ticks, Message) -- | A sequence of MIDI events. type MidiTrack = Track Ticks ------------------------------------------------------------------------------- -- Operations ------------------------------------------------------------------------------- -- | Play a MIDI note with the specified duration and default velocity. midiNote :: Int -> Ticks -> MidiNote midiNote root dur = MidiNote {noteNum = root, vel = 100, start = 0, noteDur = dur} midiRest :: Ticks -> MidiNote midiRest dur = MidiNote {noteNum = 60, vel = 0, start = 0, noteDur = dur} -- | Start playing the specified 'MidiNote'. keyDown :: MidiNote -> MidiEvent keyDown n = (start n, NoteOn {channel = 0, key = noteNum n, velocity = vel n}) -- | Stop playing the specified 'MidiNote'. keyUp :: MidiNote -> MidiEvent keyUp n = (start n + noteDur n, NoteOn {channel = 0, key = noteNum n, velocity = 0}) -- | Play the specified 'MidiNote'. playNote :: Int -> Ticks -> MidiTrack playNote root dur = map ($ midiNote root dur) [keyDown, keyUp] -- | Play a rest of the specified duration. playRest :: Ticks -> MidiTrack playRest dur = map ($ midiRest dur) [keyDown, keyUp] -- | Merge two parallel MIDI tracks. (><) :: MidiTrack -> MidiTrack -> MidiTrack m1 >< m2 = removeTrackEnds $ m1 `merge` m2 -- | Convert a 'Dur' to 'Ticks'. durToTicks :: Primitive d => Dur d -> Ticks durToTicks d = prim d * 60 -- 1 Mezzo tick (a 32nd note) ~ 60 MIDI ticks ------------------------------------------------------------------------------- -- Rendering ------------------------------------------------------------------------------- -- | A basic skeleton of a MIDI file. midiSkeleton :: MidiTrack -> Midi midiSkeleton mel = Midi { fileType = MultiTrack , timeDiv = TicksPerBeat 480 , tracks = [ [ (0, ChannelPrefix 0) , (0, TrackName " Grand Piano ") , (0, InstrumentName "GM Device 1") , (0, TimeSignature 4 2 24 8) , (0, KeySignature 0 0) ] ++ mel ++ [ (0, TrackEnd) ] ] } -- | Convert a 'Music' piece into a 'MidiTrack'. musicToMidi :: Music m -> MidiTrack musicToMidi (Note root dur) = playNote (prim root) (durToTicks dur) musicToMidi (Rest dur) = playRest (durToTicks dur) musicToMidi (m1 :|: m2) = musicToMidi m1 ++ musicToMidi m2 musicToMidi (m1 :-: m2) = musicToMidi m1 >< musicToMidi m2 musicToMidi (Chord c d) = foldr1 (><) notes where notes = map (`playNote` durToTicks d) $ prim c -- | Create a MIDI file with the specified name and track. createMidi :: FilePath -> MidiTrack -> IO () createMidi f notes = exportFile f $ midiSkeleton notes -- | Create a MIDI file with the specified path and composition. renderMusic :: FilePath -> Music m -> IO () renderMusic f m = createMidi f (musicToMidi m)