Files
Battle-for-Tismo/tests/audio_test.js

187 lines
6.9 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.musicSources, [
"assets/sounds/crashed_ship.ogg",
"assets/sounds/dark_city.ogg",
"assets/sounds/constellation.ogg",
"assets/sounds/patrol.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_plays_a_shuffled_playlist() {
const env = await setupDom();
try {
FakeAudio.reset();
// random() near 1 leaves the shuffle order untouched: crashed_ship first.
const manager = new AudioManager({ random: () => 0.999 });
manager.requestMusic();
const first = FakeAudio.instances.find(
(audio) => audio.src === "assets/sounds/crashed_ship.ogg"
);
this.assertNotNull(first, "the first track was created");
this.assertFalse(first.loop, "a playlist track does not loop itself");
this.assertEqual(first.plays, 1, "the first track was started once");
this.assertTrue(typeof first.onended === "function", "the track knows what follows");
// A finished track hands over to the next one in the playlist.
first.onended();
const second = FakeAudio.instances.find(
(audio) => audio.src === "assets/sounds/dark_city.ogg"
);
this.assertNotNull(second, "the second track follows");
this.assertEqual(second.plays, 1, "the second track was started once");
second.onended();
const third = FakeAudio.instances.find(
(audio) => audio.src === "assets/sounds/constellation.ogg"
);
this.assertNotNull(third, "the third track follows");
this.assertEqual(third.plays, 1, "the third track was started once");
third.onended();
const fourth = FakeAudio.instances.find(
(audio) => audio.src === "assets/sounds/patrol.ogg"
);
this.assertNotNull(fourth, "the fourth track follows");
this.assertEqual(fourth.plays, 1, "the fourth track was started once");
// After the last track the playlist reshuffles instead of falling silent.
const before = FakeAudio.instances.length;
fourth.onended();
this.assertGreater(FakeAudio.instances.length, before, "playback wraps around");
} finally {
teardownDom(env);
}
}
async test_music_stops_and_mutes() {
const env = await setupDom();
try {
FakeAudio.reset();
const manager = new AudioManager({ random: () => 0.999 });
manager.requestMusic();
const music = FakeAudio.instances.find(
(audio) => audio.src === "assets/sounds/crashed_ship.ogg"
);
this.assertNotNull(music, "the music element was created");
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);
}
}
}