• src/doors/syncdoom/i_term

    From Rob Swindell (on Debian L@VERT to Git commit to main/sbbs/m on Sunday, June 28, 2026 23:51:00
    https://gitlab.synchro.net/main/sbbs/-/commit/256363715550c553c1dea313
    Added Files:
    src/doors/syncdoom/i_termsound.c
    Modified Files:
    src/doors/syncdoom/CMakeLists.txt i_sound.c syncdoom.c
    Log Message:
    syncdoom: digital sound effects via SyncTERM audio APC

    Wire SyncDOOM's SFX through the shared termgfx audio module (added for
    SyncDuke in the prior commit): a Doom sound_module_t (i_termsound.c)
    registered in i_sound.c's sound_modules[], so the engine's normal
    S_StartSound -> I_StartSound path drives it with no game-code changes.
    It builds the DS<name> lump name, reads the DMX header (rate + 8-bit PCM)
    and hands the samples to termgfx_audio_sfx, which WAV-wraps and ships them
    to SyncTERM for libsndfile decode + mixing.

    syncdoom.c creates the manager in DG_Init, fires the libsndfile cap probe,
    and feeds inbound bytes in pump_input (with a one-shot "audio: tier=N" dlog). Gated on a libsndfile-capable SyncTERM (tier 1); a silent no-op otherwise. Validated live on SyncTERM 1.10a.

    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian L@VERT to Git commit to main/sbbs/m on Tuesday, June 30, 2026 01:47:00
    https://gitlab.synchro.net/main/sbbs/-/commit/48adddb34d32072991d0d1ce
    Added Files:
    src/doors/syncdoom/i_termmusic.c
    Modified Files:
    src/doors/syncdoom/CMakeLists.txt i_sound.c m_menu.c syncdoom.c src/doors/syncduke/syncduke_input.c syncduke_io.c syncduke_stubs.c src/doors/termgfx/audio.c audio.h audio_mgr.c audio_mgr.h audio_midi.c
    Log Message:
    doors: SyncDOOM terminal music + cross-session audio caching (both doors)

    Wires music into SyncDOOM and adds a shared, cross-session OGG cache to
    termgfx so both doors stop re-rendering and re-uploading the same tracks.

    SyncDOOM music (new):
    - i_termmusic.c: a Doom music_module_t that renders each level's MUS/MIDI
    lump to OPL3 PCM (termgfx_midi_render/libADLMIDI -- adl_openData auto-
    detects MUS, no mus2mid) and loops it over SyncTERM's audio APC. Wired in
    i_sound.c's InitMusicModule(); title-song tier-deferral + stop-on-exit.
    - m_menu.c: restore a "Sound Volume" item (replacing the moot-in-a-terminal
    Graphic Detail toggle) so the existing Music Volume slider is reachable.

    termgfx audio caching (shared -> both doors):
    - Full-song loops: render cap 120s -> 600s (Doom tracks run to ~374s; the
    old cap looped them mid-song).
    - Door-side OGG disk cache: encode once, write to <data>/<door>/audio (the
    SCFG data_dir via $SBBSDATA, else beside the WADs/GRP), ship from disk
    after -- render happens once globally.
    - C;L client-cache skip: at tier-ready query SyncTERM's persistent cache and,
    on a name hit, Load straight from it -- no render AND no upload. Mirrors
    the zmachine's C;L pattern; name-presence (content-addressed), not MD5.
    - Cache names are consumer-namespaced + descriptive: <door>/music/<hash>_v<n>
    and <door>/sfx/<num> -- distinct + attributable in SyncTERM's one per-BBS
    cache dir (vs the terse, collision-prone shared m/ a/). Both doors content-
    hash music so a shared dir never serves the wrong track. Render-version tag
    invalidates cleanly if the synth changes.

    Input fix (both doors): the parsers now swallow APC/DCS/OSC/PM string
    sequences (ESC _ ... ESC \). The new C;L reply is an APC literally containing "C;L", and the old parsers leaked it as keystrokes -- the 'l' hit Load Game.

    Music-vs-SFX balance capped low (OPL music is sustained + peak-normalised). Each play logs which path it took (client-cached / disk-cached / rendered).

    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian L@VERT to Git commit to main/sbbs/m on Wednesday, July 01, 2026 02:20:00
    https://gitlab.synchro.net/main/sbbs/-/commit/7108fa623c0935201f64320d
    Modified Files:
    src/doors/syncdoom/i_termmusic.c syncdoom.c
    Log Message:
    syncdoom: fix RTT stale-reject ratchet + audio/frame telemetry

    - RTT: pass stale_reject=0 to termgfx_rtt_sample. That filter dropped any DSR
    round-trip below EMA/3 to reject "absurdly low" late replies -- but it created
    a one-way ratchet: once a slow JXL frame spiked the smoothed RTT, every good
    low sample was then below EMA/3 and discarded, so it could never recover
    (observed latched ~725ms on a ~20ms LAN, which also forced the synthetic
    turn / "evdev/syn"). Reclaimed-frame replies are already dropped above via
    g_dsr_stale, so the filter guarded nothing here. Matches SyncDuke, which runs
    the same shared function with stale_reject=0 and tracks the true link.
    - Telemetry: the music line now splits render=<transcode ms> xfer=<encode+cache+
    upload ms> so a load stall's cause is unambiguous; a frame-stall watchdog logs
    when emit_frame runs long (>500ms, a slow encode); and dlog gains a [+ms]
    prefix (the BBS log only stamps to the second).

    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian L@VERT to Git commit to main/sbbs/m on Wednesday, July 01, 2026 02:20:00
    https://gitlab.synchro.net/main/sbbs/-/commit/f13692ed01697574f95d9002
    Modified Files:
    src/doors/syncdoom/i_termmusic.c syncdoom.c src/doors/syncduke/syncduke_plat.c syncduke_stubs.c src/doors/termgfx/audio_mgr.c audio_mgr.h
    Log Message:
    doors: async music transcode (render off the game thread)

    A first-play (cold-miss) track had to render MIDI->OPL PCM (~seconds) then encode
    OGG inline, freezing the game during the level load (5-9s observed). termgfx now
    offloads that to a worker thread: the door SUBMITs the raw MIDI/MUS bytes and POLLs each frame; the game keeps running and the track fades in when ready. Cache
    hits still play instantly on the main thread.

    - termgfx (audio_mgr.c/.h): termgfx_audio_music_async_submit/_poll. The worker
    renders + encodes + writes the disk cache + pre-builds the C;S upload APC, so
    the main thread only memcpy-stages ready-to-send bytes (out_flush drains them
    over frames) and starts the loop. Latest-wins supersede via a generation token;
    worker created lazily, joined on destroy. POSIX threads; Windows keeps the
    synchronous path (no regression).
    - SyncDuke: PlayMusic -> submit (syncduke_stubs.c), poll in _handle_events
    (syncduke_plat.c). SyncDOOM: term_emit -> submit (i_termmusic.c), poll in
    DG_DrawFrame (syncdoom.c).

    Live: a ~7s SyncDOOM render and a ~5s SyncDuke render each ran fully in the background at sustained fps with no frame-stall, and the worker populated the disk
    cache so replays are instant. Standalone harness (submit/ship, supersede, clean destroy) passes; helgrind clean (0 races).

    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian L@VERT to Git commit to main/sbbs/m on Saturday, July 04, 2026 20:14:00
    https://gitlab.synchro.net/main/sbbs/-/commit/2a84663b40f5bfa854195657
    Modified Files:
    src/doors/syncdoom/i_termsound.c src/doors/syncduke/Game/src/actors.c config.c duke3d.h sounds.c src/doors/syncduke/syncduke_stubs.c src/doors/termgfx/audio_mgr.c audio_mgr.h
    Log Message:
    doors: terminal-audio fidelity overhaul (SFX latency, ambiences, voices)

    One-shot SFX (termgfx, both doors):
    - The SFX channels are now a voice-stealing pool: flush the target channel
    before queuing so each holds at most one live sound. Un-flushed channel
    FIFOs backed up during firefights (30-45 SFX/sec into 8 channels) and
    played gunshots out seconds late -- audio "continued after the fight".
    - Channel choice is busy-aware: each queue records when its sample ends
    (exact PCM geometry for Doom lumps and VOC transcodes, WAV byte-rate
    otherwise) and new sounds take a finished channel, stealing the one
    closest to done only when all are live. Blind rotation truncated any
    long sample (rocket launch, chopper flyby) as soon as 8 later sounds
    arrived inside its play time; DOS mixed 32 voices and never hit this.
    - SyncDOOM coalesces same-sfx re-triggers within 40ms (SyncDuke's FX path
    already had a burst window); near-simultaneous duplicates are inaudible
    but each still cost a Load+Queue on the shared socket.
    - Both dispatch paths log a one-time warning when a sound sample is
    missing from the loaded GRP (content diagnosis, e.g. shareware).

    Looping ambiences (termgfx + the Duke engine's missing-callback seams --
    our headless shim has no audiolib voice-DONE callback, which the engine's
    sound bookkeeping silently relied on):
    - Queue loops ACTIVE (full volume) and set the real level via A;Volume:
    a positional ambience first heard at its radius edge is queued at
    volume 0, and SyncTERM never un-mutes such a voice (the E1L1 fire bin
    was permanently silent) -- works around that SyncTERM bug.
    - A full loop pool no longer steals a channel: loop_start reports failure
    and the MUSICANDSFX handler leaves the sprite un-triggered to retry
    when a channel frees, instead of stopping a loop the engine still
    thinks is playing (ambiences went permanently silent mid-session).
    - FX_StopAllSounds (menu, save-load, death) bumps a generation; the
    ambient handler re-arms once per generation -- zeroing the stale
    ambient play-counts globally, then each sprite's own trigger -- so a
    looping ambience resumes on return to the game (the projector stayed
    silent after ESC/menu or loading a save). The zeroing is deliberately
    once-per-generation: several sprites can share one lotag (E1L1 has 4x
    wind), and a per-sprite reset wiped a just-restarted loop's count,
    leaving it playing but untracked (never panned or attenuated).
    - stopenvsound now removes the exact SoundOwner entry it stopped;
    routing it through TestCallBack removed the first MUSICANDSFX owner it
    found, desyncing the owner list whenever sprites share a lotag, so an
    ambience refused to restart on radius re-entry.
    - Continuous soundm&2 ambiences (FIRE_CRACKLE, WIND_AMBIENCE, ...) now
    LOOP: DOS kept them going by re-firing one-shots from the DONE
    callback; looping the sample gives the same continuous result and puts
    them in the per-frame FX_Pan3D update, so they attenuate and pan with
    player movement like the original.
    - Multi-instance ambiences restored (up to 4, as DOS's SoundOwner[][4]):
    the single-instance guard now only covers true &1 loops, so a distant
    wind sprite can't hold the one instance and block a nearby one.
    - The ambient ON/OFF radius gate gets +1024 hysteresis so a tight-radius
    source (the cinema projector, ht=2000) doesn't flicker on/off as the
    player shifts around at its edge.

    Live-validated over SyncTERM: gunshot timing, rocket/chopper full length, projector/fire-bin/rooftop-wind ambiences (continuous + pan + attenuate + menu/save-load resume), and Duke's kill quips.

    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian L@VERT to Git commit to main/sbbs/m on Friday, July 17, 2026 21:23:00
    https://gitlab.synchro.net/main/sbbs/-/commit/7154b3c2305ce19c39e97dbc
    Modified Files:
    src/doors/syncdoom/i_termmusic.c
    Log Message:
    syncdoom: map the music slider linearly in dB

    Mirror SyncDuke's perceptual mapping. The faithful percent->dB step left
    Doom's 0..127 MusicVolume slider mapping to a 0..28 percent that SyncTERM
    then log-converted -- lumpy near the bottom, and the trim was the opaque
    number 28. Map the slider LINEARLY IN DB between a shared floor and a
    readable trim: TERM_MUS_TRIM_DB -11 (the old MAXV 28 in honest units) at
    the top, TERM_MUS_FLOOR_DB -40 just above the 0 = OFF cutoff.

    Confirmed by ear. (SyncMOO1 and SyncConquer stay on the faithful mapping: SyncMOO1's music has no trim cap -- it maps linearly to unity -- and SyncConquer has no user music slider, only per-sound positional volumes.)

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net