Weapon visuals + aim: beams render from the guns; pick self-target fixes

Renderer (dpl3-revive/patha):
- Instance visibility honored at DRAW time (stored word 4 = live
  dpl_SetInstanceVisibility field): laser beams (4-frame pulses) and
  missile models now render; cache tags w3 0/1 instances 'gated'
- PSFX world bursts (dcs=0 muzzle/impact/explosion) rendered; psfx
  storage list-based so one-shot bursts stop collapsing onto one key
- fix_degenerate applied to INSTANCE chains (was camera-only): the
  beam chain rides four rank-2 rig DCSs; composed rank-2 drew beams
  away from the guns. Offline f1635 now shows both arm beams
  converging on the aim point
- First-person canopy cull: the always-armed cockpit model at the
  head drew as a vertical line down screen center (user-confirmed
  fixed live)

Device (vpx-device/vpxlog.cpp):
- CAM backchannel on the fifosock: bridge streams its validated
  camera (+ vehicle root DCS); raycast_pick aims along the player's
  real look instead of the static view-node pose, which had locked
  every missile/beam onto one fixed wrong world point
- raycast_pick skips hidden-until-armed instances and the player's
  own articulation subtree: picking own beams created a target
  feedback loop, picking own missiles retargeted salvos mid-flight,
  and a picked missile's deleted DCS froze the game (arena2 crash)

Sound (vpx-device/vweawe.cpp): stereo-linked peak limiter replaces
the hard output clamp (the "generator out" crackle); VWE_AWE_LOG
reports pre-limit peaks + duck counts, enabled in launch_pod.ps1.
Missile-fire crackle persists: per-voice EMU8000 clamp suspected.

