{- | Very simple MIDI file renderer. It uses an arbitrary set of simple instruments, that is in no way related to General MIDI or something else, ignores tempo changes and respects only MIDI channel 0. -} module Main where import qualified Synthesizer.MIDI.Example.Instrument as Instr import qualified Synthesizer.MIDI.Storable as MidiSt import qualified Synthesizer.Frame.Stereo as Stereo import qualified Data.StorableVector.Lazy as SVL import qualified Sound.Sox.Write as SoxWrite import qualified Sound.Sox.Play as SoxPlay import qualified Sound.MIDI.File as MidiFile import qualified Sound.MIDI.File.Event as FileEvent import qualified Sound.MIDI.File.Load as Load import qualified Sound.MIDI.Message.Channel.Voice as VoiceMsg import qualified Sound.MIDI.Message.Channel as ChannelMsg import Sound.MIDI.Message.Channel (Channel, ) import qualified Data.EventList.Relative.TimeBody as EventList import qualified Numeric.NonNegative.Wrapper as NonNeg import Control.Monad.Trans.State (evalState, ) import Data.Monoid (mempty, ) import qualified System.Environment as Env import qualified System.Exit as Exit import qualified System.IO as IO import NumericPrelude.Numeric ((*>), ) import Prelude hiding (Real, ) type Real = Float channel :: Channel channel = ChannelMsg.toChannel 0 sampleRate :: Num a => a -- sampleRate = 24000 -- sampleRate = 48000 sampleRate = 44100 chunkSize :: SVL.ChunkSize chunkSize = SVL.defaultChunkSize render :: MidiFile.T -> SVL.Vector (Stereo.T Real) render = SVL.map ((0.2::Real)*>) . evalState (MidiSt.sequenceMultiProgram chunkSize channel (VoiceMsg.toProgram 0) $ Instr.pingStereoRelease : Instr.tineStereo : Instr.softString : []) . EventList.collectCoincident . EventList.mapMaybe (\ev -> case ev of FileEvent.MIDIEvent mev -> Just mev _ -> Nothing) . EventList.mapTime (NonNeg.fromNumberMsg "MIDI.render" . fromInteger . NonNeg.toNumber) . EventList.resample sampleRate . (\(MidiFile.Cons typ division tracks) -> MidiFile.mergeTracks typ $ map (MidiFile.secondsFromTicks division) tracks) handleSoxExit :: IO Exit.ExitCode -> IO () handleSoxExit sox = do soxResult <- sox case soxResult of Exit.ExitSuccess -> return () Exit.ExitFailure n -> do IO.hPutStrLn IO.stderr $ "'sox' aborted with exit code " ++ show n Exit.exitFailure main :: IO () main = do args <- Env.getArgs case args of [midiPath] -> handleSoxExit . SoxPlay.simple SVL.hPut mempty sampleRate . render =<< Load.fromFile midiPath [midiPath, wavePath] -> handleSoxExit . SoxWrite.simple SVL.hPut mempty wavePath sampleRate . render =<< Load.fromFile midiPath _ -> do IO.hPutStrLn IO.stderr "need arguments: infile.mid [outfile.wav]" Exit.exitFailure