Let the places of a song share what it keeps to itself (#357)
game-maker / gate (push) Successful in 3m54s
game-maker / suite (push) Successful in 15m58s

With clips, one take serves many items: four items on the four clips
of one take. The rule from #355 gave every further place its own copy
of an embedded asset, so a cut made in the Sound tab reached one item
only. Now the song, its tracks and its items share a sound, a pattern
or a sound FX the song keeps.

- settleEmbedded no longer copies within the song. An embedded asset
  stays as long as one place names it, and goes with the last one.
- A paste from another song still gets a copy, but one copy for all
  the places naming the same asset, with its clip ids mapped.
- The picks in a song offer what the song keeps, under a line after
  the project's assets: the Sound row of an item the song's sounds
  and their clips, the Pattern row its patterns, every sound FX pick
  its sound FX.
- Make project asset and Embed a copy are unchanged.

The project cbbf160bb7e8 was merged by hand afterwards: the items of
Song 2 on three copies of Take 1 now play the same clips of the first
one, and the copies are gone.

Tests:
- Unit embedded, sound-clips, own-fx and the song tests: 66 green,
  3 counter-checks red as expected
- e2e 65 on melektaus-atx: ALL PASSED, 52 checks
- Gate on melektaus-atx: CHECK EXIT 0, 271 files, 4198 tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-08 21:13:22 +02:00
1 parent 95edf9959b
commit e721bba120
6 files changed
+131 -56

No files matched your search

+19 -10
View File
@@ -2,8 +2,7 @@ import { SetSongCommand } from '../../core/commands/songCommands';
import type { SongAsset } from '../../core/model/Asset';
import { ASSET_TYPE_LABELS } from '../../core/model/AssetTypes';
import { findAssetOf, type Project } from '../../core/model/Project';
import { findSound } from '../../core/model/sound';
import { isEmbedded } from '../../core/music/embedded';
import { embeddedIn, markedName } from '../../core/music/embedded';
import { assetRefLabel, soundEntries } from '../../core/properties/assetRef';
import type { PropertyDef } from '../../core/properties/PropertySchema';
import {
@@ -204,8 +203,9 @@ function buildItem(host: InspectorHost, asset: SongAsset, item: SongItem, picked
});
if (def.key === FX_ROW.key)
handlers.moreChoices = (current) => ownFxEntries(state, asset.id, current, commit);
if (def.key === 'asset' && item.kind === 'sound')
handlers.moreChoices = (current) => ownSoundEntries(state, current, (id) => state.execute(commit(id)));
if (def.key === 'asset')
handlers.moreChoices = (current) =>
keptEntries(state, asset.id, item.kind, current, (id) => state.execute(commit(id)));
const row = createFieldRow(host, def, item[def.key as keyof SongItem], handlers);
host.fields.set(def.key, row);
host.body.add(row.row);
@@ -220,15 +220,24 @@ function buildItem(host: InspectorHost, asset: SongAsset, item: SongItem, picked
}
/**
* The sound the song keeps for the item (#355) and its clips (#357), which no list offers: a take's
* clips are picked here.
* The sounds with their clips (#357) or the patterns the song keeps (#355), which no list offers:
* its items share them, so a take's clips are picked here.
*/
function ownSoundEntries(state: EditorState, current: string, pick: (id: string) => void): MenuEntry[] {
const sound = findSound(state.project, current)?.asset;
if (!sound || !isEmbedded(sound)) return [];
function keptEntries(
state: EditorState,
songId: string,
kind: SongItem['kind'],
current: string,
pick: (id: string) => void,
): MenuEntry[] {
const entries = embeddedIn(state.project, songId)
.filter((a) => a.type === kind)
.sort((a, b) => a.name.localeCompare(b.name))
.flatMap((a) => (a.type === 'sound' ? soundEntries(a) : [{ id: a.id, label: markedName(a) }]));
if (entries.length === 0) return [];
return [
{ separator: true },
...soundEntries(sound).map((entry) => ({
...entries.map((entry) => ({
label: entry.label,
current: entry.id === current,
onSelect: () => pick(entry.id),