feat(audio): add jitter option for per-voice pitch/volume/rate variance#12
feat(audio): add jitter option for per-voice pitch/volume/rate variance#12raphaelsalaja merged 1 commit intomainfrom
Conversation
Extends `PlayOptions` with an optional `jitter` bag so repeated triggers of the same sound vary slightly instead of sounding identical. Users opt in to any combination of `detune` (cents), `volume`, and `playbackRate`, and each is applied as a symmetric random offset computed once per voice — so layers within a single multi-layer voice stay internally in tune, but each press differs from the last. Made-with: Cursor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Adds an opt-in jitter field to PlayOptions to introduce per-trigger random variation (detune/volume/playbackRate) when rendering sounds, and documents the new option for consumers of defineSound.
Changes:
- Extend
PlayOptionswith ajitterobject for per-voice detune/volume/playbackRate variance. - Apply jitter values once per
render()call and fan them out to all layers of the rendered voice. - Update API docs and ship a changeset for a minor version bump of
@web-kits/audio.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| packages/audio/src/types.ts | Adds PlayOptions.jitter type + inline docs/examples. |
| packages/audio/src/engine.ts | Computes jitter once per voice and applies it to gain/detune/playbackRate during render. |
| apps/web/content/docs/api/sounds/define-sound.mdx | Documents jitter under PlayOptions and adds a dedicated section + example. |
| .changeset/jitter-play-option.md | Publishes the feature as a minor bump with a short usage snippet. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const jitter = opts?.jitter; | ||
| const detuneJitter = jitter?.detune | ||
| ? (Math.random() * 2 - 1) * jitter.detune | ||
| : 0; | ||
| const volumeJitter = jitter?.volume | ||
| ? 1 + (Math.random() * 2 - 1) * jitter.volume | ||
| : 1; | ||
| const rateJitter = jitter?.playbackRate | ||
| ? 1 + (Math.random() * 2 - 1) * jitter.playbackRate | ||
| : 1; |
There was a problem hiding this comment.
PR description says this works across defineSequence because it forwards PlayOptions into render(), but playSequence() currently passes { volume } when a step resolves a volume override, which drops any other options (including the new jitter). To match the stated behavior, preserve the rest of opts when overriding volume (e.g., merge with opts), or update the PR description/docs to call out this limitation.
Summary
Adds a per-call
jitteroption toPlayOptionsso repeated triggers of the same sound no longer feel identical. Requested Twitter — buttons that fire the same click sound in quick succession now sound alive.Users opt in to whichever parameters they want humanized:
detune— random pitch in cents (±value)volume— random gain offset (0.1=±10%)playbackRate— random rate offset (0.05=±5%)Implementation notes
render()call, so layers inside a single multi-layer voice stay internally in tune while each trigger differs from the last.defineSound,useSound,patch.play, anddefineSequencewithout hook or patch-layer changes, since they all forwardPlayOptionsintorender.detuneParam) gracefully ignore detune jitter; sample sources pick up both detune andplaybackRatejitter.Layer,SoundDefinition, or the JSON patch schema — per-call only.Files
packages/audio/src/types.ts—jitteronPlayOptionspackages/audio/src/engine.ts— per-voice random offsets applied inrender()apps/web/content/docs/api/sounds/define-sound.mdx— docs table + example.changeset/jitter-play-option.md— minor bump for@web-kits/audioTest plan
turbo run typecheckand@web-kits/audio:buildpass locally (ran in pre-commit hook)click({ jitter: { detune: 60 } })repeatedly and confirm pitch variesjitter: { detune: 100 }plays normally (no-op)Made with Cursor