A trailer needs its music and effects on the same timeline as its
pictures. Until now a composition could only place objects, so sound
had to be started by hand from a behavior and drifted against the
picture.
- Audio items: a sound, a clip of a sound, a pattern or a song dragged
from the asset panel onto a track becomes an item as long as one
pass of it. It may share a track with object and video items and
overlap them.
- Editing works as for other items: move, resize, trim on the left
(moves the offset), fit to media, delete, copy and paste, box
selection. Sounds draw their waveform, the colour tells the kind.
- The inspector shows what it plays, start, length, offset, volume
(-60 to +12 dB) and bus. The bus is "From asset", the master or one
of the buses, as asked for in the issue.
- An item longer than its material plays in rounds. Sounds and
patterns start over, a song runs on in its loop. Loop marks on the
item follow the same rule, a song's first mark sits at its loop end.
- compositionAudio.ts turns the items into sequences, each with its
own tempo. CompositionSound plays them, in the tab and in a game.
- In the tab the items are heard from the playhead on, straight into
the master like every tab. A scrub is silent. Playing a song or
pattern in its own tab stops the composition, and the other way
round.
- In a game they are planned on the audio clock for the moment the
composition clock reaches them, with the scheduler's LEAD made up.
While the clock stands (waiting for a video, paused, placement off)
they are silent and resume at the right spot.
Found along the way:
- The asset panel never let a song out of its list, so a song could
not be dragged anywhere. Song and scene tab reject it as they reject
a pattern.
- The asset panel scrolled to the selected asset on every rebuild of
its list, so folding a group far below the selection jumped back up.
Only a new selection scrolls now.
- At the end of the list, folding a group pulled every row down by the
rows that went away. ScrollContainer has a new opt-in holdOffset,
set only in the asset panel, that keeps the offset until the user
scrolls up.
Limits: a time scale other than 1 or a frame longer than 0.1 s moves
the picture and not the sound. A baked song plays its notes here, not
the bake.
Tests:
- tsc, eslint, Prettier clean. Gate on melektaus-atx green,
309 files, 4788 tests (4772 plus 16 new).
- Unit: composition-audio.test.ts 13 new, scroll-container 3 new.
Counter-checks: without catching up on load time or without the
silence on pause the unit tests go red.
- e2e 68h: the sound is planned 1.2 ms after the composition reaches
0.5 s. Without the LEAD make-up it is 54 ms late and red.
- e2e 04 and 69 ALL PASSED, 86 checks. 69o-ii drags a pattern and a
song onto a track, 20 frames each.
- e2e 04: folding Sound at the end of the list keeps its header at
y 234. With the old panel it jumps to 984 and the check is red.
- Not run in a browser: the loop marks of a song item, copy and paste
of audio items, the bus choice.
- README and wiki count 2072 checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The asset panel stopped with its last group flush on the bottom edge,
and both lists on the left jumped on every wheel step while the tabs
next to them already zoom with a transition.
- ScrollContainer gets tailSpace, empty room always kept below the
content. Content that fits only without it scrolls by the rest. The
content is made that much taller, so clicks and the context menu on
that room work as on any empty part of the list.
- The asset panel keeps six rows of it. The other lists keep none.
- ScrollContainer gets wheelStep, which by default scrolls at once.
- scrollTween() in viewTween.ts hands the wheel step to an axisTween.
The duration is the zoom transition's from the General settings,
quick steps add up and stop at the end of the list, and dragging the
bar or a jump to a row ends the glide where it stands.
- Only the asset panel and Scene objects use it. Inspector, menus and
the console still jump.
- Wiki: Concepts describes the glide and the room below the assets.
Not checked in the browser by me. The user picked the six rows and
asked for the glide after trying the room below the list.
Tests:
- Unit scroll-container: 10 tests (3 new for tailSpace).
- Unit view-tween: 4 new with a fake clock (glide lands on target,
quick steps add up and stop at the end, a jump ends it, 0 ms jumps).
- Gate on melektaus-atx: CHECK EXIT 0, 299 files,
4593 tests (4586 before).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A node's description and every note in the inspector were one line
cut with an ellipsis. Stop pattern says what it does with no pattern
picked, but only past the cut, so nobody could read it.
- Label takes a wrap option. It breaks its text at the width it gets
and keeps the margin a single line has, above and below all lines.
- Widget.heightFor(width) gives the height at a width. It is
measure().h everywhere except where text wraps.
- VStack and SectionStack lay a column out with it, at the width
each row will get.
- ScrollContainer measures again at the narrower width when its
scrollbar shows, because the text then takes more lines.
- addNote and the empty inspector's hint wrap.
- The file list's own heightFor(count) is now heightOfRows, so the
layout does not hand it a width as a row count.
Tests:
- Unit scroll-container: 7 green, 2 new (height for width, again
beside the scrollbar)
- Unit ui + inspector: 17 files, 336 green (before the 2 new ones)
- e2e 02 (stein2, atx busy with suite 545): ALL PASSED, 29 checks
- Screenshot: Stop pattern shows its description in six lines
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A short list opened after a long one drew all its rows stacked on one
point. The fault was not in the list tab: rebuild() handed the row stack
to setContent() while it was still empty, so the content was sized to
max(rows, viewport) = the viewport. The rows arrived after that, and for
a short list the same formula yields the same height, so resize() saw no
change and skipped layout(). A long list changes the height and never
showed it.
relayoutContent() now runs whenever the content changed, rather than
leaning on resize()'s change detection - that detection is the wrong
instrument for "the children moved".
The list tab was the only affected caller: the panels use ListView,
which positions its rows itself, and the inspector fills its body
before setContent.
Also drops the comma line under the list. Every section that can carry a
list supplies openList, so the comma fallback in fieldFactory was
unreachable; listToText/listFromText go with it. The list-value test now
covers coerceListItem through coerceValue instead, so the boolean
spelling stays guarded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>