-- | Envelope generators.
module Sound.Sc3.Common.Envelope where

import Data.List {- base -}
import Data.Maybe {- base -}

import qualified Safe {- safe -}

import qualified Sound.Sc3.Common.Base as Base {- hsc3 -}
import qualified Sound.Sc3.Common.Math.Interpolate as Interpolate {- hsc3 -}

-- * Curve

-- | Envelope curve indicator input.
data Envelope_Curve a
  = EnvStep
  | EnvLin
  | EnvExp
  | EnvSin
  | -- | Note: not implemented at Sc3
    EnvWelch
  | EnvNum a
  | EnvSqr
  | EnvCub
  | EnvHold
  deriving (Eq, Show)

-- | Envelope curve pair.
type Envelope_Curve_2 a = Base.T2 (Envelope_Curve a)

-- | Envelope curve triple.
type Envelope_Curve_3 a = Base.T3 (Envelope_Curve a)

-- | Envelope curve quadruple.
type Envelope_Curve_4 a = Base.T4 (Envelope_Curve a)

{- | Convert 'Envelope_Curve' to shape value.

>>> map env_curve_shape [EnvSin,EnvSqr]
[3,6]
-}
env_curve_shape :: Num a => Envelope_Curve a -> a
env_curve_shape e =
  case e of
    EnvStep -> 0
    EnvLin -> 1
    EnvExp -> 2
    EnvSin -> 3
    EnvWelch -> 4
    EnvNum _ -> 5
    EnvSqr -> 6
    EnvCub -> 7
    EnvHold -> 8

{- | The /value/ of 'EnvCurve' is non-zero for 'EnvNum'.

>>> map env_curve_value [EnvWelch,EnvNum 2]
[0,2]
-}
env_curve_value :: Num a => Envelope_Curve a -> a
env_curve_value e =
  case e of
    EnvNum u -> u
    _ -> 0

-- | 'Interpolation_f' of 'Envelope_Curve'.
env_curve_interpolation_f :: (Ord t, Floating t) => Envelope_Curve t -> Interpolate.Interpolation_f t
env_curve_interpolation_f c =
  case c of
    EnvStep -> Interpolate.step
    EnvLin -> Interpolate.linear
    EnvExp -> Interpolate.exponential
    EnvSin -> Interpolate.sine
    EnvWelch -> Interpolate.welch
    EnvNum n -> Interpolate.curve n
    EnvSqr -> Interpolate.squared
    EnvCub -> Interpolate.cubed
    EnvHold -> undefined

-- | Apply /f/ to 'EnvNum' value.
env_curve_map :: (a -> b) -> Envelope_Curve a -> Envelope_Curve b
env_curve_map f e =
  case e of
    EnvStep -> EnvStep
    EnvLin -> EnvLin
    EnvExp -> EnvExp
    EnvSin -> EnvSin
    EnvWelch -> EnvWelch
    EnvNum x -> EnvNum (f x)
    EnvSqr -> EnvSqr
    EnvCub -> EnvCub
    EnvHold -> EnvHold

-- * Envelope

-- | Sc3 envelope segment model
data Envelope a = Envelope
  { env_levels :: [a]
  -- ^ Set of /n/ levels, n is >= 1
  , env_times :: [a]
  -- ^ Set of /n-1/ time intervals
  , env_curves :: [Envelope_Curve a]
  -- ^ Possibly empty curve set
  , env_release_node :: Maybe Int
  -- ^ Maybe index to release node
  , env_loop_node :: Maybe Int
  -- ^ Maybe index to loop node
  , env_offset :: a
  -- ^ An offset for all time values (IEnvGen only)
  }
  deriving (Eq, Show)

-- | Apply /f/ to all /a/ at 'Envelope'.
envelope_map :: (a -> b) -> Envelope a -> Envelope b
envelope_map f e =
  let Envelope l t c rn ln os = e
  in Envelope (map f l) (map f t) (map (env_curve_map f) c) rn ln (f os)