Eggs/confs: TESTAR2.EGG + gauge_arena2_sound.conf (map=arena2 city
variant); cavern.egg (console-era reference); plasma display on COM2
in the gauge confs; gauge_arena_net_sound.conf (slirp net stack).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-08 00:09:03 -05:00
co-authored by Claude Fable 5
parent 2bb2ff7302
commit cbf7015620
16 changed files with 830 additions and 167 deletions
+2 -2
View File
@@ -10,7 +10,7 @@ cputype=pentium
cycles=max
[serial]
serial1=directserial realport:COM1 rxpollus:100 rxburst:16
serial2=disabled
serial2=directserial realport:COM2
[autoexec]
mount c "C:\VWE\TeslaRel410\ALPHA_1"
c:
@@ -20,7 +20,7 @@ set BLASTER=A220 I5 D1 H5 P330 T6
set TEMP=c:\
set HEAPSIZE=15000000
set L4GAUGE=640x480x16
call setenv.bat r s n g
call setenv.bat r s n p
32rtm.exe -x
btl4opt.exe -egg testarn.egg
32rtm.exe -u
@@ -0,0 +1,57 @@
[sdl]
output=opengl
# higher,higher not highest: HIGH_PRIORITY_CLASS starved the host desktop;
# with the retry patches a rare dropout self-recovers (see gauge_rio.conf).
priority=higher,higher
[dosbox]
memsize=32
machine=svga_s3
[cpu]
core=dynamic
cputype=pentium
cycles=max
[sblaster]
sbtype=sb16
sbbase=220
irq=5
dma=1
hdma=5
[mixer]
# match the EMU8000s' native rate (no resample) and buffer ~60ms so brief
# emulation-thread stalls (RIO retry recovery) don't audibly chop
rate=44100
blocksize=1024
prebuffer=60
[serial]
# RIO on COM1 with the low-latency options (rxpollus/rxburst) so the board's
# few-ms ACK deadline is met; plasma display on COM2 (real pod has both).
serial1=directserial realport:COM1 rxpollus:100 rxburst:16
serial2=directserial realport:COM2
[autoexec]
mount c "C:\VWE\TeslaRel410\ALPHA_1"
c:
cd \REL410\BT
set VIDEOFORMAT=svga
rem production pod card init (PARAMETR.BAT:181-186): DIAGNOSE + AWEUTIL per
rem card -- AWEUTIL /S does the EMU8000 bring-up and DRAM detect the HMI SOS
rem driver relies on; skipping it left the cards uninitialized (silent).
rem aweutil /s SKIPPED for now: it verifies the AWE32 GM ROM, which the
rem emulated cards lack (hangs in a retry loop) -- restore once the ROM is
rem dumped from a real card. diagnose /s kept (passes, sets mixer config).
set BLASTER=A220 I5 D1 H5 P330 T6
c:\sb16\diagnose /s
set BLASTER=A240 I7 D3 H6 P300 T6
c:\sb16\diagnose /s
set BLASTER=A220 I5 D1 H5 P330 T6
set TEMP=c:\
rem arena2 STADIUM mission (TESTAR2.EGG: map=arena2, time=day - the walled arena) with the RIO
rem attached; stdout redirected so mission-load progress survives kills.
set HEAPSIZE=15000000
set L4GAUGE=640x480x16
call setenv.bat r s s p
32rtm.exe -x
btl4opt.exe -egg testar2.egg
32rtm.exe -u
echo ALPHA1-RUN-DONE
pause
@@ -0,0 +1,77 @@
[sdl]
output=opengl
# higher,higher not highest: HIGH_PRIORITY_CLASS starved the host desktop;
# with the retry patches a rare dropout self-recovers (see gauge_rio.conf).
priority=higher,higher
[dosbox]
memsize=32
machine=svga_s3
[cpu]
core=dynamic
cputype=pentium
cycles=max
[sblaster]
sbtype=sb16
sbbase=220
irq=5
dma=1
hdma=5
[mixer]
# match the EMU8000s' native rate (no resample) and buffer ~60ms so brief
# emulation-thread stalls (RIO retry recovery) don't audibly chop
rate=44100
blocksize=1024
prebuffer=60
[ne2000]
# real pods loaded the ODI stack in AUTOEXEC before ANY game launch --
# weapons-fire test: bare -egg run WITH the packet stack resident (the game's
# WATTCP identity is 200.0.0.113 from REL410\BT\WATTCP.CFG). nicbase 340:
# 300 clashes with the VDB and blanks heads. backend=slirp: THIS build lacks
# pcap ("Backend not supported"); slirp = user-mode NAT, no LAN peers, but
# the packet driver functions and accepts sends -- enough for a solo fire
# test. For pod<->console runs use a pcap-enabled build (net_full.conf).
ne2000=true
nicbase=340
nicirq=3
backend=slirp
[serial]
# RIO on COM1 with the low-latency options (rxpollus/rxburst) so the board's
# few-ms ACK deadline is met; plasma display on COM2 (real pod has both).
serial1=directserial realport:COM1 rxpollus:100 rxburst:16
serial2=directserial realport:COM2
[autoexec]
mount c "C:\VWE\TeslaRel410\ALPHA_1"
mount d "C:\VWE\TeslaRel410\emulator\net-boot"
d:
echo === loading NE2000 packet-driver stack (as production AUTOEXEC did) ===
d:\lsl
d:\ne2000
d:\odipkt
c:
cd \REL410\BT
set VIDEOFORMAT=svga
rem production pod card init (PARAMETR.BAT:181-186): DIAGNOSE + AWEUTIL per
rem card -- AWEUTIL /S does the EMU8000 bring-up and DRAM detect the HMI SOS
rem driver relies on; skipping it left the cards uninitialized (silent).
rem aweutil /s SKIPPED for now: it verifies the AWE32 GM ROM, which the
rem emulated cards lack (hangs in a retry loop) -- restore once the ROM is
rem dumped from a real card. diagnose /s kept (passes, sets mixer config).
set BLASTER=A220 I5 D1 H5 P330 T6
c:\sb16\diagnose /s
set BLASTER=A240 I7 D3 H6 P300 T6
c:\sb16\diagnose /s
set BLASTER=A220 I5 D1 H5 P330 T6
set TEMP=c:\
rem arena1 city mission (TESTARN.EGG: map=arena1, time=day), RIO attached,
rem bare -egg launch (no netnub) = the user's real-world test-egg setup.
set HEAPSIZE=15000000
set L4GAUGE=640x480x16
call setenv.bat r s s p
32rtm.exe -x
rem stdout -> log: this optimized build still emits DEBUG_STREAM lines (the
rem RIO retry spam proved it) -- capture the weapon fire-refusal reason.
rem DOS flushes in 4KB chunks; the tail lands on clean exit (mission timer).
btl4opt.exe -egg testarn.egg > c:\weaponlog.txt
32rtm.exe -u
echo ALPHA1-RUN-DONE
pause
@@ -24,9 +24,9 @@ blocksize=1024
prebuffer=60
[serial]
# RIO on COM1 with the low-latency options (rxpollus/rxburst) so the board's
# few-ms ACK deadline is met; plasma COM2 later.
# few-ms ACK deadline is met; plasma display on COM2 (real pod has both).
serial1=directserial realport:COM1 rxpollus:100 rxburst:16
serial2=disabled
serial2=directserial realport:COM2
[autoexec]
mount c "C:\VWE\TeslaRel410\ALPHA_1"
c:
@@ -48,7 +48,7 @@ rem arena1 city mission (TESTARN.EGG: map=arena1, time=day) with the RIO
rem attached; stdout redirected so mission-load progress survives kills.
set HEAPSIZE=15000000
set L4GAUGE=640x480x16
call setenv.bat r s s g
call setenv.bat r s s p
32rtm.exe -x
btl4opt.exe -egg testarn.egg
32rtm.exe -u
+1
View File
@@ -57,6 +57,7 @@ if ($NoSound) {
} else {
$env:VWE_AWE32 = '1'
$env:VWE_AWE_ROM = (Resolve-Path "$PSScriptRoot\..\roms\awe32.raw").Path
$env:VWE_AWE_LOG = '1' # 10s reports incl. limiter stats (crackle diagnosis)
}
$dosbox = (Resolve-Path "$PSScriptRoot\..\src\src\dosbox-x.exe").Path
+22
View File
@@ -85,6 +85,7 @@ def fp_cam(board, cache):
h = max(anim, key=lambda k: float(np.abs(np.array(anim[k][9:12])).sum()))
if h is None:
return None
fp_cam.root = h # player vehicle root DCS (for the pick backchannel)
f = anim[h]
R = np.array(f[:9]).reshape(3, 3)
t = np.array(f[9:12])
@@ -145,6 +146,26 @@ def fp_cam(board, cache):
M[0, :3], M[1, :3], M[2, :3], M[3, :3] = right, up, back, eye
return M
def send_cam(M):
# Backchannel on the fifosock: hand the device OUR camera (the
# user-validated cockpit view, twist + glance included) so its reticle
# raycast aims where the player actually looks. The device's own view
# decode sits at the static view-node pose, which aimed every pick --
# and thus every missile volley and laser beam -- at one fixed wrong
# world point (the "missiles fly off to a fixed spot" bug).
if sock is None:
return
eye, fwd = M[3, :3], -M[2, :3]
# 7th field: player vehicle root DCS -- the device skips instances on
# this articulation subtree (own arms/torso/muzzle-flash), which the ray
# grazes intermittently and which retargeted mid-volley missiles.
root = getattr(fp_cam, 'root', 0) or 0
try:
sock.send(f"CAM {eye[0]:.3f} {eye[1]:.3f} {eye[2]:.3f} "
f"{fwd[0]:.6f} {fwd[1]:.6f} {fwd[2]:.6f} {root:x}\n".encode())
except OSError:
pass
def render(board):
try:
pg = r.pygame
@@ -161,6 +182,7 @@ def render(board):
M = fp_cam(board, r.cache)
if M is not None:
r.cam_matrix = lambda _b, _M=M: _M
send_cam(M)
r.draw(board)
except KeyboardInterrupt:
raise
+117 -5
View File
@@ -265,13 +265,83 @@ static void fifo_sock_write(const unsigned char *p, size_t n) {
} else { fifo_sock_drop(); return; }
}
}
/* bridge -> device backchannel on the same socket. The bridge streams its
* camera pose (the user-validated cockpit view: cam chain + torso twist +
* hat glance) as text lines: CAM ex ey ez dx dy dz\n
* raycast_pick prefers this over the device's own S.view decode -- S.view
* tracks the static view-node pose (~arena origin, not the player's aim),
* which pointed every reticle pick, and therefore every missile volley and
* laser beam, at one fixed wrong world point. */
static char brcam_line[192];
static size_t brcam_len = 0;
static float brcam[6];
static bool brcam_valid = false;
static DWORD brcam_tick = 0;
static unsigned brcam_root = 0; /* player vehicle root DCS (7th CAM field) */
static void fifo_sock_poll(void) {
if (fifo_csock == INVALID_SOCKET) return;
char buf[512];
for (;;) {
int r = recv(fifo_csock, buf, (int)sizeof buf, 0);
if (r == 0) { fifo_sock_drop(); return; } /* client closed */
if (r < 0) {
if (WSAGetLastError() != WSAEWOULDBLOCK) fifo_sock_drop();
return;
}
for (int i = 0; i < r; i++) {
char ch = buf[i];
if (ch == '\n') {
brcam_line[brcam_len] = 0;
float v[6]; unsigned rt = 0;
int nf = sscanf(brcam_line, "CAM %f %f %f %f %f %f %x",
&v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &rt);
if (nf >= 6) {
memcpy(brcam, v, sizeof brcam);
brcam_valid = true;
brcam_tick = GetTickCount();
brcam_root = (nf >= 7) ? rt : 0;
}
brcam_len = 0;
} else if (brcam_len + 1 < sizeof brcam_line) {
brcam_line[brcam_len++] = ch;
} else {
brcam_len = 0; /* oversize: resync */
}
}
}
}
#else
static bool fifo_sock_active(void) { return false; }
static void fifo_sock_init(int) {}
static void fifo_sock_accept(void) {}
static void fifo_sock_write(const unsigned char *, size_t) {}
static void fifo_sock_poll(void) {}
#endif
/* Latest bridge camera, if fresh: the pick must not outlive a dead bridge
* by more than a couple of seconds (stale aim = the fixed-point bug again,
* just slower), so fall back to the device view after 2s of silence. */
static bool bridge_cam_get(float O[3], float D[3]) {
#ifdef _WIN32
if (brcam_valid && (GetTickCount() - brcam_tick) < 2000) {
O[0] = brcam[0]; O[1] = brcam[1]; O[2] = brcam[2];
D[0] = brcam[3]; D[1] = brcam[4]; D[2] = brcam[5];
return true;
}
#endif
return false;
}
static unsigned bridge_cam_root(void) {
#ifdef _WIN32
return brcam_root;
#else
return 0;
#endif
}
static void fifo_buf_push(unsigned char v) {
if (fifo_dump_fp == NULL && !vpx_render && !fifo_sock_active()) return;
if (fifo_buf_len >= (1u << 20)) return; /* runaway guard */
@@ -295,6 +365,7 @@ static void fifo_flush_record(void) {
}
if (fifo_sock_active()) {
fifo_sock_accept();
fifo_sock_poll(); /* drain bridge CAM backchannel */
fifo_sock_write(hdr, sizeof hdr);
fifo_sock_write(fifo_buf, fifo_buf_len);
}
@@ -2132,9 +2203,18 @@ static float ray_tri(const float O[3], const float D[3],
* geogroup -> geometry -> polys, transformed by the DCS world matrix. */
static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out,
unsigned *geom_out, float xyz_out[3]) {
if (!S.view.has_cam) return false;
const float O[3] = { S.view.eye[0], S.view.eye[1], S.view.eye[2] };
float D[3] = { -S.view.rot[6], -S.view.rot[7], -S.view.rot[8] }; /* cam fwd */
/* Ray = the player's actual look: prefer the bridge's camera (cam chain
* + torso twist + hat glance, streamed back over the fifosock). The
* device's own S.view decode tracks the static view-node pose, so with
* it every pick -- and every missile/beam -- aimed at one fixed wrong
* world point. Fallback keeps weapons firing bridge-less. */
float O[3], D[3];
const bool from_bridge = bridge_cam_get(O, D);
if (!from_bridge) {
if (!S.view.has_cam) return false;
O[0] = S.view.eye[0]; O[1] = S.view.eye[1]; O[2] = S.view.eye[2];
D[0] = -S.view.rot[6]; D[1] = -S.view.rot[7]; D[2] = -S.view.rot[8];
}
float dl = sqrtf(D[0]*D[0] + D[1]*D[1] + D[2]*D[2]);
if (dl < 1e-6f) return false;
D[0] /= dl; D[1] /= dl; D[2] /= dl;
@@ -2153,6 +2233,36 @@ static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out
M16 world; dcs_world(di->first, cache, world);
for (size_t ii = 0; ii < di->second.size(); ii++) {
unsigned inst = di->second[ii];
/* Skip hidden-until-armed instances (w3 0/1: laser beams,
* missile models, effects). Picking them is poison: the beam
* stretches to the pick point, the next pick hits the BEAM at
* that point and locks the target there (feedback loop); a
* volley's own missiles get picked and homed on; and a picked
* missile whose DCS deletes on impact hands the game a stale
* handle (the arena2 freeze). The reticle resolves the WORLD:
* terrain, buildings, vehicles. */
std::map<unsigned, unsigned>::const_iterator wi =
S.inst_w3.find(inst);
if (wi != S.inst_w3.end() && wi->second != 2 && wi->second != 3)
continue;
/* Never pick the player's OWN articulation subtree (arms, torso,
* muzzle flash, searchlight): the ray grazes own geometry from
* the over-the-shoulder eye and a mid-volley pick of the muzzle
* flash retargets the salvo ("missiles hare off sometimes").
* Root DCS comes from the bridge's CAM line, 7th field. */
unsigned own_root = bridge_cam_root();
if (own_root) {
unsigned a = di->first;
bool own = false;
for (int hop = 0; hop < 64; hop++) {
if (a == own_root) { own = true; break; }
std::map<unsigned, unsigned>::const_iterator ppi =
S.dcs_parent.find(a);
if (ppi == S.dcs_parent.end() || ppi->second == a) break;
a = ppi->second;
}
if (own) continue;
}
std::map<unsigned, unsigned>::const_iterator oi =
S.inst_object.find(inst);
if (oi == S.inst_object.end()) continue;
@@ -2210,8 +2320,10 @@ static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out
if ((best_inst != last_i || best_dcs != last_d) && vpx_fp) {
flush_run();
fprintf(vpx_fp, "# raycast pick: inst=%08X dcs=%08X gg=%08X t=%.1f "
"pos(%.1f,%.1f,%.1f)\n", best_inst, best_dcs, best_gg, best_t,
xyz_out[0], xyz_out[1], xyz_out[2]);
"pos(%.1f,%.1f,%.1f) cam=%s eye(%.1f,%.1f,%.1f)\n",
best_inst, best_dcs, best_gg, best_t,
xyz_out[0], xyz_out[1], xyz_out[2],
from_bridge ? "bridge" : "view", O[0], O[1], O[2]);
fflush(vpx_fp);
last_i = best_inst; last_d = best_dcs;
}
+65 -21
View File
@@ -22,8 +22,10 @@
* from the emulation thread: the real cards were autonomous silicon, so
* sustained voices keep sounding even while the emulation thread stalls
* (e.g. RIO retry storms during mission staging). Both cards are summed
* into one stereo stream (headset); per-card host routing for the real
* 4-speaker cockpit comes later (see SOUND-NOTES.md).
* into one stereo stream (headset) through a peak limiter (the raw voice
* accumulator routinely exceeds int16; hard-clamping it was the "generator
* out" speech crackle -- the SBK samples themselves are clean); per-card
* host routing for the real 4-speaker cockpit comes later (SOUND-NOTES.md).
*
* Environment (host-side, like the VPX device):
* VWE_AWE32=1 enable the device (inert otherwise)
@@ -217,15 +219,28 @@ static volatile LONG awe_stop = 0;
#define AWE_SLOTS 28 /* absolute pool size */
static WAVEHDR awe_hdr[AWE_SLOTS];
static int16_t awe_slotbuf[AWE_SLOTS][AWE_SLOT_FRAMES * 2];
static int16_t awe_cardbuf[2][AWE_SLOT_FRAMES * 2];
static int32_t awe_cardbuf[2][AWE_SLOT_FRAMES * 2];
/* Output limiter (always on). The EMU8000 core sums its 32 voices into a raw
* int32 accumulator; a busy mix (speech over the engine loop, weapon hits)
* easily exceeds int16 and the old hard clamp flat-topped it -- the user's
* "generator out" crackle (baseline WAV from the SBK proved the sample itself
* is clean). Instead of clipping, duck: instant attack to keep the summed
* peak under AWE_LIM_TARGET, ~46ms release back to unity. Stereo-linked so
* the image doesn't wander. The safety clamp after it should never engage. */
#define AWE_LIM_TARGET 32000
static int32_t awe_lim_gain = 1 << 16; /* Q16, <= 1.0 */
static unsigned long awe_lim_engaged = 0; /* frames ducked (diagnostic) */
static int awe_prelim_peak[2]; /* per-card pre-limit |peak| */
static void awe_render_chunk(emu8k_t *card, int ci, unsigned n) {
wavetable_pos_global = (int)n;
emu8k_update(card);
for (unsigned i = 0; i < n * 2; i++) {
int32_t v = card->buffer[i] >> awe_shift;
if (v > 32767) v = 32767; else if (v < -32768) v = -32768;
awe_cardbuf[ci][i] = (int16_t)v;
awe_cardbuf[ci][i] = v; /* full-range; limited at sum */
if (v < 0) v = -v;
if (v > awe_prelim_peak[ci]) awe_prelim_peak[ci] = v;
}
memset(card->buffer, 0, n * 2 * sizeof(int32_t));
memset(card->chorus_in_buffer, 0, n * sizeof(int32_t));
@@ -264,27 +279,55 @@ static DWORD WINAPI awe_thread_proc(LPVOID) {
awe_last_render_qpc = now.QuadPart;
LeaveCriticalSection(&awe_lock);
int16_t *out = awe_slotbuf[free_slot];
for (unsigned i = 0; i < AWE_SLOT_FRAMES * 2; i++) {
int32_t v = (int32_t)awe_cardbuf[0][i] + awe_cardbuf[1][i];
if (v > 32767) v = 32767; else if (v < -32768) v = -32768;
out[i] = (int16_t)v;
int a0 = awe_cardbuf[0][i] < 0 ? -awe_cardbuf[0][i]
: awe_cardbuf[0][i];
int a1 = awe_cardbuf[1][i] < 0 ? -awe_cardbuf[1][i]
: awe_cardbuf[1][i];
if (a0 > peak[0]) peak[0] = a0;
if (a1 > peak[1]) peak[1] = a1;
for (unsigned i = 0; i < AWE_SLOT_FRAMES; i++) {
int32_t l = awe_cardbuf[0][i * 2] + awe_cardbuf[1][i * 2];
int32_t r = awe_cardbuf[0][i * 2 + 1] + awe_cardbuf[1][i * 2 + 1];
/* stereo-linked peak limiter: instant attack, ~46ms release */
int32_t pk = l < 0 ? -l : l;
int32_t pr = r < 0 ? -r : r;
if (pr > pk) pk = pr;
if (pk > AWE_LIM_TARGET) {
int32_t need = (int32_t)(((int64_t)AWE_LIM_TARGET << 16) / pk);
if (need < awe_lim_gain) awe_lim_gain = need;
}
if (awe_lim_gain < (1 << 16)) {
awe_lim_engaged++;
l = (int32_t)(((int64_t)l * awe_lim_gain) >> 16);
r = (int32_t)(((int64_t)r * awe_lim_gain) >> 16);
/* release: exponential toward unity, min step 1 so it
* actually GETS there (>>11 alone stalls 2048 short) */
int32_t step = ((1 << 16) - awe_lim_gain) >> 11;
awe_lim_gain += step ? step : 1;
if (awe_lim_gain > (1 << 16)) awe_lim_gain = 1 << 16;
}
if (l > 32767) l = 32767; else if (l < -32768) l = -32768;
if (r > 32767) r = 32767; else if (r < -32768) r = -32768;
out[i * 2] = (int16_t)l;
out[i * 2 + 1] = (int16_t)r;
}
if (awe_prelim_peak[0] > peak[0]) peak[0] = awe_prelim_peak[0];
if (awe_prelim_peak[1] > peak[1]) peak[1] = awe_prelim_peak[1];
awe_prelim_peak[0] = awe_prelim_peak[1] = 0;
WAVEHDR *h = &awe_hdr[free_slot];
memset(h, 0, sizeof(WAVEHDR));
h->lpData = (LPSTR)out;
h->dwBufferLength = AWE_SLOT_FRAMES * 4;
waveOutPrepareHeader(awe_wo, h, sizeof(WAVEHDR));
waveOutWrite(awe_wo, h, sizeof(WAVEHDR));
if (awe_dump_front)
fwrite(awe_cardbuf[0], 4, AWE_SLOT_FRAMES, awe_dump_front);
if (awe_dump_rear)
fwrite(awe_cardbuf[1], 4, AWE_SLOT_FRAMES, awe_dump_rear);
if (awe_dump_front || awe_dump_rear) {
/* dumps stay s16le: clamp the raw int32 card stream per file */
static int16_t dump16[AWE_SLOT_FRAMES * 2];
for (int ci = 0; ci < 2; ci++) {
FILE *df = ci ? awe_dump_rear : awe_dump_front;
if (!df) continue;
for (unsigned i = 0; i < AWE_SLOT_FRAMES * 2; i++) {
int32_t v = awe_cardbuf[ci][i];
if (v > 32767) v = 32767; else if (v < -32768) v = -32768;
dump16[i] = (int16_t)v;
}
fwrite(dump16, 4, AWE_SLOT_FRAMES, df);
}
}
for (int ci = 0; ci < 2; ci++) {
frames[ci] += AWE_SLOT_FRAMES;
if (awe_log_on > 0 && frames[ci] - last_report[ci] >= 441000) {
@@ -294,11 +337,12 @@ static DWORD WINAPI awe_thread_proc(LPVOID) {
for (int i = 0; i < 32; i++)
if (card->voice[i].cvcf_curr_volume) voices++;
fprintf(stderr, "VWE AWE32%c 10s: wr=%lu wcRd=%lu rd=%lu "
"smldW=%lu voices=%d peak=%d\n", ci ? 'R' : 'F',
"smldW=%lu voices=%d peak=%d limF=%lu\n", ci ? 'R' : 'F',
awe_stats[ci].wr, awe_stats[ci].rd_wc,
awe_stats[ci].rd, awe_stats[ci].smld_w,
voices, peak[ci]);
voices, peak[ci], awe_lim_engaged);
peak[ci] = 0;
if (ci) awe_lim_engaged = 0;
}
}
}