Vendored 86Box EMU8000 core (GPL-2, emu8k.cpp/.h + shim) plus vweawe.cpp: two cards at the production addresses (0x620/0x640 register triplets), rear-card DSP/mixer stub at 0x240, synthesis on an autonomous render thread with direct winmm output so sustained voices ride through emulation-thread stalls (RIO staging retries) like the real silicon did. WC sample-counter interpolation between render chunks keeps HMI SOS busy-waits fast. VWE_AWE32/VWE_AWE_ROM/VWE_AWE_RAM_KB/VWE_AWE_SHIFT/ VWE_AWE_LEAD_MS/VWE_AWE_DUMP/VWE_AWE_LOG env knobs. emulator/roms: the AWE32 1MGM GM ROM dumped from our own pod card (SF2) plus the reconstructed raw image the emulated EMU8000 loads. The HMI driver refuses the AUDIO*.RES SoundFont upload without it (banks declare irom=1MGM) -- that was the root cause of the first silent runs; full debug chain in SOUND-NOTES.md. Repo serves the pod-owner community only. .gitignore: drop the ROM excludes and un-ignore ALPHA_1/ and sda4/ (committed separately). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
424 lines
17 KiB
C++
424 lines
17 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); per-card host routing for the real
|
|
* 4-speaker cockpit comes later (see 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_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 int16_t awe_cardbuf[2][AWE_SLOT_FRAMES * 2];
|
|
|
|
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;
|
|
}
|
|
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 * 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;
|
|
}
|
|
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);
|
|
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\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]);
|
|
peak[ci] = 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;
|
|
|
|
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" : "");
|
|
}
|