music-score-1.6: src/Music/Score/Convert.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
-------------------------------------------------------------------------------------
-- |
-- Copyright : (c) Hans Hoglund 2012
--
-- License : BSD-style
--
-- Maintainer : hans@hanshoglund.se
-- Stability : experimental
-- Portability : non-portable (TF,GNTD)
--
-- Provides conversion betwen temporal container types.
--
-------------------------------------------------------------------------------------
module Music.Score.Convert (
-- * Conversion
noteToVoice,
noteToScore,
-- scoreToNotes,
-- notesToScore,
voiceToScore,
voicesToScore,
trackToScore,
trackToScore',
scoreToVoice,
reactiveToVoice,
reactiveToVoice',
noteToReactive,
splitReactive,
activate,
) where
import Control.Applicative
import Control.Lens
import Control.Monad
import Control.Monad.Plus
import Data.AffineSpace
import Data.AffineSpace.Point
import Data.Foldable (Foldable (..))
import Data.Ord
import Data.Ratio
import Data.Semigroup
import Data.String
import Data.Traversable
import Data.VectorSpace
import Music.Score.Note
import Music.Score.Part
import Music.Score.Score
import Music.Score.Track
import Music.Score.Voice
import Music.Time
import Music.Time.Reactive
import qualified Data.Foldable as Foldable
import qualified Data.List as List
-- | Convert a note to an onset and a voice.
noteToVoice :: Note a -> (Time, Voice a)
noteToVoice (getNote -> (s,x)) = (onset s, stretchTo (duration s) $ return x)
-- | Convert a note to a score.
noteToScore :: Note a -> Score a
noteToScore (getNote -> (s,x)) = s `sapp` return x
-- scoreToNotes :: Score a -> [Note a]
-- scoreToNotes = Foldable.toList . reifyScore
-- notesToScore :: [Note a] -> Score a
-- notesToScore = pcat . fmap noteToScore
reactiveToVoice :: Duration -> Reactive a -> Voice a
reactiveToVoice d r = (^. voice) $ durs `zip` (fmap (r ?) times)
where
times = origin : filter (\t -> origin < t && t < origin .+^ d) (occs r)
durs = toRelN' (origin .+^ d) times
reactiveToVoice' :: Span -> Reactive a -> Voice a
reactiveToVoice' (view range -> (u,v)) r = (^. voice) $ durs `zip` (fmap (r ?) times)
where
times = origin : filter (\t -> u < t && t < v) (occs r)
durs = toRelN' v times
-- |
-- Convert a score to a voice. Fails if the score contain overlapping events.
--
scoreToVoice :: Score a -> Voice (Maybe a)
scoreToVoice = (^. voice) . fmap throwTime . addRests . (^. events)
where
throwTime (t,d,x) = (d,x)
addRests = concat . snd . mapAccumL g origin
where
g u (t, d, x)
| u == t = (t .+^ d, [(t, d, Just x)])
| u < t = (t .+^ d, [(u, t .-. u, Nothing), (t, d, Just x)])
| otherwise = error "addRests: Strange prevTime"
-- |
-- Convert a voice to a score.
--
voiceToScore :: Voice a -> Score a
voiceToScore = scat . fmap g . (^. from voice)
where
g (d,x) = stretch d (return x)
-- | Join voices in a given part into a score.
voicesToScore :: HasPart a => [(Part a, Voice a)] -> Score a
voicesToScore = pcat . fmap (voiceToScore . uncurry (\n -> fmap (setPart n)))
-- |
-- Convert a voice which may contain rests to a score.
--
voiceToScore' :: Voice (Maybe a) -> Score a
voiceToScore' = mcatMaybes . voiceToScore
-- |
-- Convert a track to a score where each event is given a fixed duration.
--
trackToScore :: Duration -> Track a -> Score a
trackToScore x = trackToScore' (const x)
-- |
-- Convert a track to a score, using durations determined by the values.
--
trackToScore' :: (a -> Duration) -> Track a -> Score a
trackToScore' f = (^. from events) . fmap (\(t,x) -> (t,f x,x)) . (^. from track)
-- Convert to delta (time to wait before this note)
toRel :: [Time] -> [Duration]
toRel = snd . mapAccumL g origin where g prev t = (t, t .-. prev)
-- Convert to delta (time to wait before next note)
toRelN :: [Time] -> [Duration]
toRelN [] = []
toRelN xs = snd $ mapAccumR g (last xs) xs where g prev t = (t, prev .-. t)
-- Convert to delta (time to wait before next note)
toRelN' :: Time -> [Time] -> [Duration]
toRelN' end xs = snd $ mapAccumR g end xs where g prev t = (t, prev .-. t)
-- 0 x,1 x,1 x,1 x
-- x 1,x 1,x 1,x 0
-- Convert from delta (time to wait before this note)
toAbs :: [Duration] -> [Time]
toAbs = snd . mapAccumL g origin where g now d = (now .+^ d, now .+^ d)
-- TODO rename during
noteToReactive :: Monoid a => Note a -> Reactive a
noteToReactive n = (pure <$> n) `activate` pure mempty
-- | Split a reactive into notes, as well as the values before and after the first/last update
splitReactive :: Reactive a -> Either a ((a, Time), [Note a], (Time, a))
splitReactive r = case updates r of
[] -> Left (initial r)
(t,x):[] -> Right ((initial r, t), [], (t, x))
(t,x):xs -> Right ((initial r, t), fmap note $ mrights (res $ (t,x):xs), head $ mlefts (res $ (t,x):xs))
where
note (t,u,x) = t <-> u =: x
-- Always returns a 0 or more Right followed by one left
res :: [(Time, a)] -> [Either (Time, a) (Time, Time, a)]
res rs = let (ts,xs) = unzip rs in
flip fmap (withNext ts `zip` xs) $
\ ((t, mu), x) -> case mu of
Nothing -> Left (t, x)
Just u -> Right (t, u, x)
-- lenght xs == length (withNext xs)
withNext :: [a] -> [(a, Maybe a)]
withNext = go
where
go [] = []
go [x] = [(x, Nothing)]
go (x:y:rs) = (x, Just y) : withNext (y : rs)
activate :: Note (Reactive a) -> Reactive a -> Reactive a
activate (getNote -> (view range -> (start,stop), x)) y = y `turnOn` (x `turnOff` y)
where
turnOn = switch start
turnOff = switch stop