gladly-plot 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/axes/Axis.js +253 -0
- package/src/{AxisQuantityKindRegistry.js → axes/AxisQuantityKindRegistry.js} +7 -0
- package/src/{AxisRegistry.js → axes/AxisRegistry.js} +48 -0
- package/src/axes/ColorAxisRegistry.js +93 -0
- package/src/{FilterAxisRegistry.js → axes/FilterAxisRegistry.js} +63 -0
- package/src/axes/ZoomController.js +141 -0
- package/src/colorscales/BivariateColorscales.js +205 -0
- package/src/colorscales/ColorscaleRegistry.js +124 -0
- package/src/compute/ComputationRegistry.js +237 -0
- package/src/compute/axisFilter.js +47 -0
- package/src/compute/conv.js +230 -0
- package/src/compute/fft.js +292 -0
- package/src/compute/filter.js +227 -0
- package/src/compute/hist.js +180 -0
- package/src/compute/kde.js +102 -0
- package/src/{Layer.js → core/Layer.js} +4 -3
- package/src/{LayerType.js → core/LayerType.js} +72 -7
- package/src/core/Plot.js +735 -0
- package/src/{Colorbar.js → floats/Colorbar.js} +19 -5
- package/src/floats/Colorbar2d.js +77 -0
- package/src/{Filterbar.js → floats/Filterbar.js} +18 -4
- package/src/{FilterbarFloat.js → floats/Float.js} +17 -30
- package/src/{EpsgUtils.js → geo/EpsgUtils.js} +1 -1
- package/src/index.js +35 -22
- package/src/{ColorbarLayer.js → layers/ColorbarLayer.js} +2 -2
- package/src/layers/ColorbarLayer2d.js +97 -0
- package/src/{FilterbarLayer.js → layers/FilterbarLayer.js} +2 -2
- package/src/layers/HistogramLayer.js +212 -0
- package/src/layers/LinesLayer.js +199 -0
- package/src/layers/PointsLayer.js +114 -0
- package/src/layers/ScatterShared.js +142 -0
- package/src/{TileLayer.js → layers/TileLayer.js} +4 -4
- package/src/Axis.js +0 -48
- package/src/ColorAxisRegistry.js +0 -49
- package/src/ColorscaleRegistry.js +0 -52
- package/src/Float.js +0 -159
- package/src/Plot.js +0 -1073
- package/src/ScatterLayer.js +0 -287
- /package/src/{AxisLink.js → axes/AxisLink.js} +0 -0
- /package/src/{MatplotlibColorscales.js → colorscales/MatplotlibColorscales.js} +0 -0
- /package/src/{Data.js → core/Data.js} +0 -0
- /package/src/{LayerTypeRegistry.js → core/LayerTypeRegistry.js} +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { fftConvolution } from "./fft.js"
|
|
2
|
+
import { registerTextureComputation, TextureComputation, EXPRESSION_REF } from "./ComputationRegistry.js"
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
============================================================
|
|
6
|
+
Utilities
|
|
7
|
+
============================================================
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const MAX_KERNEL_LOOP = 1024;
|
|
11
|
+
|
|
12
|
+
function nextPow2(n) {
|
|
13
|
+
return 1 << Math.ceil(Math.log2(n));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function make1DTexture(regl, data, width) {
|
|
17
|
+
return regl.texture({
|
|
18
|
+
data,
|
|
19
|
+
width,
|
|
20
|
+
height: 1,
|
|
21
|
+
format: "rgba",
|
|
22
|
+
type: "float",
|
|
23
|
+
wrap: "clamp",
|
|
24
|
+
min: "nearest",
|
|
25
|
+
mag: "nearest"
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function makeFBO(regl, width) {
|
|
30
|
+
return regl.framebuffer({
|
|
31
|
+
color: regl.texture({
|
|
32
|
+
width,
|
|
33
|
+
height: 1,
|
|
34
|
+
format: "rgba",
|
|
35
|
+
type: "float"
|
|
36
|
+
})
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/*
|
|
41
|
+
============================================================
|
|
42
|
+
1) Single-pass convolution (kernel ≤ 1024)
|
|
43
|
+
============================================================
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
function singlePassConvolution(regl) {
|
|
47
|
+
return regl({
|
|
48
|
+
frag: `#version 300 es
|
|
49
|
+
precision highp float;
|
|
50
|
+
uniform sampler2D signal, kernel;
|
|
51
|
+
uniform int N, K;
|
|
52
|
+
out vec4 outColor;
|
|
53
|
+
|
|
54
|
+
void main() {
|
|
55
|
+
int x = int(gl_FragCoord.x);
|
|
56
|
+
float sum = 0.0;
|
|
57
|
+
|
|
58
|
+
for (int i = 0; i < ${MAX_KERNEL_LOOP}; i++) {
|
|
59
|
+
if (i >= K) break;
|
|
60
|
+
int xi = x - i;
|
|
61
|
+
if (xi < 0 || xi >= N) continue;
|
|
62
|
+
|
|
63
|
+
float s = texelFetch(signal, ivec2(xi, 0), 0).r;
|
|
64
|
+
float k = texelFetch(kernel, ivec2(i, 0), 0).r;
|
|
65
|
+
sum += s * k;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
outColor = vec4(sum, 0, 0, 1);
|
|
69
|
+
}`,
|
|
70
|
+
vert: `#version 300 es
|
|
71
|
+
in vec2 position;
|
|
72
|
+
void main() {
|
|
73
|
+
gl_Position = vec4(position, 0, 1);
|
|
74
|
+
}`,
|
|
75
|
+
attributes: {
|
|
76
|
+
position: [[-1,-1],[1,-1],[-1,1],[1,1]]
|
|
77
|
+
},
|
|
78
|
+
uniforms: {
|
|
79
|
+
signal: regl.prop("signal"),
|
|
80
|
+
kernel: regl.prop("kernel"),
|
|
81
|
+
N: regl.prop("N"),
|
|
82
|
+
K: regl.prop("K")
|
|
83
|
+
},
|
|
84
|
+
framebuffer: regl.prop("fbo"),
|
|
85
|
+
count: 4,
|
|
86
|
+
primitive: "triangle strip"
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/*
|
|
91
|
+
============================================================
|
|
92
|
+
2) Two-pass chunked convolution (arbitrary kernel size)
|
|
93
|
+
============================================================
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
function chunkedConvolution(regl) {
|
|
97
|
+
const partialPass = regl({
|
|
98
|
+
frag: `#version 300 es
|
|
99
|
+
precision highp float;
|
|
100
|
+
uniform sampler2D signal, kernel2D;
|
|
101
|
+
uniform int N, chunkOffset;
|
|
102
|
+
out vec4 outColor;
|
|
103
|
+
|
|
104
|
+
void main() {
|
|
105
|
+
int x = int(gl_FragCoord.x);
|
|
106
|
+
float sum = 0.0;
|
|
107
|
+
|
|
108
|
+
for (int i = 0; i < ${MAX_KERNEL_LOOP}; i++) {
|
|
109
|
+
int kIndex = chunkOffset + i;
|
|
110
|
+
int xi = x - kIndex;
|
|
111
|
+
if (xi < 0 || xi >= N) continue;
|
|
112
|
+
|
|
113
|
+
float s = texelFetch(signal, ivec2(xi, 0), 0).r;
|
|
114
|
+
float k = texelFetch(kernel2D, ivec2(i, 0), 0).r;
|
|
115
|
+
sum += s * k;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
outColor = vec4(sum, 0, 0, 1);
|
|
119
|
+
}`,
|
|
120
|
+
vert: `#version 300 es
|
|
121
|
+
in vec2 position;
|
|
122
|
+
void main() {
|
|
123
|
+
gl_Position = vec4(position, 0, 1);
|
|
124
|
+
}`,
|
|
125
|
+
attributes: {
|
|
126
|
+
position: [[-1,-1],[1,-1],[-1,1],[1,1]]
|
|
127
|
+
},
|
|
128
|
+
uniforms: {
|
|
129
|
+
signal: regl.prop("signal"),
|
|
130
|
+
kernel2D: regl.prop("kernel"),
|
|
131
|
+
N: regl.prop("N"),
|
|
132
|
+
chunkOffset: regl.prop("offset")
|
|
133
|
+
},
|
|
134
|
+
framebuffer: regl.prop("fbo"),
|
|
135
|
+
blend: {
|
|
136
|
+
enable: true,
|
|
137
|
+
func: { src: "one", dst: "one" }
|
|
138
|
+
},
|
|
139
|
+
count: 4,
|
|
140
|
+
primitive: "triangle strip"
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return function run({ signalTex, kernel, N }) {
|
|
144
|
+
const chunks = Math.ceil(kernel.length / MAX_KERNEL_LOOP);
|
|
145
|
+
const fbo = makeFBO(regl, N);
|
|
146
|
+
|
|
147
|
+
regl.clear({ framebuffer: fbo, color: [0,0,0,0] });
|
|
148
|
+
|
|
149
|
+
for (let c = 0; c < chunks; c++) {
|
|
150
|
+
const slice = kernel.slice(
|
|
151
|
+
c * MAX_KERNEL_LOOP,
|
|
152
|
+
(c + 1) * MAX_KERNEL_LOOP
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const kernelTex = make1DTexture(regl, slice, MAX_KERNEL_LOOP);
|
|
156
|
+
|
|
157
|
+
partialPass({
|
|
158
|
+
signal: signalTex,
|
|
159
|
+
kernel: kernelTex,
|
|
160
|
+
N,
|
|
161
|
+
offset: c * MAX_KERNEL_LOOP,
|
|
162
|
+
fbo
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return fbo.color[0];
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/*
|
|
171
|
+
============================================================
|
|
172
|
+
Adaptive wrapper
|
|
173
|
+
============================================================
|
|
174
|
+
*/
|
|
175
|
+
|
|
176
|
+
export default function adaptiveConvolution(regl, signalArray, kernelArray) {
|
|
177
|
+
const single = singlePassConvolution(regl);
|
|
178
|
+
const chunked = chunkedConvolution(regl);
|
|
179
|
+
|
|
180
|
+
const N = signalArray.length;
|
|
181
|
+
const K = kernelArray.length;
|
|
182
|
+
|
|
183
|
+
const signalTex = make1DTexture(regl, signalArray, N);
|
|
184
|
+
|
|
185
|
+
// Case 1: single pass
|
|
186
|
+
if (K <= MAX_KERNEL_LOOP) {
|
|
187
|
+
const kernelTex = make1DTexture(regl, kernelArray, K);
|
|
188
|
+
const fbo = makeFBO(regl, N);
|
|
189
|
+
|
|
190
|
+
single({
|
|
191
|
+
signal: signalTex,
|
|
192
|
+
kernel: kernelTex,
|
|
193
|
+
N,
|
|
194
|
+
K,
|
|
195
|
+
fbo
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
return fbo.color[0];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Case 2: chunked
|
|
202
|
+
if (K <= 8192) {
|
|
203
|
+
return chunked({
|
|
204
|
+
signalTex,
|
|
205
|
+
kernel: kernelArray,
|
|
206
|
+
N
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Case 3: FFT
|
|
211
|
+
return fftConvolution(regl, signalArray, kernelArray);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
class ConvolutionComputation extends TextureComputation {
|
|
215
|
+
compute(regl, params) {
|
|
216
|
+
return adaptiveConvolution(regl, params.signal, params.kernel)
|
|
217
|
+
}
|
|
218
|
+
schema(data) {
|
|
219
|
+
return {
|
|
220
|
+
type: 'object',
|
|
221
|
+
properties: {
|
|
222
|
+
signal: EXPRESSION_REF,
|
|
223
|
+
kernel: EXPRESSION_REF
|
|
224
|
+
},
|
|
225
|
+
required: ['signal', 'kernel']
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
registerTextureComputation('convolution', new ConvolutionComputation())
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { registerTextureComputation, TextureComputation, EXPRESSION_REF } from "./ComputationRegistry.js"
|
|
2
|
+
|
|
3
|
+
/* ============================================================
|
|
4
|
+
Utilities
|
|
5
|
+
============================================================ */
|
|
6
|
+
|
|
7
|
+
function nextPow2(n) {
|
|
8
|
+
return 1 << Math.ceil(Math.log2(n));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function makeComplexTexture(regl, data, N) {
|
|
12
|
+
// data: Float32Array (real), imag assumed 0
|
|
13
|
+
const texData = new Float32Array(N * 4);
|
|
14
|
+
for (let i = 0; i < data.length; i++) {
|
|
15
|
+
texData[i * 4] = data[i];
|
|
16
|
+
}
|
|
17
|
+
return regl.texture({
|
|
18
|
+
data: texData,
|
|
19
|
+
width: N,
|
|
20
|
+
height: 1,
|
|
21
|
+
format: "rgba",
|
|
22
|
+
type: "float",
|
|
23
|
+
min: "nearest",
|
|
24
|
+
mag: "nearest",
|
|
25
|
+
wrap: "clamp"
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function makeEmptyComplexTexture(regl, N) {
|
|
30
|
+
return regl.texture({
|
|
31
|
+
width: N,
|
|
32
|
+
height: 1,
|
|
33
|
+
format: "rgba",
|
|
34
|
+
type: "float",
|
|
35
|
+
min: "nearest",
|
|
36
|
+
mag: "nearest",
|
|
37
|
+
wrap: "clamp"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function makeFBO(regl, tex) {
|
|
42
|
+
return regl.framebuffer({ color: tex });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* ============================================================
|
|
46
|
+
Fullscreen quad
|
|
47
|
+
============================================================ */
|
|
48
|
+
|
|
49
|
+
const quad = {
|
|
50
|
+
attributes: {
|
|
51
|
+
position: [[-1, -1], [1, -1], [-1, 1], [1, 1]]
|
|
52
|
+
},
|
|
53
|
+
count: 4,
|
|
54
|
+
primitive: "triangle strip"
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/* ============================================================
|
|
58
|
+
FFT shaders
|
|
59
|
+
============================================================ */
|
|
60
|
+
|
|
61
|
+
function bitReversePass(regl, N) {
|
|
62
|
+
return regl({
|
|
63
|
+
frag: `#version 300 es
|
|
64
|
+
precision highp float;
|
|
65
|
+
uniform sampler2D inputTex;
|
|
66
|
+
uniform int N;
|
|
67
|
+
out vec4 outColor;
|
|
68
|
+
|
|
69
|
+
int bitReverse(int x, int bits) {
|
|
70
|
+
int y = 0;
|
|
71
|
+
for (int i = 0; i < 16; i++) {
|
|
72
|
+
if (i >= bits) break;
|
|
73
|
+
y = (y << 1) | (x & 1);
|
|
74
|
+
x >>= 1;
|
|
75
|
+
}
|
|
76
|
+
return y;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
void main() {
|
|
80
|
+
int x = int(gl_FragCoord.x);
|
|
81
|
+
int bits = int(log2(float(N)));
|
|
82
|
+
int rx = bitReverse(x, bits);
|
|
83
|
+
vec2 v = texelFetch(inputTex, ivec2(rx, 0), 0).rg;
|
|
84
|
+
outColor = vec4(v, 0.0, 1.0);
|
|
85
|
+
}`,
|
|
86
|
+
vert: `#version 300 es
|
|
87
|
+
in vec2 position;
|
|
88
|
+
void main() {
|
|
89
|
+
gl_Position = vec4(position, 0, 1);
|
|
90
|
+
}`,
|
|
91
|
+
uniforms: {
|
|
92
|
+
inputTex: regl.prop("input"),
|
|
93
|
+
N
|
|
94
|
+
},
|
|
95
|
+
framebuffer: regl.prop("fbo"),
|
|
96
|
+
...quad
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function fftStagePass(regl, stage, inverse) {
|
|
101
|
+
return regl({
|
|
102
|
+
frag: `#version 300 es
|
|
103
|
+
precision highp float;
|
|
104
|
+
uniform sampler2D inputTex;
|
|
105
|
+
uniform int stage;
|
|
106
|
+
uniform int N;
|
|
107
|
+
out vec4 outColor;
|
|
108
|
+
|
|
109
|
+
void main() {
|
|
110
|
+
int x = int(gl_FragCoord.x);
|
|
111
|
+
int half = stage >> 1;
|
|
112
|
+
int block = (x / stage) * stage;
|
|
113
|
+
int i = block + (x % half);
|
|
114
|
+
int j = i + half;
|
|
115
|
+
|
|
116
|
+
vec2 a = texelFetch(inputTex, ivec2(i, 0), 0).rg;
|
|
117
|
+
vec2 b = texelFetch(inputTex, ivec2(j, 0), 0).rg;
|
|
118
|
+
|
|
119
|
+
float sign = ${inverse ? "1.0" : "-1.0"};
|
|
120
|
+
float angle = sign * 6.28318530718 * float(x % stage) / float(stage);
|
|
121
|
+
vec2 w = vec2(cos(angle), sin(angle));
|
|
122
|
+
|
|
123
|
+
vec2 t = vec2(
|
|
124
|
+
b.x * w.x - b.y * w.y,
|
|
125
|
+
b.x * w.y + b.y * w.x
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
vec2 outv = (x < j) ? a + t : a - t;
|
|
129
|
+
outColor = vec4(outv, 0.0, 1.0);
|
|
130
|
+
}`,
|
|
131
|
+
vert: `#version 300 es
|
|
132
|
+
in vec2 position;
|
|
133
|
+
void main() {
|
|
134
|
+
gl_Position = vec4(position, 0, 1);
|
|
135
|
+
}`,
|
|
136
|
+
uniforms: {
|
|
137
|
+
inputTex: regl.prop("input"),
|
|
138
|
+
stage,
|
|
139
|
+
N: regl.prop("N")
|
|
140
|
+
},
|
|
141
|
+
framebuffer: regl.prop("fbo"),
|
|
142
|
+
...quad
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function scalePass(regl, N) {
|
|
147
|
+
return regl({
|
|
148
|
+
frag: `#version 300 es
|
|
149
|
+
precision highp float;
|
|
150
|
+
uniform sampler2D inputTex;
|
|
151
|
+
out vec4 outColor;
|
|
152
|
+
void main() {
|
|
153
|
+
vec2 v = texelFetch(inputTex, ivec2(int(gl_FragCoord.x),0),0).rg;
|
|
154
|
+
outColor = vec4(v / float(${N}), 0.0, 1.0);
|
|
155
|
+
}`,
|
|
156
|
+
vert: `#version 300 es
|
|
157
|
+
in vec2 position;
|
|
158
|
+
void main() {
|
|
159
|
+
gl_Position = vec4(position, 0, 1);
|
|
160
|
+
}`,
|
|
161
|
+
uniforms: {
|
|
162
|
+
inputTex: regl.prop("input")
|
|
163
|
+
},
|
|
164
|
+
framebuffer: regl.prop("fbo"),
|
|
165
|
+
...quad
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* ============================================================
|
|
170
|
+
Public: GPU FFT (top-level API)
|
|
171
|
+
============================================================ */
|
|
172
|
+
|
|
173
|
+
export function fft1d(regl, realArray, inverse = false) {
|
|
174
|
+
const N = nextPow2(realArray.length);
|
|
175
|
+
|
|
176
|
+
let texA = makeComplexTexture(regl, realArray, N);
|
|
177
|
+
let texB = makeEmptyComplexTexture(regl, N);
|
|
178
|
+
let fboA = makeFBO(regl, texA);
|
|
179
|
+
let fboB = makeFBO(regl, texB);
|
|
180
|
+
|
|
181
|
+
// bit reversal
|
|
182
|
+
bitReversePass(regl, N)({
|
|
183
|
+
input: texA,
|
|
184
|
+
fbo: fboB
|
|
185
|
+
});
|
|
186
|
+
[fboA, fboB] = [fboB, fboA];
|
|
187
|
+
|
|
188
|
+
// FFT stages
|
|
189
|
+
const stages = Math.log2(N);
|
|
190
|
+
for (let s = 1; s <= stages; s++) {
|
|
191
|
+
fftStagePass(regl, 1 << s, inverse)({
|
|
192
|
+
input: fboA,
|
|
193
|
+
N,
|
|
194
|
+
fbo: fboB
|
|
195
|
+
});
|
|
196
|
+
[fboA, fboB] = [fboB, fboA];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// scale for inverse
|
|
200
|
+
if (inverse) {
|
|
201
|
+
scalePass(regl, N)({
|
|
202
|
+
input: fboA,
|
|
203
|
+
fbo: fboB
|
|
204
|
+
});
|
|
205
|
+
return fboB.color[0];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return fboA.color[0];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/* ============================================================
|
|
212
|
+
FFT-based convolution
|
|
213
|
+
============================================================ */
|
|
214
|
+
|
|
215
|
+
export function fftConvolution(regl, signal, kernel) {
|
|
216
|
+
const N = nextPow2(signal.length + kernel.length);
|
|
217
|
+
|
|
218
|
+
const sigTex = fft1d(regl, signal, false);
|
|
219
|
+
const kerTex = fft1d(regl, kernel, false);
|
|
220
|
+
|
|
221
|
+
const outTex = makeEmptyComplexTexture(regl, N);
|
|
222
|
+
const outFBO = makeFBO(regl, outTex);
|
|
223
|
+
|
|
224
|
+
// pointwise complex multiply
|
|
225
|
+
regl({
|
|
226
|
+
frag: `#version 300 es
|
|
227
|
+
precision highp float;
|
|
228
|
+
uniform sampler2D A, B;
|
|
229
|
+
out vec4 outColor;
|
|
230
|
+
void main() {
|
|
231
|
+
int x = int(gl_FragCoord.x);
|
|
232
|
+
vec2 a = texelFetch(A, ivec2(x,0),0).rg;
|
|
233
|
+
vec2 b = texelFetch(B, ivec2(x,0),0).rg;
|
|
234
|
+
outColor = vec4(
|
|
235
|
+
a.x*b.x - a.y*b.y,
|
|
236
|
+
a.x*b.y + a.y*b.x,
|
|
237
|
+
0, 1
|
|
238
|
+
);
|
|
239
|
+
}`,
|
|
240
|
+
vert: `#version 300 es
|
|
241
|
+
in vec2 position;
|
|
242
|
+
void main() {
|
|
243
|
+
gl_Position = vec4(position,0,1);
|
|
244
|
+
}`,
|
|
245
|
+
uniforms: {
|
|
246
|
+
A: sigTex,
|
|
247
|
+
B: kerTex
|
|
248
|
+
},
|
|
249
|
+
framebuffer: outFBO,
|
|
250
|
+
...quad
|
|
251
|
+
})();
|
|
252
|
+
|
|
253
|
+
// inverse FFT
|
|
254
|
+
return fft1d(regl, new Float32Array(N), true);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
class Fft1dComputation extends TextureComputation {
|
|
258
|
+
compute(regl, params) {
|
|
259
|
+
return fft1d(regl, params.input, params.inverse ?? false)
|
|
260
|
+
}
|
|
261
|
+
schema(data) {
|
|
262
|
+
return {
|
|
263
|
+
type: 'object',
|
|
264
|
+
properties: {
|
|
265
|
+
input: EXPRESSION_REF,
|
|
266
|
+
inverse: { type: 'boolean' }
|
|
267
|
+
},
|
|
268
|
+
required: ['input']
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
class FftConvolutionComputation extends TextureComputation {
|
|
274
|
+
compute(regl, params) {
|
|
275
|
+
return fftConvolution(regl, params.signal, params.kernel)
|
|
276
|
+
}
|
|
277
|
+
schema(data) {
|
|
278
|
+
return {
|
|
279
|
+
type: 'object',
|
|
280
|
+
properties: {
|
|
281
|
+
signal: EXPRESSION_REF,
|
|
282
|
+
kernel: EXPRESSION_REF
|
|
283
|
+
},
|
|
284
|
+
required: ['signal', 'kernel']
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// fft1d: output is a complex texture — R = real part, G = imaginary part.
|
|
290
|
+
// Use a downstream computation (e.g. magnitude) to get a single scalar per bin.
|
|
291
|
+
registerTextureComputation('fft1d', new Fft1dComputation())
|
|
292
|
+
registerTextureComputation('fftConvolution', new FftConvolutionComputation())
|