Sinua
Catalog

Signal

Sound as bars, waveforms, a scrolling history or an LED meter.

The signal draws sound: bars, waveforms, a scrolling history or an LED meter. Feed it a voice source and it follows the audio.

See every signal pattern live

When to use which pattern

PatternpatternWhen to use it
BarssignalingLive level: bars that follow the audio.
WaveformwaveformLive voice: layered waveform lines.
ScrollingscrollingVoice over time: a scrolling history of levels.
MetermeteringA precise meter: an LED matrix with peaks.
PlayingplayingA recorded voice message: its waveform and how far it has played.

A voice message

playing draws a recorded message the way a chat app shows one: the waveform, the played part in full ink and the rest dimmed. It fills a wide box instead of a square. SinuaVoiceMessage wraps it with drag-to-seek and a slider for screen readers (not on React Native yet). Sinua decodes no audio: your app passes up to 64 loudness values as envelope, and progress from 0 to 1.

voice-message-react.tsx
import { useState } from "react";
import { SinuaVoiceMessage } from "@sinua/web/react";

// A voice message in a chat bubble: the waveform, the played part in full ink, and a
// drag or tap anywhere on it to seek. Your app plays the audio; Sinua draws it.
export function VoiceMessage({ peaks, audio }: { peaks: number[]; audio: HTMLAudioElement }) {
  const [progress, setProgress] = useState(0);
  audio.ontimeupdate = () => setProgress(audio.currentTime / audio.duration);
  return (
    <SinuaVoiceMessage
      envelope={peaks} // up to 64 loudness values, 0..1
      progress={progress}
      onSeek={(p) => (audio.currentTime = p * audio.duration)}
      style={{ width: 220, height: 40 }}
    />
  );
}

// Sinua decodes no audio. One way to get the peaks in a browser:
export async function peaksOf(url: string, count = 48): Promise<number[]> {
  const data = await (await fetch(url)).arrayBuffer();
  const samples = (await new AudioContext().decodeAudioData(data)).getChannelData(0);
  const step = Math.floor(samples.length / count);
  const peaks = Array.from({ length: count }, (_, i) => {
    let max = 0;
    for (let j = i * step; j < (i + 1) * step; j++) max = Math.max(max, Math.abs(samples[j]));
    return max;
  });
  const top = Math.max(...peaks) || 1;
  return peaks.map((p) => p / top);
}

Props

Every prop, per pattern. Values are your app's data; everything else is style. Defaults can differ per size (20 · 32 · 64).

The signal component is SinuaSignal. Pick a look with pattern; each pattern reads the props below. Sizes are engine sizes (20, 32, 64): defaults can differ per size.

PatternModeSpeedValues (app data)
Bars (signaling)bar1×—
Waveform (waveform)waveform1×—
Scrolling (scrolling)scroll1×—
Meter (metering)matrix1×—
Playing (playing)playback1×progress, envelope

Bars

pattern: "signaling" · mode bar · speed 1× · sizes 20, 32, 64

PropCategoryTypeRangeDefaultDescription
barCountAppearanceinteger1–64 (UI 4–20)9Number of level bars.
barWidthAppearancenumber0.05–1 (UI 0.2–0.9) fraction0.55Bar width as a fraction of each slot.
hueAppearancenumber0–360 deg200Ink hue in degrees; applies once saturation is above 0.
inkAppearancenumber0–1 fraction1How present the whole visual is: 1 draws it as the pattern defines it, lower fades everything including its halos. A voice assistant rests below 1 when idle and comes to full ink when it listens.
minHeightAppearancenumber0–1 (UI 0–0.6) fraction0.2Height of a silent bar, as a fraction of full height.
saturationAppearancenumber0–1 fraction0How far the ink moves from grey toward the hue (0 = the grey ink).

Waveform

pattern: "waveform" · mode waveform · speed 1× · sizes 20, 32, 64

