@sebastienrousseau/sine-wave-generator
v0.0.3
Published
A JavaScript library for generating animated sine waves on a canvas.
Maintainers
Readme
Sine Wave Generator — Smooth Canvas Animation
Overview
Render animated sine waves on a canvas element with minimal configuration. Sine Wave Generator is a performance-focused JavaScript library that draws smooth, layered waveforms at a steady 60 fps with zero dependencies.
The library ships at roughly 3 KB gzipped. It uses requestAnimationFrame for battery-efficient rendering, caps pixel ratios for memory control, and supports pointer interactions out of the box.
Table of contents
- Overview
- Key features
- Get started
- API reference
- React
- Examples
- TypeScript
- AI tools & MCP
- Contributing
- License
Key features
Core capabilities
- Fast rendering. Segment-based drawing with capped pixel ratios keeps frame budgets tight.
- Flexible configuration. Tune amplitude, wavelength, speed, and easing to match any visual style.
- Layered waves. Stack multiple waves with independent settings for depth and parallax.
- Responsive sizing. Element-bound dimensions with device pixel ratio support and a configurable cap.
- Pointer control. Built-in mouse and touch tracking adjusts wave phase in real time.
- Audio-reactive sync. Bind a live audio source so wave amplitude, speed, and rotation react to beats, tempo, and frequency energy.
Performance
- Memory control.
maxPixelRatiolimits the offscreen buffer size on high-DPI screens. - Adjustable detail.
segmentLengthcontrols point density for smooth or fast rendering. - Efficient redraws. A single animation loop with cached gradients minimises per-frame overhead.
Get started
Installation
npm install @sebastienrousseau/sine-wave-generator
# or
yarn add @sebastienrousseau/sine-wave-generator
# or
pnpm add @sebastienrousseau/sine-wave-generatorRequirements
- Browser: any evergreen browser with Canvas 2D support (Chrome, Firefox, Safari, Edge).
AudioSyncadditionally requires the Web Audio API (supported in all evergreen browsers). - Node.js:
>=16— only relevant for the build/test tooling in this repo; the published package is plain browser JavaScript with zero runtime dependencies. - No bundler or build step is required to consume the library: drop
src/sine-wave-generator.jsin with a<script>tag, orrequire/importit directly.
Basic usage
<canvas id="sine"></canvas>
<script src="./node_modules/@sebastienrousseau/sine-wave-generator/src/sine-wave-generator.js"></script>
<script>
const generator = new SineWaveGenerator({
el: "#sine",
maxPixelRatio: 2,
waves: [{ amplitude: 26, wavelength: 120, speed: 0.8 }],
});
generator.start();
</script>Module usage (CommonJS or ESM)
// 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:
const {
AudioSync,
} = require("@sebastienrousseau/sine-wave-generator/audio-sync");
// or: import { AudioSync } from "@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. The deep-import paths used in older versions of these docs — require("@sebastienrousseau/sine-wave-generator/src/sine-wave-generator.js") and .../src/audio-sync.js — still work unchanged; they resolve straight to the unbundled source.
Set strokeStyle to null to use the built-in gradient stroke.
Sync animation to audio (BPM-reactive)
Bind an AudioSync instance to a SineWaveGenerator so wave parameters react to music in real time — amplitude pulses with bass, speed tracks overall energy, and beats trigger a short amplitude boost.
const { SineWaveGenerator } = require("@sebastienrousseau/sine-wave-generator");
const {
AudioSync,
} = require("@sebastienrousseau/sine-wave-generator/audio-sync");
const generator = new SineWaveGenerator({
el: "#sine",
waves: [{ amplitude: 20, wavelength: 120, speed: 0.5 }],
});
const audioSync = new AudioSync(); // or new AudioSync({ bpm: 128 }) for a known tempo
const audioEl = document.querySelector("audio");
audioEl.addEventListener(
"play",
() => {
audioSync.connect(audioEl); // also accepts a MediaStream, e.g. from getUserMedia()
generator.syncToAudio(audioSync);
},
{ once: true },
);
generator.start();Pass a custom mapping as the second argument to syncToAudio() to control which metric ("energy", "bass", "mid", "treble") drives which wave property (amplitude, speed, rotate), and how strongly:
generator.syncToAudio(audioSync, {
amplitude: { source: "bass", intensity: 2 },
speed: { source: "energy", intensity: 1 },
rotate: { source: "treble", intensity: 0.5 },
});Call generator.unsyncAudio() to detach and restore each wave's original amplitude, speed, and rotation.
API reference
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 |
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) |
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
By default, SineWaveGenerator:
- Marks the canvas
aria-hidden="true"(it's decorative by default) unless you passariaLabel, in which case it setsrole="img"and that label instead — set your ownaria-*attributes on the element beforehand to opt out. - Scales animation speed to
reducedMotionScale(default0.25) when the user hasprefers-reduced-motionenabled, and updates live if that preference changes. PassrespectReducedMotion: falseto disable, orreducedMotionScale: 0to fully pause instead of slowing down. - Tracks
devicePixelRatiolive via amatchMedialistener whenpixelRatioisn't explicitly set, so moving the window to a display with different pixel density stays sharp. - Observes the canvas element itself with
ResizeObserver(in addition to the windowresizeevent) whenautoResizeistrue, so layout-driven size changes — not just window resizes — are picked up automatically. - Picks the default gradient's palette from
prefers-color-scheme(a cooler, higher-contrast palette for dark backgrounds) and updates live if the OS/browser theme changes, when using the built-in gradient (strokeStyle: null). PasscolorScheme: "light"or"dark"to force a palette instead of following the system preference.
const generator = new SineWaveGenerator({
el: "#sine",
ariaLabel: "Ambient background animation",
reducedMotionScale: 0, // fully pause instead of slowing down
});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. It's a variance-thresholded energy detector on the bass band alone — cheap enough to run once per animation frame, but 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. See the detectBeat() JSDoc in src/audio-sync.js for the full algorithm basis and limitations. For more robust detection, pass a known bpm manually, or pair AudioSync with a dedicated analysis library (e.g. realtime-bpm-analyzer, web-audio-beat-detector, or Meyda for richer spectral features) and feed its output through a custom object exposing update(timestampMs).
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.
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 <canvas ref={canvasRef} style={{ width: "100%", height: "100%" }} />;
}The hook is intentionally thin: it 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.
Examples
Open docs/index.html for a full interactive demo covering:
- Fundamental wave controls (amplitude, wavelength, easing, pause/resume)
- Pattern examples (pulse matrix, DNA helix, fluid column, Lissajous orbits)
- Advanced modes (Moire interference, kinetic typography, damped sine)
- A live playground with presets and configurable parameters
TypeScript
Type definitions ship with the package.
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:
try {
new SineWaveGenerator({ el: "#missing-canvas" });
} catch (error) {
if (error instanceof CanvasError) {
// canvas element or its 2D context is missing/unusable
}
throw error;
}AI tools & MCP
This project publishes llms.txt and llms-full.txt (the llmstxt.org convention) — a structured, link-indexed summary and a full-content reference respectively, meant for LLMs and AI coding assistants to consume at inference time rather than crawling the whole site.
To expose these directly to an MCP-capable assistant (Claude Desktop, Claude Code, Cursor, Windsurf), point a generic MCP documentation server such as mcpdoc at the llms.txt URL instead of building or running anything project-specific. A typical MCP client config looks like:
{
"mcpServers": {
"mcpdoc": {
"command": "uvx",
"args": [
"--from",
"mcpdoc",
"mcpdoc",
"--urls",
"SineWaveGenerator:https://sine-wave-generator.com/llms.txt"
]
}
}
}Check mcpdoc's own docs for the current invocation flags and where your specific client expects this config — it may change between mcpdoc versions.
Note: neither llms.txt adoption nor MCP exposure is proven to affect search ranking or how often an AI cites this project — treat both as low-cost hygiene for tools that respect them, not as an SEO or marketing lever.
Contributing
Please read CONTRIBUTING.md before opening a pull request.
For security issues, see SECURITY.md.
License
This project is licensed under the Apache-2.0 License. See LICENSE.
