The MIDI-clip-end-grows-too-fast bug was a units confusion: AudioClip.duration
is documented "seconds" but MIDI clips store their length in BEATS (they share
the AudioClip struct with sampled clips). The timeline's effective_clip_duration
read it as raw seconds, so at 120 BPM a MIDI clip rendered ~2x too long. The old
backend-snapshot display had hidden this by forcing timeline_duration in beats.
Root fix — make the domain explicit and unforgeable:
- `duration` is now a private field. Reading it in the wrong unit is impossible
because access goes through typed accessors: AudioClip::content_duration() ->
ClipDuration (a Seconds|Beats enum tagged by clip_type) and
set_content_duration(). serde still serializes the private field, so the .beam
format is unchanged (bare number).
- ClipDuration::to_seconds(tempo_map) for display/sizing; ::native() for code
that already works in the clip's native domain (trim math shares it).
- get_clip_duration + the timeline-endpoint calc go through to_seconds, so MIDI
is converted correctly; effective_clip_duration delegates to get_clip_duration.
- Recording mirrors (audio/MIDI progress + finalize) write via set_content_duration
(debug-asserts the value's domain matches the clip type).
Every former raw read/write of the field (core actions, timeline, piano roll,
infopanel, asset library, recording handlers) now goes through the accessors.
Whole workspace compiles; 299 core tests pass.
The export render bucket was dominated by re-rendering the static document
background through Vello every frame and by nearest-sampling the video on
upscale.
- Background cache: render the (static) background through Vello once, snapshot
the composited HDR accumulator, and restore it with a single texture copy on
every later frame instead of a Vello render + sRGB-convert + composite (+2
submits). Invalidated on resize. background-render 3.6ms -> 0.56ms (1080p),
7.5ms -> ~0.5ms (4K).
- blit_straight now uses the bilinear sampler — video frames are scaled to the
output size, and nearest made that blocky. Fixes export and live preview.
- LB_RENDER_PROFILE: gated per-frame timing split (build/decode vs composite/
upload vs srgb, and background vs layers) used to find all of the above. Kept
as a debug aid for the remaining decode stages.
Net: export render ~12.8ms -> ~7.4ms/frame on a 1080p video clip.