{-# OPTIONS -fno-implicit-prelude #-}
{- |
Copyright   :  (c) Henning Thielemann 2006, 2008
License     :  GPL

Maintainer  :  synthesizer@henning-thielemann.de
Stability   :  provisional
Portability :  requires multi-parameter type classes
-}
module Synthesizer.Inference.Func.Cut (
   {- * dissection -}
   -- splitAt,
   -- take,
   -- drop,
   takeUntilPause,
   -- unzip,
   -- unzip3,

   {- * glueing -}
   concat,
   concatVolume,
   append,
   zip,
   -- zip3,
   arrange,
   arrangeVolume,
  ) where

import qualified Synthesizer.Physical.Signal      as SigP
import qualified Synthesizer.Physical.Cut         as CutP
import qualified Synthesizer.Inference.Func.Signal as SigF

import qualified Synthesizer.SampleRateContext.Signal as SigC
import qualified Synthesizer.SampleRateContext.Rate as Rate
import qualified Synthesizer.SampleRateContext.Cut as CutC

import qualified Data.EventList.Relative.TimeBody as EventList
import qualified Numeric.NonNegative.Class as NonNeg

import qualified Algebra.NormedSpace.Maximum as NormedMax
import qualified Algebra.OccasionallyScalar  as OccScalar
import qualified Algebra.Module              as Module
import qualified Algebra.RealField           as RealField
import qualified Algebra.Field               as Field
import qualified Algebra.Real                as Real
import qualified Algebra.Ring                as Ring

-- import qualified Data.List as List

-- import Control.Monad.Fix(mfix)

import PreludeBase hiding (zip, zip3, concat, )
-- import NumericPrelude
import Prelude (RealFrac)

{-
{- * dissection -}

splitAt :: (RealField.C a, Field.C q, OccScalar.C a q) =>
   q -> SigI.T a q v -> Process.T q (SigI.T a q v, SigI.T a q v)
splitAt t0 x@(Cons sr amp ss) =
   do t <- SigI.toTimeScalar x (Expr.constant t0)
      let (ss0,ss1) = List.splitAt (round t) ss
      return (Cons sr amp ss0, Cons sr amp ss1)

take :: (RealField.C a, Field.C q, OccScalar.C a q) =>
   q -> SigI.T a q v -> SigI.Process a q v
take t = fmap fst . splitAt t

drop :: (RealField.C a, Field.C q, OccScalar.C a q) =>
   q -> SigI.T a q v -> SigI.Process a q v
drop t = fmap snd . splitAt t
-}

takeUntilPause :: (RealField.C t, Ring.C t', OccScalar.C t t',
                   Field.C y', NormedMax.C y yv, OccScalar.C y y') =>
   y' -> t' -> SigF.T t t' y y' yv -> SigF.T t t' y y' yv
takeUntilPause y' t' x =
   SigF.cons $ \infered@(isr,iamp) ->
      let x' = SigF.eval x infered
          xp = SigP.replaceParameters isr iamp x'
          zp = CutP.takeUntilPause y' t' xp
      in  SigP.replaceParameters
             (SigP.sampleRate x') (SigP.amplitude x') zp


{-
How can we assert sharing of the input signal
with the output signals?

unzip ::
       SigF.T t t' y y' (yv0, yv1)
   -> (SigF.T t t' y y' yv0, SigF.T t t' y y' yv1)
unzip x =
   (SigF.cons $ \inferedY@(isrY,iampY) -> ,
    SigF.cons $ \inferedZ@(isrZ,iampZ) -> )


unzip3 ::
       SigF.T t t' y y' (yv0, yv1, yv2)
   -> (SigF.T t t' y y' yv0, SigF.T t t' y y' yv1, SigF.T t t' y y' yv2)
unzip3 = return . CutC.unzip3
-}


{- * glueing -}

{- |
  Similar to @foldr1 append@ but more efficient and accurate,
  because it reduces the number of amplifications.
  Does not work for infinite lists,
  because in this case a maximum amplitude cannot be computed.
-}
concat ::
   (Eq t', Real.C y', Field.C y', Module.C y yv, OccScalar.C y y') =>
      [SigF.T t t' y y' yv]
   ->  SigF.T t t' y y' yv
concat xs =
   SigF.cons $ \(isr,iamp) ->
      let xs' = zipWith (\x amp -> SigF.eval x (isr, amp)) xs amps
          amps = map SigF.guessAmplitude xs'
          xps = zipWith SigF.contextFixAmplitude amps xs'
          sampleRate = SigF.mergeSampleRates xs'
      in  SigF.fromContextCheckAmplitude sampleRate iamp
             (CutC.concat (Rate.fromNumber isr) xps)

{- |
  Like 'concat' but it expects a fixed output amplitude.
  This way it can also handle infinitely many inputs
  if one input or the output has a fixed sample rate.

  'concatVolume' is one reason for the complicated handling
  of sampling rates by lists of @Maybe@s.

  The problem of finding an apropriate sampling rate is that
  we must have an order of processing parallel signal processors
  which guarantees termination if termination is possible.
  Say @mix (concat infinitelist0) (concat infinitelist1)@.
  Either infinite list can have signal with fixed sample rate or not.
  There is no way to determine this a priori.
  The only safe way is to process them in parallel.
  That's why we must have a @[Maybe t']@ instead of @Maybe t'@.
  Also @[t']@ is not enough,
  because e.g. a concatenation of infinitely many sounds
  with undetermined sampling rate
  would have an empty list representing the sampling rate,
  but computing the empty list needs infinite time.
-}
concatVolume ::
   (Eq t', Real.C y', Field.C y', Module.C y yv, OccScalar.C y y') =>
      [SigF.T t t' y y' yv]
   ->  SigF.T t t' y y' yv
concatVolume xs =
   SigF.cons $ \(isr,iamp) ->
      let xs' = zipWith (\x amp -> SigF.eval x (isr, amp)) xs amps
          amps = map SigF.guessAmplitude xs'
          xps = zipWith SigF.contextFixAmplitude amps xs'
          sampleRate = SigF.mergeSampleRates xs'
      in  SigF.fromContextFreeAmplitude sampleRate
             (CutC.concatVolume iamp (Rate.fromNumber isr) xps)


merge :: (Eq t', Real.C y', Field.C y', OccScalar.C y y',
          Module.C y v0, Module.C y v1) =>
      (Rate.T t t' -> SigC.T y y' v0 -> SigC.T y y' v1 -> SigC.T y y' v2)
   -> SigF.T t t' y y' v0
   -> SigF.T t t' y y' v1
   -> SigF.T t t' y y' v2
merge f x y =
   SigF.cons $ \(isr,iamp) ->
      let x' = SigF.eval x (isr, ampX)
          y' = SigF.eval y (isr, ampY)
          ampX = SigF.guessAmplitude x'
          ampY = SigF.guessAmplitude y'
          xp = SigF.contextFixAmplitude ampX x'
          yp = SigF.contextFixAmplitude ampY y'
          sampleRate = SigF.mergeSampleRate x' y'
      in  SigF.fromContextCheckAmplitude sampleRate iamp
             (f (Rate.fromNumber isr) xp yp)


append :: (Eq t', Real.C y', Field.C y', OccScalar.C y y',
         Module.C y yv) =>
      SigF.T t t' y y' yv
   -> SigF.T t t' y y' yv
   -> SigF.T t t' y y' yv
append = merge CutC.append


zip :: (Eq t', Real.C y', Field.C y', OccScalar.C y y',
        Module.C y v0, Module.C y v1) =>
      SigF.T t t' y y' v0
   -> SigF.T t t' y y' v1
   -> SigF.T t t' y y' (v0,v1)
zip = merge CutC.zip

{-
zip3 :: (Real.C q, Field.C q, Ord q, OccScalar.C a q,
         Module.C a v0, Module.C a v1, Module.C a v2)
   => SigI.T a q v0
   -> SigI.T a q v1
   -> SigI.T a q v2
   -> SigI.Process a q (v0, v1, v2)
zip3 x0 x1 x2 =
   mfix (\z ->
      do sampleRate <- Process.equalValues
            [SigP.sampleRate x0, SigP.sampleRate x1, SigP.sampleRate x2]
         amplitude  <- Process.fromExpr
            (Expr.maximum [amplitudeExpr x0, amplitudeExpr x1, amplitudeExpr x2])
         samp0 <- SigI.vectorSamples (toAmplitudeScalar z) x0
         samp1 <- SigI.vectorSamples (toAmplitudeScalar z) x1
         samp2 <- SigI.vectorSamples (toAmplitudeScalar z) x2
         SigI.returnCons sampleRate amplitude
            (List.zip3 samp0 samp1 samp2))
-}



scheduleToContext ::
      t'
   -> EventList.T time (SigF.T t t' y y' yv)
   -> (SigF.Parameter t',
       EventList.T time (SigC.T y y' yv))
scheduleToContext isr sched =
   let xps =
          EventList.mapBody
             (\x ->
                 let y = SigF.eval x (isr, amp)
                     amp = SigF.guessAmplitude y
                     z = SigF.contextFixAmplitude amp y
                 in  (y,z)) sched
       schedp = EventList.mapBody snd xps
       sampleRate = SigF.mergeSampleRates (map fst (EventList.getBodies xps))
   in  (sampleRate, schedp)


{- |
  Given a list of signals with time stamps,
  mix them into one signal as they occur in time.
  Ideally for composing music.
  Infinite schedules are not supported,
  because no maximum amplitude can be computed.
-}
arrange ::
   (RealFrac t, NonNeg.C t, Eq t', Ring.C t, Ring.C t', OccScalar.C t t',
    Ord y', Field.C y', OccScalar.C y y',
    Module.C y yv) =>
      t'
   -> EventList.T t (SigF.T t t' y y' yv)
          {-^ A list of pairs: (relative start time, signal part),
              The start time is relative
              to the start time of the previous event. -}
   -> SigF.T t t' y y' yv
          {-^ The mixed signal. -}
arrange unit sched =
   SigF.cons $ \(isr,iamp) ->
      let (sampleRate, schedp) = scheduleToContext isr sched
      in  SigF.fromContextCheckAmplitude sampleRate iamp
             (CutC.arrange unit (Rate.fromNumber isr) schedp)

arrangeVolume ::
   (RealFrac t, NonNeg.C t, Eq t', Ring.C t, Ring.C t', OccScalar.C t t',
    Field.C y', OccScalar.C y y',
    Module.C y yv) =>
      t'
   -> EventList.T t (SigF.T t t' y y' yv)
          {-^ A list of pairs: (relative start time, signal part),
              The start time is relative
              to the start time of the previous event. -}
   -> SigF.T t t' y y' yv
          {-^ The mixed signal. -}
arrangeVolume unit sched =
   SigF.cons $ \(isr,iamp) ->
      let (sampleRate, schedp) = scheduleToContext isr sched
      in  SigF.fromContextFreeAmplitude sampleRate
             (CutC.arrangeVolume iamp unit (Rate.fromNumber isr) schedp)