Let's fucking go
This commit is contained in:
133
src/lib/Spectrogram.svelte
Normal file
133
src/lib/Spectrogram.svelte
Normal file
@ -0,0 +1,133 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user