Skip to content

fix(web): render web haptics audio as a fixed-frequency, fixed-amplitude buzz#77

Open
piaskowyk wants to merge 1 commit into
mainfrom
fix/web-audio-fixed-carrier
Open

fix(web): render web haptics audio as a fixed-frequency, fixed-amplitude buzz#77
piaskowyk wants to merge 1 commit into
mainfrom
fix/web-audio-fixed-carrier

Conversation

@piaskowyk

Copy link
Copy Markdown
Member

Summary

On web, haptics drive the vibration motor at a single fixed frequency and amplitudenavigator.vibrate can only control when the motor is on. Intensity and sharpness are therefore expressed purely through PWM timing (longer shots feel stronger, tighter shots feel buzzier), which is exactly what PatternComposer already produces.

The audio simulation was not honoring this. AudioGenerator.renderInterval derived pitch and loudness from each pulse's duration:

const baseFrequency = this.lerp(220, 95, normalizedDuration); // ❌ pitch from duration
const volume = this.lerp(0.14, 0.42, normalizedDuration);     // ❌ loudness from duration

So short vs long pulses came out as different notes at different volumes (plus a 1.5× perfect-fifth triangle harmonic), making it sound musical/electronic rather than like a real actuator.

Change

Every "on" window now renders the identical buzz; only the on/off timing varies:

  • CARRIER_FREQUENCY_HZ = 180 — one fixed carrier (representative LRA resonance)
  • PULSE_VOLUME = 0.5 — one fixed amplitude
  • BUZZ_HARMONICS — fixed 1×/2×/3× sine stack (integer harmonics → reads as a buzzing actuator, not a chord)

Removed the now-dead normalizeIntervalDuration/lerp helpers, the unused timingCapabilities field, and the Engine import.

Tests

  • Added a regression test asserting a 40 ms and a 180 ms pulse render the same frequency and amplitude (differing only in oscillator run length).
  • All 27 AudioGenerator tests pass.

Notes

  • Scope is the Pulsar web SDK (web/Pulsar). The separate docs-site + Figma AudioPatternUtility engine still maps amplitude→volume and frequency→pitch sweeps; left untouched for now.
  • Documented the constraint in web/Pulsar/AGENT_CONTEXT.md so future work doesn't reintroduce duration-based pitch/volume.

🤖 Generated with Claude Code

…ude buzz

Web haptics drive the vibration motor at a single fixed frequency and
amplitude; navigator.vibrate can only gate the motor on and off, so
intensity and sharpness are expressed purely through PWM timing.

AudioGenerator.renderInterval previously derived pitch (lerp 220->95Hz)
and loudness (lerp 0.14->0.42) from each pulse's duration, so short vs
long pulses came out as different notes at different volumes — musical
and unfaithful to the real actuator.

Now every "on" window renders the identical buzz: fixed carrier
(CARRIER_FREQUENCY_HZ), fixed amplitude (PULSE_VOLUME), and a fixed
integer-harmonic timbre. Only the on/off timing varies, mirroring the
real fixed-frequency motor. Remove the now-dead duration-mapping
helpers and the unused Engine timing dependency.

Add a regression test asserting a 40ms and a 180ms pulse produce the
same frequency and amplitude, and document the constraint in
AGENT_CONTEXT.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant