Skip to content

Add Web Audio API support with three procedural audio drivers#51

Draft
KurtGokhan wants to merge 4 commits into
mainfrom
claude/add-handwriting-sound-effects-4SUd4
Draft

Add Web Audio API support with three procedural audio drivers#51
KurtGokhan wants to merge 4 commits into
mainfrom
claude/add-handwriting-sound-effects-4SUd4

Conversation

@KurtGokhan

Copy link
Copy Markdown
Owner

Summary

This PR adds comprehensive Web Audio API support to tegaki, enabling sound effects synchronized with stroke rendering. Three fully procedural audio drivers are included (pencil, chalk, brush) that require zero audio assets and work offline.

Key Changes

  • Audio Registry & Runtime (audio-registry.ts, audio-runtime.ts)

    • New TegakiAudioDriver interface for pluggable audio implementations
    • AudioRuntime class manages AudioContext lifecycle, stroke event scheduling, and driver instance swapping
    • resolveSoundProp() utility handles multiple sound option formats (string name, object with config, raw driver)
    • Stroke events are computed from timeline + font data and scheduled with start/end callbacks
  • Built-in Audio Drivers (audio/pencil.ts, audio/chalk.ts, audio/brush.ts)

    • Pencil: Continuous bandpassed white noise with sawtooth LFO for "scratch" texture
    • Chalk: Sparse grain-based synthesis with occasional tonal squeaks, scheduled via budget-driven rate control
    • Brush: Low-frequency pink noise with slow sine LFO sweep for soft "swish" effect
    • All drivers support per-instance configuration (gain, frequency, Q, attack/release, etc.)
  • Audio Utilities (audio/utils.ts)

    • createNoiseBuffer(): Generates white/pink noise buffers for reuse across driver instances
    • clampNumber(): Safe config value validation with defaults
    • rampParam(): Exponential parameter ramping to avoid clicks/zippers
  • Engine Integration (engine.ts)

    • AudioRuntime instance created per engine, destroyed on cleanup
    • setSound() called when sound option changes; supports hot-swapping drivers
    • advance() called each frame to fire stroke events and tick callbacks
    • pause()/resume() suspend/resume AudioContext; silence() on seek/restart
    • rebuildStrokes() recomputes events when timeline/font/fontSize changes
  • Framework Bindings

    • Web Component, React, Vue, Solid, Astro, Svelte all expose sound prop
    • Public API: TegakiEngine.registerAudio(), unregisterAudio(), getAudio()
    • Export entry point: tegaki/audio with registerBuiltInAudio() convenience function
  • Type Exports (core/index.ts, core/types.ts)

    • TegakiSoundProp, TegakiAudioDriver, TegakiAudioInstance, StrokeAudioInfo, TegakiAudioContext
    • TegakiEngineOptions extended with sound field

Notable Implementation Details

  • Stroke Event Scheduling: Strokes are sorted by startTime and indexed; the runtime uses a pointer to emit start events and a Set to track active strokes for end events. Backward seeks (scrubs) reset state without firing spurious events.
  • Width-to-Gain Mapping: Stroke widths (averaged from bundled point data) are converted to CSS pixels via render scale and used to modulate per-voice amplitude, making thicker strokes slightly louder.
  • Grain Scheduling (Chalk): Uses a budget-driven accumulator to smooth grain emission across variable frame intervals, preventing bunching on long frames.
  • Pink Noise: Paul Kellet's economical approximation used for brush driver's low-end character.
  • Error Resilience: Driver creation/callback failures are caught and logged without crashing the engine; missing drivers warn once per name.
  • SSR Safe: AudioContext creation is guarded by typeof window check; no errors on server.

Testing

Comprehensive test suite in audio-registry.test.ts covers:

  • Driver registration/lookup and de-duplication
  • Sound prop resolution (string, object, raw driver, missing names)
  • Stroke event generation with timing, scaling, and width calculations
  • Timeline features (strokeTimeScale, strokeDelays, missing glyphs)

https://claude.ai/code/session_0137C65gmSdCRy2ZHoKzT7PN

Audio plays during stroke animation via a new `sound` engine option and
a name-based registry mirroring `registerBundle` / `registerShaper`.
Drivers live in a separate `tegaki/audio` entry point so the core
renderer stays Web-Audio-free; the three built-ins are fully procedural
(no asset bytes).

- `tegaki/core` ships the registry, runtime, and types
  (`registerAudio`, `getAudio`, `TegakiAudioDriver`, etc.)
- `tegaki/audio` ships the built-in drivers (`pencilAudio`,
  `chalkAudio`, `brushAudio`) plus `registerBuiltInAudio()`
- engine wires `sound` through play / pause / seek / restart, suspends
  the AudioContext during pause and reduced motion, and rebuilds stroke
  events on timeline / font-size changes
- all framework adapters forward the new `sound` prop
@changeset-bot

changeset-bot Bot commented May 17, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: c925513

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

💥 An error occurred when fetching the changed packages and changesets in this PR
Some errors occurred when validating the changesets config:
The package "tegaki" depends on the ignored package "tegaki-generator", but "tegaki" is not being ignored. Please add "tegaki" to the `ignore` option.

claude and others added 3 commits May 17, 2026 21:02
- `TegakiTextPreview` registers `pencil` / `chalk` / `brush` once at
  module load and forwards a new `sound` prop to the engine.
- URL state grows an `sn` key (default `none`); the generator's text
  controls add a Sound dropdown and `/preview` reads the same key so
  agents can drive audio via the URL.
- Documented in the AGENTS.md URL-state table.

Browser autoplay policy keeps the AudioContext suspended until the
first user gesture, so visual snapshots stay deterministic.
@KurtGokhan KurtGokhan marked this pull request as draft May 17, 2026 22:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants