blob: 19fab1bd2aad31fd06830ac7f9baf130d308d9f5 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {IS_IOS} from './constants.js';
export interface GeneratedSoundFxProvider {
getGeneratedSoundFx(): GeneratedSoundFx|null;
}
/**
* Generated sound FX class for audio cues.
*/
export class GeneratedSoundFx {
constructor() {
this.context = new AudioContext();
if (IS_IOS) {
this.context.onstatechange = () => {
if (this.context.state !== 'running') {
this.context.resume();
}
};
this.context.resume();
}
this.panner = this.context.createStereoPanner ?
this.context.createStereoPanner() :
null;
}
private context: AudioContext;
private panner: StereoPannerNode|null = null;
private bgSoundIntervalId: number|null = null;
stopAll() {
this.cancelFootSteps();
}
/**
* Play oscillators at certain frequency and for a certain time.
*/
playNote(
frequency: number, startTime: number, duration: number,
vol: number = 0.01, pan: number = 0) {
const osc1 = this.context.createOscillator();
const osc2 = this.context.createOscillator();
const volume = this.context.createGain();
// Set oscillator wave type
osc1.type = 'triangle';
osc2.type = 'triangle';
volume.gain.value = 0.1;
// Set up node routing
if (this.panner) {
this.panner.pan.value = pan;
osc1.connect(volume).connect(this.panner);
osc2.connect(volume).connect(this.panner);
this.panner.connect(this.context.destination);
} else {
osc1.connect(volume);
osc2.connect(volume);
volume.connect(this.context.destination);
}
// Detune oscillators for chorus effect
osc1.frequency.value = frequency + 1;
osc2.frequency.value = frequency - 2;
// Fade out
volume.gain.setValueAtTime(vol, startTime + duration - 0.05);
volume.gain.linearRampToValueAtTime(0.00001, startTime + duration);
// Start oscillators
osc1.start(startTime);
osc2.start(startTime);
// Stop oscillators
osc1.stop(startTime + duration);
osc2.stop(startTime + duration);
}
background() {
const now = this.context.currentTime;
this.playNote(493.883, now, 0.116);
this.playNote(659.255, now + 0.116, 0.232);
this.loopFootSteps();
}
loopFootSteps() {
if (!this.bgSoundIntervalId) {
this.bgSoundIntervalId = setInterval(() => {
this.playNote(73.42, this.context.currentTime, 0.05, 0.16);
this.playNote(69.30, this.context.currentTime + 0.116, 0.116, 0.16);
}, 280);
}
}
cancelFootSteps() {
if (this.bgSoundIntervalId) {
clearInterval(this.bgSoundIntervalId);
this.bgSoundIntervalId = null;
this.playNote(103.83, this.context.currentTime, 0.232, 0.02);
this.playNote(116.54, this.context.currentTime + 0.116, 0.232, 0.02);
}
}
collect() {
this.cancelFootSteps();
const now = this.context.currentTime;
this.playNote(830.61, now, 0.116);
this.playNote(1318.51, now + 0.116, 0.232);
}
jump() {
const now = this.context.currentTime;
this.playNote(659.25, now, 0.116, 0.3, -0.6);
this.playNote(880, now + 0.116, 0.232, 0.3, -0.6);
}
}