zmidi-score-0.3.0.0: src/ZMidi/Score/Show.hs
{-# 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 <w.b.dehaas@uu.nl>
-- 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