> {-# OPTIONS -XFlexibleInstances #-} > {-# OPTIONS -XTypeSynonymInstances #-} > module Euterpea ( > module Euterpea.Music, > module Euterpea.MoreMusic, > module Euterpea.Performance, > module Euterpea.GeneralMidi, > module Euterpea.MidiIO, > fromMidi, toMidi, > mToMF, defUpm, defPMap, defCon, > testMidi, test, testA, play, playM, playA, > makeMidi, gmUpm, gmTest, Performable, perfDur) where > > import Euterpea.Music > import Euterpea.MoreMusic > import Euterpea.Performance > import Euterpea.GeneralMidi > import Euterpea.MidiIO > import Euterpea.ToMidi > import Euterpea.FromMidi > import Codec.Midi(exportFile, importFile, Midi) > import Sound.PortMidi To make things easier, we'll declare both Music and Primitive as members of Functor class. Eventually this will be moved into a separate module. > instance Functor Music where > fmap f (Prim x) = Prim (fmap f x) > fmap f (x :+: y) = fmap f x :+: fmap f y > fmap f (x :=: y) = fmap f x :=: fmap f y > -- fmap f (x :=/ y) = fmap f x :=/ fmap f y > fmap f (Modify c x) = Modify c (fmap f x) > instance Functor Primitive where > fmap f (Note d x) = Note d (f x) > fmap f (Rest d) = Rest d Recall that: type Note1 = (Pitch, [NoteAttribute]) type Music1 = Music Note1 Convert from Music to Music1: > toMusic1 :: Music Pitch -> Music1 > toMusic1 = fmap (\p -> (p, [])) Convert from Music(Pitch, Volume) to Music1: > toMusic1' :: Music (Pitch, Volume) -> Music1 > toMusic1' = mMap (\(p, v) -> (p, [Volume v])) Generating Performances ----------------------- Make the default translation to Performance as a class in order to deal with both Music Pitch and Music Note1: > class Performable a where > perfDur :: PMap Note1 -> Context Note1 -> Music a -> (Performance, DurT) Using the defaults below, from a Music value we can generate a Performance: > instance Performable Note1 where > perfDur pm c m = perf pm c m > instance Performable Pitch where > perfDur pm c = perfDur pm c . toMusic1 > instance Performable (Pitch, Volume) where > perfDur pm c = perfDur pm c . toMusic1' > defToPerf :: Performable a => Music a -> Performance > defToPerf = fst . perfDur defPMap defCon > toPerf :: Performable a => PMap Note1 -> Context Note1 -> Music a -> Performance > toPerf pm con = fst . perfDur pm con A default UserPatchMap: > -- Note: the PC sound card I'm using is limited to 9 instruments > defUpm :: UserPatchMap > defUpm = [(AcousticGrandPiano,1), > (Vibraphone,2), > (AcousticBass,3), > (Flute,4), > (TenorSax,5), > (AcousticGuitarSteel,6), > (Viola,7), > (StringEnsemble1,8), > (AcousticGrandPiano,9)] > -- the GM name for drums is unimportant, only channel 9 A default PMap: > defPMap :: PMap Note1 -- = PlayerName -> Player Note1 > defPMap "Fancy" = fancyPlayer > defPMap "Default" = defPlayer > defPMap n = defPlayer { pName = n } I don't see the point in this old version that makes everything into a fancyPlayer: defPMap pname = MkPlayer pname nf pf sf where MkPlayer _ nf pf sf = fancyPlayer A default Context: > defCon :: Context Note1 > defCon = Context { cTime = 0, > cPlayer = fancyPlayer, > cInst = AcousticGrandPiano, > cDur = metro 120 qn, > cKey = 0, > cVol = 127 } Generating MIDI values and MIDI files ------------------------------------- Generate a MIDI datatype: > testMidi :: Performable a => Music a -> Midi > testMidi m = toMidi (defToPerf m) defUpm > testMidiA :: Performable a => PMap Note1 -> Context Note1 -> Music a -> Midi > testMidiA pm con m = toMidi (toPerf pm con m) defUpm Generate a MIDI file: > test :: Performable a => Music a -> IO () > test m = exportFile "test.mid" (testMidi m) > testA :: Performable a => PMap Note1 -> Context Note1 -> Music a -> IO () > testA pm con m = exportFile "test.mid" (testMidiA pm con m) Alternatively, just run "play m", which will play the music through the default Midi output device on your computer: > play :: Performable a => Music a -> IO () > play = playM . testMidi Or play a Midi data directly: > playM :: Midi -> IO () > playM midi = do > initialize > (defaultOutput playMidi) midi > terminate > return () A play function that takes a PMap and Context: > playA :: Performable a => PMap Note1 -> Context Note1 > -> Music a -> IO () > playA pm con m = > let pf = fst $ perfDur pm con m > in playM (toMidi pf defUpm) A more general function in the tradition of testMidi, makeMidi also takes a Context and a UserPatchMap. > makeMidi :: (Music1, Context Note1, UserPatchMap) -> Midi > makeMidi (m,c,upm) = toMidi (perform defPMap c m) upm The most general export function from Music to a Midi file. > mToMF :: PMap a -> Context a -> UserPatchMap -> FilePath -> Music a -> IO () > mToMF pmap c upm fn m = > let pf = perform pmap c m > mf = toMidi pf upm > in exportFile fn mf Some General Midi test functions (use with caution) --------------------------------------------------- A General MIDI user patch map; i.e. one that maps GM instrument names to themselves, using a channel that is the patch number modulo 16. This is for use ONLY in the code that follows, o/w channel duplication is possible, which will screw things up in general. > gmUpm :: UserPatchMap > gmUpm = map (\n -> (toEnum n, mod n 16 + 1)) [0..127] Something to play each "instrument group" of 8 GM instruments; this function will play a C major arpeggio on each instrument. > gmTest :: Int -> IO () > gmTest i = let gMM = take 8 (drop (i*8) [0..127]) > mu = line (map simple gMM) > simple n = Modify (Instrument (toEnum n)) cMajArp > in mToMF defPMap defCon gmUpm "test.mid" mu > cMaj = [ n 4 qn | n <- [c,e,g] ] -- octave 4, quarter notes > cMajArp = toMusic1 (line cMaj)