mirror of https://github.com/xemu-project/xemu.git
mcpx: Implement APU multipass voice processing
Voice Processor (VP) multipass feature allows configuring lists of voices that are first mixed (in order) into a designated mixbin which is then used as a sample source when processing voices with multipass flag set to true in NV_PAVS_VOICE_CFG_FMT. Setting correct voice order in lists is the responsibility of the game/application and in practice is handled by the DirectSound library. The multipass mixbin is hardcoded to 31 in DirectSound, but hardware would allow other bins. This implementation also adds additional info to audio debug UI to see what the source and destination voices involved are. The info is only shown when DSP processing is off, i.e. "VP Only" (MON_VP) is selected. This is because storing the voice numbers requires additional digging which is required for MON_VP anyway and therefore is free. The multipass feature itself works fine with DSP (i.e. GP and EP) enabled, only the additional debug info is not shown.
This commit is contained in:
parent
ac781ea8d1
commit
0427ae8cfc
|
@ -218,7 +218,7 @@ static int voice_resample(MCPXAPUState *d, uint16_t v, float samples[][2],
|
|||
static void voice_reset_filters(MCPXAPUState *d, uint16_t v);
|
||||
static void voice_process(MCPXAPUState *d,
|
||||
float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME],
|
||||
uint16_t v);
|
||||
uint16_t v, int voice_list);
|
||||
static int voice_get_samples(MCPXAPUState *d, uint32_t v, float samples[][2],
|
||||
int num_samples_requested);
|
||||
static void se_frame(MCPXAPUState *d);
|
||||
|
@ -245,6 +245,7 @@ static void mcpx_debug_begin_frame(void)
|
|||
{
|
||||
for (int i = 0; i < MCPX_HW_MAX_VOICES; i++) {
|
||||
g_dbg.vp.v[i].active = false;
|
||||
g_dbg.vp.v[i].multipass_dst_voice = 0xFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -286,6 +287,19 @@ void mcpx_apu_debug_clear_isolations(void)
|
|||
static bool voice_should_mute(uint16_t v)
|
||||
{
|
||||
bool m = (g_dbg_voice_monitor >= 0) && (v != g_dbg_voice_monitor);
|
||||
|
||||
if (m && g_dbg_cache.vp.v[g_dbg_voice_monitor].multipass) {
|
||||
uint8_t mp_bin = g_dbg_cache.vp.v[g_dbg_voice_monitor].multipass_bin;
|
||||
struct McpxApuDebugVoice *d = &g_dbg_cache.vp.v[v];
|
||||
|
||||
for (int i = 0; i < sizeof(d->bin) / sizeof(d->bin[0]); i++) {
|
||||
if (d->bin[i] == mp_bin) {
|
||||
m = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m || mcpx_apu_debug_is_muted(v);
|
||||
}
|
||||
|
||||
|
@ -1590,9 +1604,101 @@ static void voice_reset_filters(MCPXAPUState *d, uint16_t v)
|
|||
}
|
||||
}
|
||||
|
||||
static int peek_ahead_multipass_bin(MCPXAPUState *d, uint16_t v,
|
||||
uint16_t *dst_voice)
|
||||
{
|
||||
bool first = true;
|
||||
|
||||
while (v != 0xFFFF) {
|
||||
bool multipass = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
|
||||
NV_PAVS_VOICE_CFG_FMT_MULTIPASS);
|
||||
if (multipass) {
|
||||
if (first) {
|
||||
break;
|
||||
}
|
||||
|
||||
*dst_voice = v;
|
||||
int mp_bin = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
|
||||
NV_PAVS_VOICE_CFG_FMT_MULTIPASS_BIN);
|
||||
return mp_bin;
|
||||
}
|
||||
|
||||
v = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_PITCH_LINK,
|
||||
NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE);
|
||||
first = false;
|
||||
}
|
||||
|
||||
*dst_voice = 0xFFFF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void dump_multipass_unused_debug_info(MCPXAPUState *d, uint16_t v)
|
||||
{
|
||||
unsigned int sample_size = voice_get_mask(
|
||||
d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE);
|
||||
unsigned int container_size_index = voice_get_mask(
|
||||
d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE);
|
||||
bool stream = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
|
||||
NV_PAVS_VOICE_CFG_FMT_DATA_TYPE);
|
||||
bool loop =
|
||||
voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_LOOP);
|
||||
uint32_t ebo = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_NEXT,
|
||||
NV_PAVS_VOICE_PAR_NEXT_EBO);
|
||||
uint32_t cbo = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_OFFSET,
|
||||
NV_PAVS_VOICE_PAR_OFFSET_CBO);
|
||||
uint32_t lbo = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_PSH_SAMPLE,
|
||||
NV_PAVS_VOICE_CUR_PSH_SAMPLE_LBO);
|
||||
uint32_t ba = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_PSL_START,
|
||||
NV_PAVS_VOICE_CUR_PSL_START_BA);
|
||||
bool persist = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
|
||||
NV_PAVS_VOICE_CFG_FMT_PERSIST);
|
||||
bool linked = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
|
||||
NV_PAVS_VOICE_CFG_FMT_LINKED);
|
||||
|
||||
struct McpxApuDebugVoice *dbg = &g_dbg.vp.v[v];
|
||||
dbg->container_size = container_size_index;
|
||||
dbg->sample_size = sample_size;
|
||||
dbg->stream = stream;
|
||||
dbg->loop = loop;
|
||||
dbg->ebo = ebo;
|
||||
dbg->cbo = cbo;
|
||||
dbg->lbo = lbo;
|
||||
dbg->ba = ba;
|
||||
dbg->samples_per_block = 0; // Value overloaded with multipass bin
|
||||
dbg->persist = persist;
|
||||
dbg->linked = linked;
|
||||
}
|
||||
|
||||
static void get_multipass_samples(MCPXAPUState *d,
|
||||
float mixbins[][NUM_SAMPLES_PER_FRAME],
|
||||
uint16_t v, float samples[][2])
|
||||
{
|
||||
struct McpxApuDebugVoice *dbg = &g_dbg.vp.v[v];
|
||||
|
||||
// DirectSound sets bin to 31, but hardware would allow other bins
|
||||
int mp_bin = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
|
||||
NV_PAVS_VOICE_CFG_FMT_MULTIPASS_BIN);
|
||||
dbg->multipass_bin = mp_bin;
|
||||
|
||||
for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) {
|
||||
samples[i][0] = mixbins[mp_bin][i];
|
||||
samples[i][1] = mixbins[mp_bin][i];
|
||||
}
|
||||
|
||||
// DirectSound sets clear mix to true
|
||||
bool clear_mix = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
|
||||
NV_PAVS_VOICE_CFG_FMT_CLEAR_MIX);
|
||||
if (clear_mix) {
|
||||
memset(&mixbins[mp_bin][0], 0, sizeof(mixbins[0]));
|
||||
}
|
||||
|
||||
// Dump irrelevant data for audio debug UI to avoid showing stale info
|
||||
dump_multipass_unused_debug_info(d, v);
|
||||
}
|
||||
|
||||
static void voice_process(MCPXAPUState *d,
|
||||
float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME],
|
||||
uint16_t v)
|
||||
uint16_t v, int voice_list)
|
||||
{
|
||||
assert(v < MCPX_HW_MAX_VOICES);
|
||||
bool stereo = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
|
||||
|
@ -1633,18 +1739,28 @@ static void voice_process(MCPXAPUState *d,
|
|||
assert(ea_value <= 1.0f);
|
||||
|
||||
float samples[NUM_SAMPLES_PER_FRAME][2] = { 0 };
|
||||
for (int sample_count = 0; sample_count < NUM_SAMPLES_PER_FRAME;) {
|
||||
int active = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE,
|
||||
NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE);
|
||||
if (!active) {
|
||||
return;
|
||||
|
||||
bool multipass = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
|
||||
NV_PAVS_VOICE_CFG_FMT_MULTIPASS);
|
||||
dbg->multipass = multipass;
|
||||
|
||||
if (multipass) {
|
||||
get_multipass_samples(d, mixbins, v, samples);
|
||||
} else {
|
||||
for (int sample_count = 0; sample_count < NUM_SAMPLES_PER_FRAME;) {
|
||||
int active = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE,
|
||||
NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE);
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
int count =
|
||||
voice_resample(d, v, &samples[sample_count],
|
||||
NUM_SAMPLES_PER_FRAME - sample_count, rate);
|
||||
if (count < 0) {
|
||||
break;
|
||||
}
|
||||
sample_count += count;
|
||||
}
|
||||
int count = voice_resample(d, v, &samples[sample_count],
|
||||
NUM_SAMPLES_PER_FRAME - sample_count, rate);
|
||||
if (count < 0) {
|
||||
break;
|
||||
}
|
||||
sample_count += count;
|
||||
}
|
||||
|
||||
int active = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE,
|
||||
|
@ -1771,9 +1887,32 @@ static void voice_process(MCPXAPUState *d,
|
|||
/* For VP mon, simply mix all voices together here, selecting the
|
||||
* maximal volume used for any given mixbin as the overall volume for
|
||||
* this voice.
|
||||
*
|
||||
* If the current voice belongs to a multipass sub-voice group we must
|
||||
* skip it here to avoid mixing it in twice because the sub-voices are
|
||||
* mixed into the multipass bin and that sub-mix will be mixed in here
|
||||
* later when the destination (i.e. second pass) voice is processed.
|
||||
* TODO: Are the 2D, 3D and MP voice lists merely a DirectSound
|
||||
* convention? Perhaps hardware doesn't care if e.g. a multipass
|
||||
* voice is in the 2D or 3D list. On the other hand, MON_VP is
|
||||
* not how the hardware works anyway so not much point worrying
|
||||
* about precise emulation here. DirectSound compatibility is
|
||||
* enough.
|
||||
*/
|
||||
int mp_bin = -1;
|
||||
uint16_t mp_dst_voice = 0xFFFF;
|
||||
if (voice_list == NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_MP_TOP - 1) {
|
||||
mp_bin = peek_ahead_multipass_bin(d, v, &mp_dst_voice);
|
||||
}
|
||||
dbg->multipass_dst_voice = mp_dst_voice;
|
||||
|
||||
bool debug_isolation =
|
||||
g_dbg_voice_monitor >= 0 && g_dbg_voice_monitor == v;
|
||||
float g = 0.0f;
|
||||
for (int b = 0; b < 8; b++) {
|
||||
if (bin[b] == mp_bin && !debug_isolation) {
|
||||
continue;
|
||||
}
|
||||
float hr = 1 << d->vp.submix_headroom[bin[b]];
|
||||
g = fmax(g, attenuate(vol[b]) / hr);
|
||||
}
|
||||
|
@ -1822,6 +1961,8 @@ static int voice_get_samples(MCPXAPUState *d, uint32_t v, float samples[][2],
|
|||
bool linked = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT,
|
||||
NV_PAVS_VOICE_CFG_FMT_LINKED); /* FIXME? */
|
||||
|
||||
assert(!multipass); // Multipass is handled before this
|
||||
|
||||
int ssl_index = 0;
|
||||
int ssl_seg = 0;
|
||||
int page = 0;
|
||||
|
@ -1954,11 +2095,6 @@ static int voice_get_samples(MCPXAPUState *d, uint32_t v, float samples[][2],
|
|||
|
||||
DPRINTF("CBO=%d EBO=%d\n", cbo, ebo);
|
||||
|
||||
if (multipass) {
|
||||
// FIXME
|
||||
samples_per_block = 1;
|
||||
}
|
||||
|
||||
block_size *= samples_per_block;
|
||||
|
||||
// FIXME: Restructure this loop
|
||||
|
@ -2147,7 +2283,7 @@ static void se_frame(MCPXAPUState *d)
|
|||
qemu_cond_wait(&d->cond, &d->lock);
|
||||
qemu_spin_lock(&d->vp.voice_spinlocks[v]);
|
||||
}
|
||||
voice_process(d, mixbins, v);
|
||||
voice_process(d, mixbins, v, list);
|
||||
qemu_spin_unlock(&d->vp.voice_spinlocks[v]);
|
||||
}
|
||||
d->regs[current] = d->regs[next];
|
||||
|
|
|
@ -44,6 +44,8 @@ struct McpxApuDebugVoice
|
|||
bool persist;
|
||||
bool multipass;
|
||||
bool linked;
|
||||
uint8_t multipass_bin;
|
||||
uint16_t multipass_dst_voice;
|
||||
int container_size, sample_size;
|
||||
unsigned int samples_per_block;
|
||||
uint32_t ebo, cbo, lbo, ba;
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
#include "font-manager.hh"
|
||||
#include "viewport-manager.hh"
|
||||
|
||||
#define MAX_VOICES 256
|
||||
|
||||
DebugApuWindow::DebugApuWindow() : m_is_open(false)
|
||||
{
|
||||
}
|
||||
|
@ -60,8 +62,7 @@ void DebugApuWindow::Draw()
|
|||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2*g_viewport_mgr.m_scale, 2*g_viewport_mgr.m_scale));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4*g_viewport_mgr.m_scale, 4*g_viewport_mgr.m_scale));
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
for (int i = 0; i < MAX_VOICES; i++) {
|
||||
if (i % 16) {
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
@ -132,8 +133,15 @@ void DebugApuWindow::Draw()
|
|||
|
||||
assert(voice->container_size < 4);
|
||||
assert(voice->sample_size < 4);
|
||||
ImGui::Text("Container Size: %s, Sample Size: %s, Samples per Block: %d",
|
||||
cs[voice->container_size], ss[voice->sample_size], voice->samples_per_block);
|
||||
const char *spb_or_bin_label = "Samples per Block";
|
||||
unsigned int spb_or_bin = voice->samples_per_block;
|
||||
if (voice->multipass) {
|
||||
spb_or_bin_label = "Multipass Bin";
|
||||
spb_or_bin = voice->multipass_bin;
|
||||
}
|
||||
ImGui::Text("Container Size: %s, Sample Size: %s, %s: %d",
|
||||
cs[voice->container_size], ss[voice->sample_size],
|
||||
spb_or_bin_label, spb_or_bin);
|
||||
ImGui::Text("Rate: %f (%d Hz)", voice->rate, (int)(48000.0/voice->rate));
|
||||
ImGui::Text("EBO=%d CBO=%d LBO=%d BA=%x",
|
||||
voice->ebo, voice->cbo, voice->lbo, voice->ba);
|
||||
|
@ -153,6 +161,30 @@ void DebugApuWindow::Draw()
|
|||
}
|
||||
ImGui::Text("%-17s", buf);
|
||||
}
|
||||
|
||||
int mon = mcpx_apu_debug_get_monitor();
|
||||
if (mon == MCPX_APU_DEBUG_MON_VP) {
|
||||
if (voice->multipass_dst_voice != 0xFFFF) {
|
||||
ImGui::Text("Multipass Dest Voice: 0x%02x",
|
||||
voice->multipass_dst_voice);
|
||||
}
|
||||
if (voice->multipass) {
|
||||
ImGui::Text("Multipass Src Voices:");
|
||||
int n = 0;
|
||||
|
||||
for (int i = 0; i < MAX_VOICES; i++) {
|
||||
if (dbg->vp.v[i].multipass_dst_voice == voice_info) {
|
||||
if (n > 0 && ((n & 7) == 0)) {
|
||||
ImGui::Text(" ");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("0x%02x", i);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopFont();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue