Decode a clip from a file of its own, not its whole sound (#349)
game-maker / gate (push) Successful in 4m3s
game-maker / suite (push) Failing after 14m58s

A clip was cut from its sound's file, so a game decoded the whole file
for it and held it for as long as the page was open. A long recording
played through a few short clips cost the recording. Every clip now has
a copy of its cut as Opus, with Stream on or off, and a game decodes
that instead.

- StreamKeeper makes the copy of each clip whether its sound streams
  or not. Stream only decides whether a sound gets a copy of its own.
- clipCopyTable says which cut decodes from which copy. The sound
  library loads the copy for a clip unless the whole file is decoded
  already. Then the clip is cut from that as before.
- projectFiles decodes the whole file ahead only where something may
  play the sound itself: no clips, a clip without a fitting copy, or
  the sound's id anywhere in an asset that can name one. That last
  test walks the data, not the fields, so no place is missed.
- A copy that cannot be had falls back to the whole file. A library
  that bakes keeps to the whole files, as the bake's fingerprint does.

Tests:
- Gate: CHECK EXIT 0, 280 files, 4319 tests (7 new: streams +4,
  sound-library +3).
- e2e 34: ALL PASSED, 108 checks (2 new). With Stream off a fresh
  library decodes the ten-second reversed clip's copy and not the
  five-minute file. It is the cut made from the original to within
  0.0091 a sample and 0.013 dB in any 20 ms. Once a node plays the
  sound itself, the whole file is decoded ahead again.
- e2e 65: all passed with this change.
- Counter-checks red as they should be: the library ignoring copies,
  the whole file always decoded ahead, clip copies only with Stream.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-09 20:53:49 +02:00
1 parent 2764919bb8
commit 82e96e680f
10 files changed
+332 -42

No files matched your search

+73 -9
View File
@@ -10,11 +10,14 @@ import {
SetAssetPropertyCommand,
} from '../../src/core/commands/assetCommands';
import { AddClipCommand, SetClipPropsCommand } from '../../src/core/commands/clipCommands';
import { SetSongCommand } from '../../src/core/commands/songCommands';
import type { SongAsset, SoundAsset, SoundStream } from '../../src/core/model/Asset';
import { projectFiles } from '../../src/core/model/projectFiles';
import { cutName } from '../../src/core/model/sound';
import { newItem } from '../../src/core/music/song';
import { STREAM_RATE } from '../../src/core/music/streamQueue';
import {
clipCopyTable,
heldStream,
isStreamUpload,
playedPlan,
@@ -73,9 +76,12 @@ describe('which sounds stream', () => {
expect([...streamTable(f.project).keys()]).toEqual([FILE]);
});
it('asks for a copy of the sound and of each clip with a cut of its own, and lets them go once it is off', () => {
it('asks for a copy of each clip with a cut of its own, of the sound once it streams, and lets that go once it is off', () => {
const f = streamed();
expect(streamWork(f.project)).toEqual({ jobs: [], drops: [] });
expect(streamWork(f.project)).toEqual({
jobs: [{ assetId: f.sound.id, clipId: f.clipId, from: f.cut() }],
drops: [],
});
f.turn(true);
expect(streamWork(f.project).jobs).toEqual([
{ assetId: f.sound.id, clipId: null, from: FILE },
@@ -86,14 +92,16 @@ describe('which sounds stream', () => {
expect(streamWork(f.project)).toEqual({ jobs: [], drops: [] });
f.stack.execute(new SetClipPropsCommand(f.sound.id, f.clipId, { reverse: true }));
expect(streamWork(f.project).jobs).toEqual([{ assetId: f.sound.id, clipId: f.clipId, from: f.cut() }]);
f.sound.clips![0]!.stream = f.copy(f.cut(), CLIP_COPY);
f.turn(false);
expect(streamWork(f.project)).toEqual({
jobs: [],
drops: [
{ assetId: f.sound.id, clipId: null },
{ assetId: f.sound.id, clipId: f.clipId },
],
});
expect(streamWork(f.project)).toEqual({ jobs: [], drops: [{ assetId: f.sound.id, clipId: null }] });
});
it('lets the copy of a clip go once it plays the whole file', () => {
const f = streamed();
f.sound.clips![0]!.stream = f.copy(f.cut(), CLIP_COPY);
f.stack.execute(new SetClipPropsCommand(f.sound.id, f.clipId, { start: 0, end: 0 }));
expect(streamWork(f.project)).toEqual({ jobs: [], drops: [{ assetId: f.sound.id, clipId: f.clipId }] });
});
it('leaves a sound that streams out of what is decoded ahead, its copy made or not', () => {
@@ -121,6 +129,62 @@ describe('which sounds stream', () => {
});
});
describe('a clip decoded from its own copy', () => {
/** A sound only played through one clip of it, by a song. */
function clipped() {
const f = makeSpriteScene('Box');
const made = new CreateSoundCommand('Music', FILE);
f.stack.execute(made);
const sound = made.asset;
const clip = new AddClipCommand(sound.id, { start: 1, end: 3 });
f.stack.execute(clip);
const song = new CreateAssetCommand('song', 'Theme');
f.stack.execute(song);
const track = (song.asset as SongAsset).tracks[0]!.id;
const play = (asset: string) =>
f.stack.execute(
new SetSongCommand(song.asset.id, {
items: [newItem({ track, kind: 'sound', asset, tick: 0, length: 96 })],
}),
);
play(clip.clipId);
const cut = () => cutName(FILE, cutOf(sound.clips![0]!.props));
const copy = () =>
(sound.clips![0]!.stream = { file: CLIP_COPY, from: cut(), frames: 96000, bytes: 4000 });
return { ...f, sound, clipId: clip.clipId, play, cut, copy };
}
it('decodes the copy of each clip whose copy fits, whether its sound streams or not', () => {
const f = clipped();
expect(clipCopyTable(f.project).size).toBe(0);
f.copy();
expect([...clipCopyTable(f.project)]).toEqual([[f.cut(), CLIP_COPY]]);
f.stack.execute(new SetAssetPropertyCommand(f.sound.id, 'stream', true));
expect([...clipCopyTable(f.project)]).toEqual([[f.cut(), CLIP_COPY]]);
f.stack.execute(new SetClipPropsCommand(f.sound.id, f.clipId, { fadeIn: 0.5 }));
expect(clipCopyTable(f.project).size).toBe(0);
});
it('loads the copies of its clips ahead rather than the whole file, until something plays it whole', () => {
const f = clipped();
expect(projectFiles(f.project).sounds).toEqual([FILE]);
f.copy();
expect(projectFiles(f.project).sounds).toEqual([f.cut()]);
f.play(f.sound.id);
expect(projectFiles(f.project).sounds).toEqual([FILE]);
});
it('loads the whole file while a clip has no copy of its cut, or is the whole file', () => {
const f = clipped();
f.copy();
const whole = new AddClipCommand(f.sound.id, { start: 0, end: 0 });
f.stack.execute(whole);
expect(projectFiles(f.project).sounds).toEqual([FILE]);
f.stack.execute(new SetClipPropsCommand(f.sound.id, whole.clipId, { start: 0.5 }));
expect(projectFiles(f.project).sounds).toEqual([FILE]);
});
});
describe('walking a copy', () => {
it('plays a sound from where it is asked to, round its whole length where it loops', () => {
expect(playedPlan(1000, 0, false)).toEqual({ from: 0, until: 1000, loop: null });