zmidi-score-0.3.0.0: src/ZMidi/Score/BarBeatPos.hs
-- |
-- Module : ZMidi.Score.BarBeatPos
-- 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 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))