Files
TeslaRel410/emulator/vpx-device/vweawe.cpp
T
CydandClaude Fable 5 351cce26b3 AWE32: VWE_AWE_WAV crackle-hunt recording taps
Three WAVs written from the render thread: per-card PRE-limiter
streams as float32 (clipping baked in upstream shows as flat tops
above full scale) and the POST-limiter int16 mix (what the user
hears). Headers re-patched every second so files survive a hard
kill. launch_pod.ps1 arms it into the work dir by default.

Diagnosis logic: flat tops in a card tap = per-voice clipping inside
the EMU8000 core; clean cards + dirty mix = limiter artifact; all
clean = winmm path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 14:06:11 -05:00

541 lines
23 KiB
C++

/* VWE dual-AWE32 wavetable device (Tesla/Red Planet pod sound)
*
* The production pods carried TWO Sound Blaster AWE32 ISA cards (confirmed
* from hardware 2026-07-04: AWE32s with 2x 30-pin DRAM SIMMs each), driving
* the four cockpit speakers as front/rear stereo pairs:
*
* AWE_FRONT = A220 I5 D1 H5 P330 T6 -> EMU8000 at 0x620/0xA20/0xE20
* AWE_REAR = A240 I7 D3 H6 P300 T6 -> EMU8000 at 0x640/0xA40/0xE40
*
* The game (BTL4OPT.EXE, HMI SOS MIDI layer with the AWE32 driver linked in)
* is MIDI-only wavetable: it uploads SoundFont banks (AUDIO/AUDIO1.RES +
* AUDIO2.RES, RIFF sfbk) into card DRAM and plays voices with AWE NRPN
* steering. It never streams PCM through the SB16 DSP, so the front card's
* DSP is DOSBox-X's native [sblaster] (set irq=5 to match I5) and the rear
* card only needs the minimal DSP/mixer stub at 0x240 provided here.
* NOTE: the HMI driver verifies the AWE32 GM ROM (the banks declare
* irom=1MGM) and silently refuses the SBK upload without one -- supply a
* 1MB ROM image via VWE_AWE_ROM or all voices play silence.
*
* The EMU8000 itself is the vendored 86Box core (emu8k.cpp/.h). Synthesis
* runs on a DEDICATED THREAD with its own winmm audio output, decoupled
* 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) 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)
* VWE_AWE_RAM_KB=N sample DRAM per card, KB (default 8192 = the pods'
* 2x4MB SIMM fit; clamped 512..28672)
* VWE_AWE_ROM=path awe32.raw 1MB GM ROM image (REQUIRED for the SBK
* upload -- see above; without it, silence)
* VWE_AWE_SHIFT=N output attenuation shift (default 0)
* VWE_AWE_LEAD_MS=N audio queue depth in ms (default 80, min 30 max 250)
* VWE_AWE_DUMP=dir append raw s16le stereo 44100 streams to
* <dir>/awe_front.s16 + <dir>/awe_rear.s16
* VWE_AWE_WAV=prefix crackle-hunt taps: <prefix>_front/_rear.wav =
* per-card PRE-limiter float32 (overs survive) +
* <prefix>_mix.wav = post-limiter int16 (as heard)
* VWE_AWE_LOG=1 port trace + 10s activity reports (smldW counter =
* SoundFont upload progress; 99 = upload refused)
*/
#include "dosbox.h"
#include "inout.h"
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <mmsystem.h>
#include "emu8k_shim.h"
#include "emu8k.h"
int wavetable_pos_global = 0;
/* one lock serializes all EMU8000 state access: guest port I/O (emulation
* thread) vs synthesis (render thread). Held only for register accesses and
* per-chunk renders, so contention stays in the microseconds. */
static CRITICAL_SECTION awe_lock;
/* WC interpolation: real-time samples elapsed since the render thread's
* last pass, so guest busy-waits on the 44kHz sample counter see smooth
* advancement between render chunks */
static volatile LONGLONG awe_last_render_qpc = 0;
static LONGLONG awe_qpc_freq = 1;
unsigned emu8k_shim_wc_extra(void) {
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
LONGLONG d = now.QuadPart - awe_last_render_qpc;
if (d < 0) d = 0;
LONGLONG smp = (d * 44100) / awe_qpc_freq;
if (smp > 4410) smp = 4410;
return (unsigned)smp;
}
/* activity stats (reported every ~10s of rendered audio per card) */
struct AweStats { unsigned long wr, rd_wc, rd, smld_w; };
static AweStats awe_stats[2]; /* 0 = front, 1 = rear */
/* ---- 86Box io_sethandler shim: dispatch DOSBox port I/O into the core --- */
struct AweIoRange {
uint16_t base, size;
void *priv;
emu8k_io_inb_t inb; emu8k_io_inw_t inw;
emu8k_io_outb_t outb; emu8k_io_outw_t outw;
bool used;
};
static AweIoRange awe_io[16];
static AweIoRange *awe_io_find(Bitu port) {
for (size_t i = 0; i < sizeof(awe_io) / sizeof(awe_io[0]); i++)
if (awe_io[i].used && port >= awe_io[i].base &&
port < (Bitu)(awe_io[i].base + awe_io[i].size))
return &awe_io[i];
return NULL;
}
/* VWE_AWE_LOG=1: trace the first accesses + growth-of-count milestones */
static int awe_log_on = -1;
static unsigned long awe_access_count = 0;
static void awe_io_trace(const char *dir, Bitu port, Bitu val, Bitu iolen) {
if (awe_log_on < 0) {
const char *l = getenv("VWE_AWE_LOG");
awe_log_on = (l && l[0] && l[0] != '0') ? 1 : 0;
}
if (!awe_log_on) return;
awe_access_count++;
if (awe_access_count <= 64 ||
(awe_access_count & (awe_access_count - 1)) == 0)
fprintf(stderr, "VWE AWE32 io[%lu]: %s %03lx val=%04lx len=%lu\n",
awe_access_count, dir, (unsigned long)port,
(unsigned long)val, (unsigned long)iolen);
}
/* PTR shadow per card: mirror of the core's cur_reg/cur_voice, so the glue
* can identify sample-memory (SMALW/SMLD/SMRD) traffic */
static uint16_t awe_ptr[2];
static int awe_sm_lines = 0;
static void awe_sm_trace(const char *dir, int ci, Bitu port, Bitu val) {
unsigned reg = (awe_ptr[ci] >> 5) & 7, voice = awe_ptr[ci] & 0x1F;
if (reg != 1) return;
if (voice == 26 && dir[0] == 'W') { /* SMLD data: count only */
awe_stats[ci].smld_w++;
return;
}
/* log the write-pointer setups: each upload chunk starts with one */
if (dir[0] != 'W' || (voice != 22 && voice != 23)) return;
if (awe_log_on <= 0 || awe_sm_lines >= 200) return;
awe_sm_lines++;
fprintf(stderr, "VWE AWE32 sm[%d]: card%d W %s(%s) val=%04lx\n",
awe_sm_lines, ci, (voice == 22) ? "SMALW" : "SMARW",
(port & 2) ? "hi/D2" : "lo/D1", (unsigned long)val);
}
static Bitu awe_io_read(Bitu port, Bitu iolen) {
AweIoRange *r = awe_io_find(port);
if (!r) return ~0ul;
Bitu v;
EnterCriticalSection(&awe_lock);
if (iolen >= 2 && r->inw) v = r->inw((uint16_t)port, r->priv);
else if (r->inb) v = r->inb((uint16_t)port, r->priv);
else v = ~0ul;
LeaveCriticalSection(&awe_lock);
AweStats &s = awe_stats[(port & 0x40) ? 1 : 0];
if ((port & 0xF9F) == 0xA02) s.rd_wc++; else s.rd++; /* A22/A42 */
if ((port & 0xF1C) == 0xA00) /* A20-A23/A40-A43 */
awe_sm_trace("R", (port & 0x40) ? 1 : 0, port, v);
awe_io_trace("R", port, v, iolen);
return v;
}
static void awe_io_write(Bitu port, Bitu val, Bitu iolen) {
AweIoRange *r = awe_io_find(port);
if (!r) return;
const int ci = (port & 0x40) ? 1 : 0;
awe_stats[ci].wr++;
if ((port & 0xF9E) == 0xE02) { /* E22/E23/E42/E43 */
/* mirror the core's byte-write semantics (low byte lost on odd) */
if (iolen >= 2) awe_ptr[ci] = (uint16_t)val;
else if (port & 1) awe_ptr[ci] = (uint16_t)(val << 8);
else awe_ptr[ci] = (uint16_t)(val & 0xFF);
}
if ((port & 0xF1C) == 0xA00)
awe_sm_trace("W", ci, port, val);
awe_io_trace("W", port, val, iolen);
EnterCriticalSection(&awe_lock);
if (iolen >= 2 && r->outw) r->outw((uint16_t)port, (uint16_t)val, r->priv);
else if (r->outb) r->outb((uint16_t)port, (uint8_t)val, r->priv);
LeaveCriticalSection(&awe_lock);
}
void io_sethandler(uint16_t base, int size,
emu8k_io_inb_t inb, emu8k_io_inw_t inw, emu8k_io_inl_t,
emu8k_io_outb_t outb, emu8k_io_outw_t outw,
emu8k_io_outl_t, void *priv) {
for (size_t i = 0; i < sizeof(awe_io) / sizeof(awe_io[0]); i++) {
if (awe_io[i].used) continue;
awe_io[i].base = base; awe_io[i].size = (uint16_t)size;
awe_io[i].priv = priv;
awe_io[i].inb = inb; awe_io[i].inw = inw;
awe_io[i].outb = outb; awe_io[i].outw = outw;
awe_io[i].used = true;
IO_RegisterReadHandler(base, awe_io_read, IO_MB | IO_MW, (Bitu)size);
IO_RegisterWriteHandler(base, awe_io_write, IO_MB | IO_MW, (Bitu)size);
return;
}
fprintf(stderr, "VWE AWE32: io_sethandler table full\n");
}
void io_removehandler(uint16_t base, int size,
emu8k_io_inb_t, emu8k_io_inw_t, emu8k_io_inl_t,
emu8k_io_outb_t, emu8k_io_outw_t, emu8k_io_outl_t,
void *priv) {
for (size_t i = 0; i < sizeof(awe_io) / sizeof(awe_io[0]); i++)
if (awe_io[i].used && awe_io[i].base == base &&
awe_io[i].size == (uint16_t)size && awe_io[i].priv == priv)
awe_io[i].used = false; /* DOSBox handler stays; range inert */
}
FILE *emu8k_shim_rom_fopen(void) {
const char *p = getenv("VWE_AWE_ROM");
if (!p || !p[0]) return NULL;
FILE *f = fopen(p, "rb");
if (!f)
fprintf(stderr, "VWE AWE32: cannot open VWE_AWE_ROM '%s'\n", p);
return f;
}
/* ---- the two cards + autonomous render thread --------------------------- */
static emu8k_t awe_front, awe_rear;
static FILE *awe_dump_front = NULL, *awe_dump_rear = NULL;
static int awe_shift = 0;
static int awe_lead_ms = 80;
static HWAVEOUT awe_wo = NULL;
static volatile LONG awe_stop = 0;
/* winmm slot ring: SLOT_FRAMES per buffer, SLOTS in flight max */
#define AWE_SLOT_FRAMES 441 /* 10ms at 44100 */
#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 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| */
/* VWE_AWE_WAV=<prefix>: diagnostic recording taps for the crackle hunt.
* Three 44.1k stereo WAVs: <prefix>_front.wav / _rear.wav = per-card
* PRE-limiter streams as float32 (values >1.0 survive, so clipping baked
* in upstream is visible as flat tops ABOVE full scale), and
* <prefix>_mix.wav = the POST-limiter int16 the user actually hears.
* Localizes the crackle: flat tops already in a card tap = per-voice
* clipping inside the EMU8000 core; clean taps but dirty mix = limiter
* artifact; everything clean = the winmm path. Headers are re-patched
* every second so the files survive a hard kill. */
static FILE *awe_wav_card[2];
static FILE *awe_wav_mix;
static uint32_t awe_wav_frames;
static void awe_wav_header(FILE *f, int fmt_tag, int bits) {
uint32_t rate = 44100, four = 0;
uint16_t ch = 2, b16 = (uint16_t)bits, tag = (uint16_t)fmt_tag;
uint16_t align = (uint16_t)(ch * (bits / 8));
uint32_t bps = rate * align, fmtlen = 16;
fwrite("RIFF", 1, 4, f); fwrite(&four, 4, 1, f);
fwrite("WAVEfmt ", 1, 8, f);
fwrite(&fmtlen, 4, 1, f);
fwrite(&tag, 2, 1, f); fwrite(&ch, 2, 1, f);
fwrite(&rate, 4, 1, f); fwrite(&bps, 4, 1, f);
fwrite(&align, 2, 1, f); fwrite(&b16, 2, 1, f);
fwrite("data", 1, 4, f); fwrite(&four, 4, 1, f);
}
static void awe_wav_patch(FILE *f, uint32_t data_bytes) {
uint32_t riff = 36 + data_bytes;
fseek(f, 4, SEEK_SET); fwrite(&riff, 4, 1, f);
fseek(f, 40, SEEK_SET); fwrite(&data_bytes, 4, 1, f);
fseek(f, 0, SEEK_END);
fflush(f);
}
static void awe_wav_open(const char *prefix) {
char path[512];
static const char *suffix[2] = { "_front.wav", "_rear.wav" };
for (int ci = 0; ci < 2; ci++) {
snprintf(path, sizeof path, "%s%s", prefix, suffix[ci]);
awe_wav_card[ci] = fopen(path, "wb");
if (awe_wav_card[ci]) awe_wav_header(awe_wav_card[ci], 3, 32);
}
snprintf(path, sizeof path, "%s_mix.wav", prefix);
awe_wav_mix = fopen(path, "wb");
if (awe_wav_mix) awe_wav_header(awe_wav_mix, 1, 16);
fprintf(stderr, "VWE AWE32: recording taps -> %s_{front,rear,mix}.wav\n",
prefix);
}
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;
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));
memset(card->reverb_in_buffer, 0, n * sizeof(int32_t));
card->pos = 0;
wavetable_pos_global = 0;
}
static DWORD WINAPI awe_thread_proc(LPVOID) {
unsigned long frames[2] = { 0, 0 }, last_report[2] = { 0, 0 };
int peak[2] = { 0, 0 };
const unsigned lead_slots = (unsigned)(awe_lead_ms / 10);
while (!awe_stop) {
/* reclaim finished slots, count in-flight */
unsigned queued = 0;
int free_slot = -1;
for (int i = 0; i < AWE_SLOTS; i++) {
if (awe_hdr[i].dwFlags & WHDR_PREPARED) {
if (awe_hdr[i].dwFlags & WHDR_DONE)
waveOutUnprepareHeader(awe_wo, &awe_hdr[i],
sizeof(WAVEHDR));
else { queued++; continue; }
}
if (free_slot < 0) free_slot = i;
}
if (queued >= lead_slots || free_slot < 0) {
Sleep(2);
continue;
}
/* render one 10ms chunk from both cards, sum into the slot */
LARGE_INTEGER now;
EnterCriticalSection(&awe_lock);
awe_render_chunk(&awe_front, 0, AWE_SLOT_FRAMES);
awe_render_chunk(&awe_rear, 1, AWE_SLOT_FRAMES);
QueryPerformanceCounter(&now);
awe_last_render_qpc = now.QuadPart;
LeaveCriticalSection(&awe_lock);
int16_t *out = awe_slotbuf[free_slot];
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_wav_mix) { /* VWE_AWE_WAV taps */
float fb[AWE_SLOT_FRAMES * 2];
for (int ci = 0; ci < 2; ci++) {
if (!awe_wav_card[ci]) continue;
for (unsigned i = 0; i < AWE_SLOT_FRAMES * 2; i++)
fb[i] = (float)awe_cardbuf[ci][i] / 32768.0f;
fwrite(fb, sizeof(float), AWE_SLOT_FRAMES * 2,
awe_wav_card[ci]);
}
fwrite(out, sizeof(int16_t), AWE_SLOT_FRAMES * 2, awe_wav_mix);
awe_wav_frames += AWE_SLOT_FRAMES;
if (awe_wav_frames % 44100 < AWE_SLOT_FRAMES) { /* ~1s */
for (int ci = 0; ci < 2; ci++)
if (awe_wav_card[ci])
awe_wav_patch(awe_wav_card[ci], awe_wav_frames * 8);
awe_wav_patch(awe_wav_mix, awe_wav_frames * 4);
}
}
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 || 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) {
last_report[ci] = frames[ci];
emu8k_t *card = ci ? &awe_rear : &awe_front;
int voices = 0;
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 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], awe_lim_engaged);
peak[ci] = 0;
if (ci) awe_lim_engaged = 0;
}
}
}
return 0;
}
/* ---- rear-card SB16 front-end stub at 0x240 -----------------------------
* Just enough DSP + mixer for detection (sb16set, SOS probes): reset ->
* 0xAA, E0 identification, E1 version 4.13, mixer register file. The DSP
* digital path is never used by the game (MIDI-only engine). */
static uint8_t rdsp_mixer_idx = 0, rdsp_mixer[256];
static uint8_t rdsp_q[8];
static int rdsp_qn = 0, rdsp_qr = 0;
static uint8_t rdsp_reset_latch = 0;
static int rdsp_pending_e0 = 0;
static void rdsp_push(uint8_t b) {
if (rdsp_qn < (int)sizeof(rdsp_q)) rdsp_q[(rdsp_qr + rdsp_qn++) % 8] = b;
}
static Bitu rdsp_read(Bitu port, Bitu /*iolen*/) {
switch (port & 0xF) {
case 0x4: return rdsp_mixer_idx;
case 0x5: return rdsp_mixer[rdsp_mixer_idx];
case 0xA:
if (rdsp_qn) { uint8_t b = rdsp_q[rdsp_qr]; rdsp_qr = (rdsp_qr + 1) % 8; rdsp_qn--; return b; }
return 0xFF;
case 0xC: return 0x00; /* ready for command */
case 0xE: return rdsp_qn ? 0xFF : 0x7F; /* bit7 = data available */
default: return 0xFF;
}
}
static void rdsp_write(Bitu port, Bitu val, Bitu /*iolen*/) {
switch (port & 0xF) {
case 0x4: rdsp_mixer_idx = (uint8_t)val; break;
case 0x5:
if (rdsp_mixer_idx == 0) memset(rdsp_mixer, 0, sizeof(rdsp_mixer));
else rdsp_mixer[rdsp_mixer_idx] = (uint8_t)val;
break;
case 0x6:
if (rdsp_reset_latch == 1 && (val & 1) == 0) {
rdsp_qn = rdsp_qr = 0; rdsp_pending_e0 = 0;
rdsp_push(0xAA);
}
rdsp_reset_latch = (uint8_t)(val & 1);
break;
case 0xC:
if (rdsp_pending_e0) { rdsp_push((uint8_t)~val); rdsp_pending_e0 = 0; break; }
switch (val) {
case 0xE0: rdsp_pending_e0 = 1; break; /* identify */
case 0xE1: rdsp_push(4); rdsp_push(13); break; /* version */
default: break; /* swallow */
}
break;
default: break;
}
}
/* ---- init ---------------------------------------------------------------- */
void VWEAWE_Init(void) {
const char *en = getenv("VWE_AWE32");
if (!en || !en[0] || en[0] == '0') return;
int ram_kb = 8192;
const char *r = getenv("VWE_AWE_RAM_KB");
if (r && atoi(r) > 0) ram_kb = atoi(r);
if (ram_kb < 512) ram_kb = 512;
if (ram_kb > 28672) ram_kb = 28672;
const char *sh = getenv("VWE_AWE_SHIFT");
if (sh) awe_shift = atoi(sh);
if (awe_shift < 0) awe_shift = 0;
if (awe_shift > 15) awe_shift = 15;
const char *lm = getenv("VWE_AWE_LEAD_MS");
if (lm && atoi(lm) > 0) awe_lead_ms = atoi(lm);
if (awe_lead_ms < 30) awe_lead_ms = 30;
if (awe_lead_ms > 250) awe_lead_ms = 250;
const char *wv = getenv("VWE_AWE_WAV");
if (wv && wv[0]) awe_wav_open(wv);
InitializeCriticalSection(&awe_lock);
LARGE_INTEGER f;
QueryPerformanceFrequency(&f);
awe_qpc_freq = f.QuadPart ? f.QuadPart : 1;
emu8k_init(&awe_front, 0x620, ram_kb);
emu8k_init(&awe_rear, 0x640, ram_kb);
WAVEFORMATEX wfx;
memset(&wfx, 0, sizeof wfx);
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = 2;
wfx.nSamplesPerSec = 44100;
wfx.wBitsPerSample = 16;
wfx.nBlockAlign = 4;
wfx.nAvgBytesPerSec = 44100 * 4;
if (waveOutOpen(&awe_wo, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL)
!= MMSYSERR_NOERROR) {
fprintf(stderr, "VWE AWE32: waveOutOpen failed -- no audio output\n");
awe_wo = NULL;
} else {
HANDLE th = CreateThread(NULL, 0, awe_thread_proc, NULL, 0, NULL);
if (th) {
SetThreadPriority(th, THREAD_PRIORITY_TIME_CRITICAL);
CloseHandle(th);
}
}
IO_RegisterReadHandler(0x240, rdsp_read, IO_MB, 16);
IO_RegisterWriteHandler(0x240, rdsp_write, IO_MB, 16);
const char *dd = getenv("VWE_AWE_DUMP");
if (dd && dd[0]) {
char path[1024];
snprintf(path, sizeof path, "%s\\awe_front.s16", dd);
awe_dump_front = fopen(path, "ab");
snprintf(path, sizeof path, "%s\\awe_rear.s16", dd);
awe_dump_rear = fopen(path, "ab");
}
fprintf(stderr, "VWE AWE32: front EMU8000 @620h + rear @640h (DSP stub "
"@240h), %dKB DRAM/card, GM ROM %s, shift %d, own render "
"thread + waveout (%dms lead)%s\n",
ram_kb, getenv("VWE_AWE_ROM") ? "loaded" : "ABSENT (silence!)",
awe_shift, awe_lead_ms, dd ? ", dumping" : "");
}