{-# LANGUAGE RebindableSyntax #-} module Fourier where import qualified Math.FFT as FFT import qualified Synthesizer.Generic.Analysis as Ana import qualified Data.StorableVector as SV import qualified Data.StorableVector.CArray as SVCArr import qualified Data.Array.CArray as CArray import qualified Data.Array.IArray as IArray import Data.Array.CArray (CArray) import Data.Array.IArray ((//), (!)) import Data.Tuple.HT (mapFst) import Foreign.Storable (Storable) import Control.Monad (liftM2) import qualified Data.Complex as Complex import qualified Algebra.IntegralDomain as Integral import qualified Algebra.Additive as Additive import qualified Algebra.Ring as Ring import NumericPrelude import Prelude () -- see synthesizer-core:Synthesizer.Basic.NumberTheory {- | It's not awfully efficient, but ok for our uses. -} ceilingPower :: (Integral.C a, Ord a) => a -> a -> a ceilingPower base n = base ^ fromIntegral (ceilingLog base n) ceilingLog :: (Integral.C a, Ord a) => a -> a -> Int ceilingLog base = length . takeWhile (>0) . iterate (flip div base) . subtract 1 {- | It must hold @transformSize > chunkSize@. -} layoutBlocks :: Int -> Int -> SV.Vector Float -> CArray (Int, Int) Float layoutBlocks transformSize chunkSize xs = let numChunks = Integral.divUp (SV.length xs) chunkSize in CArray.listArray ((0,0), (numChunks-1, transformSize-1)) (repeat 0) // zip (liftM2 (,) [0..] (take chunkSize [0..])) (SV.unpack xs) mul1d2d :: (Ring.C a, Storable a) => CArray Int a -> CArray (Int,Int) a -> CArray (Int,Int) a mul1d2d xs ys = IArray.array (IArray.bounds ys) $ map (\((i,j),y) -> ((i,j), xs!j * y)) $ IArray.assocs ys permuteClip :: (IArray.Ix i, IArray.Ix j, Additive.C a, Storable a) => (i -> j) -> (j,j) -> CArray i a -> CArray j a permuteClip f bnds xs = IArray.accumArray (+) zero bnds $ filter (IArray.inRange bnds . fst) $ map (mapFst f) $ IArray.assocs xs sizes :: Int -> (Int, Int) sizes len = let transformSize = ceilingPower 2 $ 2 * len chunkSize = transformSize - len + 1 in (transformSize, chunkSize) padRight :: Int -> SV.Vector Float -> SV.Vector Float padRight size xs = SV.append xs $ SV.replicate (size - SV.length xs) 0 convolve :: SV.Vector Float -> SV.Vector Float -> SV.Vector Float convolve xs ys = let (transformSize, chunkSize) = sizes $ SV.length xs dims = [1] in SVCArr.from $ permuteClip (\(i,j) -> i*chunkSize+j) (0, SV.length xs + SV.length ys - 2) $ FFT.dftCRN dims $ mul1d2d (FFT.dftRC $ SVCArr.to $ padRight transformSize xs) (FFT.dftRCN dims $ layoutBlocks transformSize chunkSize ys) correlate :: SV.Vector Float -> SV.Vector Float -> SV.Vector Float correlate xs ys = let (transformSize, chunkSize) = sizes $ SV.length xs dims = [1] in SVCArr.from $ permuteClip (\(i,j) -> i*chunkSize+j - SV.length xs + 1) (0, SV.length ys - 1) $ FFT.dftCRN dims $ mul1d2d (FFT.dftRC $ SVCArr.to $ padRight transformSize $ SV.reverse xs) (FFT.dftRCN dims $ layoutBlocks transformSize chunkSize ys) pillow :: Int -> CArray Int Float pillow n = CArray.listArray (0, n-1) $ map (\i -> sin (pi * fromIntegral i / fromIntegral n)) [(0::Int) .. ] {- | The array is not padded. Instead we choose the longest array that only contains valid data. -} layoutOverlapBlocks :: Int -> Int -> SV.Vector Float -> CArray (Int, Int) Float layoutOverlapBlocks shift chunkSize xs = let lastChunk = Integral.div (SV.length xs - chunkSize) shift bnds = ((0,0), (lastChunk, chunkSize-1)) in CArray.array bnds $ flip map (IArray.range bnds) $ \(i,j) -> ((i,j), SV.index xs $ i*shift + j) absoluteBlockSpectra :: Int -> Int -> SV.Vector Float -> CArray (Int, Int) Float absoluteBlockSpectra shift chunkSize xs = CArray.amap Complex.magnitude $ FFT.dftRCN [1] $ mul1d2d (pillow chunkSize) $ layoutOverlapBlocks shift chunkSize xs slice :: CArray (Int, Int) Float -> [SV.Vector Float] slice arr = let ((firstChunk, firstElem), (lastChunk, lastElem)) = IArray.bounds arr in map (\i -> SV.sample (IArray.rangeSize (firstElem, lastElem)) $ \j -> arr ! (i, firstElem+j)) $ IArray.range (firstChunk, lastChunk) spectralFlatness :: SV.Vector Float -> Float spectralFlatness xs = 2 ** Ana.average (SV.map (logBase 2) xs) / Ana.average xs