diff --git a/port/src/mixer.c b/port/src/mixer.c index aaee4b65..a6d80d21 100644 --- a/port/src/mixer.c +++ b/port/src/mixer.c @@ -28,6 +28,7 @@ #include #include +#include "system.h" /* D202 diag (temporary): per-opcode DMEM address/context trace, gated by * GE_MIXERTRACE=1, to test the M-56 "concurrent voices share/clobber DMEM @@ -407,6 +408,16 @@ void aSetVolumeImpl(u32 flags, u16 v, u16 t, u16 r) } } +/* D202/M-67 diag (temporary): GE_VOICEDUMP=1 writes each voice's resampled + * mono stream (the aEnvMixer input, i.e. post-resample/pre-envelope) to + * voicedump.raw as records of [u32 stateAddr][u32 nSamples][u64 us] + * [s16 x nSamples], where us is sysGetMicroseconds() at mix time so records + * can be correlated with the timestamped [WIRE] (load.c) and [AUDIOTRACE] + * (snd.c) lines. Offline, each record is matched against all 261 ROM SFX + * decodes (pitch-resampled per keymap) to prove exactly which sample data a + * voice played, with no music/reverb masking. Remove once D202 closes. */ +static FILE *s_voiceDumpFile = NULL; + void aEnvMixerImpl(u32 flags, u32 stateAddr) { struct { @@ -420,6 +431,19 @@ void aEnvMixerImpl(u32 flags, u32 stateAddr) MTRACE("[ENVMIX] flags=%u state=%p in=%u out=%u dryR=%u wetL=%u wetR=%u count=%u\n", flags, (void *)saved, sCtx.in, sCtx.out, sCtx.dryR, sCtx.wetL, sCtx.wetR, sCtx.count); const s16 *in = DMEM_S16(sCtx.in); + + if (getenv("GE_VOICEDUMP")) { + if (!s_voiceDumpFile) + s_voiceDumpFile = fopen("voicedump.raw", "wb"); + if (s_voiceDumpFile) { + u32 hdr[2] = { stateAddr, (u32)(sCtx.count >> 1) }; + u64 us = sysGetMicroseconds(); + fwrite(hdr, 4, 2, s_voiceDumpFile); + fwrite(&us, 8, 1, s_voiceDumpFile); + fwrite(in, sizeof(s16), sCtx.count >> 1, s_voiceDumpFile); + } + } + s16 *dry[2] = { DMEM_S16(sCtx.out), DMEM_S16(sCtx.dryR) }; s16 *wet[2] = { DMEM_S16(sCtx.wetL), DMEM_S16(sCtx.wetR) }; u32 nsamples = sCtx.count >> 1; diff --git a/src/libultra/audio/load.c b/src/libultra/audio/load.c index a95112c5..a2bd2738 100644 --- a/src/libultra/audio/load.c +++ b/src/libultra/audio/load.c @@ -394,8 +394,10 @@ alLoadParam(void *filter, s32 paramID, void *param) #if defined(__x86_64__) if (getenv("GE_AUDIOTRACE")) { static FILE *tf = NULL; + extern uint64_t sysGetMicroseconds(void); /* port/src/system.c */ if (!tf) { tf = fopen("audiotrace_wire.log", "a"); if (tf) setvbuf(tf, NULL, _IONBF, 0); } - if (tf) fprintf(tf, "[WIRE] filter=%p <- table=%p base=%p len=%d book=%p\n", + if (tf) fprintf(tf, "[WIRE] t=%llu filter=%p <- table=%p base=%p len=%d book=%p\n", + (unsigned long long)sysGetMicroseconds(), (void *)a, (void *)a->table, (void *)a->table->base, a->table->len, (a->table->type == AL_ADPCM_WAVE) ? (void *)a->table->waveInfo.adpcmWave.book : NULL); diff --git a/src/libultra/audio/synthesizer.c b/src/libultra/audio/synthesizer.c index 7d28d38f..f870d287 100644 --- a/src/libultra/audio/synthesizer.c +++ b/src/libultra/audio/synthesizer.c @@ -20,6 +20,10 @@ #include "synthInternals.h" #include "include/assert.h" +#if defined(__x86_64__) +#include +#include +#endif #ifdef AUD_PROFILE #include @@ -117,6 +121,24 @@ void alSynNew(ALSynth *drvr, ALSynConfig *c) alAuxBusParam(drvr->auxBus, AL_FILTER_ADD_SOURCE, &pv->envmixer); pv->channelKnob = (ALFilter *)&pv->envmixer; + +#if defined(__x86_64__) + /* D202/M-69 diag (temporary): GE_AUDIOTRACE=1 logs the full + * PVoice -> decoder/resampler/env-state address map at synth init, + * so offline voicedump slot addresses can be tied to WIRE filter + * addresses (and hence to wave tables / sound indices). */ + if (getenv("GE_AUDIOTRACE")) { + static FILE *mf = NULL; + if (!mf) { mf = fopen("audiotrace_wire.log", "a"); if (mf) setvbuf(mf, NULL, _IONBF, 0); } + if (i == 0) + fprintf(mf, "[VOICEMAP] sizes pv=%u dec=%u res=%u env=%u\n", + (unsigned)sizeof(PVoice), (unsigned)sizeof(ALLoadFilter), + (unsigned)sizeof(ALResampler), (unsigned)sizeof(ALEnvMixer)); + if (mf) fprintf(mf, "[VOICEMAP] i=%d pv=%p dec=%p res=%p envstate=%p\n", + i, (void *)pv, (void *)&pv->decoder, + (void *)&pv->resampler, (void *)pv->envmixer.state); + } +#endif } alSaveParam(save, AL_FILTER_SET_SOURCE, drvr->mainBus); diff --git a/src/snd.c b/src/snd.c index 9ffa1ec8..cc82d127 100644 --- a/src/snd.c +++ b/src/snd.c @@ -292,6 +292,19 @@ void sndHandleEvent(ALSndPlayer *sndp, ALSndpEvent *event) { soundState = event->common.state; sound = soundState->sound; +#ifdef PORT + /* D202/M-67 diag (temporary): log every event the player processes, + * so each VOICE- can be attributed to the exact killing event. + * Remove once root-caused. */ + if (getenv("GE_AUDIOTRACE")) { + static FILE *fe = NULL; + extern uint64_t sysGetMicroseconds(void); + if (!fe) { fe = fopen("audiotrace.log", "a"); if (fe) setvbuf(fe, NULL, _IONBF, 0); } + if (fe) fprintf(fe, "[EVT] t=%llu type=%d state=%p\n", + (unsigned long long)sysGetMicroseconds(), + (int)event->common.type, (void *)soundState); + } +#endif if (sound == NULL) { s16 numFree, numAlloc; @@ -486,6 +499,18 @@ void sndHandleEvent(ALSndPlayer *sndp, ALSndpEvent *event) { case SOUND_STATE_PLAYING: sndRemoveEvents(&sndp->evtq, soundState, AL_SNDP_DECAY_EVT); delta = sound->envelope->releaseTime / soundState->pitch_28 / soundState->pitch_2c; +#ifdef PORT + /* D202/M-67 diag (temporary): log the release ramp a + * STOP/DEACTIVATE computes; delta==0 means the voice + * is disposed immediately. Remove once root-caused. */ + if (getenv("GE_AUDIOTRACE")) { + static FILE *fs = NULL; + if (!fs) { fs = fopen("audiotrace.log", "a"); if (fs) setvbuf(fs, NULL, _IONBF, 0); } + if (fs) fprintf(fs, "[STOP-EVT] type=%d state=%p playingState=%d deltaUs=%d\n", + (int)event->common.type, (void *)soundState, + (int)soundState->playingState, (int)delta); + } +#endif alSynSetVol(sndp->drvr, &soundState->voice, 0, delta); if (delta != 0) { spAC.common.type = AL_SNDP_END_EVT; @@ -1059,16 +1084,22 @@ ALSoundState *sndPlaySfx(struct ALBankAlt_s *soundBank, s16 soundIndex, ALSoundS (int)sound->envelope->releaseTime, (unsigned)sound->envelope->attackVolume, (unsigned)sound->envelope->decayVolume); - if (f2) fprintf(f2, "[AUDIOTRACE] sndPlaySfx: soundIndex=%d -> newState=%p flags=%d " + if (f2) { + extern uint64_t sysGetMicroseconds(void); + fprintf(f2, "[AUDIOTRACE] sndPlaySfx: t=%llu soundIndex=%d -> newState=%p wavetable=%p flags=%d " "keyMap=%p velMin(next)=%d velMax(period)=%d keyMin=0x%02x keyMax=0x%02x " "keyBase=%d detune=%d retrig=%d deltaLoopUs=%d\n", - (int)soundIndex, (void *)newState, newState ? (int)newState->unk3e : -1, + (unsigned long long)sysGetMicroseconds(), + (int)soundIndex, (void *)newState, + (void *)(sound ? sound->wavetable : NULL), + newState ? (int)newState->unk3e : -1, (void *)kmp, kmp ? (int)kmp->velocityMin : -1, kmp ? (int)kmp->velocityMax : -1, kmp ? (unsigned)kmp->keyMin : 0u, kmp ? (unsigned)kmp->keyMax : 0u, kmp ? (int)kmp->keyBase : -1, kmp ? (int)kmp->detune : -1, (newState && (newState->unk3e & 0x10)) ? 1 : 0, kmp ? (int)(kmp->velocityMax * DELTA_33_MS) : -1); + } /* D202/M-65: only 8 voices exist (MUSIC_SFX_SEQ_MAYBE_MAX_SOUNDS). * A SOUND_FLAG_LOOPED voice never self-releases and is skipped by * the preemption scan, so each leaked one permanently costs a diff --git a/tools_pc/audiodebug.ps1 b/tools_pc/audiodebug.ps1 index 797b787b..1669359e 100644 --- a/tools_pc/audiodebug.ps1 +++ b/tools_pc/audiodebug.ps1 @@ -45,8 +45,9 @@ param( [int]$FireEvery = 45, # frames between pulses (45 = 0.75 s @60) # Probes - [switch]$Trace, # GE_AUDIOTRACE=1 -> audiotrace.log + [switch]$Trace, # GE_AUDIOTRACE=1 -> audiotrace.log (+ _wire.log) [switch]$Dump, # GE_AUDIODUMP=1 -> audiodump.raw + [switch]$VoiceDump, # GE_VOICEDUMP=1 -> voicedump.raw (per-voice, D202/M-67) [switch]$MixerTrace, # GE_MIXERTRACE=1 -- SLOW, see warning below [switch]$Old, # GE_D204_OLD=1: pre-D204 behaviour [switch]$AB, # run twice (old, then new) and compare @@ -132,6 +133,7 @@ function Invoke-AudioRun { if ($OldMode) { $env:GE_D204_OLD = "1" } else { Remove-Item -EA SilentlyContinue Env:\GE_D204_OLD } if ($Trace) { $env:GE_AUDIOTRACE = "1" } else { Remove-Item -EA SilentlyContinue Env:\GE_AUDIOTRACE } if ($Dump) { $env:GE_AUDIODUMP = "1" } else { Remove-Item -EA SilentlyContinue Env:\GE_AUDIODUMP } + if ($VoiceDump) { $env:GE_VOICEDUMP = "1" } else { Remove-Item -EA SilentlyContinue Env:\GE_VOICEDUMP } if ($MixerTrace) { $env:GE_MIXERTRACE = "1" } else { Remove-Item -EA SilentlyContinue Env:\GE_MIXERTRACE } if ($inputScript) { $env:GE_INPUTSCRIPT = $inputScript } else { Remove-Item -EA SilentlyContinue Env:\GE_INPUTSCRIPT }