@@ -8,7 +8,6 @@ import React, {
88 useCallback ,
99} from "react" ;
1010import { useTheme } from "next-themes" ;
11- import { BitSelection } from "./DataPass" ;
1211import BandPowerGraph from "./BandPowerGraph" ;
1312import { fft } from "fft-js" ;
1413import { WebglPlot , ColorRGBA , WebglLine } from "webgl-plot" ;
@@ -34,8 +33,7 @@ const FFT = forwardRef(
3433 ) => {
3534 const fftBufferRef = useRef < number [ ] [ ] > ( Array . from ( { length : 16 } , ( ) => [ ] ) ) ;
3635 const [ fftData , setFftData ] = useState < number [ ] [ ] > ( Array . from ( { length : 16 } , ( ) => [ ] ) ) ;
37- const fftSize = currentSamplingRate + 6 * ( currentSamplingRate / 250 ) ;
38- // console.log(fftSize);
36+ const fftSize = Math . pow ( 2 , Math . round ( Math . log2 ( currentSamplingRate / 2 ) ) ) ;
3937 const canvasRef = useRef < HTMLCanvasElement > ( null ) ;
4038 const containerRef = useRef < HTMLDivElement > ( null ) ;
4139 const { theme } = useTheme ( ) ;
@@ -47,43 +45,157 @@ const FFT = forwardRef(
4745 const wglPlotsref = useRef < WebglPlot [ ] > ( [ ] ) ;
4846 const linesRef = useRef < WebglLine [ ] > ( [ ] ) ;
4947 const sweepPositions = useRef < number [ ] > ( new Array ( 6 ) . fill ( 0 ) ) ; // Array for sweep positions
50-
48+ let samplesReceived = 0 ;
49+ class SmoothingFilter {
50+ private bufferSize : number ;
51+ private circularBuffers : number [ ] [ ] ;
52+ private sums : number [ ] ;
53+ private dataIndex : number = 0 ;
54+
55+ constructor ( bufferSize : number = 5 , initialLength : number = 0 ) {
56+ this . bufferSize = bufferSize ;
57+ this . circularBuffers = Array . from ( { length : initialLength } , ( ) =>
58+ new Array ( bufferSize ) . fill ( 0 )
59+ ) ;
60+ this . sums = new Array ( initialLength ) . fill ( 0 ) ;
61+ }
62+
63+ getSmoothedValues ( newValues : number [ ] ) : number [ ] {
64+ // Initialize buffers if first run or size changed
65+ if ( this . circularBuffers . length !== newValues . length ) {
66+ this . circularBuffers = Array . from ( { length : newValues . length } , ( ) =>
67+ new Array ( this . bufferSize ) . fill ( 0 )
68+ ) ;
69+ this . sums = new Array ( newValues . length ) . fill ( 0 ) ;
70+ }
71+
72+ const smoothed = new Array ( newValues . length ) ;
73+
74+ for ( let i = 0 ; i < newValues . length ; i ++ ) {
75+ this . sums [ i ] -= this . circularBuffers [ i ] [ this . dataIndex ] ;
76+ this . sums [ i ] += newValues [ i ] ;
77+ this . circularBuffers [ i ] [ this . dataIndex ] = newValues [ i ] ;
78+ smoothed [ i ] = this . sums [ i ] / this . bufferSize ;
79+ }
80+
81+ this . dataIndex = ( this . dataIndex + 1 ) % this . bufferSize ;
82+ return smoothed ;
83+ }
84+ }
85+ const filter = new SmoothingFilter ( 32 , fftSize / 2 ) ; // 5-point moving average
5186
5287 useImperativeHandle (
5388 ref ,
5489 ( ) => ( {
5590 updateData ( data : number [ ] ) {
5691 for ( let i = 0 ; i < 1 ; i ++ ) {
57-
5892 const sensorValue = data [ i + 1 ] ;
93+ // Add new sample to the buffer
5994 fftBufferRef . current [ i ] . push ( sensorValue ) ;
95+ // Update the plot with the new sensor value
6096 updatePlot ( sensorValue , Zoom ) ;
97+ // Ensure the buffer does not exceed fftSize
98+ if ( fftBufferRef . current [ i ] . length > fftSize ) {
99+ fftBufferRef . current [ i ] . shift ( ) ; // Remove the oldest sample
100+ }
101+ samplesReceived ++ ;
61102
62- if ( fftBufferRef . current [ i ] . length >= fftSize ) {
103+ // Trigger FFT computation every 5 samples
104+ if ( samplesReceived % 25 === 0 ) {
63105 const processedBuffer = fftBufferRef . current [ i ] . slice ( 0 , fftSize ) ; // Ensure exact length
64- const dcRemovedBuffer = removeDCComponent ( processedBuffer ) ;
65- const filteredBuffer = applyHighPassFilter ( dcRemovedBuffer , 0.5 ) ; // 0.5 Hz cutoff
66- const windowedBuffer = applyHannWindow ( filteredBuffer ) ;
67- const complexFFT = fft ( windowedBuffer ) ; // Perform FFT
68- const magnitude = complexFFT . map ( ( [ real , imaginary ] ) =>
69- Math . sqrt ( real ** 2 + imaginary ** 2 )
70- ) ; // Calculate the magnitude
71- // console.log("magnitude", complexFFT);
72- const freqs = Array . from ( { length : fftSize / 2 } , ( _ , i ) => ( i * currentSamplingRate ) / fftSize ) ;
73- // console.log(freqs);
106+ const floatInput = new Float32Array ( processedBuffer ) ;
107+
108+ // const dcRemovedBuffer = removeDCComponent(processedBuffer);
109+ // const filteredBuffer = applyHighPassFilter(dcRemovedBuffer, 0.5); // 0.5 Hz cutoff
110+ // const windowedBuffer = applyHannWindow(filteredBuffer);
111+ // const complexFFT = fft(windowedBuffer); // Perform FFT
112+ // const magnitude = complexFFT.map(([real, imaginary]) =>
113+ // Math.sqrt(real ** 2 + imaginary ** 2)
114+ // );
115+ // Calculate frequencies for the FFT result
116+ const fftMags = fftProcessor . computeMagnitudes ( floatInput ) ;
117+ const magArray = Array . from ( fftMags ) ; // Convert Float32Array to regular array
118+ const smoothedMags = filter . getSmoothedValues ( magArray ) ;
119+ // Update the FFT data state
74120 setFftData ( ( prevData ) => {
75121 const newData = [ ...prevData ] ;
76- newData [ i ] = magnitude . slice ( 0 , fftSize / 2 ) ; // Assign to the corresponding channel
122+ newData [ i ] = smoothedMags ;
77123 return newData ;
78124 } ) ;
79125
80- fftBufferRef . current [ i ] = [ ] ; // Clear buffer after processing
126+ // Clear the buffer after processing (optional, depending on your use case)
127+ // fftBufferRef.current[i] = [];
81128 }
82129 }
83130 } ,
84131 } ) ,
85- [ Zoom , timeBase , canvasCount ]
132+ [ Zoom , timeBase , canvasCount , fftSize , currentSamplingRate ]
86133 ) ;
134+
135+ ////
136+ class FFT {
137+ private size : number ;
138+ private cosTable : Float32Array ;
139+ private sinTable : Float32Array ;
140+
141+ constructor ( size : number ) {
142+ this . size = size ;
143+ this . cosTable = new Float32Array ( size / 2 ) ;
144+ this . sinTable = new Float32Array ( size / 2 ) ;
145+ for ( let i = 0 ; i < size / 2 ; i ++ ) {
146+ this . cosTable [ i ] = Math . cos ( - 2 * Math . PI * i / size ) ;
147+ this . sinTable [ i ] = Math . sin ( - 2 * Math . PI * i / size ) ;
148+ }
149+ }
150+
151+ computeMagnitudes ( input : Float32Array ) : Float32Array {
152+ const real = new Float32Array ( this . size ) ;
153+ const imag = new Float32Array ( this . size ) ;
154+ for ( let i = 0 ; i < input . length && i < this . size ; i ++ ) {
155+ real [ i ] = input [ i ] ;
156+ }
157+ this . fft ( real , imag ) ;
158+ const mags = new Float32Array ( this . size / 2 ) ;
159+ for ( let i = 0 ; i < this . size / 2 ; i ++ ) {
160+ mags [ i ] = Math . sqrt ( real [ i ] * real [ i ] + imag [ i ] * imag [ i ] ) / ( this . size / 2 ) ;
161+ }
162+ return mags ;
163+ }
164+
165+ private fft ( real : Float32Array , imag : Float32Array ) : void {
166+ const n = this . size ;
167+ let j = 0 ;
168+ for ( let i = 0 ; i < n - 1 ; i ++ ) {
169+ if ( i < j ) {
170+ [ real [ i ] , real [ j ] ] = [ real [ j ] , real [ i ] ] ;
171+ [ imag [ i ] , imag [ j ] ] = [ imag [ j ] , imag [ i ] ] ;
172+ }
173+ let k = n / 2 ;
174+ while ( k <= j ) { j -= k ; k /= 2 ; }
175+ j += k ;
176+ }
177+ for ( let l = 2 ; l <= n ; l *= 2 ) {
178+ const le2 = l / 2 ;
179+ for ( let k = 0 ; k < le2 ; k ++ ) {
180+ const kth = k * ( n / l ) ;
181+ const c = this . cosTable [ kth ] , s = this . sinTable [ kth ] ;
182+ for ( let i = k ; i < n ; i += l ) {
183+ const i2 = i + le2 ;
184+ const tr = c * real [ i2 ] - s * imag [ i2 ] ;
185+ const ti = c * imag [ i2 ] + s * real [ i2 ] ;
186+ real [ i2 ] = real [ i ] - tr ;
187+ imag [ i2 ] = imag [ i ] - ti ;
188+ real [ i ] += tr ;
189+ imag [ i ] += ti ;
190+ }
191+ }
192+ }
193+ }
194+ }
195+
196+
197+ const fftProcessor = new FFT ( fftSize ) ;
198+
87199 ///
88200 const createCanvasElement = ( ) => {
89201
@@ -253,7 +365,7 @@ const FFT = forwardRef(
253365
254366 const xScale = ( width - leftMargin - 10 ) / displayPoints ;
255367
256- let yMax = 1 ; // Default to prevent division by zero
368+ let yMax = 0 ; // Default to prevent division by zero
257369 fftData . forEach ( ( channelData ) => {
258370 if ( channelData . length > 0 ) {
259371 yMax = Math . max ( yMax , ...channelData . slice ( 0 , displayPoints ) ) ;
@@ -326,27 +438,27 @@ const FFT = forwardRef(
326438
327439 return (
328440 < div className = "flex flex-col w-full h-screen overflow-hidden" >
329- { /* Plotting Data / Main content area */ }
330- < main
331- ref = { canvasContainerRef }
332- className = "flex-1 bg-highlight rounded-2xl m-2 overflow-hidden min-h-0"
333- >
334- { /* Main content goes here */ }
335- </ main >
336-
337- { /* Responsive container for FFT (canvas) and BandPowerGraph */ }
338- < div className = "flex-1 m-2 flex flex-col md:flex-row justify-center overflow-hidden min-h-0 gap-12" >
339-
340- { /* FFT Canvas container */ }
341- < div ref = { containerRef } className = "flex-1 overflow-hidden min-h-0 min-w-0 ml-12 " >
342- < canvas ref = { canvasRef } className = "w-full h-full" />
343- </ div >
344- { /* BandPowerGraph container */ }
345- < div className = "flex-1 overflow-hidden min-h-0 min-w-0 ml-4" >
346- < BandPowerGraph fftData = { fftData } samplingRate = { currentSamplingRate } />
441+ { /* Plotting Data / Main content area */ }
442+ < main
443+ ref = { canvasContainerRef }
444+ className = "flex-1 bg-highlight rounded-2xl m-2 overflow-hidden min-h-0"
445+ >
446+ { /* Main content goes here */ }
447+ </ main >
448+
449+ { /* Responsive container for FFT (canvas) and BandPowerGraph */ }
450+ < div className = "flex-1 m-2 flex flex-col md:flex-row justify-center overflow-hidden min-h-0 gap-12" >
451+
452+ { /* FFT Canvas container */ }
453+ < div ref = { containerRef } className = "flex-1 overflow-hidden min-h-0 min-w-0 ml-12 " >
454+ < canvas ref = { canvasRef } className = "w-full h-full" />
455+ </ div >
456+ { /* BandPowerGraph container */ }
457+ < div className = "flex-1 overflow-hidden min-h-0 min-w-0 ml-4" >
458+ < BandPowerGraph fftData = { fftData } samplingRate = { currentSamplingRate } />
459+ </ div >
347460 </ div >
348461 </ div >
349- </ div >
350462 ) ;
351463 }
352464) ;
0 commit comments