```-- | @sclang@ math functions.
module Sound.SC3.Lang.Math where

import Data.Bits {- base -}

-- * SimpleNumber

-- | @SimpleNumber.ampdb@ converts linear amplitude to decibels.
--
-- > > [1,0.5,0.25,0.13,6e-2].collect({|i| i.ampdb.round}) == [0,-6,-12,-18,-24]
-- > map (round . ampdb) [1,0.5,0.25,0.13,6e-2] == [0,-6,-12,-18,-24]
--
-- > > [1,0.7,0.5,0.35,0.25].collect({|i| i.ampdb.round}) == [0,-3,-6,-9,-12]
-- > map (round . ampdb) [1,0.7,0.5,0.35,0.25] == [0,-3,-6,-9,-12]
ampdb :: Floating a => a -> a
ampdb = (* 20) . log10

-- | @SimpleNumber.dbamp@ converts decibels to a linear amplitude.
--
-- > > [0,-3,-6,-9,-12].collect({|i| (i.dbamp * 100).floor}) == [100,70,50,35,25]
-- > map (floor . (* 100) . dbamp) [0,-3,-6,-9,-12] == [100,70,50,35,25]
dbamp :: Floating a => a -> a
dbamp = (10 **) .  (* 0.05)

-- | @SimpleNumber.degreeToKey@ translates degree, scale and steps per
-- octave to key.
--
-- > > (0..5).collect({|i| i.degreeToKey([0,1,5,9,11],12)}) == [0,1,5,9,11,12]
-- > map (degreeToKey [0,1,5,9,11] 12) [0..5] == [0,1,5,9,11,12]
--
-- > degreeToKey [0,2,4,5,7,9,11] 12 5 == 9
degreeToKey :: (RealFrac a) => [a] -> a -> a -> a
degreeToKey s n d =
let l = length s
d' = round d
a = (d - fromIntegral d') * 10.0 * (n / 12.0)
in (n * fromIntegral (d' `div` l)) + (s !! (d' `mod` l)) + a

-- | Psuedo-inifite bounded value.
--
-- > inf == maxBound
inf :: Bounded a => a
inf = maxBound

-- | Predicate for 'inf'.
--
-- > isInf inf == True
isInf :: (Eq a,Bounded a) => a -> Bool
isInf = (== inf)

-- | @SimpleNumber.linexp@ shifts from linear to exponential ranges.
--
-- > > [1,1.5,2].collect({|i| i.linexp(1,2,10,100).floor}) == [10,31,100]
-- > map (floor . linexp 1 2 10 100) [1,1.5,2] == [10,31,100]
linexp :: (Ord a, Floating a) => a -> a -> a -> a -> a -> a
linexp l r l' r' n =
if n <= l
then l'
else if n >= r
then r'
else ((r'/l') ** ((n-l)/(r-l))) * l'

-- | @SimpleNumber.log10@ is the base 10 logarithm.
log10 :: Floating a => a -> a
log10 = logBase 10

-- | @SimpleNumber.midicps@ translates from midi note number to cycles
-- per second.
--
-- > > [57,69].collect({|i| i.midicps}) == [220,440]
-- > map midicps [57,69] == [220,440]
midicps :: (Floating a) => a -> a
midicps a = 440.0 * (2.0 ** ((a - 69.0) * (1.0 / 12.0)))

-- * UGen

-- | @UGen.exprand@ shifts a linear (0,1) value to an exponential
-- range.
--
-- > map (floor . exprange 10 100) [0,0.5,1] == [10,31,100]
exprange :: (Floating b) => b -> b -> b -> b
exprange l r i = l * exp (log (r / l) * i)

-- * Binary

-- | @0@ is false, @1@ is True, else error.
--
-- > map bitChar "01" == [False,True]
bitChar :: Char -> Bool
bitChar c =
case c of
'0' -> False
'1' -> True
_ -> error "bitChar"

-- | Parse a sequence of 0 and 1 characters as a BE bit sequence
--
-- > parseBits "101" == 5
-- > parseBits "00001111" == 15
parseBits :: (Num a,Bits a) => String -> a
parseBits x =
let x' = filter (id . bitChar . snd) (zip [0..] (reverse x))
in foldr ((.|.) . bit . fst) 0 x'
```