init
This commit is contained in:
126
src/lib/components/WaveformDisplay.svelte
Normal file
126
src/lib/components/WaveformDisplay.svelte
Normal file
@ -0,0 +1,126 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user