-- | This module contains some functions and datatypes for envelopes.
--   An envelope in generall is a kind of controll signal that modulates 
--   (for example) the volume of a sound in a non-periodic way (getting
--   loud at the beginning and fading out at the end...). It is
--   also used to controll the duration of a sound, since (here) envelopes
--   always produce finite signals.
module Sound.Hommage.Envelope
 ( 

 -- | An envelope is represented by a function of type 'Env'. 
 --   Such functions (or instances of class 'IsEnv') can be combined
 --   into a sequence where every 'Env' is given a 'EnvLength' (using the
 --   binary operator '<?>'.
 --   The whole sequence can be turnde into an 'Env' (and be played then)
 --   with 'runEnv'.

 -- * Env
   Env
 , runEnv
 , (<?>) 
 , IsEnv (..)
 -- * EnvLength
 , calculateEnvLengths 
 , EnvLength (..)

 -- * ADSR
 , ADSR 
 , EnvShape (..)
 , EnvMode (..)
 , playADSR 

 -- | Here is the code of 'playADSR' to show the use of 'Env', '<?>' and 'EnvLength':
 -- 
 -- > playADSR :: EnvMode -> EnvShape -> ADSR -> Int -> [Double]
 -- > playADSR mode shape (a,d,s,r) = case mode of
 -- >  FitADR -> runEnv
 -- >            [ Interpolate shape (0.0,1.0) <?> Flex (fromIntegral a)
 -- >            , Interpolate shape (1.0, s) <?> Flex (fromIntegral d)
 -- >            , Interpolate shape (s, 0.0) <?> Flex (fromIntegral r)
 -- >            ]
 -- >  FitS   -> runEnv
 -- >            [ Interpolate shape (0.0,1.0) <?> Abs a
 -- >            , Interpolate shape (1.0, s) <?> Abs d
 -- >            , Constant s <?> Flex 1.0
 -- >            , Interpolate shape (s, 0.0) <?> Abs r
 -- >            ]
 -- >  HoldS  -> runEnv
 -- >            [ Interpolate shape (0.0,1.0) <?> Abs a
 -- >            , Interpolate shape (1.0, s) <?> Abs d
 -- >            , Constant s <?> Flex 1.0
 -- >            , Interpolate shape (s, 0.0) <?> Abs_ r
 -- >            ]

 -- * Interpolate
 , Interpolate (..)
 , Constant (..)
 , interpolate
 , interpolate_cos


 )
 where

import Data.Ratio

---------------------------------------------------------------------------------------------------
-- | 'EnvLength' represents the length of a segment of an Envelope. 
data EnvLength = Abs Int            -- ^ A fixed length.
               | Abs_ Int           -- ^ A fixed length that is not subtracted from the total time.
               | Flex Double        -- ^ A flexible length. Resuming length is distributed to all 
                                    --   flexible lengths proportionally to its value.
               | Rel (Ratio Int)    -- ^ A length relative to the total length.
               | Rel_ (Ratio Int)   -- ^ A relative length that is not subtracted from the total time.

-- | Takes an absolute total length and a list of EnvLengths. Each 'EnvLength' is mapped
--   to its length with respect to the total length and a resuming length that is
--   the result of @ total length - (all fixed lengths + all relative lengths) @. 
--   This resuming lengths is distributed to the flexible lengths.
calculateEnvLengths :: Int -> [EnvLength] -> [Int]
calculateEnvLengths n eds = map lengthE normed_eds
 where
  fixlen = sum ( eds >>= \ed -> case ed of { Abs i -> [i]; _ -> [] } )
  rellen = round (fromIntegral n * sum (eds >>= \ed -> case ed of { Rel r -> [r]; _ -> [] } ))
  len = max 0 (n - (fixlen + rellen))
  flexsum = sum (eds >>= \ed -> case ed of { Flex d -> [abs d]; _ -> [] })
  normed_eds = if flexsum == 0.0 then eds else 
               map (\e -> case e of { Flex d -> Flex (d / flexsum); a -> a }) eds
  lengthE :: EnvLength -> Int
  lengthE (Abs i)     = i
  lengthE (Abs_ i)    = i
  lengthE (Flex d)  = round (d * fromIntegral len)
  lengthE (Rel r)  = round (r * fromIntegral n)
  lengthE (Rel_ r) = round (r * fromIntegral n)
---------------------------------------------------------------------------------------------------
type Env = Int -> [Double]

runEnv :: [(Env, EnvLength)] -> Env
runEnv eps n = concat $ zipWith ($) (map fst eps) $ calculateEnvLengths n $ map snd eps
---------------------------------------------------------------------------------------------------
class IsEnv a where
 toEnv :: a -> Env

instance IsEnv Env where
 toEnv = id

(<?>) :: IsEnv a => a -> EnvLength -> (Env, EnvLength)
a <?> l = (toEnv a, l)
---------------------------------------------------------------------------------------------------
-- | A linear or a cosinus-like shape
data EnvShape = Linear | CosLike
 deriving (Eq, Read, Show)

data Interpolate = Interpolate EnvShape (Double, Double)

instance IsEnv Interpolate where
 toEnv (Interpolate Linear v) = interpolate v
 toEnv (Interpolate CosLike v) = interpolate_cos v

-- | produces a line with given length that starts with fst value and ends with snd value
interpolate :: Fractional a => (a, a) -> Int -> [a]
interpolate (i,e) len = 
 let d = (e - i) / fromIntegral len 
 in take len (iterate (+d) i) 

-- | produces a curve with given length that starts with fst value and ends with snd value. 
--   this curve has the shape of a half cosinus curve (values for 0 to PI).
interpolate_cos :: Floating a => (a, a) -> Int -> [a]
interpolate_cos (i,e) len = map fun [0 .. len-1]
 where
     dlen = fromIntegral len
     diff = e - i
     d   = diff * 0.5
     fak = pi / dlen
     fun x = i + d * (1.0 - cos (fak * fromIntegral x))
---------------------------------------------------------------------------------------------------
data Constant = Constant Double

instance IsEnv Constant where
 toEnv (Constant d) = flip replicate d
---------------------------------------------------------------------------------------------------
-- | The four components of 'ADSR' are:
--
--   * Attack (time to reach value 1.0, starting from 0.0)
--
--   * Decay (time to reach sustain level)
--
--   * Sustain (level to hold until note is released, should be a value between 0.0 and 1.0)
--
--   * Release (time to reach value 0.0 after note is released)
--
-- time is measured in sample points, 44100 is one second.
type ADSR = (Int,Int,Double,Int)
---------------------------------------------------------------------------------------------------
data EnvMode = HoldS   -- ^ Sustain value is kept until duration is over, Release part is appended.
             | FitS    -- ^ Envelope has given duration by fitting only duration of constant Sustain level.
             | FitADR  -- ^ Attack, Decay and Released are together stretched to given duration.
 deriving (Eq, Read, Show)

-- | Playing an ADSR
playADSR :: EnvMode -> EnvShape -> ADSR -> Int -> [Double]
playADSR mode shape (a,d,s,r) = case mode of
 FitADR -> runEnv
           [ Interpolate shape (0.0,1.0) <?> Flex (fromIntegral a)
           , Interpolate shape (1.0, s) <?> Flex (fromIntegral d)
           , Interpolate shape (s, 0.0) <?> Flex (fromIntegral r)
           ]
 FitS   -> runEnv
           [ Interpolate shape (0.0,1.0) <?> Abs a
           , Interpolate shape (1.0, s) <?> Abs d
           , Constant s <?> Flex 1.0
           , Interpolate shape (s, 0.0) <?> Abs r
           ]
 HoldS  -> runEnv
           [ Interpolate shape (0.0,1.0) <?> Abs a
           , Interpolate shape (1.0, s) <?> Abs d
           , Constant s <?> Flex 1.0
           , Interpolate shape (s, 0.0) <?> Abs_ r
           ]