Appearance
Audio Level Indicator Component
- File: src/components/app/audioLevelIndicator.svelte
Overview
The Audio Level Indicator component provides a visual representation of audio input levels using a canvas-based bar graph. It analyzes audio streams in real-time and displays the audio levels using animated bars.
Core Features
1. Audio Analysis
svelte
function analyze(stream: MediaStream) {
if (!stream) {
level = 0;
maxLevel = 0;
oldLevel = 0;
return;
}
// Clean up previous context
if (audioContext) {
audioContext.close();
}
// Create new audio context and nodes
audioContext = new AudioContext();
var microphone = audioContext.createMediaStreamSource(stream);
var javascriptNode = audioContext.createScriptProcessor(1024, 1, 1);
// Connect nodes
microphone.connect(javascriptNode);
javascriptNode.connect(audioContext.destination);
// Process audio data
javascriptNode.onaudioprocess = function (event) {
var channelData = event.inputBuffer.getChannelData(0);
var sum = 0.0;
// Calculate RMS value
for (var i = 0; i < channelData.length; ++i) {
sum += channelData[i] * channelData[i];
}
var current = Math.sqrt(sum / channelData.length);
maxLevel = Math.max(maxLevel, current);
current = Math.max(current, oldLevel - 0.008);
oldLevel = current;
// Calculate normalized level
level = (length * current) / maxLevel;
}
}2. Bar Generation
svelte
export function createBars(
length: number,
startX: number,
startY: number,
width: number,
height: number,
gap: number
) {
return Array.from({ length }).map((_, i) => {
const x = startX + i * width + gap * i;
return [
[x, startY],
[x + width, startY],
[x + width, startY + height],
[x, startY + height],
];
});
}3. Canvas Drawing
svelte
function draw(level: number) {
if (!canvas) return;
const ctx = canvas.getContext("2d");
bars.forEach((x, i) => {
drawPolygon(ctx, x, 3);
const style = i < level ? activeColor : inactiveColor;
ctx.strokeStyle = style;
ctx.stroke();
ctx.fillStyle = style;
ctx.fill();
});
}Utility Functions
1. Polygon Drawing
svelte
export function drawPolygon(ctx, pts, radius) {
if (radius > 0) {
pts = getRoundedPoints(pts, radius);
}
var i, pt, len = pts.length;
for (i = 0; i < len; i++) {
pt = pts[i];
if (i == 0) {
ctx.beginPath();
ctx.moveTo(pt[0], pt[1]);
} else {
ctx.lineTo(pt[0], pt[1]);
}
if (radius > 0) {
ctx.quadraticCurveTo(pt[2], pt[3], pt[4], pt[5]);
}
}
ctx.closePath();
}2. Point Rounding
svelte
function getRoundedPoints(pts, radius) {
var i1, i2, i3, p1, p2, p3, prevPt, nextPt,
len = pts.length,
res = new Array(len);
for (i2 = 0; i2 < len; i2++) {
i1 = i2 - 1;
i3 = i2 + 1;
if (i1 < 0) i1 = len - 1;
if (i3 == len) i3 = 0;
p1 = pts[i1];
p2 = pts[i2];
p3 = pts[i3];
prevPt = getRoundedPoint(p1[0], p1[1], p2[0], p2[1], radius, false);
nextPt = getRoundedPoint(p2[0], p2[1], p3[0], p3[1], radius, true);
res[i2] = [prevPt[0], prevPt[1], p2[0], p2[1], nextPt[0], nextPt[1]];
}
return res;
}Component Implementation
1. State Management
svelte
let canvas: HTMLCanvasElement;
let bars = createBars(length, startX, startY, barWidth, barHeight, gap);
let level: number;
let audioContext: AudioContext;
var maxLevel = 0;
var oldLevel = 0;2. Reactive Statements
svelte
$: analyze(stream)
$: draw(level)3. UI Template
svelte
<Tooltip placement="bottom">
<canvas
bind:this={canvas}
width="{canvasWidth}px"
height="{canvasHeight}px"
/>
<div slot="content">
{`Audio Level: ${level?.toLocaleString()}`}
</div>
</Tooltip>Usage Example
svelte
<AudioLevelIndicator
length={10}
gap={7}
startX={5}
startY={5}
barWidth={10}
barHeight={20}
stream={audioStream}
activeColor="#0f0"
inactiveColor="#fff"
canvasWidth={250}
canvasHeight={30}
/>