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>
This commit is contained in:
@@ -58,6 +58,7 @@ if ($NoSound) {
|
|||||||
$env:VWE_AWE32 = '1'
|
$env:VWE_AWE32 = '1'
|
||||||
$env:VWE_AWE_ROM = (Resolve-Path "$PSScriptRoot\..\roms\awe32.raw").Path
|
$env:VWE_AWE_ROM = (Resolve-Path "$PSScriptRoot\..\roms\awe32.raw").Path
|
||||||
$env:VWE_AWE_LOG = '1' # 10s reports incl. limiter stats (crackle diagnosis)
|
$env:VWE_AWE_LOG = '1' # 10s reports incl. limiter stats (crackle diagnosis)
|
||||||
|
$env:VWE_AWE_WAV = "$Work\awe" # crackle taps: awe_front/_rear/_mix.wav
|
||||||
}
|
}
|
||||||
|
|
||||||
$dosbox = (Resolve-Path "$PSScriptRoot\..\src\src\dosbox-x.exe").Path
|
$dosbox = (Resolve-Path "$PSScriptRoot\..\src\src\dosbox-x.exe").Path
|
||||||
|
|||||||
@@ -37,6 +37,9 @@
|
|||||||
* VWE_AWE_LEAD_MS=N audio queue depth in ms (default 80, min 30 max 250)
|
* 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
|
* VWE_AWE_DUMP=dir append raw s16le stereo 44100 streams to
|
||||||
* <dir>/awe_front.s16 + <dir>/awe_rear.s16
|
* <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 =
|
* VWE_AWE_LOG=1 port trace + 10s activity reports (smldW counter =
|
||||||
* SoundFont upload progress; 99 = upload refused)
|
* SoundFont upload progress; 99 = upload refused)
|
||||||
*/
|
*/
|
||||||
@@ -233,6 +236,56 @@ static int32_t awe_lim_gain = 1 << 16; /* Q16, <= 1.0 */
|
|||||||
static unsigned long awe_lim_engaged = 0; /* frames ducked (diagnostic) */
|
static unsigned long awe_lim_engaged = 0; /* frames ducked (diagnostic) */
|
||||||
static int awe_prelim_peak[2]; /* per-card pre-limit |peak| */
|
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) {
|
static void awe_render_chunk(emu8k_t *card, int ci, unsigned n) {
|
||||||
wavetable_pos_global = (int)n;
|
wavetable_pos_global = (int)n;
|
||||||
emu8k_update(card);
|
emu8k_update(card);
|
||||||
@@ -305,6 +358,24 @@ static DWORD WINAPI awe_thread_proc(LPVOID) {
|
|||||||
out[i * 2] = (int16_t)l;
|
out[i * 2] = (int16_t)l;
|
||||||
out[i * 2 + 1] = (int16_t)r;
|
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[0] > peak[0]) peak[0] = awe_prelim_peak[0];
|
||||||
if (awe_prelim_peak[1] > peak[1]) peak[1] = awe_prelim_peak[1];
|
if (awe_prelim_peak[1] > peak[1]) peak[1] = awe_prelim_peak[1];
|
||||||
awe_prelim_peak[0] = awe_prelim_peak[1] = 0;
|
awe_prelim_peak[0] = awe_prelim_peak[1] = 0;
|
||||||
@@ -418,6 +489,8 @@ void VWEAWE_Init(void) {
|
|||||||
if (lm && atoi(lm) > 0) awe_lead_ms = atoi(lm);
|
if (lm && atoi(lm) > 0) awe_lead_ms = atoi(lm);
|
||||||
if (awe_lead_ms < 30) awe_lead_ms = 30;
|
if (awe_lead_ms < 30) awe_lead_ms = 30;
|
||||||
if (awe_lead_ms > 250) awe_lead_ms = 250;
|
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);
|
InitializeCriticalSection(&awe_lock);
|
||||||
LARGE_INTEGER f;
|
LARGE_INTEGER f;
|
||||||
|
|||||||
Reference in New Issue
Block a user