feat(sfx): wire Howler.js SFX audio into renderer #86

Merged
onnwee merged 10 commits from feat/sfx-audio into main 2026-03-01 10:58:50 -06:00
onnwee commented 2026-03-01 10:26:52 -06:00 (Migrated from github.com)

Summary

  • Adds Howler.js 2.2.4 CDN to public/index.html so window.Howl is available in the browser
  • Creates public/renderer/audio.js — a factory module wrapping Howler with graceful load-error handling and a NOOP_AUDIO fallback
  • Wires audio.playSfx() into objection(), holdIt(), and takeThat() composite cues in effects.js
  • Initialises audio in createCourtRenderer and exposes the handle on the returned renderer object for DevTools inspection
  • Adds assets/sfx/README.md documenting the 5 required MP3 files and suggested CC0 sources

Test Plan

  • npm run lint passes (no TypeScript changes)
  • Browser: DevTools console shows [Audio] Loaded N SFX (or graceful per-file warnings if files absent)
  • Browser: courtRenderer.effects.objection() plays audio + shows OBJECTION! stamp
  • Browser: courtRenderer.effects.holdIt() plays audio + shows HOLD IT! stamp
  • Browser: courtRenderer.effects.takeThat() plays audio + shows TAKE THAT! stamp
  • Browser: courtRenderer.effects.trigger('unknown_cue') → no crash, no audio
  • During live session: render_directive with effect: 'objection' fires the stinger

Closes #73

🤖 Generated with Claude Code

## Summary - Adds Howler.js 2.2.4 CDN to `public/index.html` so `window.Howl` is available in the browser - Creates `public/renderer/audio.js` — a factory module wrapping Howler with graceful load-error handling and a `NOOP_AUDIO` fallback - Wires `audio.playSfx()` into `objection()`, `holdIt()`, and `takeThat()` composite cues in `effects.js` - Initialises audio in `createCourtRenderer` and exposes the handle on the returned renderer object for DevTools inspection - Adds `assets/sfx/README.md` documenting the 5 required MP3 files and suggested CC0 sources ## Test Plan - [ ] `npm run lint` passes (no TypeScript changes) - [ ] Browser: DevTools console shows `[Audio] Loaded N SFX` (or graceful per-file warnings if files absent) - [ ] Browser: `courtRenderer.effects.objection()` plays audio + shows OBJECTION! stamp - [ ] Browser: `courtRenderer.effects.holdIt()` plays audio + shows HOLD IT! stamp - [ ] Browser: `courtRenderer.effects.takeThat()` plays audio + shows TAKE THAT! stamp - [ ] Browser: `courtRenderer.effects.trigger('unknown_cue')` → no crash, no audio - [ ] During live session: `render_directive` with `effect: 'objection'` fires the stinger Closes #73 🤖 Generated with [Claude Code](https://claude.com/claude-code)
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2026-03-01 10:31:16 -06:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull request overview

This PR wires SFX playback into the browser renderer by introducing a Howler-backed audio module, initializing it in the renderer bootstrap, and triggering SFX during key composite effect cues (OBJECTION/HOLD IT/TAKE THAT).

Changes:

  • Add Howler.js (CDN) to the main HTML to expose Howl globally in the browser.
  • Introduce public/renderer/audio.js and initialize/load SFX in createCourtRenderer, passing an audio handle into the effects layer.
  • Play SFX within objection(), holdIt(), and takeThat() cues; add documentation for required SFX files.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
public/renderer/layers/effects.js Accepts an audio handle and plays SFX for composite cues.
public/renderer/index.js Initializes audio, starts non-blocking SFX loading, passes audio into effects, exposes audio on renderer.
public/renderer/audio.js New Howler wrapper providing loadSFX and playSfx with graceful error handling.
public/index.html Loads Howler from CDN before Pixi and the app module.
docs/plans/2026-03-01-sfx-audio.md Adds an implementation plan doc for the SFX audio work.
docs/plans/2026-03-01-twitch-bot.md Adds a Twitch bot integration plan doc (additional scope).
assets/sfx/README.md Documents required SFX MP3 filenames and sources (but location is currently misaligned with served assets).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

## Pull request overview This PR wires SFX playback into the browser renderer by introducing a Howler-backed audio module, initializing it in the renderer bootstrap, and triggering SFX during key composite effect cues (OBJECTION/HOLD IT/TAKE THAT). **Changes:** - Add Howler.js (CDN) to the main HTML to expose `Howl` globally in the browser. - Introduce `public/renderer/audio.js` and initialize/load SFX in `createCourtRenderer`, passing an audio handle into the effects layer. - Play SFX within `objection()`, `holdIt()`, and `takeThat()` cues; add documentation for required SFX files. ### Reviewed changes Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments. <details> <summary>Show a summary per file</summary> | File | Description | | ---- | ----------- | | public/renderer/layers/effects.js | Accepts an audio handle and plays SFX for composite cues. | | public/renderer/index.js | Initializes audio, starts non-blocking SFX loading, passes audio into effects, exposes `audio` on renderer. | | public/renderer/audio.js | New Howler wrapper providing `loadSFX` and `playSfx` with graceful error handling. | | public/index.html | Loads Howler from CDN before Pixi and the app module. | | docs/plans/2026-03-01-sfx-audio.md | Adds an implementation plan doc for the SFX audio work. | | docs/plans/2026-03-01-twitch-bot.md | Adds a Twitch bot integration plan doc (additional scope). | | assets/sfx/README.md | Documents required SFX MP3 filenames and sources (but location is currently misaligned with served assets). | </details> --- 💡 <a href="/subculture-collective/court/new/main?filename=.github/instructions/*.instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add Copilot custom instructions</a> for smarter, more guided reviews. <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn how to get started</a>.
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-01 10:31:15 -06:00

