Files
oldboy/src/lib/Spectrogram.svelte
2025-10-14 23:21:00 +02:00

134 lines
3.1 KiB
Svelte

<script lang="ts">
import { onMount, onDestroy } from 'svelte';
interface Props {
analyserNode?: AnalyserNode | null;
}
let { analyserNode }: Props = $props();
let container: HTMLDivElement;
let canvas: HTMLCanvasElement;
let canvasContext: CanvasRenderingContext2D | null = null;
let animationFrameId: number | null = null;
let dataArray: Uint8Array | null = null;
let width = $state(800);
let height = $state(600);
$effect(() => {
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
if (analyserNode && canvasContext) {
setupSpectrogram(analyserNode);
}
});
function setupSpectrogram(node: AnalyserNode) {
try {
const bufferLength = node.frequencyBinCount;
dataArray = new Uint8Array(bufferLength);
startDrawing(node);
} catch (error) {
console.error('Failed to setup spectrogram:', error);
}
}
function startDrawing(node: AnalyserNode) {
if (!canvasContext || !dataArray) return;
const draw = () => {
if (!canvasContext || !dataArray || !node || !canvas) return;
animationFrameId = requestAnimationFrame(draw);
node.getByteFrequencyData(dataArray);
const imageData = canvasContext.getImageData(1, 0, canvas.width - 1, canvas.height);
canvasContext.putImageData(imageData, 0, 0);
const barWidth = 1;
const x = canvas.width - barWidth;
for (let i = 0; i < dataArray.length; i++) {
const value = dataArray[i];
const percent = value / 255;
const y = canvas.height - (i / dataArray.length) * canvas.height;
const height = (canvas.height / dataArray.length);
const hue = (1 - percent) * 240;
const saturation = 100;
const lightness = percent * 50;
canvasContext.fillStyle = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
canvasContext.fillRect(x, y - height, barWidth, height);
}
};
draw();
}
function updateSize() {
if (container && canvas) {
const rect = container.getBoundingClientRect();
width = Math.floor(rect.width);
height = Math.floor(rect.height);
}
}
onMount(() => {
if (canvas) {
canvasContext = canvas.getContext('2d');
if (canvasContext) {
canvasContext.fillStyle = '#000';
canvasContext.fillRect(0, 0, canvas.width, canvas.height);
}
}
updateSize();
const resizeObserver = new ResizeObserver(() => {
updateSize();
});
if (container) {
resizeObserver.observe(container);
}
return () => {
resizeObserver.disconnect();
};
});
onDestroy(() => {
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId);
}
});
</script>
<div class="spectrogram" bind:this={container}>
<canvas bind:this={canvas} {width} {height}></canvas>
</div>
<style>
.spectrogram {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: #0a0a0a;
overflow: hidden;
}
canvas {
display: block;
width: 100%;
height: 100%;
background-color: #0a0a0a;
}
</style>