Skip to content

Commit

Permalink
Separate helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
benfoxall committed Nov 30, 2024
1 parent 03d5edf commit 64cbce1
Showing 1 changed file with 61 additions and 43 deletions.
104 changes: 61 additions & 43 deletions src/Audiovis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,65 +71,83 @@ const Waveform: FC<{ audio: AudioBuffer }> = ({ audio }) => {
const Sonogram: FC<{ audio: AudioBuffer }> = ({ audio }) => {
const canvas = useRef<HTMLCanvasElement>(null);

const fttData = useMemo(() => {
const fftSize = 512; // Ensure this is a power of two
const fft = new FFT(fftSize);
const data = audio.getChannelData(0);
const imageData = useMemo(() => {
const fftSize = 512;
const data = spectrum(audio, fftSize);
const image = spectrumToImage(data, fftSize);

const chunks = Math.floor(audio.length / fftSize);
const target = new Float32Array(fftSize * chunks);

const sample = new Array(fftSize);
const out = fft.createComplexArray();

let max = -Infinity;
return image;
}, [audio]);

for (let i = 0; i < chunks; i++) {
const offset = i * fftSize;
for (let i = 0; i < sample.length; i++) sample[i] = data[i + offset];
fft.realTransform(out, sample);
useEffect(() => {
const ctx = canvas.current?.getContext("2d");
if (ctx) {
ctx.canvas.width = imageData.width;
ctx.canvas.height = imageData.height;

for (let j = 0; j < fftSize; j++) {
target[i * fftSize + j] = Math.abs(out[j]);
max = Math.max(max, target[i * fftSize + j]);
}
ctx.putImageData(imageData, 0, 0);
}
}, [imageData]);

max = max / 4;
return <canvas ref={canvas} className={styles.waveform} />;
};

const image = new ImageData(chunks, fftSize);
// load the spectrum for an entire audio buffer
function spectrum(
audio: AudioBuffer,
fftSize: 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096
): Float32Array {
const fft = new FFT(fftSize);
const data = audio.getChannelData(0);

for (let i = 0; i < chunks; i++) {
for (let j = 0; j < fftSize; j++) {
const idx = j * chunks + i;
const value = target[i * fftSize + j];
const chunks = Math.floor(audio.length / fftSize);
const target = new Float32Array(fftSize * chunks);

const scaledValue = Math.pow(value / max, 0.3); // Apply a power scale
const sample = new Array(fftSize);
const out = fft.createComplexArray();

const [r, g, b, a] = valueToColor(scaledValue);
for (let i = 0; i < chunks; i++) {
const offset = i * fftSize;
for (let i = 0; i < sample.length; i++) sample[i] = data[i + offset];
fft.realTransform(out, sample);

image.data[idx * 4] = r;
image.data[idx * 4 + 1] = g;
image.data[idx * 4 + 2] = b;
image.data[idx * 4 + 3] = a;
}
for (let j = 0; j < fftSize; j++) {
target[i * fftSize + j] = Math.abs(out[j]);
}
}

return image;
}, [audio]);
return target;
}

useEffect(() => {
const ctx = canvas.current?.getContext("2d");
if (ctx) {
ctx.canvas.width = fttData.width;
ctx.canvas.height = fttData.height;
// Render a spectrum as a visible image to be written to canvas
function spectrumToImage(
spectrum: Float32Array,
fftSize: 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096
) {
const chunks = spectrum.length / fftSize;

const image = new ImageData(chunks, fftSize);

const max = spectrum.reduce((a, b) => Math.max(a, b), -Infinity);

ctx.putImageData(fttData, 0, 0);
for (let i = 0; i < chunks; i++) {
for (let j = 0; j < fftSize; j++) {
const idx = j * chunks + i;
const value = spectrum[i * fftSize + j];

const scaledValue = Math.pow(value / max, 0.3); // Apply a power scale

const [r, g, b, a] = valueToColor(scaledValue);

image.data[idx * 4] = r;
image.data[idx * 4 + 1] = g;
image.data[idx * 4 + 2] = b;
image.data[idx * 4 + 3] = a;
}
}, [fttData]);
}

return <canvas ref={canvas} className={styles.waveform} />;
};
return image;
}

function valueToColor(value: number) {
const r = value < 0.5 ? 0 : 255 * (value - 0.5) * 2;
Expand Down

0 comments on commit 64cbce1

Please sign in to comment.