DEFAULT_SFX_CONFIG points at /assets/sfx/..., which is served from public/assets/sfx (since only public/ is mounted via express.static). This README instructs placing MP3s under repo-root assets/sfx, which won’t be reachable by the browser; either move this doc (and any SFX assets) to public/assets/sfx/ or change the URLs/config to match the served directory.

# public/assets/sfx — Sound Effect Files

Place the following MP3 files in `public/assets/sfx` for SFX playback in the browser:
`DEFAULT_SFX_CONFIG` points at `/assets/sfx/...`, which is served from `public/assets/sfx` (since only `public/` is mounted via `express.static`). This README instructs placing MP3s under repo-root `assets/sfx`, which won’t be reachable by the browser; either move this doc (and any SFX assets) to `public/assets/sfx/` or change the URLs/config to match the served directory. ```suggestion # public/assets/sfx — Sound Effect Files Place the following MP3 files in `public/assets/sfx` for SFX playback in the browser: ```
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-01 10:31:16 -06:00

This PR is scoped to wiring Howler SFX into the renderer, but it also adds a large Twitch bot integration plan doc. If the Twitch plan isn’t required for the SFX feature/issue #73, consider moving it to a separate PR to keep changesets focused and reduce review noise.

This PR is scoped to wiring Howler SFX into the renderer, but it also adds a large Twitch bot integration plan doc. If the Twitch plan isn’t required for the SFX feature/issue #73, consider moving it to a separate PR to keep changesets focused and reduce review noise.
@@ -546,6 +546,7 @@
</aside>
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-01 10:31:16 -06:00

Loading Howler from a third-party CDN without Subresource Integrity (SRI) or a self-hosted fallback increases supply-chain risk. If this project already accepts CDN scripts, consider at least adding integrity/crossorigin attributes (or vendoring Howler under public/ like other static assets) to reduce the impact of a compromised CDN.

        <script
            src="https://cdn.jsdelivr.net/npm/howler@2.2.4/dist/howler.min.js"
            integrity="sha384-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            crossorigin="anonymous"
        ></script>
        <script
            src="https://cdn.jsdelivr.net/npm/pixi.js@8.9.1/dist/pixi.min.js"
            integrity="sha384-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
            crossorigin="anonymous"
        ></script>
Loading Howler from a third-party CDN without Subresource Integrity (SRI) or a self-hosted fallback increases supply-chain risk. If this project already accepts CDN scripts, consider at least adding `integrity`/`crossorigin` attributes (or vendoring Howler under `public/` like other static assets) to reduce the impact of a compromised CDN. ```suggestion <script src="https://cdn.jsdelivr.net/npm/howler@2.2.4/dist/howler.min.js" integrity="sha384-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" crossorigin="anonymous" ></script> <script src="https://cdn.jsdelivr.net/npm/pixi.js@8.9.1/dist/pixi.min.js" integrity="sha384-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" crossorigin="anonymous" ></script> ```
@@ -0,0 +53,4 @@
resolve();
},
});
} catch (err) {
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-01 10:31:16 -06:00

On onloaderror, the Howl is still stored in sounds, so [Audio] Loaded ${sounds.size} SFX counts failed loads and later playSfx will attempt to play entries that never loaded. Consider only adding to sounds after a successful onload, or deleting the entry inside onloaderror so the map reflects playable sounds and the log is accurate.

On `onloaderror`, the `Howl` is still stored in `sounds`, so `[Audio] Loaded ${sounds.size} SFX` counts failed loads and later `playSfx` will attempt to play entries that never loaded. Consider only adding to `sounds` after a successful `onload`, or deleting the entry inside `onloaderror` so the map reflects playable sounds and the log is accurate.
@@ -0,0 +74,4 @@
const sound = sounds.get(name);
if (sound) {
sound.play();
}
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-01 10:31:15 -06:00

playSfx currently returns early when ready is false, which means effects triggered before loadSFX() finishes will never play audio even if the requested sound has already loaded. Consider allowing playback whenever a Howl instance exists (optionally calling sound.play() and letting Howler handle lazy loading), and reserve ready only for logging/metrics.

`playSfx` currently returns early when `ready` is false, which means effects triggered before `loadSFX()` finishes will never play audio even if the requested sound has already loaded. Consider allowing playback whenever a `Howl` instance exists (optionally calling `sound.play()` and letting Howler handle lazy loading), and reserve `ready` only for logging/metrics.
PatrickFanella commented 2026-03-01 10:39:22 -06:00 (Migrated from github.com)

@copilot open a new pull request to apply changes based on the comments in this thread

@copilot open a new pull request to apply changes based on the comments in [this thread](https://github.com/subculture-collective/court/pull/86#pullrequestreview-3872675115)
Copilot commented 2026-03-01 10:39:27 -06:00 (Migrated from github.com)

@PatrickFanella I've opened a new pull request, #88, to work on those changes. Once the pull request is ready, I'll request review from you.

@PatrickFanella I've opened a new pull request, #88, to work on those changes. Once the pull request is ready, I'll request review from you.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: subculture-collective/jury-rigged#86