classify-frog-0.2.3: src/SignalProcessing.hs
module SignalProcessing where
import qualified Rate
import Parameters (Freq, freq, )
import qualified Synthesizer.Plain.Filter.Recursive.Universal as UniFilt
import qualified Synthesizer.Plain.Filter.Recursive.FirstOrder as Filt1
import qualified Synthesizer.Plain.Filter.Recursive as FiltRec
import qualified Synthesizer.Causal.Process as Causal
import qualified Synthesizer.Generic.Analysis as Ana
import qualified Synthesizer.Generic.Cut as Cut
import qualified Synthesizer.Generic.Signal as SigG
import qualified Synthesizer.State.Signal as SigS
import qualified Data.StorableVector.Lazy as SVL
import qualified Data.StorableVector as SV
import Foreign.Storable (Storable, )
import qualified Control.Category as Cat
import Control.Arrow (Arrow, (^<<), (<<<), (&&&))
import Control.DeepSeq (NFData, ($!!))
import qualified Data.NonEmpty as NonEmpty
import qualified Data.Foldable as Fold
import qualified Data.List.Match as Match
import qualified Data.List.HT as ListHT
import qualified Data.List as List
import Data.Traversable (Traversable, mapAccumL)
import Data.Foldable (Foldable, )
import Data.Maybe.HT (toMaybe, )
import Data.Tuple.HT (swap, )
import qualified Algebra.RealRing as Real
import qualified Algebra.Field as Field
import qualified Algebra.Additive as Additive
import NumericPrelude.Numeric
import NumericPrelude.Base
import Prelude ()
{-# INLINE zerothMoment #-}
zerothMoment :: Causal.T Float Float
zerothMoment =
Causal.consInit 0
{-# INLINE firstMoment #-}
firstMoment :: Causal.T Float Float
firstMoment =
(\(x0,_x1,x2) -> (x2-x0)/2) ^<< lag2
{-# INLINE secondMoment #-}
secondMoment :: Causal.T Float Float
secondMoment =
(\(x0,x1,x2) -> x2-2*x1+x0) ^<< lag2
{-# INLINE lag2 #-}
lag2 :: (Additive.C a) => Causal.T a (a,a,a)
lag2 = lag2Init zero
{-# INLINE lag2Init #-}
lag2Init :: a -> Causal.T a (a,a,a)
lag2Init x =
(\((x0,x1),x2) -> (x0,x1,x2))
^<<
(Causal.consInit x &&& Cat.id <<< Causal.consInit x) &&& Cat.id
{-# INLINE bandpass #-}
bandpass :: (Rate.C rate) => rate -> Float -> Freq -> Causal.T Float Float
bandpass rate q f =
UniFilt.bandpass
^<<
UniFilt.causal
<<<
Causal.feedConstFst (UniFilt.parameter (FiltRec.Pole q (freq rate f)))
{-# INLINE highpass #-}
highpass :: (Rate.C rate) => rate -> Float -> Freq -> Causal.T Float Float
highpass rate q f =
UniFilt.highpass
^<<
UniFilt.causal
<<<
Causal.feedConstFst (UniFilt.parameter (FiltRec.Pole q (freq rate f)))
{-# INLINE lowpass #-}
lowpass :: (Rate.C rate) => rate -> Float -> Freq -> Causal.T Float Float
lowpass rate q f =
UniFilt.lowpass
^<<
UniFilt.causal
<<<
Causal.feedConstFst (UniFilt.parameter (FiltRec.Pole q (freq rate f)))
twoPasses ::
(Storable a) =>
(SVL.Vector a -> SVL.Vector a) -> SVL.Vector a -> SVL.Vector a
twoPasses f = SVL.reverse . f . SVL.reverse . f
lowpassOnePass ::
(Rate.C rate) => rate -> Freq -> SVL.Vector Float -> SVL.Vector Float
lowpassOnePass rate f sig =
let fr = freq rate f
in Causal.apply
(Filt1.lowpass_
^<<
Filt1.causalInit
(Ana.average $ Cut.take (ceiling (1/fr)) $ SigG.toState sig)
<<<
Causal.feedConstFst (Filt1.parameter fr))
sig
lowpassTwoPass ::
(Rate.C rate) => rate -> Freq -> SVL.Vector Float -> SVL.Vector Float
lowpassTwoPass rate f = twoPasses (lowpassOnePass rate f)
-- ToDo: move to synthesizer-core:Causal.Filter
{-# INLINE differentiate #-}
differentiate :: Causal.T Float Float
differentiate =
Cat.id - Causal.consInit zero
{-# INLINE differentiateMin3 #-}
differentiateMin3 :: Causal.T Float Float
differentiateMin3 = differentiateMin3Init zero
{-# INLINE differentiateMin3Init #-}
differentiateMin3Init :: Float -> Causal.T Float Float
differentiateMin3Init x =
Cat.id
-
((\(x0,x1,x2) -> x0 `min` x1 `min` x2) ^<< lag2Init x
<<< Causal.consInit x)
{- |
This one produces narrow pikes for the click beginnings.
However this turns out to make the click recognition worse.
-}
differentiateMin3Narrow :: Causal.T Float Float
differentiateMin3Narrow =
(\(x, (x0,x1,x2)) ->
let xmin = x0 `min` x1 `min` x2
xmax = x0 `max` x1 `max` x2
in if x>xmax then x-xmin else x-x2) ^<<
Cat.id &&& (lag2 <<< Causal.consInit zero)
downSampleMax :: Int -> SVL.Vector Float -> SVL.Vector Float
downSampleMax k =
SigG.fromState SigG.defaultLazySize .
fmap SVL.maximum . Cut.sliceVertical k
downSampleChunkSizes ::
(Field.C t, Real.C t) =>
t -> SigS.T Int
downSampleChunkSizes sizeFrac =
SigS.unfoldR
(\sizeRem -> Just $ splitFraction $ sizeRem + sizeFrac)
zero
{-
ToDo: move to synthesizer-core
could be generalized to Causal arrows
sometimes reversed parameter seems to be more appropriate
-}
chop :: (Traversable f, Cut.Transform sig) => sig -> f Int -> f sig
chop xs = snd . mapAccumL (\xsr d -> swap $ Cut.splitAt d xsr) xs
chopFrac :: Double -> SVL.Vector Float -> SigS.T (SVL.Vector Float)
chopFrac sizeFrac xs =
SigG.crochetL (\n xi -> toMaybe (not $ SVL.null xi) $ SVL.splitAt n xi) xs $
downSampleChunkSizes sizeFrac
{- |
It must be @sizeFrac >= 1@.
-}
downSampleMaxFrac :: Double -> SVL.Vector Float -> SVL.Vector Float
downSampleMaxFrac sizeFrac =
SigG.fromState SigG.defaultLazySize . fmap SVL.maximum .
chopFrac sizeFrac
downSampleMaxAbsFrac :: Double -> SVL.Vector Float -> SVL.Vector Float
downSampleMaxAbsFrac sizeFrac =
SigG.fromState SigG.defaultLazySize . fmap Ana.volumeMaximum .
chopFrac sizeFrac
downSampleAvgFrac :: Double -> SVL.Vector Float -> SVL.Vector Float
downSampleAvgFrac sizeFrac =
SigG.fromState SigG.defaultLazySize . fmap Ana.average .
chopFrac sizeFrac
takeSlices :: (Cut.Transform sig) => Int -> sig -> [sig] -> [sig]
takeSlices blockSize xs =
Match.take (SigS.toList $ Cut.sliceVertical blockSize xs)
-- ToDo: move to synthesizer-core
sliceOverlappingAbs, sliceOverlappingDiff, sliceOverlappingRel,
sliceOverlapping :: (Cut.Transform sig) => Int -> (Int,Int) -> sig -> [sig]
sliceOverlappingAbs blockSize (pre,suf) xs =
takeSlices blockSize xs $
map
(\t ->
let start = max 0 $ t-pre
stop = t+blockSize+suf
in Cut.take (stop-start) $ Cut.drop start xs) $
iterate (blockSize+) 0
sliceOverlappingDiff blockSize (pre,suf) xs =
let offsets = iterate (blockSize+) (-pre)
in takeSlices blockSize xs $
zipWith (\offset -> Cut.take (blockSize+pre+suf+min 0 offset)) offsets $
snd $ List.mapAccumL (\xsi k -> (Cut.drop k xsi, xsi)) xs $
ListHT.mapAdjacent subtract $ map (max 0) offsets
sliceOverlappingRel blockSize (pre,suf) xs =
let offsets = iterate (min 0 . (blockSize+)) (-pre)
in takeSlices blockSize xs $
zipWith (\offset -> Cut.take (blockSize+pre+suf+offset)) offsets $ snd $
List.mapAccumL
(\xsi offset -> (Cut.drop (max 0 $ offset+blockSize) xsi, xsi))
xs offsets
sliceOverlapping = sliceOverlappingRel
propSliceOverlapping :: NonEmpty.T [] Int -> ([Int], [Int]) -> String -> Bool
propSliceOverlapping blockSizeList (preList,sufList) xs =
let blockSize = length $ NonEmpty.flatten blockSizeList
pre = length preList
suf = length sufList
in ListHT.allEqual $
sliceOverlappingAbs blockSize (pre,suf) xs :
sliceOverlappingDiff blockSize (pre,suf) xs :
sliceOverlappingRel blockSize (pre,suf) xs :
[]
centroidVariance3 ::
Field.C a => (a, a) -> (a, a) -> (a, a) -> (a, a)
centroidVariance3 (f0,x0) (f1,x1) (f2,x2) =
let s = x0+x1+x2
mean y0 y1 y2 = (y0*x0 + y1*x1 + y2*x2) / s
center = mean f0 f1 f2
in (center, mean ((f0-center)^2) ((f1-center)^2) ((f2-center)^2))
svlConcat :: SVL.Vector Float -> SV.Vector Float
svlConcat = SV.concat . SVL.chunks
foldLength :: (Foldable f) => f a -> Int
foldLength = length . Fold.toList
{-
ToDo: move to utility-ht?
could be the basis of a Synthesizer.State.argmax function
it is now included in 'semigroups' but with swapped order
better maxKey :: (Ord b) => (a -> b) -> a -> a -> a ?
-}
argMax :: (Ord b) => (a,b) -> (a,b) -> (a,b)
argMax x0 x1 =
if snd x0 >= snd x1 then x0 else x1
argMin :: (Ord b) => (a,b) -> (a,b) -> (a,b)
argMin x0 x1 =
if snd x0 <= snd x1 then x0 else x1
-- ToDo: move to synthesizer-core
argMaximum ::
(NFData a, Ord a, Additive.C a, SigG.Read sig a) => sig a -> (Int, a)
argMaximum =
SigS.foldL (\x y -> (argMax $!! x) y) (0,zero) .
SigS.zip (SigS.iterate (1+) 0) . SigG.toState
{-# INLINE fanout3 #-}
fanout3 ::
(Arrow arrow) =>
arrow a b -> arrow a c -> arrow a d -> arrow a (b,c,d)
fanout3 arrb arrc arrd =
(\(b,(c,d)) -> (b,c,d)) ^<< arrb &&& arrc &&& arrd
chirpingPauseDur :: SVL.Vector Float -> Int
chirpingPauseDur xs =
let x = Ana.average xs
in SVL.length $ SVL.takeWhile (<=x) $ SVL.reverse xs
chirpingMainDur :: SVL.Vector Float -> Int
chirpingMainDur xs =
SVL.length xs - chirpingPauseDur xs