# Sine Wave Generator (full reference)
> A ~3KB gzipped, zero-runtime-dependency JavaScript library that renders animated sine waves on an HTML5 Canvas 2D context, with an optional Web Audio API audio-reactivity layer (AudioSync) and an optional React hook (useSineWaveGenerator).
Package: `@sebastienrousseau/sine-wave-generator` (npm). Version: 0.0.3. License: Apache-2.0.
Homepage: https://sine-wave-generator.com
Repository: https://github.com/sebastienrousseau/sine-wave-generator
This file inlines the full API reference and guide content for tools that can't follow links. For narrative/example content see https://sine-wave-generator.com/llms.txt and the linked pages.
## Install
```bash
npm install @sebastienrousseau/sine-wave-generator
# or: yarn add @sebastienrousseau/sine-wave-generator
# or: pnpm add @sebastienrousseau/sine-wave-generator
```
No bundler or build step is required to consume the library: drop `src/sine-wave-generator.js` in with a `
```
## Quick start — CommonJS / ESM
```js
// CommonJS
const { SineWaveGenerator } = require("@sebastienrousseau/sine-wave-generator");
// ESM
import { SineWaveGenerator } from "@sebastienrousseau/sine-wave-generator";
const generator = new SineWaveGenerator({
el: "#sine",
maxPixelRatio: 2,
waves: [{ amplitude: 26, wavelength: 120, speed: 0.8 }],
});
generator.start();
```
`AudioSync` is available the same way from the `/audio-sync` subpath:
```js
const { AudioSync } = require("@sebastienrousseau/sine-wave-generator/audio-sync");
```
The package ships both a CommonJS and a native ESM build (resolved automatically via `package.json` exports), plus bundled TypeScript types. Set `strokeStyle` to `null` on a wave to use the built-in gradient stroke.
## Quick start — React
An optional `useSineWaveGenerator` hook is available from the `/use-sine-wave-generator` subpath (raw source, not bundled — `react` is a peer dependency, only required if you import this). It creates the generator on mount, starts it, and destroys it on unmount.
```jsx
import { useSineWaveGenerator } from "@sebastienrousseau/sine-wave-generator/use-sine-wave-generator";
function AmbientBackground() {
const { canvasRef } = useSineWaveGenerator({
waves: [{ amplitude: 20, wavelength: 120, speed: 0.5 }],
ariaLabel: "Ambient background animation",
});
return ;
}
```
The hook creates the generator once from the options passed on the first render (a new `waves` array on a later render calls `setWaves()` automatically), and returns `generatorRef` as an escape hatch — call any instance method on `generatorRef.current` (`addWave`, `syncToAudio`, `setQualityPreset`, ...) for anything else you need to update imperatively (for example, binding a live microphone via `AudioSync` on a button click, not on mount).
## Quick start — TypeScript
Type definitions ship with the package.
```ts
import {
SineWaveGenerator,
Wave,
Ease,
WaveConfig,
SineWaveGeneratorOptions,
ValidationError,
CanvasError,
} from "@sebastienrousseau/sine-wave-generator";
import {
AudioSync,
AudioSyncOptions,
AudioMapping,
AudioMetrics,
AudioSyncError,
} from "@sebastienrousseau/sine-wave-generator/audio-sync";
```
Every error thrown by this library is one of `ValidationError`, `CanvasError`, or `AudioSyncError` (all extend `Error`), so you can discriminate failure modes with `instanceof` instead of matching on message strings:
```ts
try {
new SineWaveGenerator({ el: "#missing-canvas" });
} catch (error) {
if (error instanceof CanvasError) {
// canvas element or its 2D context is missing/unusable
}
throw error;
}
```
## API reference — SineWaveGenerator constructor
`new SineWaveGenerator(options)`
| Option | Type | Description | Required |
| --- | --- | --- | --- |
| `el` | `HTMLCanvasElement \| string` | Canvas element or CSS selector | Yes |
| `waves` | `WaveConfig[]` | Initial wave configurations | No |
| `pixelRatio` | `number` | Override device pixel ratio. Omit to track it automatically, including live display changes | No |
| `maxPixelRatio` | `number` | Cap pixel ratio for memory control | No |
| `autoResize` | `boolean` | Auto-resize on canvas box changes (ResizeObserver) and window resize | No |
| `respectReducedMotion` | `boolean` | Honor prefers-reduced-motion by scaling animation speed down. Defaults to true | No |
| `reducedMotionScale` | `number` | Speed multiplier while reduced motion is preferred. Defaults to 0.25; set to 0 to fully pause | No |
| `ariaLabel` | `string \| null` | Accessible label for the canvas (sets role="img"). Omit for decorative canvases (aria-hidden) | No |
| `colorScheme` | `"auto" \| "light" \| "dark"` | Default gradient palette. "auto" follows prefers-color-scheme live. Defaults to "auto" | No |
## API reference — WaveConfig
| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `phase` | `number` | Random | Phase offset in radians |
| `speed` | `number` | Random 0.5-1.0 | Animation speed multiplier |
| `amplitude` | `number` | 10 | Wave height in pixels |
| `wavelength` | `number` | 100 | Peak-to-peak distance in pixels |
| `strokeStyle` | `string \| null` | null | CSS colour or null for gradient |
| `segmentLength` | `number` | 10 | Point density (lower is smoother) |
| `easing` | `function` | `Ease.sineInOut` | Easing curve for wave shape |
| `rotate` | `number` | 0 | Rotation angle in degrees (0-359) |
## API reference — SineWaveGenerator instance methods
| Method | Description |
| --- | --- |
| `start()` | Start the animation loop |
| `stop()` | Stop the animation loop and unbind events |
| `resize()` | Recalculate canvas size and rebuild gradients |
| `addWave(config)` | Add a new wave at runtime |
| `removeWave(index)` | Remove a wave by index |
| `bindEvents()` | Bind resize, mouse, touch, and responsiveness/accessibility listeners |
| `unbindEvents()` | Unbind all events and listeners |
| `syncToAudio(audioSync, mapping?)` | Bind an audio source's live metrics to wave parameters |
| `unsyncAudio()` | Detach the bound audio source and restore original wave values |
A high `maxPixelRatio` on large canvases will increase memory use proportionally.
## Accessibility & responsiveness behavior (on by default)
- Marks the canvas `aria-hidden="true"` (decorative by default) unless you pass `ariaLabel`, in which case it sets `role="img"` and that label instead.
- Scales animation speed to `reducedMotionScale` (default 0.25) when the user has `prefers-reduced-motion` enabled, and updates live if that preference changes. Pass `respectReducedMotion: false` to disable, or `reducedMotionScale: 0` to fully pause instead of slowing down.
- Tracks `devicePixelRatio` live via a `matchMedia` listener when `pixelRatio` isn't explicitly set.
- Observes the canvas element itself with `ResizeObserver` (in addition to the window resize event) when `autoResize` is true.
- Picks the default gradient's palette from `prefers-color-scheme` and updates live if the OS/browser theme changes, when using the built-in gradient (`strokeStyle: null`). Pass `colorScheme: "light"` or `"dark"` to force a palette.
## API reference — AudioSync
`new AudioSync(options?)` — analyzes an `HTMLMediaElement` or `MediaStream` with the Web Audio API and derives real-time metrics for `syncToAudio()`.
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `fftSize` | `number` | 1024 | FFT size for the analyser (must be power of 2) |
| `smoothingTimeConstant` | `number` | 0.8 | Analyser smoothing, 0-1 |
| `bpm` | `number \| null` | null | Manual tempo override; omit to auto-detect |
| Method | Description |
| --- | --- |
| `connect(source)` | Connect an HTMLMediaElement or MediaStream |
| `disconnect()` | Disconnect and reset analysis state |
| `update(timestampMs)` | Sample the source and refresh metrics |
| `getMetrics()` | Return the last computed metrics without sampling |
Metrics returned by `update()`/`getMetrics()`: `energy`, `bass`, `mid`, `treble` (all normalized 0-1), `beat` (boolean, true on the detected frame), `beatPhase` (0-1 progress through the current beat), and `bpm` (manual or auto-detected tempo, or null if unknown).
Beat detection is a lightweight heuristic, not a validated DSP algorithm — a variance-thresholded energy detector on the bass band alone. It under-detects material whose rhythm isn't bass-driven (ambient, classical, sparse/syncopated percussion), and only reports a bpm once two or more beats land 60-200 BPM apart. For more robust detection, pass a known bpm manually, or pair AudioSync with a dedicated analysis library (realtime-bpm-analyzer, web-audio-beat-detector, or Meyda) and feed its output through a custom object exposing `update(timestampMs)`.
Custom mapping example (which metric drives which wave property, and how strongly):
```js
generator.syncToAudio(audioSync, {
amplitude: { source: "bass", intensity: 2 },
speed: { source: "energy", intensity: 1 },
rotate: { source: "treble", intensity: 0.5 },
});
```
## Guide — Hero background
Purpose: give the page depth and calm motion without competing with copy.
Why it works: two slow waves create parallax at low contrast; the eye reads the motion as atmosphere, not content.
How to apply: keep amplitude under 20px to avoid stealing attention; use two wavelengths for depth (e.g. 220 + 160); lower stroke opacity to 0.15-0.4 for softness; set `autoResize: true` for responsive headers.
```js
const gen = new SineWaveGenerator({
el: "#hero",
autoResize: true,
waves: [
{ amplitude: 16, wavelength: 220, speed: 0.6 },
{ amplitude: 10, wavelength: 160, speed: 0.4 },
],
});
gen.start();
```
## Guide — Scroll-reactive parallax
Purpose: let users feel scroll progression without extra UI.
Why it works: amplitude increases while wavelength tightens, so the motion intensifies naturally as users move through the page.
How to apply: throttle updates with `requestAnimationFrame`; clamp progress to 0-1 for stable motion; provide a reduced-motion fallback (slow or pause).
```js
window.addEventListener("scroll", () => {
const progress = scrollY / (docHeight - innerHeight);
wave.amplitude = 10 + 30 * progress;
wave.wavelength = 140 + 240 * (1 - progress);
});
```
## Guide — Audio-driven waves
Purpose: turn audio energy into visual rhythm.
Why it works: the analyser hands you a fresh energy reading many times a second — steady enough that the wave doesn't jitter, fast enough that it still feels live.
How to apply: start audio on a user gesture to satisfy mobile autoplay policies; normalize 0-255 analyser data into 0-1 amplitudes; use light smoothing for natural motion.
## Guide — Quality presets
Purpose: keep visuals consistent across devices with one switch.
Why it works: pixel ratio is the single biggest lever on canvas cost — it sets how many actual pixels get redrawn every frame, not just how many you see on screen.
How to apply: use `balanced` as the default; switch to `battery` on mobile or low power; offer `quality` for hero moments.
```js
gen.setQualityPreset("balanced");
gen.setQualityPreset("quality");
gen.setQualityPreset("battery");
```
## Guide — Responsive resize
Purpose: keep the canvas crisp in layouts that resize independently, not just the window.
Why it works: `autoResize` observes the canvas element's own box size via ResizeObserver, in addition to the window resize event, catching layout-driven changes a window listener alone would miss.
How to apply: leave `autoResize` on (the default, so no manual ResizeObserver wiring is needed); cap `maxPixelRatio` on high-DPI displays to control memory.
```js
const gen = new SineWaveGenerator({ el: canvas, autoResize: true });
gen.start();
```
## Using this project's docs with AI coding tools
This project publishes `llms.txt` (https://sine-wave-generator.com/llms.txt) and this full reference (https://sine-wave-generator.com/llms-full.txt) per the llmstxt.org convention. To expose them directly to an MCP-capable coding assistant (Claude Desktop, Claude Code, Cursor, Windsurf), point a generic MCP docs server such as `mcpdoc` at the llms.txt URL — see the project README's "AI tools & MCP" section for a ready-to-paste config.