A sound was fetched and decoded on its first Play sound. That play started late, its beginning was not heard, and a graph that tuned the voice in the meantime had moved on by the time it started. Images and fonts were already loaded when a project opens. Sounds now are too. - projectFiles lists the sounds of a project, and preloadProjectFiles hands them to the new soundLibrary.preload. That covers the editor and the published game. - Preloading opens no audio device. It decodes with an OfflineAudioContext at 48 kHz. The real AudioContext is still made on the first play, and a source resamples where the device runs at another rate. - A run started right after opening a project can still find a sound loading. It then starts late as before. Tests: - sound-library: a preload makes only the offline context, and a play after it starts at once, opening the device only then. - sound-library + project-files: 18 tests, green. - e2e 34 on melektaus-atx: ALL PASSED, 30 checks. - Counter-check: decoding with the real AudioContext again fails the new test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
81 lines
2.5 KiB
TypeScript
81 lines
2.5 KiB
TypeScript
/**
|
|
* What the editor loads ahead of the first draw: every stored file a project's assets name, so an
|
|
* animation or an animated tile does not blink out the first time it reaches a frame of its own,
|
|
* and the first play of a sound is not late.
|
|
*/
|
|
import { describe, expect, it } from 'vitest';
|
|
import type { Asset } from '../../src/core/model/Asset';
|
|
import { createEmptyProject } from '../../src/core/model/Project';
|
|
import { projectFiles } from '../../src/core/model/projectFiles';
|
|
|
|
const PNG_A = `${'a'.repeat(64)}.png`;
|
|
const PNG_B = `${'b'.repeat(64)}.webp`;
|
|
const FONT = `${'c'.repeat(64)}.woff2`;
|
|
const CEL_A = `${'d'.repeat(64)}.cel`;
|
|
const CEL_B = `${'e'.repeat(64)}.cel`;
|
|
const SOUND = `${'f'.repeat(64)}.ogg`;
|
|
|
|
function projectWith(assets: unknown[]) {
|
|
const project = createEmptyProject('p', 'P');
|
|
project.assets.push(...(assets as Asset[]));
|
|
return project;
|
|
}
|
|
|
|
describe('the files a project names', () => {
|
|
it('lists every texture, font, cel and sound, used somewhere or not', () => {
|
|
const project = projectWith([
|
|
{
|
|
id: 't1',
|
|
type: 'texture',
|
|
name: 'Water 1',
|
|
props: {},
|
|
file: PNG_A,
|
|
width: 16,
|
|
height: 16,
|
|
frames: [],
|
|
},
|
|
{
|
|
id: 't2',
|
|
type: 'texture',
|
|
name: 'Water 2',
|
|
props: {},
|
|
file: PNG_B,
|
|
width: 16,
|
|
height: 16,
|
|
frames: [],
|
|
},
|
|
{ id: 'f', type: 'font', name: 'Font', props: {}, file: FONT },
|
|
{
|
|
id: 'px',
|
|
type: 'pixelart',
|
|
name: 'Px',
|
|
props: {},
|
|
width: 8,
|
|
height: 8,
|
|
layers: [{ id: 'l', name: 'L', opacity: 1 }],
|
|
frames: [
|
|
{ id: 'f1', cels: { l: CEL_A } },
|
|
{ id: 'f2', cels: { l: CEL_B } },
|
|
{ id: 'f3', cels: { l: CEL_A } },
|
|
],
|
|
},
|
|
{ id: 's', type: 'sound', name: 'Blip', props: {}, file: SOUND },
|
|
]);
|
|
expect(projectFiles(project)).toEqual({
|
|
textures: [PNG_A, PNG_B],
|
|
fonts: [FONT],
|
|
cels: [CEL_A, CEL_B],
|
|
sounds: [SOUND],
|
|
});
|
|
});
|
|
|
|
it('leaves out a name that is no stored file', () => {
|
|
const project = projectWith([
|
|
{ id: 't', type: 'texture', name: 'Failed', props: {}, file: '', width: 0, height: 0, frames: [] },
|
|
{ id: 'f', type: 'font', name: 'Failed', props: {}, file: 'x.woff2' },
|
|
{ id: 's', type: 'sound', name: 'Failed', props: {}, file: '' },
|
|
]);
|
|
expect(projectFiles(project)).toEqual({ textures: [], fonts: [], cels: [], sounds: [] });
|
|
});
|
|
});
|