Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 69 additions & 16 deletions packages/blockly/core/workspace_audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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.
*/
Expand Down
Loading