In [1]:
import Data.Complex
j = 0 :+ 1
exp (pi * j)
(-1.0) :+ 1.2246467991473532e-16
In [2]:
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Monoid (Sum(Sum), getSum)
import Control.Lens.Indexed (itraverse,imap,TraversableWithIndex)
import Control.Lens
import Data.Functor.Identity (Identity(Identity, runIdentity))
import Graphics.Plotly qualified as PT
import WGL.IHaskell qualified as WGL
import Data.Ratio (numerator,(%))
dftFactor :: (RealFloat a) => Int -> Int -> Int -> Complex a
dftFactor m k n =
let m' = fromIntegral m
n' = fromIntegral n
k' = fromIntegral k
in exp ((-2)*pi*j*k'*n'/m')
dftFactor 256 10 3
oneSft m k n x = x * dftFactor m k n
:t itraverse (\n x -> Identity $ oneSft 0 0 n x) ([] :: [Complex Double])
oneSum :: (TraversableWithIndex Int f) => f (Complex Double) -> Int -> Int -> Complex Double
oneSum dataIn m k = getSum . foldMap Sum . runIdentity $ itraverse (\n x -> Identity $ oneSft m k n x) dataIn
-- slow fourrier transform
sft :: [Complex Double] -> [Complex Double]
sft dataIn = let m = length dataIn in imap (\k _x -> oneSum dataIn m k) dataIn
genSinTestData :: Double -> Double -> Double -> Double -> Int -> [Double]
genSinTestData freq amp phase rate n = take n $ [ amp * sin (2*pi*freq*(fromIntegral t / rate) - phase) | t <- [0..]]
testFreq = 1e6
testAmp = 1
testPhase = 0
testSampleRate = 25e6 -- Hz
testSize = 2048
testData1 = genSinTestData testFreq testAmp testPhase testSampleRate testSize
testData2 = genSinTestData (2*testFreq) (testAmp/2) testPhase testSampleRate testSize
testData = zipWith (+) testData1 testData2
sftResult = fftShift $ sft $ map (\r -> r :+ 0) testData
simpleLinePlot xs ys = let points = zip xs ys in PT.plotly "simple" [PT.line (PT.aes & PT.x .~ fst & PT.y .~ snd) points]
simplerLinePlot = simpleLinePlot [0..]
fftShift :: [a] -> [a]
fftShift xs =
let nSplit = numerator (length xs % 2)
posFreqs = take nSplit xs
negFreqs = drop nSplit xs
in negFreqs <> posFreqs
testDataPlot = simplerLinePlot testData
testFTPlot =
let ys = map magnitude sftResult
xs = [-testSampleRate/2 + i*testSampleRate/testSize | i <- [0..testSize-1]]
in simpleLinePlot xs ys
WGL.displayPlotly testDataPlot
WGL.displayPlotly testFTPlot
0.7409511253549591 :+ (-0.6715589548470183)
itraverse (\n x -> Identity $ oneSft 0 0 n x) ([] :: [Complex Double]) :: Identity [Complex Double]
In [3]:
butterfly :: (Num a) => [a] -> [a] -> [a] -> ([a], [a])
butterfly w x0 x1 = let m = zipWith (*) w x1 in (zipWith (+) x0 m, zipWith (-) x0 m)
everyOther [] = []
everyOther [x] = [x]
everyOther (x:_:xs) = x : everyOther xs
fft [x] = [x]
fft samples =
let nSamples = length samples
halfNSamples = numerator (nSamples % 2)
ws = map (dftFactor nSamples 1) [0..halfNSamples-1]
xEven = everyOther samples
xOdd = everyOther (drop 1 samples)
(lo,hi) = butterfly ws (fft xEven) (fft xOdd)
in lo <> hi
fftResult = fft $ map (:+ 0) testData
plotRawFFT fr =
let ys = map magnitude $ fftShift fr
xs = [-(testSampleRate/2) + i*testSampleRate/testSize | i <- [0..testSize-1]]
in simpleLinePlot xs ys
plotRawFFTdB = plotRawFFT . map (\x -> 10 * logBase 10 x)
fftPlot = plotRawFFTdB fftResult
WGL.displayPlotly fftPlot
-- Should be very small
maximum $ map magnitude $ zipWith (-) (fftShift fftResult) sftResult
2.4377009118881024e-10
Hann Window¶
$$ \dfrac{1}{2}(1 + \cos\dfrac{2\pi n}{M}) $$ $$ \dfrac{1}{2}(1 + \cos\dfrac{2\pi (k - \dfrac{M}{2}}{M} $$
In [4]:
hannWindow samples =
let m = length samples
halfM = numerator (m % 2)
hann i = let n = i - halfM in (1 + cos (2 * pi * fromIntegral n / fromIntegral m)) / 2
in imap (\i x -> x * hann i) samples
realComplex = map (:+ 0)
-- hannWindow $ replicate 128 1
windowedResult = fft $ realComplex $ hannWindow testData
WGL.displayPlotly $ plotRawFFTdB windowedResult
In [ ]: