Signal Console

Client

Independent Study

Category

Creative Technology + Interaction Design

Year

2026

Three-dimensional Rhythm, Texture and Pitch mood space developed for the Being Good music classification system.

Signal Console is an interactive visual instrument inspired by analog signal equipment, radar displays, synthesizer interfaces, and laboratory control systems. Five rendering engines transform configurable signal sources through a fictional hardware console designed for direct, exploratory interaction.

System

Creative Technology · Interaction Design · Front-End Development · Generative / Canvas Graphics · Prototyping

Built with React, TypeScript, Vite, HTML Canvas, Vitest, and Playwright. Five pattern engines (Dot Field, Node Mesh, Scanline Field, Echo Trails, and Type Distortion) process four signal sources: Grid, Noise Field, Text Sample, and Image Input. Apollo Avionics, Soviet Radar, and Digital Synth Terminal shift the instrument’s surrounding presentation without changing its core behavior.

Interaction

Four rotary controls shape Density, Phase, Decay, and Frequency through mouse, touch, pen, and keyboard input. Echo Trails and Type Distortion respond directly to pointer movement in the signal field. A bundled sample supports Image Input alongside compatible external image URLs, while calibration and signal-drift overlays alter the rendered field.

From prototype to working software

Signal Console began as a Figma Make prototype. I took it into a conventional React / TypeScript / Vite application, reducing the codebase from 59 source files to 8 and the runtime dependencies from 55 to 2: React and ReactDOM. I removed the generated UI scaffold and obsolete Figma Make configuration, fixed canvas lifecycle and stale-state bugs, and replaced the external runtime image dependency with a bundled local sample.


Signal Rendering Viewport

const dx = baseX - mx
const dy = baseY - my
const dist = Math.sqrt(dx * dx + dy * dy)

const cursorInfluence = Math.max(
  0,
  1 - dist / cursorRadius
)

const waveX =
  Math.sin(t * freq * 2 + r * ph * 3 + c) *
  (2 + dec * 15) *
  (1 + cursorInfluence * 3)

const waveY =
  Math.cos(t * freq * 1.5 + c * ph * 3 + r) *
  (2 + dec * 15) *
  (1 + cursorInfluence * 3)

let charSpeed = t * 3 + r * 7 + c * 3
charSpeed += cursorInfluence * 20

const char =
  chars[Math.floor(charSpeed % chars.length)]

ctx.fillText(
  char,
  baseX + waveX,
  baseY + waveY
)
const dx = baseX - mx
const dy = baseY - my
const dist = Math.sqrt(dx * dx + dy * dy)

const cursorInfluence = Math.max(
  0,
  1 - dist / cursorRadius
)

const waveX =
  Math.sin(t * freq * 2 + r * ph * 3 + c) *
  (2 + dec * 15) *
  (1 + cursorInfluence * 3)

const waveY =
  Math.cos(t * freq * 1.5 + c * ph * 3 + r) *
  (2 + dec * 15) *
  (1 + cursorInfluence * 3)

let charSpeed = t * 3 + r * 7 + c * 3
charSpeed += cursorInfluence * 20

const char =
  chars[Math.floor(charSpeed % chars.length)]

ctx.fillText(
  char,
  baseX + waveX,
  baseY + waveY
)
const dx = baseX - mx
const dy = baseY - my
const dist = Math.sqrt(dx * dx + dy * dy)

const cursorInfluence = Math.max(
  0,
  1 - dist / cursorRadius
)

const waveX =
  Math.sin(t * freq * 2 + r * ph * 3 + c) *
  (2 + dec * 15) *
  (1 + cursorInfluence * 3)

const waveY =
  Math.cos(t * freq * 1.5 + c * ph * 3 + r) *
  (2 + dec * 15) *
  (1 + cursorInfluence * 3)

let charSpeed = t * 3 + r * 7 + c * 3
charSpeed += cursorInfluence * 20

const char =
  chars[Math.floor(charSpeed % chars.length)]

ctx.fillText(
  char,
  baseX + waveX,
  baseY + waveY
)

Responsive, accessible interaction

Desktop preserves the three-panel hardware-console composition; tablet reorganizes around the signal viewport; mobile becomes a single-column instrument without page-level horizontal overflow at tested sizes. Rotary controls use Pointer Events and keyboard controls with arrow keys, Home, and End. The interface exposes button, radio, switch, and slider semantics, visible focus states, the current engine and source, reduced-motion support, and rendering that pauses when the document is hidden. Device-pixel-ratio rendering is capped to avoid unnecessary mobile cost.