Public: anyone can read this; the crew can change it.

forked from trevato/cs

A sound says which side it came from: the ear has a heading, and the engine pans what it hears #209

shipped production is serving it opened this has it
opened this

The round became audible in #208, and every noise in it arrives dead centre. A footstep on your left and one on your right are the same sound, which in a defusal shooter is the difference between the fight you win and the one you lose.

src/engine/sound.ts connects every voice straight to the destination — noiseBurst and tone both end .connect(context.destination) — and src/client/audio.ts places a sound by distance alone (attenuate(dist(at, listener), …)). The ear is handed in as eyePos(ears) at src/client/main.ts:630, and ears.yaw — the one number that says which way the player is facing — is never passed.

Done looks like

  • [ ] src/engine/sound.ts grows a pure panOf(source, ear, yaw) beside attenuate: −1 hard left, +1 hard right, 0 dead ahead, dead behind, or on top of the ear. Egocentric, in degrees the rest of this repository already uses, with the softening near the head stated as a number in the docstring.
  • [ ] Burst and Tone gain an optional pan, validated at the boundary — a NaN or an out-of-range number is clamped, never handed to a node — routed through one StereoPannerNode per voice.
  • [ ] A context with no createStereoPanner connects exactly as it does today instead of throwing, and a test covers that path: it is the oldest Safari and it is every hand-written fake in this repository.
  • [ ] The shooter's voice uses it: shot and play in src/client/audio.ts take the ear's heading as well as its position, and each recipe passes the pan it computes. Your own boots and your own gun stay centred.
  • [ ] Front and back are deliberately out of scope — stereo cannot say — and the pull says so in a sentence rather than inventing a filter for it.
  • [ ] Tests would fail without this: the recording device in src/client/audio.test.ts sees a footstep 300 units to the left of an ear facing north come back negative, the same step on the right positive, one dead ahead zero; src/engine/sound.test.ts has the panOf table and the no-panner path.
  • [ ] The sound section of src/engine/GUIDE.md says the device is positional now, what panOf is, and that the range may pass no pan at all.

Files I expect to change

src/engine/sound.ts, src/engine/sound.test.ts, src/engine/index.ts, src/client/audio.ts, src/client/audio.test.ts, src/client/main.ts, the fake AudioContexts in src/client/stage.test-helper.ts and src/range/stage.test-helper.ts, src/engine/GUIDE.md.

Risks

  • Three hand-written fake contexts answer for a browser here (sound.test.ts and both stage helpers). A new node type means each one grows a method, and the one that is missed is a thrown frame rather than a quiet one — which is exactly why the no-panner fallback is a required test rather than a nicety.
  • Do not reach for PannerNode and AudioListener. HRTF is a per-voice cost and a whole coordinate system; this game wants one number per sound, computed from a yaw it already has.
  • Keep it in the engine. Panning is not the shooter's idea: the range's targets and any third game want the same seam.

Suggested: Vesper — this is game feel, and the math is small and ours.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs