/ concept-collection / remote-hdf5-lazy-read
Sign in
concept-collection / remote-hdf5-lazy-read
remote-hdf5-lazy-read / src / remote-h5-file / lib / lindi / fft.ts
234 lines · 8.1 KBBlameHistoryRaw
1// jfm changed Float32Array to Float32Array
3/* eslint-disable @typescript-eslint/no-inferrable-types */
4/* eslint-disable @typescript-eslint/no-unused-vars */
5/* eslint-disable prefer-const */
6/*
7 * Free FFT and convolution (TypeScript)
8 *
9 * Copyright (c) 2022 Project Nayuki. (MIT License)
10 * https://www.nayuki.io/page/free-small-fft-in-multiple-languages
11 *
12 * Permission is hereby granted, free of charge, to any person obtaining a copy of
13 * this software and associated documentation files (the "Software"), to deal in
14 * the Software without restriction, including without limitation the rights to
15 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
16 * the Software, and to permit persons to whom the Software is furnished to do so,
17 * subject to the following conditions:
18 * - The above copyright notice and this permission notice shall be included in
19 * all copies or substantial portions of the Software.
20 * - The Software is provided "as is", without warranty of any kind, express or
21 * implied, including but not limited to the warranties of merchantability,
22 * fitness for a particular purpose and noninfringement. In no event shall the
23 * authors or copyright holders be liable for any claim, damages or other
24 * liability, whether in an action of contract, tort or otherwise, arising from,
25 * out of or in connection with the Software or the use or other dealings in the
26 * Software.
27 */
29/*
30 * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector.
31 * The vector can have any length. This is a wrapper function.
32 */
33export function transform(
34 real: Array<number> | Float32Array,
35 imag: Array<number> | Float32Array,
36): void {
37 const n: number = real.length;
38 if (n != imag.length) throw new RangeError("Mismatched lengths");
39 if (n == 0) return;
40 else if ((n & (n - 1)) == 0)
41 // Is power of 2
42 transformRadix2(real, imag); // More complicated algorithm for arbitrary sizes
43 else transformBluestein(real, imag);
46/*
47 * Computes the inverse discrete Fourier transform (IDFT) of the given complex vector, storing the result back into the vector.
48 * The vector can have any length. This is a wrapper function. This transform does not perform scaling, so the inverse is not a true inverse.
49 */
50export function inverseTransform(
51 real: Array<number> | Float32Array,
52 imag: Array<number> | Float32Array,
53): void {
54 transform(imag, real);
57/*
58 * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector.
59 * The vector's length must be a power of 2. Uses the Cooley-Tukey decimation-in-time radix-2 algorithm.
60 */
61function transformRadix2(
62 real: Array<number> | Float32Array,
63 imag: Array<number> | Float32Array,
64): void {
65 // Length variables
66 const n: number = real.length;
67 if (n != imag.length) throw new RangeError("Mismatched lengths");
68 if (n == 1)
69 // Trivial transform
70 return;
71 let levels: number = -1;
72 for (let i = 0; i < 32; i++) {
73 if (1 << i == n) levels = i; // Equal to log2(n)
74 }
75 if (levels == -1) throw new RangeError("Length is not a power of 2");
77 // Trigonometric tables
78 let cosTable = new Array<number>(n / 2);
79 let sinTable = new Array<number>(n / 2);
80 for (let i = 0; i < n / 2; i++) {
81 cosTable[i] = Math.cos((2 * Math.PI * i) / n);
82 sinTable[i] = Math.sin((2 * Math.PI * i) / n);
83 }
85 // Bit-reversed addressing permutation
86 for (let i = 0; i < n; i++) {
87 const j: number = reverseBits(i, levels);
88 if (j > i) {
89 let temp: number = real[i];
90 real[i] = real[j];
91 real[j] = temp;
92 temp = imag[i];
93 imag[i] = imag[j];
94 imag[j] = temp;
95 }
96 }
98 // Cooley-Tukey decimation-in-time radix-2 FFT
99 for (let size = 2; size <= n; size *= 2) {
100 const halfsize: number = size / 2;
101 const tablestep: number = n / size;
102 for (let i = 0; i < n; i += size) {
103 for (let j = i, k = 0; j < i + halfsize; j++, k += tablestep) {
104 const l: number = j + halfsize;
105 const tpre: number = real[l] * cosTable[k] + imag[l] * sinTable[k];
106 const tpim: number = -real[l] * sinTable[k] + imag[l] * cosTable[k];
107 real[l] = real[j] - tpre;
108 imag[l] = imag[j] - tpim;
109 real[j] += tpre;
110 imag[j] += tpim;
111 }
112 }
113 }
115 // Returns the integer whose value is the reverse of the lowest 'width' bits of the integer 'val'.
116 function reverseBits(val: number, width: number): number {
117 let result: number = 0;
118 for (let i = 0; i < width; i++) {
119 result = (result << 1) | (val & 1);
120 val >>>= 1;
121 }
122 return result;
123 }
126/*
127 * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector.
128 * The vector can have any length. This requires the convolution function, which in turn requires the radix-2 FFT function.
129 * Uses Bluestein's chirp z-transform algorithm.
130 */
131function transformBluestein(
132 real: Array<number> | Float32Array,
133 imag: Array<number> | Float32Array,
134): void {
135 // Find a power-of-2 convolution length m such that m >= n * 2 + 1
136 const n: number = real.length;
137 if (n != imag.length) throw new RangeError("Mismatched lengths");
138 let m: number = 1;
139 while (m < n * 2 + 1) m *= 2;
141 // Trigonometric tables
142 let cosTable = new Array<number>(n);
143 let sinTable = new Array<number>(n);
144 for (let i = 0; i < n; i++) {
145 const j: number = (i * i) % (n * 2); // This is more accurate than j = i * i
146 cosTable[i] = Math.cos((Math.PI * j) / n);
147 sinTable[i] = Math.sin((Math.PI * j) / n);
148 }
150 // Temporary vectors and preprocessing
151 let areal: Array<number> = newArrayOfZeros(m);
152 let aimag: Array<number> = newArrayOfZeros(m);
153 for (let i = 0; i < n; i++) {
154 areal[i] = real[i] * cosTable[i] + imag[i] * sinTable[i];
155 aimag[i] = -real[i] * sinTable[i] + imag[i] * cosTable[i];
156 }
157 let breal: Array<number> = newArrayOfZeros(m);
158 let bimag: Array<number> = newArrayOfZeros(m);
159 breal[0] = cosTable[0];
160 bimag[0] = sinTable[0];
161 for (let i = 1; i < n; i++) {
162 breal[i] = breal[m - i] = cosTable[i];
163 bimag[i] = bimag[m - i] = sinTable[i];
164 }
166 // Convolution
167 let creal = new Array<number>(m);
168 let cimag = new Array<number>(m);
169 convolveComplex(areal, aimag, breal, bimag, creal, cimag);
171 // Postprocessing
172 for (let i = 0; i < n; i++) {
173 real[i] = creal[i] * cosTable[i] + cimag[i] * sinTable[i];
174 imag[i] = -creal[i] * sinTable[i] + cimag[i] * cosTable[i];
175 }
178/*
179 * Computes the circular convolution of the given real vectors. Each vector's length must be the same.
180 */
181// function convolveReal(xvec: Array<number>|Float32Array, yvec: Array<number>|Float32Array, outvec: Array<number>|Float32Array): void {
182// const n: number = xvec.length;
183// if (n != yvec.length || n != outvec.length)
184// throw new RangeError("Mismatched lengths");
185// convolveComplex(xvec, newArrayOfZeros(n), yvec, newArrayOfZeros(n), outvec, newArrayOfZeros(n));
186// }
188/*
189 * Computes the circular convolution of the given complex vectors. Each vector's length must be the same.
190 */
191function convolveComplex(
192 xreal: Array<number> | Float32Array,
193 ximag: Array<number> | Float32Array,
194 yreal: Array<number> | Float32Array,
195 yimag: Array<number> | Float32Array,
196 outreal: Array<number> | Float32Array,
197 outimag: Array<number> | Float32Array,
198): void {
199 const n: number = xreal.length;
200 if (
201 n != ximag.length ||
202 n != yreal.length ||
203 n != yimag.length ||
204 n != outreal.length ||
205 n != outimag.length
206 )
207 throw new RangeError("Mismatched lengths");
209 xreal = xreal.slice();
210 ximag = ximag.slice();
211 yreal = yreal.slice();
212 yimag = yimag.slice();
213 transform(xreal, ximag);
214 transform(yreal, yimag);
216 for (let i = 0; i < n; i++) {
217 const temp: number = xreal[i] * yreal[i] - ximag[i] * yimag[i];
218 ximag[i] = ximag[i] * yreal[i] + xreal[i] * yimag[i];
219 xreal[i] = temp;
220 }
221 inverseTransform(xreal, ximag);
223 for (let i = 0; i < n; i++) {
224 // Scaling (because this FFT implementation omits it)
225 outreal[i] = xreal[i] / n;
226 outimag[i] = ximag[i] / n;
227 }
230function newArrayOfZeros(n: number): Array<number> {
231 let result: Array<number> = [];
232 for (let i = 0; i < n; i++) result.push(0);
233 return result;
moveopenescclose