@editframe/elements 0.15.0-beta.14 → 0.15.0-beta.17
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/dist/elements/EFMedia.d.ts +1 -0
- package/dist/elements/EFMedia.js +58 -7
- package/dist/elements/EFWaveform.d.ts +2 -1
- package/dist/elements/EFWaveform.js +82 -20
- package/package.json +2 -2
- package/src/elements/EFMedia.ts +82 -10
- package/src/elements/EFWaveform.ts +117 -28
- package/types.json +1 -1
|
@@ -68,6 +68,7 @@ export declare class EFMedia extends EFMedia_base {
|
|
|
68
68
|
set fftDecay(value: number);
|
|
69
69
|
get fftSize(): number;
|
|
70
70
|
get fftDecay(): number;
|
|
71
|
+
get shouldInterpolateFrequencies(): boolean;
|
|
71
72
|
private static readonly DECAY_WEIGHT;
|
|
72
73
|
get FREQ_WEIGHTS(): Float32Array;
|
|
73
74
|
byteTimeDomainTask: Task<readonly [import('@lit/task').TaskStatus, number, number, number], Uint8Array | null>;
|
package/dist/elements/EFMedia.js
CHANGED
|
@@ -288,7 +288,7 @@ const _EFMedia = class _EFMedia2 extends EFTargetable(
|
|
|
288
288
|
analyser.minDecibels = -90;
|
|
289
289
|
analyser.maxDecibels = -20;
|
|
290
290
|
const gainNode = audioContext.createGain();
|
|
291
|
-
gainNode.gain.value =
|
|
291
|
+
gainNode.gain.value = 2;
|
|
292
292
|
source.connect(gainNode);
|
|
293
293
|
gainNode.connect(analyser);
|
|
294
294
|
analyser.connect(audioContext.destination);
|
|
@@ -384,16 +384,16 @@ const _EFMedia = class _EFMedia2 extends EFTargetable(
|
|
|
384
384
|
analyser.minDecibels = -90;
|
|
385
385
|
analyser.maxDecibels = -10;
|
|
386
386
|
const gainNode = audioContext.createGain();
|
|
387
|
-
gainNode.gain.value =
|
|
387
|
+
gainNode.gain.value = 3;
|
|
388
388
|
const filter = audioContext.createBiquadFilter();
|
|
389
389
|
filter.type = "bandpass";
|
|
390
390
|
filter.frequency.value = 15e3;
|
|
391
391
|
filter.Q.value = 0.05;
|
|
392
392
|
const audioBufferSource = audioContext.createBufferSource();
|
|
393
393
|
audioBufferSource.buffer = audioBuffer;
|
|
394
|
-
audioBufferSource.connect(
|
|
395
|
-
|
|
396
|
-
|
|
394
|
+
audioBufferSource.connect(filter);
|
|
395
|
+
filter.connect(gainNode);
|
|
396
|
+
gainNode.connect(analyser);
|
|
397
397
|
analyser.connect(audioContext.destination);
|
|
398
398
|
audioBufferSource.start(0, startTime, 1 / 30);
|
|
399
399
|
try {
|
|
@@ -428,8 +428,9 @@ const _EFMedia = class _EFMedia2 extends EFTargetable(
|
|
|
428
428
|
0,
|
|
429
429
|
Math.floor(smoothedData.length / 2)
|
|
430
430
|
);
|
|
431
|
-
this
|
|
432
|
-
|
|
431
|
+
const processedData = this.shouldInterpolateFrequencies ? processFFTData(slicedData) : slicedData;
|
|
432
|
+
this.#frequencyDataCache.set(smoothedKey, processedData);
|
|
433
|
+
return processedData;
|
|
433
434
|
}
|
|
434
435
|
});
|
|
435
436
|
}
|
|
@@ -645,10 +646,14 @@ const _EFMedia = class _EFMedia2 extends EFTargetable(
|
|
|
645
646
|
};
|
|
646
647
|
}
|
|
647
648
|
set fftSize(value) {
|
|
649
|
+
const oldValue = this.fftSize;
|
|
648
650
|
this.setAttribute("fft-size", String(value));
|
|
651
|
+
this.requestUpdate("fft-size", oldValue);
|
|
649
652
|
}
|
|
650
653
|
set fftDecay(value) {
|
|
654
|
+
const oldValue = this.fftDecay;
|
|
651
655
|
this.setAttribute("fft-decay", String(value));
|
|
656
|
+
this.requestUpdate("fft-decay", oldValue);
|
|
652
657
|
}
|
|
653
658
|
get fftSize() {
|
|
654
659
|
return Number.parseInt(this.getAttribute("fft-size") ?? "128", 10);
|
|
@@ -656,6 +661,12 @@ const _EFMedia = class _EFMedia2 extends EFTargetable(
|
|
|
656
661
|
get fftDecay() {
|
|
657
662
|
return Number.parseInt(this.getAttribute("fft-decay") ?? "8", 10);
|
|
658
663
|
}
|
|
664
|
+
get shouldInterpolateFrequencies() {
|
|
665
|
+
if (this.hasAttribute("interpolate-frequencies")) {
|
|
666
|
+
return this.getAttribute("interpolate-frequencies") !== "false";
|
|
667
|
+
}
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
659
670
|
static {
|
|
660
671
|
this.DECAY_WEIGHT = 0.7;
|
|
661
672
|
}
|
|
@@ -690,6 +701,46 @@ __decorateClass([
|
|
|
690
701
|
state()
|
|
691
702
|
], _EFMedia.prototype, "desiredSeekTimeMs", 2);
|
|
692
703
|
let EFMedia = _EFMedia;
|
|
704
|
+
function processFFTData(fftData, zeroThresholdPercent = 0.1) {
|
|
705
|
+
const totalBins = fftData.length;
|
|
706
|
+
const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);
|
|
707
|
+
let zeroCount = 0;
|
|
708
|
+
let cutoffIndex = totalBins;
|
|
709
|
+
for (let i = totalBins - 1; i >= 0; i--) {
|
|
710
|
+
if (fftData[i] < 10) {
|
|
711
|
+
zeroCount++;
|
|
712
|
+
} else {
|
|
713
|
+
if (zeroCount >= zeroThresholdCount) {
|
|
714
|
+
cutoffIndex = i + 1;
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (cutoffIndex < zeroThresholdCount) {
|
|
720
|
+
return fftData;
|
|
721
|
+
}
|
|
722
|
+
const goodData = fftData.slice(0, cutoffIndex);
|
|
723
|
+
const resampledData = interpolateData(goodData, fftData.length);
|
|
724
|
+
return resampledData;
|
|
725
|
+
}
|
|
726
|
+
function interpolateData(data, targetSize) {
|
|
727
|
+
const resampled = new Uint8Array(targetSize);
|
|
728
|
+
const dataLength = data.length;
|
|
729
|
+
for (let i = 0; i < targetSize; i++) {
|
|
730
|
+
const ratio = i / (targetSize - 1) * (dataLength - 1);
|
|
731
|
+
const index = Math.floor(ratio);
|
|
732
|
+
const fraction = ratio - index;
|
|
733
|
+
if (index >= dataLength - 1) {
|
|
734
|
+
resampled[i] = data[dataLength - 1];
|
|
735
|
+
} else {
|
|
736
|
+
resampled[i] = Math.round(
|
|
737
|
+
// biome-ignore lint/style/noNonNullAssertion: Manual bounds check
|
|
738
|
+
data[index] * (1 - fraction) + data[index + 1] * fraction
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return resampled;
|
|
743
|
+
}
|
|
693
744
|
export {
|
|
694
745
|
EFMedia,
|
|
695
746
|
deepGetMediaElements
|
|
@@ -13,7 +13,7 @@ export declare class EFWaveform extends EFWaveform_base {
|
|
|
13
13
|
private resizeObserver?;
|
|
14
14
|
private mutationObserver?;
|
|
15
15
|
render(): import('lit-html').TemplateResult<1>;
|
|
16
|
-
mode: "roundBars" | "bars" | "bricks" | "line" | "curve" | "pixel" | "wave";
|
|
16
|
+
mode: "roundBars" | "bars" | "bricks" | "line" | "curve" | "pixel" | "wave" | "spikes";
|
|
17
17
|
color: string;
|
|
18
18
|
target: string;
|
|
19
19
|
targetElement: EFAudio | EFVideo | null;
|
|
@@ -30,6 +30,7 @@ export declare class EFWaveform extends EFWaveform_base {
|
|
|
30
30
|
protected drawCurve(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array): void;
|
|
31
31
|
protected drawPixel(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array): void;
|
|
32
32
|
protected drawWave(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array): void;
|
|
33
|
+
protected drawSpikes(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array): void;
|
|
33
34
|
frameTask: Task<readonly [EFAudio | EFVideo | null, Uint8Array | null | undefined], void>;
|
|
34
35
|
get durationMs(): number;
|
|
35
36
|
protected updated(changedProperties: PropertyValueMap<this>): void;
|
|
@@ -60,10 +60,10 @@ let EFWaveform = class extends EFTemporal(TWMixin(LitElement)) {
|
|
|
60
60
|
}
|
|
61
61
|
switch (this.mode) {
|
|
62
62
|
case "bars":
|
|
63
|
-
this.drawBars(ctx,
|
|
63
|
+
this.drawBars(ctx, frequencyData);
|
|
64
64
|
break;
|
|
65
65
|
case "bricks":
|
|
66
|
-
this.drawBricks(ctx,
|
|
66
|
+
this.drawBricks(ctx, frequencyData);
|
|
67
67
|
break;
|
|
68
68
|
case "line":
|
|
69
69
|
this.drawLine(ctx, byteTimeData);
|
|
@@ -72,13 +72,16 @@ let EFWaveform = class extends EFTemporal(TWMixin(LitElement)) {
|
|
|
72
72
|
this.drawCurve(ctx, byteTimeData);
|
|
73
73
|
break;
|
|
74
74
|
case "pixel":
|
|
75
|
-
this.drawPixel(ctx,
|
|
75
|
+
this.drawPixel(ctx, frequencyData);
|
|
76
76
|
break;
|
|
77
77
|
case "wave":
|
|
78
|
-
this.drawWave(ctx,
|
|
78
|
+
this.drawWave(ctx, frequencyData);
|
|
79
|
+
break;
|
|
80
|
+
case "spikes":
|
|
81
|
+
this.drawSpikes(ctx, frequencyData);
|
|
79
82
|
break;
|
|
80
83
|
case "roundBars":
|
|
81
|
-
this.drawRoundBars(ctx,
|
|
84
|
+
this.drawRoundBars(ctx, frequencyData);
|
|
82
85
|
break;
|
|
83
86
|
}
|
|
84
87
|
ctx.restore();
|
|
@@ -156,7 +159,7 @@ let EFWaveform = class extends EFTemporal(TWMixin(LitElement)) {
|
|
|
156
159
|
ctx.clearRect(0, 0, waveWidth, waveHeight);
|
|
157
160
|
const path = new Path2D();
|
|
158
161
|
frequencyData.forEach((value, i) => {
|
|
159
|
-
const normalizedValue =
|
|
162
|
+
const normalizedValue = value / 255;
|
|
160
163
|
const barHeight = normalizedValue * waveHeight;
|
|
161
164
|
const y = (waveHeight - barHeight) / 2;
|
|
162
165
|
const x = waveWidth * paddingOuter + i * (barWidth * (1 + paddingInner));
|
|
@@ -175,7 +178,7 @@ let EFWaveform = class extends EFTemporal(TWMixin(LitElement)) {
|
|
|
175
178
|
const verticalGap = boxSize * 0.2;
|
|
176
179
|
const maxBricks = Math.floor(waveHeight / (boxSize + verticalGap));
|
|
177
180
|
frequencyData.forEach((value, i) => {
|
|
178
|
-
const normalizedValue =
|
|
181
|
+
const normalizedValue = value / 255;
|
|
179
182
|
const brickCount = Math.floor(normalizedValue * maxBricks);
|
|
180
183
|
for (let j = 0; j < brickCount; j++) {
|
|
181
184
|
const x = columnWidth * i;
|
|
@@ -197,7 +200,7 @@ let EFWaveform = class extends EFTemporal(TWMixin(LitElement)) {
|
|
|
197
200
|
ctx.clearRect(0, 0, waveWidth, waveHeight);
|
|
198
201
|
const path = new Path2D();
|
|
199
202
|
frequencyData.forEach((value, i) => {
|
|
200
|
-
const normalizedValue =
|
|
203
|
+
const normalizedValue = value / 255;
|
|
201
204
|
const height = normalizedValue * waveHeight;
|
|
202
205
|
const x = waveWidth * paddingOuter + i * (barWidth * (1 + paddingInner));
|
|
203
206
|
const y = (waveHeight - height) / 2;
|
|
@@ -258,7 +261,7 @@ let EFWaveform = class extends EFTemporal(TWMixin(LitElement)) {
|
|
|
258
261
|
ctx.clearRect(0, 0, waveWidth, waveHeight);
|
|
259
262
|
const path = new Path2D();
|
|
260
263
|
frequencyData.forEach((value, i) => {
|
|
261
|
-
const normalizedValue =
|
|
264
|
+
const normalizedValue = value / 255;
|
|
262
265
|
const x = i * (waveWidth / frequencyData.length);
|
|
263
266
|
const barHeight = normalizedValue * (waveHeight / 2);
|
|
264
267
|
const y = baseline - barHeight;
|
|
@@ -275,40 +278,99 @@ let EFWaveform = class extends EFTemporal(TWMixin(LitElement)) {
|
|
|
275
278
|
const startX = waveWidth * paddingOuter;
|
|
276
279
|
ctx.clearRect(0, 0, waveWidth, waveHeight);
|
|
277
280
|
const path = new Path2D();
|
|
278
|
-
const firstValue = ((frequencyData[0] ??
|
|
279
|
-
const firstY = waveHeight
|
|
281
|
+
const firstValue = Math.min((frequencyData[0] ?? 0) / 255 * 2, 1);
|
|
282
|
+
const firstY = (waveHeight - firstValue * waveHeight) / 2;
|
|
280
283
|
path.moveTo(startX, firstY);
|
|
281
284
|
frequencyData.forEach((value, i) => {
|
|
282
|
-
const normalizedValue = (value
|
|
285
|
+
const normalizedValue = Math.min(value / 255 * 2, 1);
|
|
283
286
|
const x = startX + i / (frequencyData.length - 1) * availableWidth;
|
|
284
|
-
const
|
|
287
|
+
const barHeight = normalizedValue * waveHeight;
|
|
288
|
+
const y = (waveHeight - barHeight) / 2;
|
|
285
289
|
if (i === 0) {
|
|
286
290
|
path.moveTo(x, y);
|
|
287
291
|
} else {
|
|
288
292
|
const prevX = startX + (i - 1) / (frequencyData.length - 1) * availableWidth;
|
|
289
|
-
const prevValue = ((frequencyData[i - 1] ??
|
|
290
|
-
const
|
|
293
|
+
const prevValue = Math.min((frequencyData[i - 1] ?? 0) / 255 * 2, 1);
|
|
294
|
+
const prevBarHeight = prevValue * waveHeight;
|
|
295
|
+
const prevY = (waveHeight - prevBarHeight) / 2;
|
|
291
296
|
const xc = (prevX + x) / 2;
|
|
292
297
|
const yc = (prevY + y) / 2;
|
|
293
298
|
path.quadraticCurveTo(prevX, prevY, xc, yc);
|
|
294
299
|
}
|
|
295
300
|
});
|
|
296
301
|
for (let i = frequencyData.length - 1; i >= 0; i--) {
|
|
297
|
-
const normalizedValue = ((frequencyData[i] ??
|
|
302
|
+
const normalizedValue = Math.min((frequencyData[i] ?? 0) / 255 * 2, 1);
|
|
298
303
|
const x = startX + i / (frequencyData.length - 1) * availableWidth;
|
|
299
|
-
const
|
|
304
|
+
const barHeight = normalizedValue * waveHeight;
|
|
305
|
+
const y = (waveHeight + barHeight) / 2;
|
|
306
|
+
if (i === frequencyData.length - 1) {
|
|
307
|
+
path.lineTo(x, y);
|
|
308
|
+
} else {
|
|
309
|
+
const nextX = startX + (i + 1) / (frequencyData.length - 1) * availableWidth;
|
|
310
|
+
const nextValue = Math.min((frequencyData[i + 1] ?? 0) / 255 * 2, 1);
|
|
311
|
+
const nextBarHeight = nextValue * waveHeight;
|
|
312
|
+
const nextY = (waveHeight + nextBarHeight) / 2;
|
|
313
|
+
const xc = (nextX + x) / 2;
|
|
314
|
+
const yc = (nextY + y) / 2;
|
|
315
|
+
path.quadraticCurveTo(nextX, nextY, xc, yc);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const lastY = (waveHeight + firstValue * waveHeight) / 2;
|
|
319
|
+
const controlX = startX;
|
|
320
|
+
const controlY = (lastY + firstY) / 2;
|
|
321
|
+
path.quadraticCurveTo(controlX, controlY, startX, firstY);
|
|
322
|
+
ctx.fill(path);
|
|
323
|
+
}
|
|
324
|
+
drawSpikes(ctx, frequencyData) {
|
|
325
|
+
const canvas = ctx.canvas;
|
|
326
|
+
const waveWidth = canvas.width;
|
|
327
|
+
const waveHeight = canvas.height;
|
|
328
|
+
const paddingOuter = 0.01;
|
|
329
|
+
const availableWidth = waveWidth * (1 - 2 * paddingOuter);
|
|
330
|
+
const startX = waveWidth * paddingOuter;
|
|
331
|
+
ctx.clearRect(0, 0, waveWidth, waveHeight);
|
|
332
|
+
const path = new Path2D();
|
|
333
|
+
const firstValue = (frequencyData[0] ?? 0) / 255;
|
|
334
|
+
const firstY = (waveHeight - firstValue * waveHeight) / 2;
|
|
335
|
+
path.moveTo(startX, firstY);
|
|
336
|
+
frequencyData.forEach((value, i) => {
|
|
337
|
+
const normalizedValue = Math.min(value / 255 * 2, 1);
|
|
338
|
+
const x = startX + i / (frequencyData.length - 1) * availableWidth;
|
|
339
|
+
const barHeight = normalizedValue * (waveHeight / 2);
|
|
340
|
+
const y = (waveHeight - barHeight * 2) / 2;
|
|
341
|
+
if (i === 0) {
|
|
342
|
+
path.moveTo(x, y);
|
|
343
|
+
} else {
|
|
344
|
+
const prevX = startX + (i - 1) / (frequencyData.length - 1) * availableWidth;
|
|
345
|
+
const prevValue = (frequencyData[i - 1] ?? 0) / 255;
|
|
346
|
+
const prevBarHeight = prevValue * (waveHeight / 2);
|
|
347
|
+
const prevY = (waveHeight - prevBarHeight * 2) / 2;
|
|
348
|
+
const xc = (prevX + x) / 2;
|
|
349
|
+
const yc = (prevY + y) / 2;
|
|
350
|
+
path.quadraticCurveTo(prevX, prevY, xc, yc);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
for (let i = frequencyData.length - 1; i >= 0; i--) {
|
|
354
|
+
const normalizedValue = Math.min((frequencyData[i] ?? 0) / 255 * 2, 1);
|
|
355
|
+
const x = startX + i / (frequencyData.length - 1) * availableWidth;
|
|
356
|
+
const barHeight = normalizedValue * (waveHeight / 2);
|
|
357
|
+
const y = (waveHeight + barHeight * 2) / 2;
|
|
300
358
|
if (i === frequencyData.length - 1) {
|
|
301
359
|
path.lineTo(x, y);
|
|
302
360
|
} else {
|
|
303
361
|
const nextX = startX + (i + 1) / (frequencyData.length - 1) * availableWidth;
|
|
304
|
-
const nextValue = (
|
|
305
|
-
const
|
|
362
|
+
const nextValue = (frequencyData[i + 1] ?? 0) / 255;
|
|
363
|
+
const nextBarHeight = nextValue * (waveHeight / 2);
|
|
364
|
+
const nextY = (waveHeight + nextBarHeight * 2) / 2;
|
|
306
365
|
const xc = (nextX + x) / 2;
|
|
307
366
|
const yc = (nextY + y) / 2;
|
|
308
367
|
path.quadraticCurveTo(nextX, nextY, xc, yc);
|
|
309
368
|
}
|
|
310
369
|
}
|
|
311
|
-
|
|
370
|
+
const lastY = (waveHeight + firstValue * waveHeight) / 2;
|
|
371
|
+
const controlX = startX;
|
|
372
|
+
const controlY = (lastY + firstY) / 2;
|
|
373
|
+
path.quadraticCurveTo(controlX, controlY, startX, firstY);
|
|
312
374
|
ctx.fill(path);
|
|
313
375
|
}
|
|
314
376
|
get durationMs() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/elements",
|
|
3
|
-
"version": "0.15.0-beta.
|
|
3
|
+
"version": "0.15.0-beta.17",
|
|
4
4
|
"description": "",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"license": "UNLICENSED",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@bramus/style-observer": "^1.3.0",
|
|
30
|
-
"@editframe/assets": "0.15.0-beta.
|
|
30
|
+
"@editframe/assets": "0.15.0-beta.17",
|
|
31
31
|
"@lit/context": "^1.1.2",
|
|
32
32
|
"@lit/task": "^1.0.1",
|
|
33
33
|
"d3": "^7.9.0",
|
package/src/elements/EFMedia.ts
CHANGED
|
@@ -591,11 +591,15 @@ export class EFMedia extends EFTargetable(
|
|
|
591
591
|
}
|
|
592
592
|
|
|
593
593
|
set fftSize(value: number) {
|
|
594
|
+
const oldValue = this.fftSize;
|
|
594
595
|
this.setAttribute("fft-size", String(value));
|
|
596
|
+
this.requestUpdate("fft-size", oldValue);
|
|
595
597
|
}
|
|
596
598
|
|
|
597
599
|
set fftDecay(value: number) {
|
|
600
|
+
const oldValue = this.fftDecay;
|
|
598
601
|
this.setAttribute("fft-decay", String(value));
|
|
602
|
+
this.requestUpdate("fft-decay", oldValue);
|
|
599
603
|
}
|
|
600
604
|
|
|
601
605
|
get fftSize() {
|
|
@@ -606,6 +610,13 @@ export class EFMedia extends EFTargetable(
|
|
|
606
610
|
return Number.parseInt(this.getAttribute("fft-decay") ?? "8", 10);
|
|
607
611
|
}
|
|
608
612
|
|
|
613
|
+
get shouldInterpolateFrequencies() {
|
|
614
|
+
if (this.hasAttribute("interpolate-frequencies")) {
|
|
615
|
+
return this.getAttribute("interpolate-frequencies") !== "false";
|
|
616
|
+
}
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
|
|
609
620
|
private static readonly DECAY_WEIGHT = 0.7;
|
|
610
621
|
|
|
611
622
|
// Update FREQ_WEIGHTS to use the instance fftSize instead of a static value
|
|
@@ -683,7 +694,7 @@ export class EFMedia extends EFTargetable(
|
|
|
683
694
|
analyser.maxDecibels = -20;
|
|
684
695
|
|
|
685
696
|
const gainNode = audioContext.createGain();
|
|
686
|
-
gainNode.gain.value =
|
|
697
|
+
gainNode.gain.value = 2.0; // Amplify the signal
|
|
687
698
|
|
|
688
699
|
source.connect(gainNode);
|
|
689
700
|
gainNode.connect(analyser);
|
|
@@ -806,10 +817,9 @@ export class EFMedia extends EFTargetable(
|
|
|
806
817
|
analyser.fftSize = this.fftSize;
|
|
807
818
|
analyser.minDecibels = -90;
|
|
808
819
|
analyser.maxDecibels = -10;
|
|
809
|
-
// analyser.smoothingTimeConstant = 0.4;
|
|
810
820
|
|
|
811
821
|
const gainNode = audioContext.createGain();
|
|
812
|
-
gainNode.gain.value =
|
|
822
|
+
gainNode.gain.value = 3.0;
|
|
813
823
|
|
|
814
824
|
const filter = audioContext.createBiquadFilter();
|
|
815
825
|
filter.type = "bandpass";
|
|
@@ -819,9 +829,9 @@ export class EFMedia extends EFTargetable(
|
|
|
819
829
|
const audioBufferSource = audioContext.createBufferSource();
|
|
820
830
|
audioBufferSource.buffer = audioBuffer;
|
|
821
831
|
|
|
822
|
-
audioBufferSource.connect(
|
|
823
|
-
|
|
824
|
-
|
|
832
|
+
audioBufferSource.connect(filter);
|
|
833
|
+
filter.connect(gainNode);
|
|
834
|
+
gainNode.connect(analyser);
|
|
825
835
|
analyser.connect(audioContext.destination);
|
|
826
836
|
|
|
827
837
|
audioBufferSource.start(0, startTime, 1 / 30);
|
|
@@ -851,7 +861,7 @@ export class EFMedia extends EFTargetable(
|
|
|
851
861
|
|
|
852
862
|
framesData.forEach((frame, frameIndex) => {
|
|
853
863
|
const decayWeight = EFMedia.DECAY_WEIGHT ** frameIndex;
|
|
854
|
-
// biome-ignore lint/style/noNonNullAssertion:
|
|
864
|
+
// biome-ignore lint/style/noNonNullAssertion: Manual bounds check
|
|
855
865
|
weightedSum += frame[i]! * decayWeight;
|
|
856
866
|
weightSum += decayWeight;
|
|
857
867
|
});
|
|
@@ -861,7 +871,7 @@ export class EFMedia extends EFTargetable(
|
|
|
861
871
|
|
|
862
872
|
// Apply frequency weights using instance FREQ_WEIGHTS
|
|
863
873
|
smoothedData.forEach((value, i) => {
|
|
864
|
-
// biome-ignore lint/style/noNonNullAssertion:
|
|
874
|
+
// biome-ignore lint/style/noNonNullAssertion: Manual bounds check
|
|
865
875
|
const freqWeight = this.FREQ_WEIGHTS[i]!;
|
|
866
876
|
smoothedData[i] = Math.min(255, Math.round(value * freqWeight));
|
|
867
877
|
});
|
|
@@ -872,8 +882,70 @@ export class EFMedia extends EFTargetable(
|
|
|
872
882
|
0,
|
|
873
883
|
Math.floor(smoothedData.length / 2),
|
|
874
884
|
);
|
|
875
|
-
this
|
|
876
|
-
|
|
885
|
+
const processedData = this.shouldInterpolateFrequencies
|
|
886
|
+
? processFFTData(slicedData)
|
|
887
|
+
: slicedData;
|
|
888
|
+
this.#frequencyDataCache.set(smoothedKey, processedData);
|
|
889
|
+
return processedData;
|
|
877
890
|
},
|
|
878
891
|
});
|
|
879
892
|
}
|
|
893
|
+
|
|
894
|
+
function processFFTData(fftData: Uint8Array, zeroThresholdPercent = 0.1) {
|
|
895
|
+
// Step 1: Determine the threshold for zeros
|
|
896
|
+
const totalBins = fftData.length;
|
|
897
|
+
const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);
|
|
898
|
+
|
|
899
|
+
// Step 2: Interrogate the FFT output to find the cutoff point
|
|
900
|
+
let zeroCount = 0;
|
|
901
|
+
let cutoffIndex = totalBins; // Default to the end of the array
|
|
902
|
+
|
|
903
|
+
for (let i = totalBins - 1; i >= 0; i--) {
|
|
904
|
+
// biome-ignore lint/style/noNonNullAssertion: Manual bounds check
|
|
905
|
+
if (fftData[i]! < 10) {
|
|
906
|
+
zeroCount++;
|
|
907
|
+
} else {
|
|
908
|
+
// If we encounter a non-zero value, we can stop
|
|
909
|
+
if (zeroCount >= zeroThresholdCount) {
|
|
910
|
+
cutoffIndex = i + 1; // Include this index
|
|
911
|
+
break;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
if (cutoffIndex < zeroThresholdCount) {
|
|
917
|
+
return fftData;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Step 3: Resample the "good" portion of the data
|
|
921
|
+
const goodData = fftData.slice(0, cutoffIndex);
|
|
922
|
+
const resampledData = interpolateData(goodData, fftData.length);
|
|
923
|
+
|
|
924
|
+
return resampledData;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function interpolateData(data: Uint8Array, targetSize: number) {
|
|
928
|
+
const resampled = new Uint8Array(targetSize);
|
|
929
|
+
const dataLength = data.length;
|
|
930
|
+
|
|
931
|
+
for (let i = 0; i < targetSize; i++) {
|
|
932
|
+
// Calculate the corresponding index in the original data
|
|
933
|
+
const ratio = (i / (targetSize - 1)) * (dataLength - 1);
|
|
934
|
+
const index = Math.floor(ratio);
|
|
935
|
+
const fraction = ratio - index;
|
|
936
|
+
|
|
937
|
+
// Handle edge cases
|
|
938
|
+
if (index >= dataLength - 1) {
|
|
939
|
+
// biome-ignore lint/style/noNonNullAssertion: Manual bounds check
|
|
940
|
+
resampled[i] = data[dataLength - 1]!; // Last value
|
|
941
|
+
} else {
|
|
942
|
+
// Linear interpolation
|
|
943
|
+
resampled[i] = Math.round(
|
|
944
|
+
// biome-ignore lint/style/noNonNullAssertion: Manual bounds check
|
|
945
|
+
data[index]! * (1 - fraction) + data[index + 1]! * fraction,
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
return resampled;
|
|
951
|
+
}
|