```-----------------------------------------------------------------------------
-- |
-- Module      :  DSP.Filter.FIR.FIR
--
-- Stability   :  experimental
-- Portability :  portable
--
-- Finite Impuse Response filtering functions
--
-----------------------------------------------------------------------------

module DSP.Filter.FIR.FIR (fir, test) where

import Data.Array

-- | Implements the following function, which is a FIR filter
--
-- @y[n] = sum(k=0,M) h[k]*x[n-k]@
--
-- We implement the fir function with five helper functions, depending on
-- the type of the filter.  In the following functions, we use the O&S
-- convention that m is the order of the filter, which is equal to the
-- number of taps minus one.

{-# specialize fir :: Array Int Float ->  [Float]  -> [Float]  #-}
{-# specialize fir :: Array Int Double -> [Double] -> [Double] #-}

fir :: Num a => Array Int a -- ^ h[n]
-> [a] -- ^ x[n]
-> [a] -- ^ y[n]

fir _ [] = []
fir h (x:xs) | isFIRType1 h = fir'1 h w xs
| isFIRType2 h = fir'2 h w xs
| isFIRType3 h = fir'3 h w xs
| isFIRType4 h = fir'4 h w xs
| otherwise    = fir'0 h w xs
where w = listArray (0,m) \$ x : replicate m 0
m = snd \$ bounds h

-- This is for testing the symetric helpers.

fir0 :: Num a => Array Int a -> [a] -> [a]
fir0 _ []     = []
fir0 h (x:xs) = fir'0 h w xs
where w = listArray (0,m) \$ x : replicate m 0
m = snd \$ bounds h

-- Asymetric FIR

{-# specialize fir'0 :: Array Int Float ->  Array Int Float ->  [Float]  -> [Float]  #-}
{-# specialize fir'0 :: Array Int Double -> Array Int Double -> [Double] -> [Double] #-}

fir'0 :: Num a => Array Int a -> Array Int a -> [a] -> [a]
fir'0 h w []     = y : []
where y  = sum [ h!i * w!i | i <- [0..m] ]
m  = snd \$ bounds h
fir'0 h w (x:xs) = y : fir'0 h w' xs
where y  = sum [ h!i * w!i | i <- [0..m] ]
w' = listArray (0,m) \$ x : elems w
m  = snd \$ bounds h

-- Type 1: symetric FIR, even order / odd length

{-# specialize fir'1 :: Array Int Float ->  Array Int Float ->  [Float]  -> [Float]  #-}
{-# specialize fir'1 :: Array Int Double -> Array Int Double -> [Double] -> [Double] #-}

fir'1 :: Num a => Array Int a -> Array Int a -> [a] -> [a]
fir'1 h w []     = y : []
where y  = h!m2 * w!m2 + sum [ h!i * (w!i + w!(m-i)) | i <- [0..m2-1] ]
m  = snd \$ bounds h
m2 = m `div` 2
fir'1 h w (x:xs) = y : fir'1 h w' xs
where y  = h!m2 * w!m2 + sum [ h!i * (w!i + w!(m-i)) | i <- [0..m2-1] ]
w' = listArray (0,m) \$ x : elems w
m  = snd \$ bounds h
m2 = m `div` 2

-- Type 2: symetric FIR, odd order / even length

{-# specialize fir'2 :: Array Int Float ->  Array Int Float ->  [Float]  -> [Float]  #-}
{-# specialize fir'2 :: Array Int Double -> Array Int Double -> [Double] -> [Double] #-}

fir'2 :: Num a => Array Int a -> Array Int a -> [a] -> [a]
fir'2 h w []     = y : []
where y  = sum [ h!i * (w!i + w!(m-i)) | i <- [0..m2] ]
m  = snd \$ bounds h
m2 = m `div` 2
fir'2 h w (x:xs) = y : fir'2 h w' xs
where y  = sum [ h!i * (w!i + w!(m-i)) | i <- [0..m2] ]
w' = listArray (0,m) \$ x : elems w
m  = snd \$ bounds h
m2 = m `div` 2

-- Type 3: anti-symetric FIR, even order / odd length

{-# specialize fir'3 :: Array Int Float ->  Array Int Float ->  [Float]  -> [Float]  #-}
{-# specialize fir'3 :: Array Int Double -> Array Int Double -> [Double] -> [Double] #-}

fir'3 :: Num a => Array Int a -> Array Int a -> [a] -> [a]
fir'3 h w []     = y : []
where y  = h!m2 * w!m2 + sum [ h!i * (w!i - w!(m-i)) | i <- [0..m2-1] ]
m  = snd \$ bounds h
m2 = m `div` 2
fir'3 h w (x:xs) = y : fir'3 h w' xs
where y  = h!m2 * w!m2 + sum [ h!i * (w!i - w!(m-i)) | i <- [0..m2-1] ]
w' = listArray (0,m) \$ x : elems w
m  = snd \$ bounds h
m2 = m `div` 2

-- Type 4: anti-symetric FIR, off order / even length

{-# specialize fir'4 :: Array Int Float ->  Array Int Float ->  [Float]  -> [Float]  #-}
{-# specialize fir'4 :: Array Int Double -> Array Int Double -> [Double] -> [Double] #-}

fir'4 :: Num a => Array Int a -> Array Int a -> [a] -> [a]
fir'4 h w []     = y : []
where y  = sum [ h!i * (w!i - w!(m-i)) | i <- [0..m2] ]
m  = snd \$ bounds h
m2 = m `div` 2
fir'4 h w (x:xs) = y : fir'4 h w' xs
where y  = sum [ h!i * (w!i - w!(m-i)) | i <- [0..m2] ]
w' = listArray (0,m) \$ x : elems w
m  = snd \$ bounds h
m2 = m `div` 2

-- Aux functions.  Note that the tap numbers go from [0..m], so if m is
-- even, then the filter has odd length, and vice versa.

{-# specialize isFIRType1 :: Array Int Float ->  Bool  #-}
{-# specialize isFIRType1 :: Array Int Double -> Bool #-}

isFIRType1  :: Num a => Array Int a -> Bool
isFIRType1 h = even m && (h' == (reverse h'))
where m = snd \$ bounds h
h' = elems h

{-# specialize isFIRType2 :: Array Int Float ->  Bool  #-}
{-# specialize isFIRType2 :: Array Int Double -> Bool #-}

isFIRType2  :: Num a => Array Int a -> Bool
isFIRType2 h = odd m && (h' == (reverse h'))
where m = snd \$ bounds h
h' = elems h

{-# specialize isFIRType3 :: Array Int Float ->  Bool  #-}
{-# specialize isFIRType3 :: Array Int Double -> Bool #-}

isFIRType3  :: Num a => Array Int a -> Bool
isFIRType3 h = even m && ha == reverse hb
where m = snd \$ bounds h
h' = elems h
ha = take n h'
hb = map negate (drop (n+1) h')
n = m `div` 2

{-# specialize isFIRType4 :: Array Int Float ->  Bool #-}
{-# specialize isFIRType4 :: Array Int Double -> Bool #-}

isFIRType4  :: Num a => Array Int a -> Bool
isFIRType4 h = odd m && ha == reverse hb
where m = snd \$ bounds h
ha = elems h
hb = fmap negate \$ ha

-- Test routines

-- This tests out fir'0

ht :: Array Int Double
ht = listArray (0,4) [ 1, 2, 0, -1, 1 ]

xt :: [Double]
xt = [1, 3, -1, -2, 0, 0, 0, 0 ]

yt :: [Double]
yt = [1, 5, 5, -5, -6, 4, 1, -2]

yt' :: [Double]
yt' = fir ht xt

-- This checks the symetric routines against fir'0

h1 :: Array Int Double
h1 = listArray (0,4) [ 1, 2, 3, 2, 1 ]
h2 :: Array Int Double
h2 = listArray (0,5) [ 1, 2, 3, 3, 2, 1 ]
h3 :: Array Int Double
h3 = listArray (0,4) [ 1, 2, 3, -2, -1 ]
h4 :: Array Int Double
h4 = listArray (0,5) [ 1, 2, 3, -3, -2, -1 ]

y1, y2, y3, y4 :: [Double]
y1 = fir0 h1 xt
y2 = fir0 h2 xt
y3 = fir0 h3 xt
y4 = fir0 h4 xt

y1', y2', y3', y4' :: [Double]
y1' = fir h1 xt
y2' = fir h2 xt
y3' = fir h3 xt
y4' = fir h4 xt

-- If everything works, then test == True

test :: Bool
test = and [ yt == yt', y1 == y1', y2 == y2', y3 == y3', y4 == y4' ]
```