packages feed

classify-frog-0.2.3: src/SpectralDistributionTest.hs

module Main where

import qualified SpectralDistribution as SD
import qualified SignalProcessing as SP

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.Generic.Signal as SigG
import qualified Synthesizer.State.Filter.NonRecursive as FiltNR
import qualified Synthesizer.State.Signal as SigS
import qualified Synthesizer.State.Oscillator as Osci
import qualified Synthesizer.State.Displacement as Disp
import qualified Synthesizer.State.Control as Ctrl
import qualified Synthesizer.State.Noise as Noise
import qualified Synthesizer.Causal.Process as Causal

import Synthesizer.Causal.Class (($<), ($*), )

import qualified Data.StorableVector.Lazy as SVL

import Control.Arrow ((&&&), (<<<), (^<<), (<<^), )
import Control.Monad (liftM2, )

import Data.Foldable (forM_, )

import NumericPrelude.Numeric
import NumericPrelude.Base
import Prelude ()


frequency :: Float
frequency = 0.01

duration :: Int
duration = 50000

toneEnvelope :: SigS.T Float
toneEnvelope =
   FiltNR.envelope (Ctrl.exponential 20000 1) $
   Osci.staticSine zero frequency

toneChirp :: SigS.T Float
toneChirp =
   Osci.freqModSine zero $
   Ctrl.exponential 10000 (0.1::Float)

{- |
@sqrt@ asserts that the spectral centroid stays constant.
This works, because our time-domain definition of spectral centroid
computes the quadratic mean of frequencies.
-}
toneBroaden :: SigS.T Float
toneBroaden =
   FiltNR.amplify 0.5 $
   Disp.mix
      (Osci.freqModSine zero $ fmap sqrt $
       Ctrl.line duration (0.01, 0.02::Float))
      (Osci.freqModSine zero $ fmap sqrt $
       Ctrl.line duration (0.01, 0::Float))


toneMix :: Float -> Float -> SigS.T Float
toneMix freq0 freq1 =
   FiltNR.amplify 0.5 $
   Disp.mix
      (Osci.staticSine zero freq0)
      (Osci.staticSine zero freq1)



noiseEnvelope :: SigS.T Float
noiseEnvelope =
   FiltNR.envelope (Ctrl.exponential 20000 1) Noise.white

noiseChirp :: SigS.T Float
noiseChirp =
   (UniFilt.bandpass ^<< UniFilt.causal)
    $< SigS.map
          (UniFilt.parameter . FiltRec.Pole 10)
          (Ctrl.exponential 10000 (0.1::Float))
    $* Noise.white

noiseBroaden :: SigS.T Float
noiseBroaden =
   (UniFilt.bandpass ^<< UniFilt.causal)
    $< SigS.map
          (UniFilt.parameter . flip FiltRec.Pole frequency)
          (Ctrl.exponential 10000 (100::Float))
    $* FiltNR.amplify 0.5 Noise.white



smooth :: Causal.T Float Float
smooth =
   Filt1.lowpass_
   ^<<
   Filt1.causal
   <<<
   Causal.feedConstFst (Filt1.parameter (0.0002::Float))


volume :: SigS.T Float -> Float
volume  =  SigS.sum . SigS.map abs

followEnvelope :: Causal.T Float Float
followEnvelope  =  smooth <<^ abs

spectralDistribution1 :: Causal.T Float (SD.T Float)
spectralDistribution1 =
   (\(d0,(d1,d2)) ->
      SD.mapSpread SD.signedSqrt $ SD.spectralDistribution1 d0 d1 d2)
   ^<<
   (followEnvelope <<< SP.zerothMoment) &&&
   (followEnvelope <<< SP.firstMoment) &&&
   (followEnvelope <<< SP.secondMoment)


volumeSquare :: SigS.T Float -> Float
volumeSquare  =  SigS.sum . SigS.map (^2)

meanSquare :: Causal.T Float Float
meanSquare  =  smooth <<^ (^2)

spectralDistribution2 :: Causal.T Float (SD.T Float)
spectralDistribution2 =
   (\(d0,(d1,d2)) ->
      SD.mapSpread SD.signedSqrt $ SD.spectralDistribution2 d0 d1 d2)
   ^<<
   (meanSquare <<< SP.zerothMoment) &&&
   (meanSquare <<< SP.firstMoment) &&&
   (meanSquare <<< SP.secondMoment)


main :: IO ()
main = do
   forM_ [(0.01, 0.01), (0.01, 0.01*sqrt 2), (0.01, 0.02), (0.01, 0.04)] $
         \(freq0,freq1) ->
      let sig = SigS.take 100000 $ toneMix freq0 freq1
          d0 = volume $ Causal.apply SP.zerothMoment sig
          d1 = volume $ Causal.apply SP.firstMoment  sig
          d2 = volume $ Causal.apply SP.secondMoment sig
          (SD.Cons centroid1 spread1) = SD.spectralDistribution1 d0 d1 d2
          s0 = volumeSquare $ Causal.apply SP.zerothMoment sig
          s1 = volumeSquare $ Causal.apply SP.firstMoment  sig
          s2 = volumeSquare $ Causal.apply SP.secondMoment sig
          (SD.Cons centroid2 spread2) = SD.spectralDistribution2 s0 s1 s2
          r1 = s1/s0
      in  do
          putStrLn $ "\nfreqs: " ++ show (freq0,freq1)
          print (s0, s1, s2, s1/s0, s2/s1)
          print (s2/s0, r1^2, s2/s0-r1^2)
          print
             (sqrt ((freq0^2+freq1^2)/2),
              (centroid1 / (2*pi), sqrt spread1 / (2*pi)),
              (centroid2 / (2*pi), sqrt spread2 / (2*pi)))

   let signals =
          ("tone-env", toneEnvelope) :
          ("tone-chirp", toneChirp) :
          ("tone-broaden", toneBroaden) :
          ("noise-env", noiseEnvelope) :
          ("noise-chirp", noiseChirp) :
          ("noise-broaden", noiseBroaden) :
          []
       write process filename =
          SVL.writeFile filename .
          SigG.fromState SigG.defaultLazySize . SigS.take duration . process
       renderers =
          ("", write id) :
          ("-distribution1", write $ Causal.apply spectralDistribution1) :
          ("-distribution2", write $ Causal.apply spectralDistribution2) :
          []

   sequence_ $
      liftM2
         (\(featureName,render) (signalName,signal) ->
            render ("/tmp/" ++ signalName ++ featureName ++ ".f32") signal)
         renderers signals