Decode a clip from a file of its own, not its whole sound (#349)
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:
1 parent
2764919bb8
commit
82e96e680f
10 files changed
+332
-42
No files matched your search
@@ -14,10 +14,10 @@ import type { EditorState } from '../state';
|
||||
const SETTLE_MS = 600;
|
||||
|
||||
/**
|
||||
* Makes the copies streamed sounds are played from (#349). A moment after the project last changed,
|
||||
* each sound with `stream` on gets a copy of its file as Opus, and each of its clips one of its cut,
|
||||
* one at a time, from the file decoded for it and let go again. A copy no longer wanted is taken off
|
||||
* its sound and deleted once the project has been saved without it. Not while the game runs.
|
||||
* Makes the copies sounds and clips are played from (#349). A moment after the project last changed,
|
||||
* each sound with `stream` on gets a copy of its file as Opus, and each clip of any sound one of its
|
||||
* cut, one at a time, from the file decoded for it and let go again. A copy no longer wanted is taken
|
||||
* off its sound and deleted once the project has been saved without it. Not while the game runs.
|
||||
*/
|
||||
export class StreamKeeper {
|
||||
private job: { job: StreamJob; share: number } | null = null;
|
||||
@@ -77,7 +77,7 @@ export class StreamKeeper {
|
||||
if (error instanceof BakeCancelled) return this.later(0);
|
||||
this.failed.add(keyOf(next));
|
||||
this.state.setNotice(
|
||||
`No stream copy of ${this.label(next)}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
`No copy of ${this.label(next)}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
'error',
|
||||
);
|
||||
} finally {
|
||||
@@ -100,7 +100,7 @@ export class StreamKeeper {
|
||||
const before = Math.round(running.share * 100);
|
||||
running.share = share;
|
||||
const now = Math.round(share * 100);
|
||||
if (now !== before) this.state.setNotice(`Making a stream copy of ${label}: ${now}%`, 'quiet');
|
||||
if (now !== before) this.state.setNotice(`Making a copy of ${label}: ${now}%`, 'quiet');
|
||||
},
|
||||
cancelled: () => !this.stillWanted(job),
|
||||
});
|
||||
@@ -119,14 +119,14 @@ export class StreamKeeper {
|
||||
bytes: uploaded.bytes,
|
||||
});
|
||||
if (old && old !== uploaded.file && !this.named(old)) dropWhenSaved(this.state, this.sounds, old);
|
||||
this.state.setNotice(`Made a stream copy of ${label}`, 'quiet');
|
||||
this.state.setNotice(`Made a copy of ${label}`, 'quiet');
|
||||
}
|
||||
|
||||
/** Whether the sound still streams and still plays what the copy is being made of. */
|
||||
/** Whether the sound, or its clip, still plays what the copy is being made of – a sound still streams. */
|
||||
private stillWanted(job: StreamJob): boolean {
|
||||
const asset = this.sound(job.assetId);
|
||||
if (!asset || !streamsOf(asset.props)) return false;
|
||||
if (job.clipId === null) return asset.file === job.from;
|
||||
if (!asset) return false;
|
||||
if (job.clipId === null) return streamsOf(asset.props) && asset.file === job.from;
|
||||
const clip = clipsOf(asset).find((c) => c.id === job.clipId);
|
||||
return clip !== undefined && cutName(asset.file, cutOf(clip.props)) === job.from;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user