|
1 | 1 | import { FC, useEffect, useMemo, useRef, useState } from "react";
|
2 | 2 | import styles from "./Audiovis.module.css";
|
3 | 3 | import { useAudioCtx } from "./AudioCtxCtx";
|
4 |
| -import FFT from "fft.js"; |
| 4 | +import { spectrum, spectrumToImage } from "./util"; |
5 | 5 |
|
6 | 6 | export const Audiovis: FC<{ srcObject: Blob }> = ({ srcObject }) => {
|
7 | 7 | const url = useMemo(() => URL.createObjectURL(srcObject), [srcObject]);
|
@@ -91,68 +91,3 @@ const Sonogram: FC<{ audio: AudioBuffer }> = ({ audio }) => {
|
91 | 91 |
|
92 | 92 | return <canvas ref={canvas} className={styles.waveform} />;
|
93 | 93 | };
|
94 |
| - |
95 |
| -// load the spectrum for an entire audio buffer |
96 |
| -function spectrum( |
97 |
| - audio: AudioBuffer, |
98 |
| - fftSize: 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096 |
99 |
| -): Float32Array { |
100 |
| - const fft = new FFT(fftSize); |
101 |
| - const data = audio.getChannelData(0); |
102 |
| - |
103 |
| - const chunks = Math.floor(audio.length / fftSize); |
104 |
| - const target = new Float32Array(fftSize * chunks); |
105 |
| - |
106 |
| - const sample = new Array(fftSize); |
107 |
| - const out = fft.createComplexArray(); |
108 |
| - |
109 |
| - for (let i = 0; i < chunks; i++) { |
110 |
| - const offset = i * fftSize; |
111 |
| - for (let i = 0; i < sample.length; i++) sample[i] = data[i + offset]; |
112 |
| - fft.realTransform(out, sample); |
113 |
| - |
114 |
| - for (let j = 0; j < fftSize; j++) { |
115 |
| - target[i * fftSize + j] = Math.abs(out[j]); |
116 |
| - } |
117 |
| - } |
118 |
| - |
119 |
| - return target; |
120 |
| -} |
121 |
| - |
122 |
| -// Render a spectrum as a visible image to be written to canvas |
123 |
| -function spectrumToImage( |
124 |
| - spectrum: Float32Array, |
125 |
| - fftSize: 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096 |
126 |
| -) { |
127 |
| - const chunks = spectrum.length / fftSize; |
128 |
| - |
129 |
| - const image = new ImageData(chunks, fftSize); |
130 |
| - |
131 |
| - const max = spectrum.reduce((a, b) => Math.max(a, b), -Infinity); |
132 |
| - |
133 |
| - for (let i = 0; i < chunks; i++) { |
134 |
| - for (let j = 0; j < fftSize; j++) { |
135 |
| - const idx = j * chunks + i; |
136 |
| - const value = spectrum[i * fftSize + j]; |
137 |
| - |
138 |
| - const scaledValue = Math.pow(value / max, 0.3); // Apply a power scale |
139 |
| - |
140 |
| - const [r, g, b, a] = valueToColor(scaledValue); |
141 |
| - |
142 |
| - image.data[idx * 4] = r; |
143 |
| - image.data[idx * 4 + 1] = g; |
144 |
| - image.data[idx * 4 + 2] = b; |
145 |
| - image.data[idx * 4 + 3] = a; |
146 |
| - } |
147 |
| - } |
148 |
| - |
149 |
| - return image; |
150 |
| -} |
151 |
| - |
152 |
| -function valueToColor(value: number) { |
153 |
| - const r = value < 0.5 ? 0 : 255 * (value - 0.5) * 2; |
154 |
| - const g = value < 0.5 ? 255 * value * 2 : 255 * (1 - value) * 2; |
155 |
| - const b = value < 0.5 ? 255 * (1 - value * 2) : 0; |
156 |
| - const a = value > 0.2 ? 255 : value * 5 * 255; |
157 |
| - return [r, g, b, a]; |
158 |
| -} |
0 commit comments