{-# LANGUAGE ImplicitParams #-} -- | Can be used to export `Music` to a Midi file, or to play it in real time. module Export.MIDI ( module Export.MIDIConfig , writeToMidiFile , play , playDev , musicToE ) where import Codec.Midi import Control.Arrow ((>>>)) import Data.Ratio ((%)) import Export.MIDIConfig import qualified Euterpea as E import Music -- | Write `Music` to MIDI file. writeToMidiFile :: (ToMusicCore a, ?midiConfig :: MIDIConfig) => FilePath -> Music a -> IO () writeToMidiFile path = toMusicCore >>> musicToMidi >>> E.exportMidiFile path -- | Plays `Music` to the given MIDI output device (using Euterpea under the -- hood). playDev :: (ToMusicCore a, ?midiConfig :: MIDIConfig) => Int -> Music a -> IO () playDev devId = toMusicCore >>> musicToE >>> E.playDev devId -- | Plays `Music` to the standard MIDI output device. play :: (ToMusicCore a, ?midiConfig :: MIDIConfig) => Music a -> IO () play = toMusicCore >>> musicToE >>> E.play -- | Converts `MusicCore` to `Codec.Midi.Midi`. Note that this is done using -- Euterpea's `toMidi` function, which does not return a Euterpea defined -- Midi type, but rather a Midi type from the `HCodecs` library. musicToMidi :: (?midiConfig :: MIDIConfig) => MusicCore -> Midi musicToMidi m = E.toMidi $ E.perform $ musicToE m -- | Converts `MusicCore` to Euterpea `E.Music1` using a `MIDIConfig`. musicToE :: (?midiConfig :: MIDIConfig) => MusicCore -> E.Music1 musicToE ms = E.chord1 [ foldr E.Modify (musicToE' m) modifiers | (inst, m) <- zip (cycle $ instruments ?midiConfig) (voices ms) , let modifiers = [E.Tempo $ tempo ?midiConfig, E.Instrument inst] ] -- | Converts `MusicCore` to Euterpea Music1 musicToE' :: MusicCore -> E.Music1 musicToE' (m :+: m') = musicToE' m E.:+: musicToE' m' musicToE' (m :=: m') = musicToE' m E.:=: musicToE' m' musicToE' (Rest dur) = E.rest dur musicToE' (Note dur (p, attrs)) = noteToE dur (p, attrs) -- | Converts MusicCore Note to a Euterpea Music1 Note. noteToE :: Duration -> FullPitch -> E.Music1 noteToE dur (p, attrs) = do -- Initially create a note with pitch and duration, but no extra attributes. let noteE = E.note dur (pitchToE p, []) -- Add the attributes one by one. foldr (flip addAttrToE) noteE attrs -- | Converts `Pitch` to a Euterpea Pitch. pitchToE :: Pitch -> E.Pitch pitchToE (pc, oct) = (pitchClassToE pc, fromEnum oct) -- | Converts `PitchClass` to a Euterpea PitchClass. pitchClassToE :: PitchClass -> E.PitchClass pitchClassToE p = case p of C -> E.C Cs -> E.Cs D -> E.D Ds -> E.Ds E -> E.E F -> E.F Fs -> E.Fs G -> E.G Gs -> E.Gs A -> E.A As -> E.As B -> E.B addAttrToE :: E.Music1 -> PitchAttribute -> E.Music1 addAttrToE n a = E.Modify (E.Phrase [attrToE a]) n -- | Converts a PitchAttribute to its Euterpea representation. attrToE :: PitchAttribute -> E.PhraseAttribute attrToE (Dynamic d) = E.Dyn $ dynamicsToE d attrToE (Articulation a) = E.Art $ articulationToE a -- | Converts Dynamics to Euterpea Dynamic. dynamicsToE :: Dynamic -> E.Dynamic dynamicsToE d = E.StdLoudness dE where -- There are 11 Dynamics in the Music DSL and only 9 in the Euterpea -- DSL. Hence the code below. The magic 8 represents the maximum -- fromEnum value one can get from an E.Dynamic value. However, Euterpea -- has not derived Bounded for E.Dynamic, so maxBound::E.Dynamic -- couldn't be used here. dE = toEnum (min 8 (max 0 ((fromEnum d) - 1))) -- | Converts Articulation to Euterpea Articulation. articulationToE :: Articulation -> E.Articulation articulationToE Staccato = E.Staccato (1%4) articulationToE Staccatissimo = E.Staccato (1%8) articulationToE Marcato = E.Marcato articulationToE Tenuto = E.Tenuto