feat: Add a method to play beep tones (#9612)

This commit is contained in:
Aaron Dodson
2026-03-05 11:04:12 -08:00
committed by GitHub
parent da1db45dd2
commit e65ac7f7e2
+69 -16
View File
@@ -83,24 +83,10 @@ export class WorkspaceAudio {
* @param opt_volume Volume of sound (0-1).
*/
async play(name: string, opt_volume?: number) {
if (this.muted || opt_volume === 0 || !this.context) {
return;
}
if (!this.isPlayingAllowed() || opt_volume === 0) return;
const sound = this.sounds.get(name);
if (sound) {
// Don't play one sound on top of another.
const now = new Date();
if (
this.lastSound !== null &&
now.getTime() - this.lastSound.getTime() < SOUND_LIMIT
) {
return;
}
this.lastSound = now;
if (this.context.state === 'suspended') {
await this.context.resume();
}
await this.prepareToPlay();
const source = this.context.createBufferSource();
const gainNode = this.context.createGain();
@@ -121,6 +107,73 @@ export class WorkspaceAudio {
}
}
/**
* Plays a beep at the given frequency.
*
* @param tone The frequency of the beep to play, in hertz.
* @param duration The duration of the beep, in seconds. Defaults to 0.2.
*/
async beep(tone: number, duration = 0.2) {
if (!this.isPlayingAllowed()) return;
await this.prepareToPlay();
const oscillator = this.context.createOscillator();
oscillator.type = 'sine';
oscillator.frequency.setValueAtTime(tone, this.context.currentTime);
const gainNode = this.context.createGain();
gainNode.gain.setValueAtTime(0, this.context.currentTime);
// Fade in
gainNode.gain.linearRampToValueAtTime(0.5, this.context.currentTime + 0.01);
// Fade out
gainNode.gain.linearRampToValueAtTime(
0,
this.context.currentTime + duration,
);
oscillator.connect(gainNode);
gainNode.connect(this.context.destination);
oscillator.start(this.context.currentTime);
oscillator.stop(this.context.currentTime + duration);
}
/**
* Returns whether or not playing sounds is currently allowed.
*
* @returns False if audio is muted or a sound has just been played, otherwise
* true.
*/
private isPlayingAllowed(
this: WorkspaceAudio,
): this is WorkspaceAudio & Required<{context: AudioContext}> {
const now = new Date();
if (
this.getMuted() ||
!this.context ||
(this.lastSound !== null &&
now.getTime() - this.lastSound.getTime() < SOUND_LIMIT)
) {
return false;
}
return true;
}
/**
* Prepares to play audio by recording the time of the last play and resuming
* the audio context.
*/
private async prepareToPlay(
this: WorkspaceAudio & Required<{context: AudioContext}>,
) {
this.lastSound = new Date();
if (this.context.state === 'suspended') {
await this.context.resume();
}
}
/**
* @param muted If true, mute sounds. Otherwise, play them.
*/