-- | fmap = 'envelope_map'
instance Functor Envelope where
  fmap = envelope_map

{- | Variant without release and loop node inputs (defaulting to nil).

> Sound.Sc3.Plot.plotEnvelope [envelope [0,1,0] [3,2] [EnvSin,EnvSin]]
-}
envelope :: Num a => [a] -> [a] -> [Envelope_Curve a] -> Envelope a
envelope l t c = Envelope l t c Nothing Nothing 0

-- | Duration of 'Envelope', ie. 'sum' '.' 'env_times'.
envelope_duration :: Num n => Envelope n -> n
envelope_duration = sum . env_times

-- | Number of segments at 'Envelope', ie. 'length' '.' 'env_times'.
envelope_n_segments :: Integral i => Envelope n -> i
envelope_n_segments = genericLength . env_times

-- | Determine which envelope segment a given time /t/ falls in.
envelope_segment_ix :: (Ord a, Num a) => Envelope a -> a -> Maybe Int
envelope_segment_ix e t =
  let d = Base.dx_d (env_times e)
  in findIndex (>= t) d

-- | A set of start time, start level, end time, end level and curve.
type Envelope_Segment t = (t, t, t, t, Envelope_Curve t)

-- | Extract envelope segment given at index /i/.
envelope_segment :: Num t => Envelope t -> Int -> Envelope_Segment t
envelope_segment e i =
  let l = env_levels e
      t = env_times e
      x0 = l !! i
      x1 = l !! (i + 1)
      t0 = (0 : Base.dx_d t) !! i
      t1 = t0 + t !! i
      c = envelope_curves e !! i
  in (t0, x0, t1, x1, c)

-- | Extract all segments.
envelope_segments :: Num t => Envelope t -> [Envelope_Segment t]
envelope_segments e =
  let n = envelope_n_segments e
  in map (envelope_segment e) [0 .. n - 1]

-- | Transform list of 'Envelope_Segment's into lists ('env_levels','env_times','env_curves').
pack_envelope_segments :: Num t => [Envelope_Segment t] -> ([t], [t], [Envelope_Curve t])
pack_envelope_segments s =
  case s of
    [] -> error ""
    [(t0, l0, t1, l1, c)] -> ([l0, l1], [t1 - t0], [c])
    (_, l0, _, _, _) : _ ->
      let t (t0, _, t1, _, _) = t1 - t0
          c (_, _, _, _, x) = x
          l (_, _, _, x, _) = x
      in (l0 : map l s, map t s, map c s)

-- | An envelope is /normal/ if it has no segments with zero duration.
envelope_is_normal :: (Eq n, Num n) => Envelope n -> Bool
envelope_is_normal = notElem 0 . env_times

-- | Normalise envelope by deleting segments of zero duration.
envelope_normalise :: (Num a, Ord a) => Envelope a -> Envelope a
envelope_normalise e =
  let s = envelope_segments e
      f (t0, _, t1, _, _) = t1 <= t0
      s' = filter (not . f) s
      (l, t, c) = pack_envelope_segments s'
  in case e of
      Envelope _ _ _ Nothing Nothing os -> Envelope l t c Nothing Nothing os
      _ -> error "envelope_normalise: has release or loop node..."

{- | Get value for 'Envelope' at time /t/, or zero if /t/ is out of
range.  By convention if the envelope has a segment of zero
duration we give the rightmost value.
-}
envelope_at :: (Ord t, Floating t) => Envelope t -> t -> t
envelope_at e t =
  case envelope_segment_ix e t of
    Just n ->
      let (t0, x0, t1, x1, c) = envelope_segment e n
          d = t1 - t0
          t' = (t - t0) / d
          f = env_curve_interpolation_f c
      in if d <= 0
          then x1
          else f x0 x1 t'
    Nothing -> 0

-- | Render 'Envelope' to breakpoint set of /n/ equi-distant places.
envelope_render :: (Ord t, Floating t, Enum t) => Int -> Envelope t -> [(t, t)]
envelope_render n e =
  let d = envelope_duration e
      k = d / (fromIntegral n - 1)
      t = [0, k .. d]
  in zip t (map (envelope_at e) t)

