{-# OPTIONS_GHC -Wall #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE DeriveGeneric #-} -- | -- Module : ZMidi.Score.Show -- Copyright : (c) 2012--2014, Utrecht University -- License : LGPL-3 -- -- Maintainer : W. Bas de Haas -- Stability : experimental -- Portability : non-portable -- -- Summary: functions for representing a 'MidiScore' as text. module ZMidi.Score.Show ( showMidiScore , showVoices ) where import ZMidi.Score.Datatypes import Control.Monad.State ( State, modify, get , evalState ) import Control.Monad ( mapAndUnzipM ) import Data.List ( intercalate ) import Text.Printf ( printf ) -------------------------------------------------------------------------------- -- Printing MidiScores -------------------------------------------------------------------------------- -- | Show a MidiScore in a readable way showMidiScore :: MidiScore -> String showMidiScore ms@(MidiScore k ts tb mf tp st _vs) = "Key: " ++ show k ++ "\nMeter: " ++ show ts ++ "\nTicks per Beat: " ++ show tb ++ "\nMidi format: " ++ show mf ++ "\nTempo: " ++ show tp ++ "\nShortest tick: " ++ show st ++ "\nNotes:\n" ++ showVoices ms -- | Shows the voices in a MidiScore in a readable way, but this function -- only works for monophonic channels. TODO: fix showVoices :: MidiScore -> String showVoices ms = intercalate "\n" $ evalState (showTimeSlice . getVoices $ ms) 0 where -- shows a the sounding notes at a specific time showTimeSlice :: [Voice] -> State Time [String] showTimeSlice vs = do (str, vs') <- mapAndUnzipM showVoiceAtTime vs case noMoreNotesToShow vs' of -- the stopping condition True -> return [] False -> do t <- get -- update the clock modify (+ minDur ms) x <- showTimeSlice vs' -- recursively calculate a next slice -- the output string: let out = intercalate "\t" (printf "%7d" (time t) : str) return (out : x) -- The stopping condition: when there are no more notes in all of the tracks noMoreNotesToShow :: [Voice] -> Bool noMoreNotesToShow = and . map null -- Takes a Voice and shows the head of the voice appropriately depending on -- the location in time. showVoicAtTime also returns the tail of the voice. showVoiceAtTime :: Voice -> State Time (String, Voice) showVoiceAtTime [] = return (" ",[]) showVoiceAtTime x@(v:vs) = do t <- get case hasStarted t v of True -> case hasEnded t v of -- if v has ended, an new note at the same voice -- might start at the same time. Hence, call ourselves again True -> showVoiceAtTime vs False -> return (show . pitch . getEvent $ v, x) False -> return (" " , x) hasStarted, hasEnded :: Time -> Timed ScoreEvent -> Bool -- returns true if the Timed ScoreEvent is has started to sound at Time t hasStarted t tse = t >= onset tse -- returns true if the Timed ScoreEvent is still sounding at Time t hasEnded t (Timed ons se) = ons + duration se < t