SDK API reference
Flat export-by-export reference for hypercolor: effects, controls, audio, palettes, math, layout, motion, gauges, faces.
This is the complete export surface of hypercolor, generated directly from sdk/packages/core/src/index.ts. Every name below is exported from the package root, so a single import line reaches all of it:
import { canvas, effect, face, num, paletteControl, audio, lerp } from 'hypercolor'The package is hypercolor version 0.1.0. It is pre-release and not yet published to npm β scaffolded workspaces resolve it through a local file: spec pointing at this checkout. See Setup for how that wires up.
The narrative guides cover the common surface in depth: TypeScript canvas effects, Controls, Palettes, Audio, GLSL effects, and Display faces. This page is the flat index β reach for it when you want every signature in one place, including the math, layout, motion, and gauge helper families the guides do not enumerate.
πDeclarative API
The three entry points. Each one registers the effect as a side effect of the call and returns void. You export default the call, but the runtime value of that default export is undefined β registration happens through a global the build harness reads.
πcanvas
function canvas(
name: string,
controls: ControlMap,
renderFn: DrawFn | FactoryFn,
options?: CanvasFnOptions,
): void
canvas.stateful(
name: string,
controls: ControlMap,
factory: FactoryFn,
options?: CanvasFnOptions,
): voidCanvas2D effects. renderFn is either a stateless draw function or a stateful factory. Detection is purely by arity: renderFn.length === 0 is treated as a factory (runs once, returns the per-frame draw function); arity of one or more is a stateless draw function called every frame. Use canvas.stateful() to force the factory path when the heuristic fights you.
type DrawFn = (
ctx: CanvasRenderingContext2D,
time: number,
controls: Record<string, unknown>,
) => void
type FactoryFn = () => DrawFntime is in seconds. The canvas never auto-clears β your draw function owns clearing (opaque fillRect for clean frames, semi-transparent for trails). Read ctx.canvas.width / ctx.canvas.height every frame; the daemon renders at 640Γ480 by default but the size is user-configurable.
interface CanvasFnOptions {
description?: string
author?: string
audio?: boolean // required when you read audio β enforced at build time
screen?: boolean // opt into screen-zone sampling
category?: string
builtinId?: string
designBasis?: DesignBasis // author against a fixed grid, scale automatically
presets?: PresetDef[]
}audio: true is not cosmetic. If your source touches audio(, ctx.audio, getAudioData(, or engine.audio without it, the build fails with an audio reactivity validation error. Same contract for effect() shaders.
πeffect
function effect(
name: string,
shader: string, // fragment shader GLSL source
controls: ControlMap,
options?: EffectFnOptions,
): voidGLSL fragment-shader effects. These run as WebGL2 inside Servo, not as a native wgpu lane β the SDK bundles the GLSL into an HTML/WebGL artifact. There is no runnable GPU shader path in the engine today; treat wgpu as future work. See GLSL effects for the uniform contract.
Each control maps to a uniform named i + PascalCase of the key (trailLength β iTrailLength), overridable per control. Every control except asset must have a matching uniform in the shader or the build fails.
interface EffectFnOptions {
description?: string
author?: string
audio?: boolean
screen?: boolean
category?: string
builtinId?: string
presets?: PresetDef[]
vertexShader?: string
preserveDrawingBuffer?: boolean
setup?: (ctx: ShaderContext) => void | Promise<void>
frame?: (ctx: ShaderContext, time: number) => void
}
interface ShaderContext {
readonly controls: Record<string, unknown>
readonly audio: AudioData | null // getter β pulls fresh data each access
readonly gl: WebGL2RenderingContext
readonly program: WebGLProgram
readonly width: number
readonly height: number
registerUniform(name: string, value: UniformValue): void
setUniform(name: string, value: UniformValue): void
}πface
function face(
name: string,
controls: ControlMap,
options: FaceOptions,
setupFn: (ctx: FaceContext) => FaceUpdateFn,
): voidFull-screen HTML faces for device LCDs (pump caps, strips, panels). setupFn runs once and returns the per-frame update function. Data sources are opt-in through FaceOptions (audio, media, net, lighting; sensors is always present), and per-shape variants can be supplied via variants. See Display faces for the full contract, the Servo CSS matrix (flexbox yes, CSS grid no), and the two canonical displays every face must handle.
πShared types
type ControlMap // record of control key β shorthand or ControlSpec
type ControlShorthand // [min,max,default] | [min,max,default,step] | string[] | bool | "#hex" | string | number
type ControlSpec // the resolved spec a factory produces
type PresetDef = { name: string; description?: string; controls: Record<string, unknown> }πControl factories
Every factory returns a ControlSpec. You can pass shorthand instead and the SDK infers the type β see Controls for the inference rules.
num(label, range: readonly [number, number], defaultValue: number, opts?: NumOptions): ControlSpec<'number'>
combo(label, values: readonly string[], opts?: ComboOptions): ControlSpec<'combobox'>
paletteControl(label, values: readonly string[], opts?: PaletteControlOptions): ControlSpec<'combobox'>
toggle(label, defaultValue: boolean, opts?: ToggleOptions): ControlSpec<'boolean'>
color(label, defaultValue: string, opts?: ColorOptions): ControlSpec<'color'>
hue(label, range: readonly [number, number], defaultValue: number, opts?: HueOptions): ControlSpec<'hue'>
text(label, defaultValue: string, opts?: TextOptions): ControlSpec<'textfield'>
asset(label, mediaKind?: MediaKind, opts?: AssetOptions): ControlSpec<'asset'>
sensor(label, defaultValue: string, opts?: SensorOptions): ControlSpec<'sensor'>
rect(label, defaultValue: RectValue, opts?: RectOptions): ControlSpec<'rect'>
font(label, defaultFamily: string, opts?: FontOptions): ControlSpec<'combobox'>Notes that bite if you miss them:
paletteControlis a distinct factory fromcombo. It setsmeta.palette = true, which triggers the palette magic. In canvas effects the control value becomes aPaletteFn; in shaders it becomes an integer index uniform (iPalette). A plaincombodoes not get this treatment.fontis sugar overcombowhose values are font-family names. The default family is auto-prepended if it is not already in the list. The bundled default set isJetBrains Mono,Inter,Orbitron,Audiowide,Bebas Neue,DM Sans,Exo 2,Roboto Condensed,Rajdhani,Space Mono,Space Grotesk,Sora.sensorreturns a sensor-label string ("cpu_temp"); read the live value withengine.getSensorValue(label). Mostly used in faces.rectreturns aRectValue({ x, y, width, height }, normalized[0,1]).MediaKindis'any' | 'image' | 'video' | 'lottie'.
interface RectValue { x: number; y: number; width: number; height: number }
type MediaKind = 'any' | 'image' | 'video' | 'lottie'πControl value helpers
getControlValue<T>(propertyName: string, defaultValue: T): T
getAllControls<T extends Record<string, unknown>>(controls: T): T
normalizeSpeed(speed: number): number // max(0.2, (speed/5) ** 1.5)
normalizePercentage(value: number, defaultValue?: number, minValue?: number): number // value / 100
comboboxValueToIndex(value: string | number, options: string[], defaultIndex?: number): number
boolToInt(value: boolean | number): numberspeed is the only magic-normalized control name. A slider value of 5 maps to 1.0 through normalizeSpeed. These helpers are exposed for when you author a control manually and need to reproduce the same normalization.
Exported control-definition types (the resolved shapes the runtime consumes): BaseControls, ControlValues, ControlDefinition, ControlDefinitionType, NumberControlDefinition, ComboboxControlDefinition, BooleanControlDefinition, ColorControlDefinition, HueControlDefinition, TextFieldControlDefinition, AssetControlDefinition, RectControlDefinition, plus the option interfaces (AssetOptions, FontOptions, PaletteControlOptions, RectOptions, SensorOptions).
πAudio
import { audio } from 'hypercolor'
// `audio` is getAudioData re-exported. Both names work.
function getAudioData(): AudioData // pull model β call inside draw, every frame
function getScreenZoneData(): ScreenZoneData // 28Γ20 = 560-point screen grid
const FFT_SIZE = 200 // frequency / frequencyRaw / frequencyWeighted length
const MEL_BANDS = 24 // melBands / melBandsNormalized length
const PITCH_CLASSES = 12 // chromagram length (C..B)AudioData is a wide per-frame struct. Key fields (all 0β1 unless noted): level, levelRaw (dB), bass, mid, treble, beat, beatPulse (decaying β prefer this over raw beat), beatPhase, beatConfidence, tempo (BPM), frequency (200, Float32Array), frequencyRaw (200, Int8Array), frequencyWeighted (200), melBands / melBandsNormalized (24), chromagram (12), dominantPitch (0β11), harmonicHue (0β360), chordMood (β1 minor β +1 major), brightness (spectral centroid), spectralFlux, onset, onsetPulse, bassEnv / midEnv / trebleEnv, swell, momentum. See Audio for the full table and idioms.
The TypeScript field names here (camelCase, tempo, frequency) differ from the Rust AudioData used by native effects (snake_case, bpm, spectrum). Shaders also see only a subset β no chromagram, melBands, or dominantPitch uniforms. See Native Rust effects for the Rust-side names.
πAudio helpers
getBassLevel(frequency: Float32Array): number
getMidLevel(frequency: Float32Array): number
getTrebleLevel(frequency: Float32Array): number
getFrequencyRange(frequency: Float32Array, start: number, end: number): number
getMelRange(audio: AudioData, startBand: number, endBand: number): number
getPitchEnergy(audio: AudioData, pitchClass: number | string): number
getPitchClassName(pitchClass: number): string
getPitchClassIndex(name: string): number
pitchClassToHue(pitchClass: number): number // Circle of Fifths β hue
getHarmonicColor(audio: AudioData, saturation?: number, lightness?: number): [number, number, number]
getMoodColor(audio: AudioData, ...): [number, number, number]
getBeatAnticipation(audio: AudioData, anticipation?: number): number
isOnBeat(audio: AudioData, division?: number, tolerance?: number): boolean
normalizeAudioLevel(level: number): number
normalizeFrequencyBin(value: number, max?: number): number
smoothValue(currentValue: number, previousValue: number, smoothing?: number): number
hslToRgb(h: number, s: number, l: number): [number, number, number]πPalettes
paletteNames(): string[] // every registered palette name
getPalette(name: string): PaletteEntry | undefined // stops + metadata, undefined if unknown
createPaletteFn(name: string): PaletteFn // t in [0,1] β CSS color
samplePalette(name: string, t: number): [number, number, number]
samplePaletteCSS(name: string, t: number, alpha?: number): string
type PaletteFn = (t: number) => string
type PaletteEntry // { stops, background, ... } β the registry shapePalette interpolation is Oklab (256-entry LUT, cached per name). An unknown palette name samples to magenta so the mistake is visible on the hardware. The registry lives in sdk/shared/palettes.json; Palettes lists the named entries.
πBase classes
For authors who want the class form instead of canvas() / effect():
class BaseEffect // shared lifecycle + control re-poll
class CanvasEffect // Canvas2D base (designBasis, scaleContext())
class WebGLEffect // WebGL2 shader base
type EffectConfig
type CanvasEffectConfig
type WebGLEffectConfig
type UniformValueThe declarative functions generate subclasses of these; reach for the classes only when you need lifecycle control the functions do not expose.
πMath
clamp(value, min, max): number
saturate(value): number // clamp to [0,1]
lerp(a, b, t): number
mix(a, b, t): number // alias of lerp
inverseLerp(a, b, value): number
step(edge, value): number
smoothstep(edge0, edge1, value): number
smoothApproach(current, target, lambda, dt): number // frame-rate-independent
smoothAsymmetric(...): number
// Easings (number β number, domain [0,1])
easeInQuad, easeOutQuad, easeInOutQuad
easeInCubic, easeOutCubic, easeInOutCubicπScale context
scaleContext(source: CanvasSize, designBasis?: DesignBasis): ScaleContext
interface DesignBasis { width: number; height: number }
interface ScaleContext {
width: number; height: number
sx: number; sy: number; scale: number
dx(x): number; dy(y): number // design coord β live pixels
dw(w): number; dh(h): number // design size β live pixels
ds(value): number // uniform scale (radii, strokes, fonts)
nx(t): number; ny(t): number // normalized [0,1] β live pixels
}Build one per frame inside your draw function. With no designBasis the scale is the identity, so dx/dy/dw/dh echo their inputs. This is how effects stay pixel-identical when the daemonβs canvas size changes.
πLayout
Geometry helpers, primarily for faces but usable anywhere. All operate on plain Rect / Point objects.
grid(area: Rect, cols: number, rows: number, gap?: number): Rect[]
rail(area: Rect, n: number, gap?: number): Rect[] // single-axis split
ring(area: Rect, n: number, options?: RingOptions): Point[]
polar(center: Point, radius: number, angle: number): Point
center(area: Rect): Point
inset(area: Rect, amount: number): Rect
anchor(area: Rect, position: AnchorPosition, size: AnchorSize, margin?: number): Rect
fitText(ctx: CanvasRenderingContext2D, text: string, rect: Rect, options?: FitTextOptions): number
interface Rect { x: number; y: number; width: number; height: number }
interface Point { x: number; y: number }
type AnchorPosition
type AnchorSize
type RingOptions
type FitTextOptionsπMotion
Stateful animation primitives. Each holds internal state and is advanced by your update call with a delta or current time. Construct once (in a stateful factory or face setup), then drive it each frame.
class Spring // critically-dampable spring; update(target, dt) β number
spring(initial: number, options?: SpringOptions): Spring
class Tween // timed interpolation; done(elapsed) β boolean
tween(from: number, to: number, duration: number, easing?: EasingFn): Tween
class Smoothed // exponential smoothing by half-life; update(target, dt) β number
smoothed(initial: number, halflife: number): Smoothed
class Transition // eased transition that retargets on change; update(target, now) β number
transitionOnChange(duration: number, easing?: EasingFn): Transition
class Timeline // named, sequenced keyframes; add(name, start, duration, easing)
timeline(): Timeline
// Easings (EasingFn = (t: number) => number)
linear, easeInQuad, easeOutQuad, easeInOutQuad
easeInCubic, easeOutCubic, easeInOutCubic
easeOutBack, easeOutElastic
type EasingFn
type SpringOptionsFPS is adaptive across five tiers. Always drive motion off a time or delta (time_secs, performance.now() deltas, dt), never off frame counts, so the animation is correct at 15, 30, and 60 FPS alike.
πGauges
Drawing helpers for sensor dashboards and faces. The plain functions render once; the create* variants return animated objects that ease toward new values across frames.
arcGauge(ctx: CanvasRenderingContext2D, opts: ArcGaugeOptions): void
barGauge(ctx: CanvasRenderingContext2D, opts: BarGaugeOptions): void
ringGauge(ctx: CanvasRenderingContext2D, opts: RingGaugeOptions): void
sparkline(ctx: CanvasRenderingContext2D, opts: SparklineOptions): void
createArcGauge(base: Omit<ArcGaugeOptions, 'value'>, animate?: GaugeAnimateOptions): AnimatedArcGauge
createBarGauge(base: Omit<BarGaugeOptions, 'value'>, animate?: GaugeAnimateOptions): AnimatedBarGauge
createRingGauge(base, animate?: GaugeAnimateOptions): AnimatedRingGauge
class ValueHistory // rolling buffer for sparklines; push(value), values(), min, max
type ArcGaugeOptions; type BarGaugeOptions; type RingGaugeOptions
type SparklineOptions; type SparklineBand; type GaugeAnimateOptions
type AnimatedArcGauge; type AnimatedBarGauge; type AnimatedRingGaugeπFaces
Face-specific exports. The face() entry point lives under Declarative API; these are the supporting types and a token kit.
// Types
type FaceContext; type FaceUpdateFn; type FaceOptions; type FaceVariants
type FaceDisplayInfo; type FaceDisplayShape; type FaceDisplayClass
type InjectedDisplayDescriptor
type AudioAccessor; type MediaAccessor; type NetAccessor
type LightingAccessor; type SensorAccessor
type MediaInfo; type NetInfo; type LightingInfo; type SensorReading
type FaceDataSources
// Token kit (from hypercolor, sourced from faces/tokens)
const palette // named face color ramps
const spacing
const radius
const sensorColors
parseHex(hex: string): [number, number, number, number]
lerpColor(a: string, b: string, t: number): string
colorByValue(value: number, stops: readonly string[]): string
withAlpha(color: string, alpha: number): string // hex input only
withGlow(ctx, color: string, intensity: number, fn: () => void): voidπUtilities and initialization
// Debug
createDebugLogger(namespace: string, enabled?: boolean)
debug(...args: unknown[]): void
printStartupBanner(): void
type HSLColor; type RGBColor; type UpdateFunction
// Initialization (called for you by canvas/effect/face β rarely needed directly)
initializeEffect(initFunction: () => void, options?: InitOptions): void
type InitializationMode = 'immediate' | 'deferred' | 'metadata-only'
interface InitOptions { mode?: InitializationMode; instance?: unknown }metadata-only mode is what the build harness sets to extract control and preset metadata without running the effect. You will not call initializeEffect by hand in normal authoring.
πWhere to go next
- Build and ship the artifact: Dev workflow.
- The authoring CLI flags (
build/validate/install/add) are covered in the dev-workflow and setup pages; it is distinct from the systemhypercolorCLI documented under the API section. - Drive a running daemon from an agent over the CLI or MCP β see the Agents & MCP section once it lands, or the MCP server notes under the API section.