Fix stop button - track and stop all active audio nodes

- Track all oscillators/sources in APU activeNodes Set
- Add stopAll() method that immediately stops all tracked nodes
- Auto-remove nodes from tracking when they end naturally
- GameBoyPlayer.stop() now calls apu.stopAll()
This commit is contained in:
b1rdmania
2026-01-20 19:45:08 +00:00
parent e22431164f
commit 7aa1d6f660
2 changed files with 43 additions and 4 deletions
+42 -3
View File
@@ -70,6 +70,9 @@ export class GameBoyAPU {
// Note scheduling stats (no limit - Web Audio handles scheduling)
private scheduledNoteCount = 0;
// Track active audio nodes for stop functionality
private activeNodes: Set<OscillatorNode | AudioBufferSourceNode> = new Set();
constructor(audioContext?: AudioContext, config?: Partial<V2Config>) {
this.audioContext = audioContext || new AudioContext();
this.config = { ...DEFAULT_V2_CONFIG, ...config };
@@ -197,7 +200,8 @@ export class GameBoyAPU {
const channel = this.pulseChannels.get(channelId);
if (!channel) return;
channel.playNote(midiNote, duration, velocity, startTime);
const result = channel.playNote(midiNote, duration, velocity, startTime);
this.trackNode(result.oscillator, result.stopTime);
}
/**
@@ -213,7 +217,8 @@ export class GameBoyAPU {
const channel = this.waveChannels.get(channelId);
if (!channel) return;
channel.playNote(midiNote, duration, velocity, startTime);
const result = channel.playNote(midiNote, duration, velocity, startTime);
this.trackNode(result.oscillator, result.stopTime);
}
/**
@@ -229,7 +234,21 @@ export class GameBoyAPU {
const channel = this.noiseChannels.get(channelId);
if (!channel) return;
channel.playNote(midiNote, duration, velocity, startTime);
const result = channel.playNote(midiNote, duration, velocity, startTime);
this.trackNode(result.source, result.stopTime);
}
/**
* Track an audio node for stop functionality.
*/
private trackNode(node: OscillatorNode | AudioBufferSourceNode, stopTime: number): void {
this.activeNodes.add(node);
// Auto-remove when the node ends naturally
const cleanup = () => {
this.activeNodes.delete(node);
};
node.onended = cleanup;
}
/**
@@ -392,6 +411,26 @@ export class GameBoyAPU {
this.scheduledNoteCount = 0;
}
/**
* Stop all currently playing and scheduled sounds immediately.
*/
stopAll(): void {
const now = this.audioContext.currentTime;
// Stop all tracked nodes
for (const node of this.activeNodes) {
try {
node.stop(now);
} catch {
// Node may have already stopped
}
}
this.activeNodes.clear();
// Reset channel states
this.reset();
}
/**
* Get scheduled note count.
*/
+1 -1
View File
@@ -230,7 +230,7 @@ export class GameBoyPlayer {
*/
stop(): void {
this.isPlaying = false;
this.apu.reset();
this.apu.stopAll();
console.log('Playback stopped');
}