-- | Contruct a lookup table of /n/ places from 'Envelope'.
envelope_table :: (Ord t, Floating t, Enum t) => Int -> Envelope t -> [t]
envelope_table n = map snd . envelope_render n

{- | Variant on 'env_curves' that expands the, possibly empty, user
list by cycling (if not empty) or by filling with 'EnvLin'.
-}
envelope_curves :: Envelope a -> [Envelope_Curve a]
envelope_curves e =
  let c = env_curves e
      n = envelope_n_segments e
  in if null c
      then replicate n EnvLin
      else take n (cycle c)

{- | Linear Sc3 form of 'Envelope' data.

Form is: l0 #t reset loop l1 t0 c0 c0' ...

>>> envelope_sc3_array (envelope [0,1] [0.1] [EnvLin])
Just [0.0,1.0,-99.0,-99.0,1.0,0.1,1.0,0.0]

>>> let l = [0,0.6,0.3,1.0,0]
>>> let t = [0.1,0.02,0.4,1.1]
>>> let c = [EnvLin,EnvExp,EnvNum (-6),EnvSin]
>>> envelope_sc3_array (Envelope l t c Nothing Nothing 0)
Just [0.0,4.0,-99.0,-99.0,0.6,0.1,1.0,0.0,0.3,2.0e-2,2.0,0.0,1.0,0.4,5.0,-6.0,0.0,1.1,3.0,0.0]
-}
envelope_sc3_array :: Num a => Envelope a -> Maybe [a]
envelope_sc3_array e =
  let Envelope l t _ rn ln _ = e
      n = length t
      n' = fromIntegral n
      rn' = fromIntegral (fromMaybe (-99) rn)
      ln' = fromIntegral (fromMaybe (-99) ln)
      c = envelope_curves e
      f i j k = [i, j, env_curve_shape k, env_curve_value k]
  in case l of
      l0 : l' -> Just (l0 : n' : rn' : ln' : concat (zipWith3 f l' t c))
      _ -> Nothing

{- | @IEnvGen@ Sc3 form of 'Envelope' data.

>>> let l = [0,0.6,0.3,1.0,0]
>>> let t = [0.1,0.02,0.4,1.1]
>>> let c = [EnvLin,EnvExp,EnvNum (-6),EnvSin]
>>> let e = Envelope l t c Nothing Nothing 0
>>> envelope_sc3_ienvgen_array e
Just [0.0,0.0,4.0,1.62,0.1,1.0,0.0,0.6,2.0e-2,2.0,0.0,0.3,0.4,5.0,-6.0,1.0,1.1,3.0,0.0,0.0]
-}
envelope_sc3_ienvgen_array :: Num a => Envelope a -> Maybe [a]
envelope_sc3_ienvgen_array e =
  let Envelope l t _ _ _ os = e
      n = length t
      n' = fromIntegral n
      c = envelope_curves e
      f i j k = [j, env_curve_shape k, env_curve_value k, i]
  in case l of
      l0 : l' -> Just (os : l0 : n' : sum t : concat (zipWith3 f l' t c))
      _ -> Nothing

-- | 'True' if 'env_release_node' is not 'Nothing'.
env_is_sustained :: Envelope a -> Bool
env_is_sustained = isJust . env_release_node

-- | Delay the onset of the envelope (add initial segment).
env_delay :: Envelope a -> a -> Envelope a
env_delay (Envelope l t c rn ln os) d =
  let l0 = Safe.headNote "env_delay" l
      l' = l0 : l
      t' = d : t
      c' = EnvLin : c
      rn' = fmap (+ 1) rn
      ln' = fmap (+ 1) ln
  in Envelope l' t' c' rn' ln' os

{- | Connect releaseNode (or end) to first node of envelope.
z is a value that is first zero and thereafter one.
tc & cc are time and curve from first to last.
-}
env_circle_z :: Fractional a => a -> a -> Envelope_Curve a -> Envelope a -> Envelope a
env_circle_z z tc cc (Envelope l t c rn _ os) =
  let n = length t
  in case rn of
      Nothing ->
        let l' = 0 : l ++ [0]
            t' = z * tc : t ++ [1] -- inf (but drawings are poor)
            c' = cc : take n (cycle c) ++ [EnvLin]
            rn' = Just (n + 1)
        in Envelope l' t' c' rn' (Just 0) os
      Just i ->
        let l' = 0 : l
            t' = z * tc : t
            c' = cc : take n (cycle c)
            rn' = Just (i + 1)
        in Envelope l' t' c' rn' (Just 0) os

-- | env_circle_z with cycle time of zero.
env_circle_0 :: Fractional a => Envelope a -> Envelope a
env_circle_0 = env_circle_z 1 0 EnvLin

-- * Construct

{- | Trapezoidal envelope generator.

Requires (<=) and (>=) functions returning @1@ for true and @0@ for false.

The arguments are: 1. @shape@ determines the sustain time as a
proportion of @dur@, zero is a triangular envelope, one a rectangular
envelope; 2. @skew@ determines the attack\/decay ratio, zero is an
immediate attack and a slow decay, one a slow attack and an immediate
decay; 3. @duration@ in seconds; 4. @amplitude@ as linear gain.
-}
envTrapezoid_f :: Num t => (t -> t -> t, t -> t -> t) -> t -> t -> t -> t -> Envelope t
envTrapezoid_f (lte_f, gte_f) shape skew dur amp =
  let x1 = skew * (1 - shape)
      bp =
        [ (0, lte_f skew 0)
        , (x1, 1)
        , (shape + x1, 1)
        , (1, gte_f skew 1)
        ]
  in envCoord bp dur amp EnvLin

{- | Coordinate based static envelope generator.  Points are (time,value) pairs.

>>> let e = envCoord [(0,0),(1/4,1),(1,0)] 1 1 EnvLin
>>> envelope_sc3_array e
Just [0.0,2.0,-99.0,-99.0,1.0,0.25,1.0,0.0,0.0,0.75,1.0,0.0]

> import Sound.Sc3.Plot {\- hsc3-plot -\}
> plotEnvelope [envCoord [(0,0),(1/4,1),(1,0)] 1 1 EnvLin]
-}
envCoord :: Num n => [(n, n)] -> n -> n -> Envelope_Curve n -> Envelope n
envCoord xy dur amp c = envXyc (map (\(x, y) -> (x * dur, y * amp, c)) xy)

{- | Segments given as pairs of (time,level).
The input is sorted by time before processing.

>>> env_levels (envPairs [(0, 1), (3, 1.4), (2.1, 0.5)] EnvSin)
[1.0,0.5,1.4]
-}
envPairs :: (Num n, Ord n) => [(n, n)] -> Envelope_Curve n -> Envelope n
envPairs xy = envCoord (Base.sort_on fst xy) 1 1

-- | Percussive envelope, with attack, release, level and curve inputs.
envPerc_c :: Num a => a -> a -> a -> Envelope_Curve_2 a -> Envelope a
envPerc_c atk rls lvl (c0, c1) =
  let c = [c0, c1]
  in Envelope [0, lvl, 0] [atk, rls] c Nothing Nothing 0

-- | Percussive envelope, with attack and release inputs.
envPerc :: Num a => a -> a -> Envelope a
envPerc atk rls =
  let cn = EnvNum (-4)
  in envPerc_c atk rls 1 (cn, cn)

{- | Triangular envelope, with duration and level inputs.

>>> let e = envTriangle 1 0.1
>>> envelope_sc3_array e
Just [0.0,2.0,-99.0,-99.0,0.1,0.5,1.0,0.0,0.0,0.5,1.0,0.0]
-}
envTriangle :: Fractional a => a -> a -> Envelope a
envTriangle dur lvl =
  let c = replicate 2 EnvLin
      d = replicate 2 (dur / 2)
  in Envelope [0, lvl, 0] d c Nothing Nothing 0

{- | Sine envelope, with duration and level inputs.

>>> envelope_sc3_array (envSine 0 0.1)
Just [0.0,2.0,-99.0,-99.0,0.1,0.0,3.0,0.0,0.0,0.0,3.0,0.0]
-}
envSine :: Fractional a => a -> a -> Envelope a
envSine dur lvl =
  let c = replicate 2 EnvSin
      d = replicate 2 (dur / 2)
  in Envelope [0, lvl, 0] d c Nothing Nothing 0

-- | Parameters for Linen envelopes.
data Linen a = Linen
  { linen_attackTime :: a
  , linen_sustainTime :: a
  , linen_releaseTime :: a
  , linen_level :: a
  , linen_curve :: Envelope_Curve_3 a
  }

-- | Sc3 defaults for Linen.
linen_def :: Fractional t => Linen t
linen_def = let c = EnvLin in Linen 0.01 1 1 1 (c, c, c)

-- | Record ('Linen') variant of 'envLinen'.
envLinen_r :: Num a => Linen a -> Envelope a
envLinen_r (Linen aT sT rT lv (c0, c1, c2)) =
  let l = [0, lv, lv, 0]
      t = [aT, sT, rT]
      c = [c0, c1, c2]
  in Envelope l t c Nothing Nothing 0

-- | Variant of 'envLinen' with user specified 'Envelope_Curve a'.
envLinen_c :: Num a => a -> a -> a -> a -> Envelope_Curve_3 a -> Envelope a
envLinen_c aT sT rT lv c = envLinen_r (Linen aT sT rT lv c)

{- | Linear envelope parameter constructor.

>>> let e = envLinen 0 1 0 1
>>> let s = envelope_segments e
>>> let p = pack_envelope_segments s
>>> p == (env_levels e,env_times e,env_curves e)
True
-}
envLinen :: Num a => a -> a -> a -> a -> Envelope a
envLinen aT sT rT lv =
  let c = (EnvLin, EnvLin, EnvLin)
  in envLinen_c aT sT rT lv c

{- | Parameters for Adsr envelopes.
  The sustain level is given as a proportion of the peak level.
-}
data Adsr a = Adsr
  { adsr_attackTime :: a
  , adsr_decayTime :: a
  , adsr_sustainLevel :: a
  , adsr_releaseTime :: a
  , adsr_peakLevel :: a
  , adsr_curve :: Envelope_Curve_3 a
  , adsr_bias :: a
  }

-- | Sc3 defaults for Adsr.
adsr_def :: Fractional n => Adsr n
adsr_def = let c = EnvNum (-4) in Adsr 0.01 0.3 0.5 1 1 (c, c, c) 0

-- | Attack, decay, sustain, release envelope parameter constructor.
envAdsr :: Num a => a -> a -> a -> a -> a -> Envelope_Curve a -> a -> Envelope a
envAdsr aT dT sL rT pL c b = envAdsr_r (Adsr aT dT sL rT pL (c, c, c) b)

-- | Variant with defaults for pL, c and b.
envAdsr_def :: Num a => a -> a -> a -> a -> Envelope a
envAdsr_def aT dT sL rT = envAdsr aT dT sL rT 1 (EnvNum (-4)) 0

-- | Record ('Adsr') variant of 'envAdsr'.
envAdsr_r :: Num a => Adsr a -> Envelope a
envAdsr_r (Adsr aT dT sL rT pL (c0, c1, c2) b) =
  let l = map (+ b) [0, pL, pL * sL, 0]
      t = [aT, dT, rT]
      c = [c0, c1, c2]
  in Envelope l t c (Just 2) Nothing 0

-- | Parameters for Roland type Adssr envelopes.
data Adssr a = Adssr
  { adssr_attackTime :: a
  , adssr_attackLevel :: a
  , adssr_decayTime :: a
  , adssr_decayLevel :: a
  , adssr_slopeTime :: a
  , adssr_sustainLevel :: a
  , adssr_releaseTime :: a
  , adssr_curve :: Envelope_Curve_4 a
  , adssr_bias :: a
  }

-- | Attack, decay, slope, sustain, release envelope parameter constructor.
envAdssr :: Num a => a -> a -> a -> a -> a -> a -> a -> Envelope_Curve a -> a -> Envelope a
envAdssr t1 l1 t2 l2 t3 l3 t4 c b = envAdssr_r (Adssr t1 l1 t2 l2 t3 l3 t4 (c, c, c, c) b)

-- | Record ('Adssr') variant of 'envAdssr'.
envAdssr_r :: Num a => Adssr a -> Envelope a
envAdssr_r (Adssr t1 l1 t2 l2 t3 l3 t4 (c1, c2, c3, c4) b) =
  let l = map (+ b) [0, l1, l2, l3, 0]
      t = [t1, t2, t3, t4]
      c = [c1, c2, c3, c4]
  in Envelope l t c (Just 3) Nothing 0

-- | Parameters for Asr envelopes.
data Asr a = Asr
  { asr_attackTime :: a
  , asr_sustainLevel :: a
  , asr_releaseTime :: a
  , asr_curve :: Envelope_Curve_2 a
  }

-- | Sc3 default values for Asr.
asr_def :: Fractional t => Asr t
asr_def = let c = EnvNum (-4) in Asr 0.01 1 1 (c, c)

-- | Sc3 .asr has singular curve argument, hence _c suffix.
envAsr_c :: Num a => a -> a -> a -> Envelope_Curve_2 a -> Envelope a
envAsr_c aT sL rT c = envAsr_r (Asr aT sL rT c)

{- | Attack, sustain, release envelope parameter constructor.

>>> envelope_sc3_array (envAsr 3 0.1 2 EnvSin)
Just [0.0,2.0,1.0,-99.0,0.1,3.0,3.0,0.0,0.0,2.0,3.0,0.0]
-}
envAsr :: Num a => a -> a -> a -> Envelope_Curve a -> Envelope a
envAsr aT sL rT c = envAsr_c aT sL rT (c, c)

-- | Record ('Asr') variant of 'envAsr'.
envAsr_r :: Num a => Asr a -> Envelope a
envAsr_r (Asr aT sL rT (c0, c1)) =
  let l = [0, sL, 0]
      t = [aT, rT]
      c' = [c0, c1]
  in Envelope l t c' (Just 1) Nothing 0

-- | All segments are horizontal lines.
envStep :: Num a => [a] -> [a] -> Maybe Int -> Maybe Int -> Envelope a
envStep levels times releaseNode loopNode =
  if length levels /= length times
    then error "envStep: levels and times must have same size"
    else
      let levels' = Safe.headNote "envStep" levels : levels
      in Envelope levels' times [EnvStep] releaseNode loopNode 0

{- | Segments given as triples of (time,level,curve).  The final curve is ignored.

>>> env_levels (envXyc [(0, 1, EnvSin), (2.1, 0.5, EnvLin), (3, 1.4, EnvLin)])
[1.0,0.5,1.4]
-}
envXyc :: Num n => [(n, n, Envelope_Curve n)] -> Envelope n
envXyc xyc =
  let n = length xyc
      (times, levels, curves) = unzip3 xyc
      offset = times !! 0
  in Envelope levels (Base.d_dx' times) (take (n - 1) curves) Nothing Nothing offset

{- | Variant where the input is sorted by time before processing.

>>> env_levels (envXyc_sort [(0, 1, EnvSin), (3, 1.4, EnvLin), (2.1, 0.5, EnvLin)])
[1.0,0.5,1.4]
-}
envXyc_sort :: (Num n, Ord n) => [(n, n, Envelope_Curve n)] -> Envelope n
envXyc_sort = envXyc . Base.sort_on (\(x, _, _) -> x)
