Fix silent telemetry soundboard buttons before first engage

TelemetrySynth.gainNode was only ever created inside start(), which is
only called from the main ENGAGE/FULL STOP flow. Every telemetry-driven
soundboard one-shot (LCARS Single/Double Chirp, Ack Sequence, Sensor
Sweep, the TOS/NX/Cardassian era sounds, Door Chime, and the Phase 3
who/bioship/belter era generators) guarded on `!this.gainNode` with no
fallback, so clicking any of them before the ambient bed had been
engaged once did nothing at all: no sound, no console error, and
nothing for the spectrum analyzer to react to.

ExpandedSciFiAudioSynth and WhoniverseAudioSynth already self-heal this
way via their own init() (called at the top of every public synth
method) - that's why buttons like Comm Badge work standalone. Add the
same init() to TelemetrySynth and call it from every method reachable
from the soundboard, so the whole class follows the same convention.
start() is left untouched: it still unconditionally rebuilds the node
on every engage so a newly-selected preset's volume takes effect
immediately.

Verified with a small headless harness driving TelemetrySynth directly
(no prior start()): all 13 affected methods now create gainNode and
fire real oscillators on first call, where they previously did nothing.
Confirmed against the pre-fix code that the same harness reproduces the
original silent failure. Regenerated dist/SciFiAmbientDisplay_v4.html
to match (tools/package.ps1 requires PowerShell, unavailable in this
shell, so the inlining was replicated in Python matching its exact
output convention; diff against the previous build is exactly the 31
added lines, nothing else).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01582UUCEBExzp3oK3Spz1Xx
This commit is contained in:
Claude
2026-09-04 20:06:50 +00:00
co-authored by Claude Sonnet 5
parent b3407812a1
commit 9dc48e94d9
2 changed files with 62 additions and 0 deletions
+31
View File
@@ -3560,6 +3560,23 @@ class TelemetrySynth {
];
}
/**
* Lazily creates the telemetry gain node so soundboard one-shots (LCARS
* chirps, era-specific one-shots) work standalone even before the ambient
* bed has been engaged via start(). Mirrors the init() self-heal pattern
* used by ExpandedSciFiAudioSynth and WhoniverseAudioSynth. start() below
* still unconditionally rebuilds the node on every engage so a fresh
* preset's volume takes effect immediately; this only fills the gap
* before that has ever happened.
*/
init() {
if (this.gainNode || !this.am.ctx) return;
const ctx = this.am.ctx;
this.gainNode = ctx.createGain();
this.gainNode.gain.setValueAtTime(this.isMuted ? 0 : this.params.volume, ctx.currentTime);
this.gainNode.connect(this.am.compressor);
}
start() {
this.stop();
const ctx = this.am.ctx;
@@ -3663,6 +3680,7 @@ class TelemetrySynth {
* Star Trek Comm Badge Confirmation Chirp
*/
synthesizeCommBadge() {
this.init();
if (window.expandedAudio) {
window.expandedAudio.synthesizeCommBadge();
return;
@@ -3690,6 +3708,7 @@ class TelemetrySynth {
* TNG/Voyager Single LCARS Touch Tone (Soft sine with gentle attack and rapid exponential decay)
*/
synthesizeLCARSSingleChirp(pitch = null) {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -3725,6 +3744,7 @@ class TelemetrySynth {
* TNG LCARS Double Chirp (Iconic standard confirmation tone)
*/
synthesizeLCARSDoubleChirp() {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
const idx = Math.floor(Math.random() * (this.lcarsPitches.length - 2));
@@ -3741,6 +3761,7 @@ class TelemetrySynth {
* TNG LCARS Multi-Tone Data Acknowledgment Sequence
*/
synthesizeLCARSSequence() {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
const notes = [
@@ -3760,6 +3781,7 @@ class TelemetrySynth {
* High-tech Sensor Sweep Tone (Voyager/TNG Long-Range Sensor telemetry)
*/
synthesizeSensorSweep() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -3790,6 +3812,7 @@ class TelemetrySynth {
* Two detuned square/triangle oscillators modulated by high-speed vibrato LFO
*/
synthesizeTOSWarble() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -3846,6 +3869,7 @@ class TelemetrySynth {
* TOS Mechanical Relay Solenoid Click
*/
synthesizeTOSRelayClick() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -3872,6 +3896,7 @@ class TelemetrySynth {
* DS9 / Cardassian Cavernous Sensor Tone (Resonant metallic ring)
*/
synthesizeCardassianSensor() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -3906,6 +3931,7 @@ class TelemetrySynth {
* NX-01 Industrial Hydraulic Relay Click
*/
synthesizeNXRelay() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -3932,6 +3958,7 @@ class TelemetrySynth {
* NX-01 Indicator Beep (Early 22nd century industrial tone)
*/
synthesizeNXIndicatorBeep() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -3959,6 +3986,7 @@ class TelemetrySynth {
* Iconic TNG 2-Tone Door Chime ("Come in")
*/
synthesizeDoorChime() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -4005,6 +4033,7 @@ class TelemetrySynth {
* the single scheduler timeout can never orphan nodes.
*/
synthesizeWhoMechanicalTelemetry_() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -4088,6 +4117,7 @@ class TelemetrySynth {
* One 0.5-1.1 s phrase of 2-4 events on absolute ctx time.
*/
synthesizeBioshipOrganicTelemetry_() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -4198,6 +4228,7 @@ class TelemetrySynth {
* occasional late sub-cluster to mimic an unsteady clock.
*/
synthesizeBelterJuryRiggedTelemetry_() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
+31
View File
@@ -826,6 +826,23 @@ class TelemetrySynth {
];
}
/**
* Lazily creates the telemetry gain node so soundboard one-shots (LCARS
* chirps, era-specific one-shots) work standalone even before the ambient
* bed has been engaged via start(). Mirrors the init() self-heal pattern
* used by ExpandedSciFiAudioSynth and WhoniverseAudioSynth. start() below
* still unconditionally rebuilds the node on every engage so a fresh
* preset's volume takes effect immediately; this only fills the gap
* before that has ever happened.
*/
init() {
if (this.gainNode || !this.am.ctx) return;
const ctx = this.am.ctx;
this.gainNode = ctx.createGain();
this.gainNode.gain.setValueAtTime(this.isMuted ? 0 : this.params.volume, ctx.currentTime);
this.gainNode.connect(this.am.compressor);
}
start() {
this.stop();
const ctx = this.am.ctx;
@@ -929,6 +946,7 @@ class TelemetrySynth {
* Star Trek Comm Badge Confirmation Chirp
*/
synthesizeCommBadge() {
this.init();
if (window.expandedAudio) {
window.expandedAudio.synthesizeCommBadge();
return;
@@ -956,6 +974,7 @@ class TelemetrySynth {
* TNG/Voyager Single LCARS Touch Tone (Soft sine with gentle attack and rapid exponential decay)
*/
synthesizeLCARSSingleChirp(pitch = null) {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -991,6 +1010,7 @@ class TelemetrySynth {
* TNG LCARS Double Chirp (Iconic standard confirmation tone)
*/
synthesizeLCARSDoubleChirp() {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
const idx = Math.floor(Math.random() * (this.lcarsPitches.length - 2));
@@ -1007,6 +1027,7 @@ class TelemetrySynth {
* TNG LCARS Multi-Tone Data Acknowledgment Sequence
*/
synthesizeLCARSSequence() {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
const notes = [
@@ -1026,6 +1047,7 @@ class TelemetrySynth {
* High-tech Sensor Sweep Tone (Voyager/TNG Long-Range Sensor telemetry)
*/
synthesizeSensorSweep() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -1056,6 +1078,7 @@ class TelemetrySynth {
* Two detuned square/triangle oscillators modulated by high-speed vibrato LFO
*/
synthesizeTOSWarble() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -1112,6 +1135,7 @@ class TelemetrySynth {
* TOS Mechanical Relay Solenoid Click
*/
synthesizeTOSRelayClick() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -1138,6 +1162,7 @@ class TelemetrySynth {
* DS9 / Cardassian Cavernous Sensor Tone (Resonant metallic ring)
*/
synthesizeCardassianSensor() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -1172,6 +1197,7 @@ class TelemetrySynth {
* NX-01 Industrial Hydraulic Relay Click
*/
synthesizeNXRelay() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -1198,6 +1224,7 @@ class TelemetrySynth {
* NX-01 Indicator Beep (Early 22nd century industrial tone)
*/
synthesizeNXIndicatorBeep() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -1225,6 +1252,7 @@ class TelemetrySynth {
* Iconic TNG 2-Tone Door Chime ("Come in")
*/
synthesizeDoorChime() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -1271,6 +1299,7 @@ class TelemetrySynth {
* the single scheduler timeout can never orphan nodes.
*/
synthesizeWhoMechanicalTelemetry_() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -1354,6 +1383,7 @@ class TelemetrySynth {
* One 0.5-1.1 s phrase of 2-4 events on absolute ctx time.
*/
synthesizeBioshipOrganicTelemetry_() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
@@ -1464,6 +1494,7 @@ class TelemetrySynth {
* occasional late sub-cluster to mimic an unsteady clock.
*/
synthesizeBelterJuryRiggedTelemetry_() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;