• src/conio/cterm.c cterm.h cterm_cterm.c sdl_con.c src/syncterm/ooii.c

    From Deucе@1:103/705 to Git commit to main/sbbs/master on Tue Apr 21 17:31:40 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/897325cdb8ee22b19b9cf91a
    Modified Files:
    src/conio/cterm.c cterm.h cterm_cterm.c sdl_con.c src/syncterm/ooii.c ripper.c src/xpdev/xpbeep.c xpbeep.h
    Log Message:
    xpbeep: node-list FIFO mixer, dB volumes, soft-clip, -12 dB stream base

    Replaces the per-stream fixed 1 s S16 ring with a head/tail linked list
    of producer-supplied frame buffers. The caller malloc()s the PCM data
    and xp_audio_append() transfers ownership; the channel free()s it once
    the mixer has fully consumed the node. Append is non-blocking in the
    steady state — it only waits when the per-node metadata allocation
    itself fails and we need the mixer to drop a buf to free memory. This
    unblocks the scene-music use case where queuing an arbitrarily long
    playlist must return to the terminal immediately.

    Fold xp_audio_append_faded + xp_audio_append into a single entry point
    that takes a NULLable xp_audio_opts_t carrying per-entry volume (dB,
    summed with the stream base), fade_in/fade_out frames, crossfade, and
    loop. Envelopes live on the node and are evaluated per-sample in the
    mixer, which means looping bufs rise from silence exactly once and a
    crossfade append starts the overlap immediately — old tail fades out
    via an overlay envelope while the new buf fades in, both mixing
    concurrently until the overlay expires. XP_AUDIO_OPTS_INIT presets
    unity dB on all fields so = {0} is equivalent.

    Rework xp_mixer_pull to accumulate all streams into an int32_t scratch
    (grown under mixer_lock, persistent) and apply tanh soft-clipping in a
    single narrow pass at the end — replaces the old per-add int16_t
    saturate that distorted multi-stream mixes the moment any contribution
    pushed the running sum to full scale. Volumes now compose in dB (per-
    entry + stream base → one powf per channel per buf per pull) with 0 dB
    as unity.

    Lift the -12 dB headroom reduction out of xptone_makewave (both the
    U8-wrap noisy path and the double clean path) and apply it as stream
    base dB instead: cterm->music_stream and cterm->fx_stream open at
    -12 dB, xptone opens its ephemeral stream at -12 dB, sdl_beep bakes
    -12 dB into its static wave once at generation. Synth output keeps
    its full 16-bit resolution; OOII/RIP samples now ride at the same
    level as tones instead of being 12 dB louder than the rest of the mix.

    Add cterm_play_fx/_tone/_u8 on a per-cterm fx_stream (lazy-opened,
    distinct from music_stream so MF/MB ANSI-music state doesn't interact
    with RIP/OOII SFX). Migrate ripper.c rv_sound cases (A, BE, BL, M, P,
    R) and the ~40 xp_play_sample call sites in ooii.c to the new API;
    drop background bool since the persistent fx_stream handles sequencing.

    Teach parse_rip_new to forward the 0x0E music terminator to cterm when
    the preceding CSI introducer (| unconditional, M when music_enable ==
    ENABLED, N when music_enable >= BANSI) actually arms
    cterm_accumulate_music. Previously parse_rip ate all SO/SI bytes as
    RIPterm text-window controls, which left the music accumulator stuck
    until the connection ended.

    xptone chunks and play_music notes switch to per-chunk malloc + append transfer; ripper rv_sound A/BE/BL/M/P/R go through cterm_play_fx* and
    no longer re-open the device per tone.

    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)