https://gitlab.synchro.net/main/sbbs/-/commit/82010f9a06b1c941907003d1
Modified Files:
src/doors/syncretro/probe_core.c test_audio_bytes.c test_audio_bytes.golden src/doors/termgfx/audio_stream.c audio_stream.h chunk.c src/doors/termgfx/test/test_audio_stream.c
Log Message:
termgfx: remove any DC offset before the encoder sees it
Ogg/Opus cannot represent DC. Encode a chunk holding a constant level and the decoder hands back a decay from that level toward zero -- measured, a constant -17536 comes out around -6000 by the end of a 50 ms chunk. A door streams
those chunks back to back, so every boundary is a step: a 20 Hz sawtooth at about a third of full scale, heard as a continuous low click or rumble that stops only when louder audio masks it.
Nothing about that is specific to one door or one source. SyncConquer hit it through an IMA-ADPCM predictor drifting into a growing offset (its PROVENANCE.md patch 8; the hazard is written up in termgfx/README.md). SyncArcade hit it from an unrelated direction: MAME 2003-Plus drivers whose
DAC idles off zero -- Joust sits at a constant +7710 and Xenophobe at -17536, peak to peak ZERO. That is silence at the wrong level, and termgfx_chunk_is_silent() tests for exact zeros, so those chunks were encoded and uploaded as if they were music.
The constraint belongs to the encoder, so the defense goes here rather than in either door: a one-pole ~10 Hz high-pass over the source's PCM in termgfx_stream_feed(), before anything is chunked. Ten Hz is three decades below anything a game plays and below the floor of hearing, so no audible content is touched. The filter state is double precision because an integer accumulator at this pole rounds its own feedback every sample and settles into a limit cycle -- trading the buzz for a quieter buzz.
Removing the offset also restores the free silence path. Once the filter settles, an idle source at a constant level reads as exact zeros again, and a zero chunk is the cached "<prefix>/z" replay that costs no bytes at all -- so the games that provoked this now cost less than they did before. The price is at the transition: the filter decays over ~170 ms rather than snapping, so about two chunks after a sound stops still encode. test_audio_bytes' scenario feeds three silent chunks instead of one and its golden pins that whole shape, rather than hiding the cost by pinning only the settled end of it.
A DC offset is usually a symptom of something else being wrong -- a mis-converted unsigned format, a drifting decoder, a core's idling DAC -- so fixing it silently would be the wrong trade. The stream logs the raw offset once per session, which is how the next one gets found from a log rather than from a player hearing a hum.
probe_core grows -wav and a DC/peak/silent-frames tally, which is what
measured all of the above without needing a terminal.
---
þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net