module Music.Time.Voice (
Voice,
voice,
notes,
pairs,
durationsVoice,
valuesV,
durationsV,
unzipVoice,
zipVoiceScale,
zipVoiceScale3,
zipVoiceScale4,
zipVoiceNoScale,
zipVoiceNoScale3,
zipVoiceNoScale4,
zipVoiceScaleWith,
zipVoiceWithNoScale,
zipVoiceWith',
fuse,
fuseBy,
fuseRests,
coverRests,
sameDurations,
mergeIfSameDuration,
mergeIfSameDurationWith,
homoToPolyphonic,
onsetsRelative,
offsetsRelative,
midpointsRelative,
erasRelative,
withContext,
unsafeNotes,
unsafePairs,
) where
import Control.Applicative
import Control.Lens hiding (Indexable, Level, above,
below, index, inside, parts,
reversed, transform, (<|), (|>))
import Control.Monad
import Control.Monad.Compose
import Control.Monad.Plus
import Data.AffineSpace
import Data.AffineSpace.Point
import Data.Foldable (Foldable)
import Data.Functor.Adjunction (unzipR)
import Data.Functor.Context
import Data.List.NonEmpty (NonEmpty)
import Data.Maybe
import Data.Semigroup
import Data.Sequence (Seq)
import Data.Set (Set)
import Data.String
import Data.Traversable (Traversable)
import Data.Typeable (Typeable)
import Data.VectorSpace
import Data.Aeson (ToJSON (..), FromJSON(..))
import qualified Data.Aeson as JSON
import Music.Dynamics.Literal
import Music.Pitch.Literal
import Music.Time.Internal.Util
import Music.Time.Juxtapose
import Music.Time.Note
import qualified Data.List
import qualified Data.Foldable
import qualified Data.Either
newtype Voice a = Voice { getVoice :: [Note a] }
deriving (Eq, Ord, Typeable, Foldable, Traversable, Functor, Semigroup, Monoid)
instance (Show a, Transformable a) => Show (Voice a) where
show x = show (x^.notes) ++ "^.voice"
instance Applicative Voice where
pure = return
(<*>) = ap
instance Alternative Voice where
(<|>) = (<>)
empty = mempty
instance Monad Voice where
return = view _Unwrapped . return . return
xs >>= f = view _Unwrapped $ (view _Wrapped . f) `mbind` view _Wrapped xs
instance MonadPlus Voice where
mzero = mempty
mplus = mappend
instance Wrapped (Voice a) where
type Unwrapped (Voice a) = [Note a]
_Wrapped' = iso getVoice Voice
instance Rewrapped (Voice a) (Voice b)
instance Cons (Voice a) (Voice b) (Note a) (Note b) where
_Cons = prism (\(s,v) -> (view voice.return $ s) <> v) $ \v -> case view notes v of
[] -> Left mempty
(x:xs) -> Right (x, view voice xs)
instance Snoc (Voice a) (Voice b) (Note a) (Note b) where
_Snoc = prism (\(v,s) -> v <> (view voice.return $ s)) $ \v -> case unsnoc (view notes v) of
Nothing -> Left mempty
Just (xs, x) -> Right (view voice xs, x)
instance ToJSON a => ToJSON (Voice a) where
toJSON x = JSON.object [ ("notes", toJSON ns) ]
where
ns = x^.notes
instance FromJSON a => FromJSON (Voice a) where
parseJSON (JSON.Object x) = parseNL =<< (x JSON..: "notes")
where
parseNL (JSON.Array xs) = fmap ((^.voice) . toList) $ traverse parseJSON xs
toList = toListOf traverse
parseJSON _ = empty
instance Transformable (Voice a) where
transform s = over notes (transform s)
instance HasDuration (Voice a) where
_duration = sumOf (notes . each . duration)
instance Reversible a => Reversible (Voice a) where
rev = over notes reverse . fmap rev
instance (Transformable a, Splittable a) => Splittable (Voice a) where
split d v = case splitNotes d (v^.notes) of
(as,Nothing,cs) -> (as^.voice, cs^.voice)
(as,Just(b1,b2),cs) -> (as^.voice `snoc` b1, b2 `cons` cs^.voice)
splitNotes :: (Transformable a, Splittable a) => Duration -> [a] -> ([a], Maybe (a, a), [a])
splitNotes d xs = case (durAndNumNotesToFirst, needSplit) of
(Just (_,0),_) -> ([],Nothing,xs)
(Nothing ,False) -> (xs,Nothing,[])
(Just (_,n),False) -> (take n xs,Nothing,drop n xs)
(Nothing ,True) -> (init xs,Just (splitEnd (sum (fmap (^.duration) xs) d) (last xs)),[])
(Just (d',n),True) -> (
take (n1) xs
,Just (splitEnd (d'd) (xs!!pred n))
,drop n xs)
where
needSplit = case durAndNumNotesToFirst of
Nothing -> d < sum (fmap (^.duration) xs)
Just (d',_) -> d /= d'
durAndNumNotesToFirst = accumUntil (\(ds,ns) x -> if ds < d then Left(ds+x,ns+1) else Right (ds,ns))
(0,0) (fmap (^.duration) xs)
splitEnd d x = split ((x^.duration) d) x
accumUntil :: (s -> a -> Either s b) -> s -> [a] -> Maybe b
accumUntil f z xs = Data.Maybe.listToMaybe $ fmap fromRight $ dropWhile Data.Either.isLeft $ scanl (f . fromLeft) (Left z) xs
where
fromRight (Right x) = x
fromLeft (Left x) = x
instance IsString a => IsString (Voice a) where
fromString = pure . fromString
instance IsPitch a => IsPitch (Voice a) where
fromPitch = pure . fromPitch
instance IsInterval a => IsInterval (Voice a) where
fromInterval = pure . fromInterval
instance IsDynamics a => IsDynamics (Voice a) where
fromDynamics = pure . fromDynamics
instance Enum a => Enum (Voice a) where
toEnum = return . toEnum
fromEnum = list 0 (fromEnum . head) . Data.Foldable.toList
instance Num a => Num (Voice a) where
fromInteger = return . fromInteger
abs = fmap abs
signum = fmap signum
(+) = liftA2 (+)
() = liftA2 ()
(*) = liftA2 (*)
voice :: Getter [Note a] (Voice a)
voice = from unsafeNotes
notes :: Lens (Voice a) (Voice b) [Note a] [Note b]
notes = unsafeNotes
pairs :: Lens (Voice a) (Voice b) [(Duration, a)] [(Duration, b)]
pairs = unsafePairs
unsafeNotes :: Iso (Voice a) (Voice b) [Note a] [Note b]
unsafeNotes = _Wrapped
unsafePairs :: Iso (Voice a) (Voice b) [(Duration, a)] [(Duration, b)]
unsafePairs = iso (map (^.from note) . (^.notes)) ((^.voice) . map (^.note))
durationsVoice :: Iso' [Duration] (Voice ())
durationsVoice = iso (mconcat . fmap (\d -> stretch d $ pure ())) (^. durationsV)
unzipVoice :: Voice (a, b) -> (Voice a, Voice b)
unzipVoice = unzipR
zipVoiceScale :: Voice a -> Voice b -> Voice (a, b)
zipVoiceScale = zipVoiceScaleWith (,)
zipVoiceScale3 :: Voice a -> Voice b -> Voice c -> Voice (a, (b, c))
zipVoiceScale3 a b c = zipVoiceScale a (zipVoiceScale b c)
zipVoiceScale4 :: Voice a -> Voice b -> Voice c -> Voice d -> Voice (a, (b, (c, d)))
zipVoiceScale4 a b c d = zipVoiceScale a (zipVoiceScale b (zipVoiceScale c d))
zipVoiceScale5 :: Voice a -> Voice b -> Voice c -> Voice d -> Voice e -> Voice (a, (b, (c, (d, e))))
zipVoiceScale5 a b c d e = zipVoiceScale a (zipVoiceScale b (zipVoiceScale c (zipVoiceScale d e)))
zipVoiceNoScale :: Voice a -> Voice b -> Voice (a, b)
zipVoiceNoScale = zipVoiceWithNoScale (,)
zipVoiceNoScale3 :: Voice a -> Voice b -> Voice c -> Voice (a, (b, c))
zipVoiceNoScale3 a b c = zipVoiceNoScale a (zipVoiceNoScale b c)
zipVoiceNoScale4 :: Voice a -> Voice b -> Voice c -> Voice d -> Voice (a, (b, (c, d)))
zipVoiceNoScale4 a b c d = zipVoiceNoScale a (zipVoiceNoScale b (zipVoiceNoScale c d))
zipVoiceNoScale5 :: Voice a -> Voice b -> Voice c -> Voice d -> Voice e -> Voice (a, (b, (c, (d, e))))
zipVoiceNoScale5 a b c d e = zipVoiceNoScale a (zipVoiceNoScale b (zipVoiceNoScale c (zipVoiceNoScale d e)))
zipVoiceScaleWith :: (a -> b -> c) -> Voice a -> Voice b -> Voice c
zipVoiceScaleWith = zipVoiceWith' (*)
zipVoiceWithNoScale :: (a -> b -> c) -> Voice a -> Voice b -> Voice c
zipVoiceWithNoScale = zipVoiceWith' const
zipVoiceWith' :: (Duration -> Duration -> Duration) -> (a -> b -> c) -> Voice a -> Voice b -> Voice c
zipVoiceWith' f g
((unzip.view pairs) -> (ad, as))
((unzip.view pairs) -> (bd, bs))
= let cd = zipWith f ad bd
cs = zipWith g as bs
in view (from unsafePairs) (zip cd cs)
fuse :: Eq a => Voice a -> Voice a
fuse = fuseBy (==)
fuseBy :: (a -> a -> Bool) -> Voice a -> Voice a
fuseBy p = fuseBy' p head
fuseBy' :: (a -> a -> Bool) -> ([a] -> a) -> Voice a -> Voice a
fuseBy' p g = over unsafePairs $ fmap foldNotes . Data.List.groupBy (inspectingBy snd p)
where
foldNotes (unzip -> (ds, as)) = (sum ds, g as)
fuseRests :: Voice (Maybe a) -> Voice (Maybe a)
fuseRests = fuseBy (\x y -> isNothing x && isNothing y)
coverRests :: Voice (Maybe a) -> Maybe (Voice a)
coverRests x = if hasOnlyRests then Nothing else Just (fmap fromJust $ fuseBy merge x)
where
norm = fuseRests x
merge Nothing Nothing = error "Voice normalized, so consecutive rests are impossible"
merge (Just x) Nothing = True
merge Nothing (Just x) = True
merge (Just x) (Just y) = False
hasOnlyRests = all isNothing $ toListOf traverse x
withContext :: Voice a -> Voice (Ctxt a)
withContext = over valuesV addCtxt
durationsV :: Lens' (Voice a) [Duration]
durationsV = lens getDurs (flip setDurs)
where
getDurs :: Voice a -> [Duration]
getDurs = map fst . view pairs
setDurs :: [Duration] -> Voice a -> Voice a
setDurs ds as = zipVoiceWith' (\a b -> a) (\a b -> b) (mconcat $ map durToVoice ds) as
durToVoice d = stretch d $ pure ()
valuesV :: Lens (Voice a) (Voice b) [a] [b]
valuesV = lens getValues (flip setValues)
where
getValues = map snd . view pairs
setValues as bs = zipVoiceWith' (\a b -> b) (\a b -> a) (listToVoice as) bs
listToVoice = mconcat . map pure
voiceLens :: (s -> a) -> (b -> s -> t) -> Lens (Voice s) (Voice t) (Voice a) (Voice b)
voiceLens getter setter = lens (fmap getter) (flip $ zipVoiceWithNoScale setter)
sameDurations :: Voice a -> Voice b -> Bool
sameDurations a b = view durationsV a == view durationsV b
mergeIfSameDuration :: Voice a -> Voice b -> Maybe (Voice (a, b))
mergeIfSameDuration = mergeIfSameDurationWith (,)
mergeIfSameDurationWith :: (a -> b -> c) -> Voice a -> Voice b -> Maybe (Voice c)
mergeIfSameDurationWith f a b
| sameDurations a b = Just $ zipVoiceWithNoScale f a b
| otherwise = Nothing
homoToPolyphonic :: Voice [a] -> [Voice a]
homoToPolyphonic xs = case nvoices xs of
Nothing -> []
Just n -> fmap (\n -> fmap (!! n) xs) [0..n1]
where
nvoices :: Voice [a] -> Maybe Int
nvoices = maybeMinimum . fmap length . (^.valuesV)
maybeMinimum :: Ord a => [a] -> Maybe a
maybeMinimum xs = if null xs then Nothing else Just (minimum xs)
onsetsRelative :: Time -> Voice a -> [Time]
onsetsRelative o v = case offsetsRelative o v of
[] -> []
xs -> o : init xs
offsetsRelative :: Time -> Voice a -> [Time]
offsetsRelative o = fmap (\t -> o .+^ (t .-. 0)) . toAbsoluteTime . (^. durationsV)
midpointsRelative :: Time -> Voice a -> [Time]
midpointsRelative o v = zipWith between (onsetsRelative o v) (offsetsRelative o v)
where
between p q = alerp p q 0.5
erasRelative :: Time -> Voice a -> [Span]
erasRelative o v = zipWith (<->) (onsetsRelative o v) (offsetsRelative o v)