Skip to content

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}
/>

Released under the MIT License.