feat(studio): play the scenario's audio while previewing - #182
Merged
Conversation
The studio had no audio output at all — no backend in its dependencies, no stream anywhere, Present mode included. Judging a cut without sound is half the job, so the transport now plays. What it plays is the output of mix_audio_tracks, the same function the encoder calls, so start/end, fades, per-track volume and volume keyframes are heard exactly as they will be muxed. A preview that ignored the envelope would misreport the edit. The mix spans the whole scenario, silence included, which lets the playhead follow the audio clock instead of a 1/fps timer that drifts against the sound card over a long scenario. With no track — or no output device, as in CI — the timer stays in charge and nothing changes. cpal's stream is not Send, so it runs on its own thread behind a command channel; position and state are published as atomics the UI reads without touching it. Seeking publishes the new position on the caller's side so a scrub cannot read back as the old one and snap the playhead backwards. Play/pause, scrub and a mute button drive it; the mix is rebuilt whenever the model reloads, so opening another file does not keep the previous sound.
LeadcodeDev
force-pushed
the
fix/studio-audio
branch
from
August 12, 2026 11:57
8e45452 to
da4ec32
Compare
LeadcodeDev
added a commit
that referenced
this pull request
Aug 12, 2026
…nux (#184) Preview audio (#182) added rodio, which pulls cpal, which needs ALSA's development headers on Linux. The build was only ever checked on macOS, where cpal goes through CoreAudio and alsa-sys never enters the graph — so main went red on merge and every PR opened after it inherited the failure. Same line as the webkit/gtk/xdo deps already there, in both jobs.
This was referenced Aug 12, 2026
LeadcodeDev
added a commit
that referenced
this pull request
Aug 12, 2026
volume, volume_keyframes and the fades are what comes out of the speakers, and since #182 the studio plays exactly that — a waveform drawing the raw file's envelope while a fade takes the sound down contradicts what the viewer hears. The gain comes from the encoder's own envelope, factored out of the mixer loop as track_gain_at(track, t_in_track, audible) and expressed in seconds so the mixer (OUTPUT_SAMPLE_RATE, resampled) and the analysis (the file's own rate, decoded source) share one implementation. A second copy would drift, and the whole point is that the picture matches the sound. The cache fingerprint now hashes the serialised track rather than start/end alone: two scenarios can name the same file with different mixes, and adding a field to AudioTrack must not silently leave it out of the key. Not mergeable yet — see #201. The components read scene-local time against a scenario-time analysis, which is harmless while the envelope is flat and turns into a flat trace as soon as it is not.
LeadcodeDev
added a commit
that referenced
this pull request
Aug 12, 2026
volume, volume_keyframes and the fades are what comes out of the speakers, and since #182 the studio plays exactly that — a waveform drawing the raw file's envelope while a fade takes the sound down contradicts what the viewer hears. The gain comes from the encoder's own envelope, factored out of the mixer loop as track_gain_at(track, t_in_track, audible) and expressed in seconds so the mixer (OUTPUT_SAMPLE_RATE, resampled) and the analysis (the file's own rate, decoded source) share one implementation. A second copy would drift, and the whole point is that the picture matches the sound. The cache fingerprint now hashes the serialised track rather than start/end alone: two scenarios can name the same file with different mixes, and adding a field to AudioTrack must not silently leave it out of the key. Not mergeable yet — see #201. The components read scene-local time against a scenario-time analysis, which is harmless while the envelope is flat and turns into a flat trace as soon as it is not.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The studio had no audio output at all: no backend in its dependencies, no stream anywhere, Present mode included. Judging a cut without sound is half the job, so the transport now plays.
How
What it plays is the output of
mix_audio_tracks, the same function the encoder calls, sostart/end, fades, per-track volume and volume keyframes are heard exactly as they will be muxed. A preview that ignored the envelope would misreport the edit.The mix spans the whole scenario, silence included, which lets the playhead follow the audio clock instead of a
1/fpstimer that drifts against the sound card over a long scenario. With no track — or no output device, as in CI — the timer stays in charge and nothing changes.cpal's stream is not
Send, so it runs on its own thread behind a command channel; position and state are published as atomics the UI reads without touching it. Seeking publishes the new position on the caller's side, so a scrub cannot read back as the old one and snap the playhead backwards.Transport: play/pause and scrub drive it, plus a mute button next to play. The mix is rebuilt whenever the model reloads, so opening another file does not keep the previous scenario's sound.
Cost
One dependency,
rodiowithdefault-features = false, features = ["playback"]— no decoders, since the samples are already mixed. Pulls cpal and the platform audio bindings.Not covered
Preview audio for
--framessegments, and per-track soloing. Neither is needed to judge a cut.Verification
cargo test --workspacegreen,cargo fmt --checkandcargo clippy --all-targetsclean, the studio binary links. The position logic is unit-tested; the actual sound output has not been listened to from this environment.Rebased onto
mainafter #181 merged.