classify-frog-0.2.3: src/LabelChainShifted.hs
{-# LANGUAGE RebindableSyntax #-}
module LabelChainShifted (
T(..),
fromLabelChain,
shiftToLabelChain,
toLabelTrack,
chopChain,
chopClosest,
subdivideTrack,
mask,
) where
import qualified Durations as Durs
import qualified LabelChain
import qualified Sound.Audacity.LabelTrack as LabelTrack
import qualified Synthesizer.Generic.Signal as SigG
import qualified Control.Monad.Exception.Synchronous as ME
import Control.Applicative ((<$>))
import qualified Data.Monoid.HT as Mn
import qualified Data.Foldable as Fold
import qualified Data.List.HT as ListHT
import qualified Data.List as List
import Data.Maybe.HT (toMaybe)
import Data.Tuple.HT (mapFst, mapSnd)
import qualified Algebra.Absolute as Absolute
import qualified Algebra.Additive as Additive
import NumericPrelude.Numeric
import NumericPrelude.Base hiding (readFile, writeFile, null)
{- |
A chain of labels with a starting time that may differ from zero.
-}
data T t a = Cons {offset :: t, chain :: [(t,a)]}
instance Functor (T t) where
fmap f (Cons t xs) = Cons t $ map (mapSnd f) xs
instance Fold.Foldable (T t) where
foldMap f = Fold.foldMap (f . snd) . chain
fromLabelChain :: (Additive.C t) => LabelChain.T t a -> T t a
fromLabelChain = Cons zero . LabelChain.decons
shiftToLabelChain :: (Additive.C t) => T t a -> LabelChain.T t a
shiftToLabelChain (Cons t xs) =
LabelChain.Cons $ map (mapFst (subtract t)) xs
instance Durs.Track T where
intervalSizes (Cons t xs) =
Cons t $ ListHT.mapAdjacent1 (\n0 n1 lab -> (n1, (n1-n0, lab))) t xs
toLabelTrack :: T t a -> LabelTrack.T t a
toLabelTrack (Cons t xs) =
LabelTrack.Cons . ListHT.mapAdjacent1 (\l r lab -> ((l,r), lab)) t $ xs
chopChain :: (Ord t) => LabelChain.T t a -> T t b -> [(a, T t b)]
chopChain ts xs0 =
SigG.crochetL
(\(t,a) xs -> toMaybe (not $ null xs) $ mapFst ((,) a) $ splitAtTime t xs)
xs0 (LabelChain.decons ts)
_chopPattern0, _chopPattern1 :: (Ord t) => [t] -> T t a -> [T t a]
_chopPattern0 ts xs0 =
SigG.crochetL (\t xs -> toMaybe (not $ null xs) $ splitAtTime t xs) xs0 ts
_chopPattern1 ts0 =
let go [] _ = []
go (t:ts) xs =
if null xs
then []
else
case splitAtTime t xs of
(ys,zs) -> ys : go ts zs
in go ts0
subdivideTrack :: (Ord t) => LabelTrack.T t a -> T t b -> T t (Maybe a, b)
subdivideTrack ts xs0 =
(\(suffix, subd) ->
Cons (offset xs0) $ concat subd ++ chain (fmap ((,) Nothing) suffix)) $
List.mapAccumL
(\xs ((t0,t1),a) ->
let (prefix, (ys, suffix)) =
mapSnd (splitAtTime t1) $ splitAtTime t0 xs
in (suffix,
chain ((,) Nothing <$> prefix) ++
chain ((,) (Just a) <$> ys)))
xs0 (LabelTrack.decons ts)
null :: T t a -> Bool
null = List.null . chain
splitAtTime :: (Ord t) => t -> T t a -> (T t a, T t a)
splitAtTime t =
let go xs@(Cons _ []) = (xs, Cons t [])
go xt@(Cons left ((right,lab):xs)) =
if t<=left
then (Cons t [], xt)
else
mapFst (cons left lab) $
if t<right
then (Cons t [], Cons t $ (right, lab) : xs)
else go $ Cons right xs
in go
{- |
Chop @xs :: T t a@ chain with respect to @ts :: LabelChain.T t ()@.
We expect that every break in @ts@ is also present in @xs@,
however, the precise position might be distorted by rounding errors.
The positions of @xs@ are maintained,
that is the chunk boundaries are not adapted to the breaks in @ts@.
-}
chopClosest ::
(Absolute.C t, Ord t) =>
t -> LabelChain.T t () -> T t a -> [ME.Exceptional (Maybe t) (T t a)]
chopClosest maxDev ts xs0 =
(\(remainingXs, zss) ->
zss ++ Mn.when (not $ null remainingXs) [ME.throw Nothing]) $
List.mapAccumL
(\xs (t,()) ->
let (ys,zs) = splitAtClosestTime t xs
in (zs,
if abs (t - offset zs) <= maxDev
then ME.Success ys
else ME.Exception (Just t)))
xs0 (LabelChain.decons ts)
splitAtClosestTime :: (Additive.C t, Ord t) => t -> T t a -> (T t a, T t a)
splitAtClosestTime t =
let go xs@(Cons _ []) = (xs, xs)
go (Cons left ((right,lab):xs)) =
if t<=right
then
mapFst (Cons left) $
if t+t < left+right
then ([], Cons left ((right,lab):xs))
else ([(right,lab)], Cons right xs)
else mapFst (cons left lab) $ go $ Cons right xs
in \xt@(Cons left _xs) ->
if t<left
then (Cons left [], xt)
else go xt
{- |
It chooses the closest node for splitting,
but moves the node to the splitting time.
-}
_splitAtClosestTime :: (Additive.C t, Ord t) => t -> T t a -> (T t a, T t a)
_splitAtClosestTime t =
let go xs@(Cons _ []) = (xs, Cons t [])
go (Cons left ((right,lab):xs)) =
if t<=right
then
if t+t < left+right
then (Cons t [], Cons t ((right,lab):xs))
else (Cons left [(t,lab)], Cons t xs)
else mapFst (cons left lab) $ go $ Cons right xs
in \xt@(Cons left _xs) ->
if t<left
then (Cons t [], xt)
else go xt
cons :: t -> a -> T t a -> T t a
cons t x xs = Cons t $ (offset xs, x) : chain xs
mask :: (Ord t) => (t,t) -> T t a -> T t a
mask (l,r) = snd . splitAtTime l . fst . splitAtTime r