Lissajous Scope

lissajous-scopeVisualizers

A vectorscope figure from a single mono signal.

$ pnpm dlx shadcn@latest add @pluginui/core-lissajous-scope
Loading interactive preview…

Install

Copies the source into your project. Anything it depends on comes with it. Run this in your React project with shadcn initialized; no registry configuration is needed.

Pulls in

use-audio-sourcecontrol-theme
Install using the registry URL
$ pnpm dlx shadcn@latest add https://pluginui.com/r/core-lissajous-scope.json

Usage

Connect the documented props to your application or host.

example.tsx
import { LissajousScope } from "@/components/ui/lissajous-scope"

<LissajousScope analyser={analyserNode} height={260} />

// In a module face, draw from parameter values instead of audio:
<LissajousScope figure={(t) => orbitPoint(t, depth, voices)} />
About Lissajous Scope

Where an oscilloscope plots amplitude against time, this plots a buffer against a phase-shifted copy of itself. One channel can therefore draw circles and rosettes without a stereo pair. The phase shift determines the figure: a quarter period turns the degenerate diagonal into a circle, and other offsets sweep through ellipses and loops. Each frame washes over the last instead of clearing it, so trails decay like phosphor on a real CRT. The geometry is an exported pure function that can be asserted in a test or rendered without a canvas.

Props

PropTypeDefaultDescription
figure(t: number) => { x: number; y: number }—Draw a figure derived from parameter values instead of live audio: t runs 0 to 1 along the trace and each point is in -1..1 space. Sampled at trailLength points and repainted when it changes. Takes precedence over analyser and getData.
pointsreadonly { x: number; y: number }[]—An already sampled figure in -1..1 space, head last. Takes precedence over figure and the audio props.
analyserAnalyserNode—A live analyser to read time-domain data from.
getData(target: Float32Array) => boolean | void—Supply samples yourself instead of using an analyser.
phaseShiftnumber248 / 360Offset between the X and Y reads as a fraction of the buffer, not degrees. This is what sets the figure's shape.
trailLengthnumber450How many points to plot. Clamped to half the buffer, and sampled in strides so the trail always spans the whole frame.
fadeSpeednumber0.1Alpha of the per-frame wash over the previous frame. Lower means longer persistence; 0 lets the figure accumulate.
zoomLevelnumber0.6Size multiplier. 1 makes a full-scale signal span the shorter axis exactly, below 1 insets the figure, above 1 lets it overshoot.
lineWidthnumber1Trace thickness in CSS pixels at the head of the trail.
enableGlowbooleanfalseAdditive multi-pass bloom around the trace. Costs a pass per level.
glowIntensitynumber3Number of bloom passes, 1–5. Only read when enableGlow is on.
showGridbooleantrueDraw the centre crosshair.
colorstring—Trace colour. Defaults to the --uikit-accent token.
heightnumber220Display height in pixels; width defaults to 100%.
pausedbooleanfalseStop the animation loop and hold the last frame.

Interaction

  • Drag with pointer capture, so the gesture keeps tracking past the edge of the control.
  • Shift while dragging for a fine pass; release it mid-gesture and coarse tracking resumes.
  • Double-click resets to defaultValue.
  • Arrow keys step normally, Shift+arrow steps finely, PageUp/PageDown jump by ten, Home/End go to the extremes.
  • Proper slider and switch roles with live aria-valuetext, so a screen reader announces the formatted value rather than the raw number.
  • The scroll wheel adjusts the value while the pointer is over the control; stepped ranges move at least one declared step per notch by default.