PropCategoryTypeRangeDefaultDescription
hueAppearancenumber0–360 deg200Ink hue in degrees; applies once saturation is above 0.
inkAppearancenumber0–1 fraction1How present the whole visual is: 1 draws it as the pattern defines it, lower fades everything including its halos. A voice assistant rests below 1 when idle and comes to full ink when it listens.
layerCountAppearanceinteger1–6 (UI 1–5)3Stacked waveform layers (fainter behind).
lineWidthAppearancenumber0.005–0.2 (UI 0.01–0.08) fraction0.035Stroke width as a fraction of the frame.
pointCountAppearanceinteger2–512 (UI 16–128)96Points along each waveform line.
saturationAppearancenumber0–1 fraction0How far the ink moves from grey toward the hue (0 = the grey ink).
amplitudeMotionnumber0–0.5 (UI 0.1–0.45) fraction0.26Peak wave height as a fraction of the frame.

Scrolling

pattern: "scrolling" · mode scroll · speed 1× · sizes 20, 32, 64

PropCategoryTypeRangeDefaultDescription
barWidthAppearancenumber0.05–1 (UI 0.2–0.9) fraction0.6Bar width as a fraction of each slot.
hueAppearancenumber0–360 deg200Ink hue in degrees; applies once saturation is above 0.
inkAppearancenumber0–1 fraction1How present the whole visual is: 1 draws it as the pattern defines it, lower fades everything including its halos. A voice assistant rests below 1 when idle and comes to full ink when it listens.
minHeightAppearancenumber0–1 (UI 0–0.4) fraction0.08Height of a silent bar, as a fraction of full height.
saturationAppearancenumber0–1 fraction0How far the ink moves from grey toward the hue (0 = the grey ink).
fadeWidthEnergynumber0.01–1 (UI 0.05–0.8) fraction0.3Width of the fade at the edges, as a fraction of the frame.

Meter

pattern: "metering" · mode matrix · speed 1× · sizes 20, 32, 64

PropCategoryTypeRangeDefaultDescription
columnCountAppearanceinteger1–32 (UI 4–32)12LED columns.
hueAppearancenumber0–360 deg200Ink hue in degrees; applies once saturation is above 0.
inkAppearancenumber0–1 fraction1How present the whole visual is: 1 draws it as the pattern defines it, lower fades everything including its halos. A voice assistant rests below 1 when idle and comes to full ink when it listens.
ledCountAppearanceinteger2–248LEDs per column.
ledSizeAppearancenumber0.2–1 (UI 0.3–1) fraction0.7LED size as a fraction of its cell.
minLevelAppearancenumber0–1 (UI 0–0.5) fraction0.125Level of a silent column.
mirrorAppearanceboolean0Columns grow out from the middle row instead of rising from the bottom.
saturationAppearancenumber0–1 fraction0How far the ink moves from grey toward the hue (0 = the grey ink).

Playing

pattern: "playing" · mode playback · speed 1× · sizes 20, 32, 64

PropCategoryTypeRangeDefaultDescription
barCountAppearanceinteger0–128 (UI 0–96)0How many bars to draw: 0 = one per envelope value; otherwise the envelope is resampled (keeping peaks).
barWidthAppearancenumber0.05–1 (UI 0.2–0.9) fraction0.6Bar width as a fraction of each slot.
hueAppearancenumber0–360 deg200Ink hue in degrees; applies once saturation is above 0.
inkAppearancenumber0–1 fraction1How present the whole visual is: 1 draws it as the pattern defines it, lower fades everything including its halos. A voice assistant rests below 1 when idle and comes to full ink when it listens.
minHeightAppearancenumber0–1 (UI 0–0.4) fraction0.08Height of a silent bar, as a fraction of full height.
playheadAppearanceboolean0Draw a thin line at the playback position.
saturationAppearancenumber0–1 fraction0How far the ink moves from grey toward the hue (0 = the grey ink).
unplayedOpacityAppearancenumber0–1 fraction0.35Opacity of the bars not played yet, relative to the played ones.
progressInputsnumber0–1 fraction0Playback position, 0 to 1: bars before it are played (full ink), the rest dimmed.
Advanced props (1)
PropCategoryTypeRangeDefaultDescription
envelopeInputsnumber[] (1–64)0–1 fraction[]The recorded clip's loudness, oldest first, 0 to 1 per value (up to 64; resample longer clips). Without it a fixed sample message is drawn.

On this page