-- | A /warp/ is a mapping from the space @[0,1]@ to a user defined
-- space /[l,r]/.
module Sound.SC3.Lang.Math.Warp where

import Numeric {- base -}
import Sound.SC3.UGen.Math {- hsc3 -}

import Sound.SC3.Lang.Math

-- | Warp direction.  'W_Map' is forward, 'W_Unmap' is reverse.
data W_Direction = W_Map | W_Unmap
                   deriving (Eq,Enum,Bounded,Show)

-- | Warp type
type Warp t = W_Direction -> t -> t

-- | Forward warp.
w_map :: Warp t -> t -> t
w_map w = w W_Map

-- | Reverse warp.
w_unmap :: Warp t -> t -> t
w_unmap w = w W_Unmap

-- | A linear real value map.
--
-- > > w = LinearWarp(ControlSpec(1,2))
-- > > [0,0.5,1].collect{|n| w.map(n)} == [1,1.5,2]
--
-- > map (w_map (warpLinear 1 2)) [0,1/2,1] == [1,3/2,2]
-- > map (warpLinear (-1) 1 W_Map) [0,1/2,1] == [-1,0,1]
warpLinear :: (Fractional a) => a -> a -> Warp a
warpLinear l r d n =
    let z = r - l
    in if d == W_Map
       then n * z + l
       else (n - l) / z

-- | The left and right must both be non zero and have the same sign.
--
-- > > w = ExponentialWarp(ControlSpec(1,2))
-- > > [0,0.5,1].collect{|n| w.map(n)} == [1,pow(2,0.5),2]
--
-- > map (warpExponential 1 2 W_Map) [0,0.5,1] == [1,2 ** 0.5,2]
--
-- > import Sound.SC3.Plot
-- > plotTable1 (map (warpExponential 1 2 W_Map) [0,0.01 .. 1])
warpExponential :: (Floating a) => a -> a -> Warp a
warpExponential l r d n =
    let z = r / l
    in if d == W_Map
       then (z ** n) * l
       else logBase z (n / l)

-- | Cosine warp
--
-- > > w = CosineWarp(ControlSpec(1,2))
-- > > [0,0.25,0.5,0.75,1].collect{|n| w.map(n)}
--
-- > map (warpCosine 1 2 W_Map) [0,0.25,0.5,0.75,1]
--
-- > plotTable1 (map (warpCosine 1 2 W_Map) [0,0.01 .. 1])
warpCosine :: (Floating a) => a -> a -> Warp a
warpCosine l r d n =
    let w = warpLinear 0 (r - l) d
    in if d == W_Map
       then w (0.5 - (cos (pi * n) / 2))
       else acos (1.0 - (w n * 2)) / pi

-- | Sine warp
--
-- > map (warpSine 1 2 W_Map) [0,0.25,0.5,0.75,1]
--
-- > plotTable1 (map (warpSine 1 2 W_Map) [0,0.01 .. 1])
warpSine :: (Floating a) => a -> a -> Warp a
warpSine l r d n =
    let w = warpLinear 0 (r - l) d
    in if d == W_Map
       then w (sin (pi * 0.5 * n))
       else asin (w n) / (pi / 2)

-- | Fader warp.  Left and right values are ordinarily zero and one.
--
-- > map (warpFader 0 1 W_Map) [0,0.5,1] == [0,0.25,1]
--
-- > plotTable1 (map (warpFader 0 1 W_Map) [0,0.01 .. 1])
-- > plotTable1 (map (warpFader 0 2 W_Map) [0,0.01 .. 1])
warpFader :: Floating a => a -> a -> Warp a
warpFader l r d n =
    let n' = if d == W_Map then n * n else sqrt n
    in warpLinear l r d n'

-- | DB fader warp. Left and right values are ordinarily negative
-- infinity and zero.  An input of @0@ gives @-180@.
--
-- > map (round . warpDbFader W_Map) [0,0.5,1] == [-180,-12,0]
--
-- > plotTable1 (map (warpDbFader (-60) 0 W_Map) [0,0.01 .. 1])
-- > plotTable1 (map (warpDbFader 0 60 W_Unmap) [0 .. 60])
warpDbFader :: (TernaryOp a,Eq a,Floating a) => a -> a -> Warp a
warpDbFader l r d n =
    if d == W_Map
    then let n' = if n == 0 then -180 else ampdb (n * n)
         in linlin n' (-180) 0 l r
    else sqrt (dbamp (linlin n l r (-180) 0))

-- | A curve warp given by a real /n/.
--
-- > w_map (warpCurve (-3) 1 2) 0.25 == 1.5552791692202022
-- > w_map (warpCurve (-3) 1 2) 0.50 == 1.8175744761936437
--
-- > plotTable1 (map (warpCurve (-3) 1 2 W_Map) [0,0.01 .. 1])
-- > plotTable1 (map (warpCurve 9 1 2 W_Map) [0,0.01 .. 1])
warpCurve :: (Ord a,Floating a) => a -> a -> a -> Warp a
warpCurve k l r d n =
    let e = exp k
        a = (r - l) / (1 - e)
        b = l + a
    in if abs k < 0.001
       then warpLinear l r d n
       else if d == W_Map
            then b - ((e ** n) * a)
            else log ((b - n) / a) / k

-- | Select warp function by name.  Numerical names are interpreted as
-- /curve/ values for 'warpCurve'.
--
-- > let Just w = warpNamed "lin"
-- > let Just w = warpNamed "-3"
-- > let Just w = warpNamed "6"
-- > plotTable1 (map (w 1 2 W_Map) [0,0.01 .. 1])
warpNamed :: (TernaryOp a,Ord a,Eq a,RealFrac a,Floating a) =>
             String -> Maybe (a -> a -> Warp a)
warpNamed nm =
    case nm of
      "lin" -> Just warpLinear
      "exp" -> Just warpExponential
      "sin" -> Just warpSine
      "cos" -> Just warpCosine
      "amp" -> Just warpFader
      "db" -> Just warpDbFader
      _ -> case readSigned readFloat nm of
             [(c,"")] -> Just (warpCurve c)
             _ -> Nothing