135 lines
5.0 KiB
JavaScript
135 lines
5.0 KiB
JavaScript
import { TestCase } from "./framework/test_case.js";
|
|
import { setupDom, teardownDom, FakeAudio } from "./framework/dom.js";
|
|
import { AudioManager, EFFECT_PRESETS } from "../client/js/audio.js";
|
|
|
|
// A minimal Web Audio stand-in that records every node it hands out and the
|
|
// connections made between them.
|
|
function fakeAudioContext() {
|
|
const nodes = [];
|
|
const node = (kind) => {
|
|
const created = {
|
|
kind,
|
|
connected: [],
|
|
connect(target) {
|
|
this.connected.push(target);
|
|
return target;
|
|
},
|
|
disconnect() {
|
|
this.disconnected = true;
|
|
},
|
|
};
|
|
nodes.push(created);
|
|
return created;
|
|
};
|
|
return {
|
|
nodes,
|
|
state: "running",
|
|
destination: node("destination"),
|
|
resume() {
|
|
return Promise.resolve();
|
|
},
|
|
createMediaElementSource: () => node("source"),
|
|
createBiquadFilter: () =>
|
|
Object.assign(node("biquad"), { type: "", frequency: { value: 0 }, Q: { value: 0 } }),
|
|
createGain: () => Object.assign(node("gain"), { gain: { value: 0 } }),
|
|
createDelay: () => Object.assign(node("delay"), { delayTime: { value: 0 } }),
|
|
};
|
|
}
|
|
|
|
export class AudioTest extends TestCase {
|
|
test_effect_sources_pick_the_chosen_library_files() {
|
|
const manager = new AudioManager();
|
|
this.assertEqual(manager.effects.click, "assets/sounds/button_click.mp3");
|
|
this.assertEqual(manager.effects.complete, "assets/sounds/build_complete.mp3");
|
|
this.assertEqual(manager.effects.notification, "assets/sounds/notification.mp3");
|
|
this.assertEqual(manager.effects.bomb, "assets/sounds/death_flash.flac");
|
|
this.assertEqual(manager.musicSource, "assets/sounds/crashed_ship.ogg");
|
|
}
|
|
|
|
async test_plays_effects_and_mutes() {
|
|
const env = await setupDom();
|
|
try {
|
|
FakeAudio.reset();
|
|
const manager = new AudioManager();
|
|
manager.play("click");
|
|
this.assertEqual(FakeAudio.playedSources, ["assets/sounds/button_click.mp3"]);
|
|
|
|
// The bombardment passes an explicit, zoom-scaled volume.
|
|
manager.play("bomb", { volume: 0.25 });
|
|
const bomb = FakeAudio.instances[FakeAudio.instances.length - 1];
|
|
this.assertEqual(bomb.src, "assets/sounds/death_flash.flac");
|
|
this.assertApprox(bomb.volume, 0.25, 1e-9, "the volume option overrides the default");
|
|
|
|
manager.setEnabled(false);
|
|
const muted = FakeAudio.playedSources.length;
|
|
manager.play("click");
|
|
manager.play("bomb", { volume: 0.9 });
|
|
this.assertEqual(FakeAudio.playedSources.length, muted, "muted effects are silent");
|
|
|
|
let changed = null;
|
|
manager.onChange = (value) => {
|
|
changed = value;
|
|
};
|
|
manager.setEnabled(true);
|
|
this.assertEqual(changed, true, "the toggle reports its new state");
|
|
} finally {
|
|
teardownDom(env);
|
|
}
|
|
}
|
|
|
|
async test_bombardment_uses_a_lowpass_and_echo() {
|
|
const env = await setupDom();
|
|
try {
|
|
FakeAudio.reset();
|
|
const context = fakeAudioContext();
|
|
const manager = new AudioManager({ contextFactory: () => context });
|
|
// Keep the graph-cleanup timer short so the test does not linger.
|
|
manager.presets.bomb.echoTail = 0.01;
|
|
manager.play("bomb", { volume: 0.4, duration: 0.01 });
|
|
|
|
const filter = context.nodes.find((node) => node.kind === "biquad");
|
|
this.assertNotNull(filter, "the clip runs through a biquad filter");
|
|
this.assertEqual(filter.type, "lowpass", "the blasts are muffled");
|
|
this.assertApprox(filter.frequency.value, EFFECT_PRESETS.bomb.lowpassHz);
|
|
|
|
const delay = context.nodes.find((node) => node.kind === "delay");
|
|
this.assertNotNull(delay, "an echo delay is built");
|
|
this.assertApprox(delay.delayTime.value, EFFECT_PRESETS.bomb.echoDelay);
|
|
const feedback = delay.connected.find(
|
|
(node) => node.kind === "gain" && node.connected.includes(delay)
|
|
);
|
|
this.assertNotNull(feedback, "the delay feeds back on itself");
|
|
this.assertApprox(feedback.gain.value, EFFECT_PRESETS.bomb.echoFeedback);
|
|
|
|
const played = FakeAudio.instances.find((audio) => audio.src === "assets/sounds/death_flash.flac");
|
|
this.assertNotNull(played, "the clip is played through the graph");
|
|
} finally {
|
|
teardownDom(env);
|
|
}
|
|
}
|
|
|
|
async test_music_loops_and_stops() {
|
|
const env = await setupDom();
|
|
try {
|
|
FakeAudio.reset();
|
|
const manager = new AudioManager();
|
|
manager.requestMusic();
|
|
const music = FakeAudio.instances.find((audio) => audio.src === manager.musicSource);
|
|
this.assertNotNull(music, "the music element was created");
|
|
this.assertTrue(music.loop, "the music loops");
|
|
this.assertEqual(music.plays, 1, "the music was started once");
|
|
|
|
manager.stopMusic();
|
|
this.assertTrue(music.paused, "stopping pauses the music");
|
|
|
|
// Muting while music is wanted stops it; unmuting starts it again.
|
|
manager.requestMusic();
|
|
manager.setEnabled(false);
|
|
manager.setEnabled(true);
|
|
this.assertGreater(FakeAudio.instances.length, 1, "music restarts when unmuted");
|
|
} finally {
|
|
teardownDom(env);
|
|
}
|
|
}
|
|
}
|