-- | -- Module : ZMidi.Score.BarBeatPos -- Copyright : (c) 2012--2014, Utrecht University -- License : LGPL-3 -- -- Maintainer : W. Bas de Haas -- Stability : experimental -- Portability : non-portable -- -- Summary: Functions for representing MIDI time musically. module ZMidi.Score.BarBeatPos ( getBeatInBar , getBarRat , toRatInBeat , toBarRat ) where import ZMidi.Score.Datatypes import ZMidi.Score.Quantise import Control.Arrow ( (***) ) import Data.Ratio ( (%) ) -------------------------------------------------------------------------------- -- Bar & Beat position -------------------------------------------------------------------------------- -- NOTE: It is probably a good idea use a BarRat internally instead of a -- (Beat, BeatRat) because this makes rotations much more intuitive. -- | Within a 'MidiScore' we can musically describe every (quantised) -- position in time in 'Bar', Beat, and 'BarRat'. Therefore, we need the -- 'TimeSig'nature, the length of a beat ('TPB', in ticks), and the actual -- 'Time' stamp. getBeatInBar :: TimeSig -> TPB -> Time -> (Bar, Beat, BeatRat) getBeatInBar NoTimeSig _ _ = error "getBeatInBar applied to noTimeSig" getBeatInBar (TimeSig num _den _ _) t o = let (Beat bt, rat) = getRatInBeat t o (br, bib) = (succ *** succ) $ bt `divMod` num in (Bar br, Beat bib, rat) -- | Returns the position within a 'Bar', see 'getBeatInBar'. getRatInBeat :: TPB -> Time -> (Beat, BeatRat) getRatInBeat (TPB t) (Time o) = ((Beat) *** (BeatRat . (% t))) (o `divMod` t) -- | Similar to 'getBeatInBar' we can also describe the musical position as the -- combination of a 'Bar' and a 'BarRat'. The latter denotes the ratio within -- a bar, e.g. BarRat (3 % 4) denotes the 4th 'Beat' in the bar. getBarRat :: TimeSig -> TPB -> Time -> (Bar, BarRat) getBarRat NoTimeSig _ _ = error "getBeatInBar applied to noTimeSig" getBarRat (TimeSig num den _ _) (TPB t) (Time o) = let (bt, rest) = o `divMod` t (b , bib) = bt `divMod` num br = ((bib * t) + rest) % (den * t) in (Bar (succ b), BarRat br) -- | toRatInBeat allows us to convert a 'BarRat' into a ('Beat','BeatRat') toRatInBeat :: TimeSig -> BarRat -> (Beat, BeatRat) toRatInBeat ts br = countBeat (Beat 0, BeatRat . barRat $ br) where oneBeat = BeatRat (1 % tsNum ts) :: BeatRat countBeat :: (Beat, BeatRat) -> (Beat, BeatRat) countBeat (b,x) | rest < 0 = (b,x) | otherwise = countBeat (succ b, rest) where rest = x - oneBeat -- | Musically it is sometimes more intuitive to have a 'BarRat', i.e. the -- onset is defined as the ratio within the bar. For example 1%4 denotes them -- the position of the second quarter note within a 4/4 meter toBarRat :: QBins -> TimeSig -> (Beat, BeatRat) -> BarRat toBarRat _ NoTimeSig _ = error "toBarRat applied to noTimeSig" toBarRat q@(QBins x) (TimeSig _n d _ _) (Beat b, BeatRat r) = BarRat (((pred b * x) + getNumForQBins q r) % (x * d))