Files
rsgp/src/lib/components/WaveformDisplay.svelte
2025-10-10 23:13:57 +02:00

127 lines
2.9 KiB
Svelte

<script lang="ts">
import { onMount } from 'svelte';
interface Props {
buffer: AudioBuffer | null;
color?: string;
playbackPosition?: number;
onclick?: () => void;
}
let { buffer, color = '#646cff', playbackPosition = 0, onclick }: Props = $props();
let canvas: HTMLCanvasElement;
onMount(() => {
const resizeObserver = new ResizeObserver(() => {
if (canvas) {
updateCanvasSize();
draw();
}
});
resizeObserver.observe(canvas.parentElement!);
return () => resizeObserver.disconnect();
});
$effect(() => {
buffer;
color;
playbackPosition;
draw();
});
function updateCanvasSize() {
const parent = canvas.parentElement!;
canvas.width = parent.clientWidth;
canvas.height = parent.clientHeight;
}
function handleClick() {
if (onclick) {
onclick();
}
}
function draw() {
if (!canvas) return;
const ctx = canvas.getContext('2d')!;
const width = canvas.width;
const height = canvas.height;
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, width, height);
if (!buffer) {
ctx.fillStyle = '#666';
ctx.font = '24px system-ui';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('No waveform generated', width / 2, height / 2);
return;
}
const numChannels = buffer.numberOfChannels;
const channelHeight = height / numChannels;
const step = Math.ceil(buffer.length / width);
for (let channel = 0; channel < numChannels; channel++) {
const data = new Float32Array(buffer.length);
buffer.copyFromChannel(data, channel);
const channelTop = channel * channelHeight;
const channelCenter = channelTop + channelHeight / 2;
const amp = channelHeight / 2;
ctx.strokeStyle = color;
ctx.lineWidth = 2;
ctx.beginPath();
for (let i = 0; i < width; i++) {
const index = i * step;
const value = data[index] || 0;
const y = channelCenter - value * amp;
if (i === 0) {
ctx.moveTo(i, y);
} else {
ctx.lineTo(i, y);
}
}
ctx.stroke();
if (channel < numChannels - 1) {
ctx.strokeStyle = '#333';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, channelTop + channelHeight);
ctx.lineTo(width, channelTop + channelHeight);
ctx.stroke();
}
}
if (playbackPosition > 0 && buffer) {
const duration = buffer.length / buffer.sampleRate;
const x = (playbackPosition / duration) * width;
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, height);
ctx.stroke();
}
}
</script>
<canvas bind:this={canvas} onclick={handleClick} style="cursor: pointer;"></canvas>
<style>
canvas {
width: 100%;
height: 100%;
}
</style>