https://gitlab.synchro.net/main/sbbs/-/commit/54648707ec2bc73ecb4afca3
Added Files:
src/doors/syncscumm/door/audio_term.cpp audio_term.h src/doors/syncscumm/test/test_sst_io_audio.c test_sst_io_audio_backlog.c test_sst_io_audio_ini_headroom.c test_sst_io_audio_ini_off.c test_sst_io_audio_ini_tune.c test_sst_io_audio_static.c test_sst_io_audio_tone.c test_sst_io_audio_underrun.c
Modified Files:
.gitlab-ci-unix.yml docs/superpowers/plans/2026-07-16-syncscumm-m4.md src/doors/syncretro/CMakeLists.txt M4_AUDIO.md syncretro_audio.h test_audio_bytes.c src/doors/syncscumm/DESIGN.md build.sh src/doors/syncscumm/door/CMakeLists.txt module.mk sst_io.c sst_io.h syncscumm.cpp video_term.cpp src/doors/syncscumm/syncscumm.example.ini src/doors/syncscumm/test/subtitles.sh unit_sst_io.sh src/doors/termgfx/audio_stream.c audio_stream.h chunk.c src/doors/termgfx/test/CMakeLists.txt test_audio_stream.c
Log Message:
syncscumm: stream the game's audio to SyncTERM
Speech, effects and music, over the existing door connection. A terminal
that cannot play audio is untouched: silent, subtitles auto-on, not one
audio byte on the wire.
ScummVM sums every voice, effect and synthesized track into one mixer, so
one pull covers every engine and every sound type -- there is no useful per-sound boundary to hook, and music arrives already rendered. The pull
rides the wall clock rather than the game loop, so the game's pacing can neither starve nor flood the stream and audio cannot drift against video;
it ticks from both pollEvent() and the present path, because the intro
drives updateScreen() in a tight loop without pumping events, which is
exactly the cutscene the audio exists for.
The terminal is asked what it can decode, and the answer drives subtitles:
auto now means "will this player actually hear it", which the sysop's
switch also decides. Audio and video share one staged queue with nothing reordered; the door reports audio's own share of it as backlog, so the
drop rule fires on real audio congestion rather than on a large frame.
Three things had to be found by listening and then measured, and all three
had a plausible wrong answer first:
Dialogue dropped out for seconds at a time. The streaming module flushes
when its cushion releases and at stop, and otherwise leaves writing to the door's loop -- which only flushed while presenting a frame. A comic panel
is a still: drawn once, talked over. Nothing drawn, nothing flushed, so
chunks sat staged while the module blamed a link that was idle.
It popped ten times a second. Each chunk is its own Opus stream, so every boundary carried an encoder delay and a decoder ramp with nothing to lap against. Chunking is not the problem -- SyncTERM's queues are for exactly
this -- doing it ten times a second was. 250ms chunks put the boundary
rate back at chance and cut a third of the payload.
The loudest scene tore. The mix hard-clips there, a codec reconstructs
flat tops with ringing that overshoots, and libsndfile's short reader
wraps instead of clamping -- so an overshoot came back as a full-scale
sign flip, thousands of them. Merely loud passages never overshoot. The
stream is attenuated before the encoder and the channel plays at unity,
which nets to the gain it always had: the headroom simply arrives before
the encoder rather than after the decoder, where it could do nothing.
Sysops get [audio] for the rest: enabled, quality, volume, chunk_ms,
prebuffer, headroom, channels.
---
þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net