```-- | Creating Function Tables (Buffers)
module Csound.Tab (
-- | If you are not familliar with Csound's conventions
-- you are pobably not aware of the fact that for efficiency reasons Csound requires that table size is equal
-- to power of 2 or power of two plus one which stands for guard point (you do need guard point if your intention is to read the
-- table once but you don't need the guard point if you read the table in many cycles, then the guard point is the the first point of your table).
Tab,

-- * Fill table with numbers
doubles,

-- * (In)Harmonic series
PartialStrength, PartialNumber, PartialPhase, PartialDC,
sines, sines3, sines4, buzzes,

-- * Interpolants
-- | All funtions have the same shape of arguments:
--
-- > fun [a, n1, b, n2, c, ...]
--
-- where
--
-- * a, b, c .. - are ordinate values
--
-- * n1, n2 .. - are lengths of the segments relative to the total number of the points in the table
--
-- Csounders, Heads up! all segment lengths are relative to the total sum of the segments.
-- You don't need to make the sum equal to the number of points in the table. Segment's lengths will be resized
-- automatically. For example if we want to define a curve that rises to 1 over 25\% of the table and then falls down to zero
-- we can define it like this:
--
-- > segs [0, 0.25, 1, 0.75, 0]
--
-- or
--
-- > segs [0, 25, 1, 75, 0]
--
-- or
--
-- > segs [0, 1, 1, 3, 0]
--
-- all these expressions are equivalent.
consts, segs, cubes, exps, splines,
-- ** Equally spaced interpolants
econsts, esegs, ecubes, eexps, esplines,

-- * Polynomials
polys, chebs1, chebs2,

--

-- * Low level Csound definition.
gen,

-- * Modify tables
skipNorm, setSize, setDegree, guardPoint, gp,

-- ** Handy shortcuts
-- | handy shortcuts for the function 'setDegree'.
lllofi, llofi, lofi, midfi, hifi, hhifi, hhhifi
) where

import Data.Default
import Csound.Exp
import Csound.Exp.Wrapper(updateTabSize)

interp id as = Tab def id (ArgsRelative as)
plains id as = Tab def id (ArgsPlain as)

insertOnes :: [Double] -> [Double]
insertOnes as = case as of
[] -> []
a:[] -> [a]
a:as -> a : 1 : insertOnes as

tableSizes :: [Int]
tableSizes = [res | a <- twos, b <- twos1, res <- [a, b]]
where twos  = fmap (2 ^) [0 .. ]
twos1 = fmap ( +1) twos

findTableSize :: Int -> Int
findTableSize n = head \$ dropWhile (< n) tableSizes

-- loadFile :: Int -> String -> Double -> Tab

-- | Table contains all provided values
-- (table is extended to contain all values and to be of the power of 2 or the power of two plus one).
doubles :: [Double] -> Tab
doubles as = setSize (findTableSize n) \$ plains 2 as
where n = length as

-- | Segments of the exponential curves.
--
-- > exps [a, n1, b, n2, c, ...]
--
-- where
--
-- * @a, b, c, ...@ are ordinate values
--
-- * @n1, n2, ...@  are lengths of the segments relative to the total number of the points in the table
exps :: [Double] -> Tab
exps = interp 5

-- | Equally spaced segments of exponential curves.
--
-- > eexps [a, b, c, ...]
--
-- is the same as
--
-- > exps [a, 1, b, 1, c, ...]
eexps :: [Double] -> Tab
eexps = exps . insertOnes

-- | Segments of cubic polynomials.
--
-- > cubes [a, n1, b, n2, c, ...]
--
-- where
--
-- * a, b, c .. - are ordinate values
--
-- * @n1, n2, ...@  are lengths of the segments relative to the total number of the points in the table
cubes :: [Double] -> Tab
cubes = interp 6

-- | Equally spaced segments of cubic polynomials.
--
-- > ecubes [a, b, c, ...]
--
-- is the same as
--
-- > cubes [a, 1, b, 1, c, ...]
ecubes :: [Double] -> Tab
ecubes = cubes . insertOnes

-- | Segments of straight lines.
--
-- > segs [a, n1, b, n2, c, ...]
--
-- where
--
-- * a, b, c .. - are ordinate values
--
-- * @n1, n2, ...@  are lengths of the segments relative to the total number of the points in the table
segs :: [Double] -> Tab
segs = interp 7

-- | Equally spaced segments of straight lines.
--
-- > esegs [a, b, c, ...]
--
-- is the same as
--
-- > segs [a, 1, b, 1, c, ...]
esegs :: [Double] -> Tab
esegs = segs . insertOnes

-- | Cubic spline curve.
--
-- > splines [a, n1, b, n2, c, ...]
--
-- where
--
-- * a, b, c .. - are ordinate values
--
-- * @n1, n2, ...@  are lengths of the segments relative to the total number of the points in the table
splines :: [Double] -> Tab
splines = interp 8

-- | Equally spaced spline curve.
--
-- > esplines [a, b, c, ...]
--
-- is the same as
--
-- > splines [a, 1, b, 1, c, ...]
esplines :: [Double] -> Tab
esplines = splines . insertOnes

-- | Constant segments (sample and hold).
--
-- > consts [a, n1, b, n2, c, ...]
--
-- where
--
-- * a, b, c .. - are ordinate values
--
-- * @n1, n2, ...@  are lengths of the segments relative to the total number of the points in the table
consts :: [Double] -> Tab
consts = interp 17

-- | Equally spaced constant segments.
--
-- > econsts [a, b, c, ...]
--
-- is the same as
--
-- > consts [a, 1, b, 1, c, ...]
econsts :: [Double] -> Tab
econsts = consts . insertOnes

type PartialNumber = Double
type PartialStrength = Double
type PartialPhase = Double
type PartialDC = Double

-- | Series of harmonic partials:
--
-- > sine = sines [1]
--
-- > saw = sines \$ fmap (1 / ) [1 .. 10]
--
-- > square = sines \$ fmap (1 / ) [1, 3 .. 11]
--
-- > triangle = sines \$ zipWith (\a b -> a / (b ** 2)) (cycle [1, -1]) [1, 3 .. 11]
sines :: [PartialStrength] -> Tab
sines = plains 10

-- | Specifies series of possibly inharmonic partials.
sines3 :: [(PartialNumber, PartialStrength, PartialPhase)] -> Tab
sines3 xs = plains 9 [a | (pn, str, phs) <- xs, a <- [pn, str, phs]]

-- | Specifies series of possibly inharmonic partials with direct current.
sines4 :: [(PartialNumber, PartialStrength, PartialPhase, PartialDC)] -> Tab
sines4 xs = plains 19 [a | (pn, str, phs, dc) <- xs, a <- [pn, str, phs, dc]]

-- | Generates values similar to the opcode 'Csound.Opcode.Basic.buzz'.
--
-- > buzzes numberOfHarmonics [lowestHarmonic, coefficientOfAttenuation]
--
-- With @buzzes n [l, r]@ you get @n@ harmonics from @l@ that are attenuated by the factor of @r@
-- on each step.
buzzes :: Double -> [Double] -> Tab
buzzes nh opts = plains 11 (nh : take 2 opts)

-- | Modified Bessel function of the second kind, order 0 (for amplitude modulated FM).
--
-- > bessels xint
--
-- the function is defined within the interval @[0, xint]@.
bessels :: Double -> Tab
bessels xint = plains 12 [xint]

-- | Polynomials.
--
-- > polys xl xr [c0, c1, c2, ..]
--
-- where
--
-- * xl, xr - left and right values of the interval over wich polynomial is defined
--
-- * [c0, c1, c2, ...] -- coefficients of the polynomial
--
-- > c0 + c1 * x + c2 * x * x + ...
polys :: Double -> Double -> [Double] -> Tab
polys x0 x1 cs = plains 3 (x0:x1:cs)

-- | Chebyshev polynomials of the first kind.
--
-- > polys xl xr [h0, h1, h2, ..]
--
-- where
--
-- * xl, xr - left and right values of the interval over wich polynomial is defined
--
-- * [h0, h1, h2, ...] -- relative strength of the partials
chebs1 :: Double -> Double -> [Double] -> Tab
chebs1 xint xamp hs = plains 13 (xint : xamp : hs)

-- | Chebyshev polynomials of the second kind.
--
-- > polys xl xr [h0, h1, h2, ..]
--
-- where
--
-- * xl, xr - left and right values of the interval over wich polynomial is defined
--
-- * [h0, h1, h2, ...] -- relative strength of the partials
chebs2 :: Double -> Double -> [Double] -> Tab
chebs2 xint xamp hs = plains 14 (xint : xamp : hs)

-- | Creates a table of doubles (It's f-table in Csound).
-- Arguments are:
--
-- * identificator of the GEN routine
--
-- * GEN routine arguments
--
-- All tables are created at 0 and memory is never released.
gen :: Int -> [Double] -> Tab
gen id args = Tab def id (ArgsPlain args)

-- | Adds guard point to the table size (details of the interpolation schemes: you do need guard point if your intention is to read the
-- table once but you don't need the guard point if you read table in many cycles, the guard point is the the first point of your table).
guardPoint :: Tab -> Tab
guardPoint = updateTabSize \$ \x -> case x of
SizePlain n -> SizePlain \$ plainGuardPoint n
a -> a{ hasGuardPoint = True }
where plainGuardPoint n
| even n    = n + 1
| otherwise = n

-- | Shortcut for 'Csound.Tab.guardPoint'.
gp :: Tab -> Tab
gp = guardPoint

-- | Sets an absolute size value. As you can do it in the Csound files.
setSize :: Int -> Tab -> Tab
setSize n = updateTabSize \$ const (SizePlain n)

-- | Sets the relative size value. You can set the base value in the options
-- (see 'Csound.Base.tabResolution' at 'Csound.Base.CsdOptions', with tabResolution you can easily change table sizes for all your tables).
-- Here zero means the base value. 1 is the base value multiplied by 2, 2 is the base value multiplied by 4
-- and so on. Negative values mean division by the specified degree.
setDegree :: Int -> Tab -> Tab
setDegree degree = updateTabSize \$ \x -> case x of
SizePlain n -> SizePlain n
a -> a{ sizeDegree = degree }

-- | Sets degrees from -3 to 3.
lllofi, llofi, lofi, midfi, hifi, hhifi, hhhifi :: Tab -> Tab

lllofi  = setDegree (-3)
llofi   = setDegree (-2)
lofi    = setDegree (-1)
midfi   = setDegree 0
hifi    = setDegree 1
hhifi   = setDegree 2
hhhifi  = setDegree 3

-- | Skips normalization (sets table size to negative value)
skipNorm :: Tab -> Tab
skipNorm x = case x of
TabExp _ -> error "you can skip normalization only for primitive tables (made with gen-routines)"
primTab  -> primTab{ tabGen = negate \$ abs \$ tabGen primTab }

```