From 2d5c4fa34d2868d2e4bb73b2e53755730b42a59e Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Tue, 3 Jun 2025 19:17:09 -0700 Subject: [PATCH] mcpx: Move VP functions to vp.c --- hw/xbox/mcpx/apu.c | 1866 +------------------------------------ hw/xbox/mcpx/apu_int.h | 16 +- hw/xbox/mcpx/meson.build | 1 + hw/xbox/mcpx/vp.c | 1907 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 1919 insertions(+), 1871 deletions(-) create mode 100644 hw/xbox/mcpx/vp.c diff --git a/hw/xbox/mcpx/apu.c b/hw/xbox/mcpx/apu.c index 0e1f385c7d..db1f5f498b 100644 --- a/hw/xbox/mcpx/apu.c +++ b/hw/xbox/mcpx/apu.c @@ -24,24 +24,9 @@ MCPXAPUState *g_state; // Used via debug handlers static const int16_t ep_silence[256][2] = { 0 }; -static float clampf(float v, float min, float max); -static float attenuate(uint16_t vol); - -static bool voice_should_mute(uint16_t v); -static uint32_t voice_get_mask(MCPXAPUState *d, uint16_t voice_handle, - hwaddr offset, uint32_t mask); -static void voice_set_mask(MCPXAPUState *d, uint16_t voice_handle, - hwaddr offset, uint32_t mask, uint32_t val); static uint64_t mcpx_apu_read(void *opaque, hwaddr addr, unsigned int size); static void mcpx_apu_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size); -static void voice_off(MCPXAPUState *d, uint16_t v); -static void voice_lock(MCPXAPUState *d, uint16_t v, bool lock); -static bool is_voice_locked(MCPXAPUState *d, uint16_t v); -static void fe_method(MCPXAPUState *d, uint32_t method, uint32_t argument); -static uint64_t vp_read(void *opaque, hwaddr addr, unsigned int size); -static void vp_write(void *opaque, hwaddr addr, uint64_t val, - unsigned int size); static void scatter_gather_rw(MCPXAPUState *d, hwaddr sge_base, unsigned int max_sge, uint8_t *ptr, uint32_t addr, size_t len, bool dir); @@ -65,25 +50,6 @@ static void gp_write(void *opaque, hwaddr addr, uint64_t val, static uint64_t ep_read(void *opaque, hwaddr addr, unsigned int size); static void ep_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size); -static float voice_step_envelope(MCPXAPUState *d, uint16_t v, - uint32_t reg_0, uint32_t reg_a, - uint32_t rr_reg, uint32_t rr_mask, - uint32_t lvl_reg, uint32_t lvl_mask, - uint32_t count_mask, uint32_t cur_mask); -static hwaddr get_data_ptr(hwaddr sge_base, unsigned int max_sge, - uint32_t addr); -static void set_notify_status(MCPXAPUState *d, uint32_t v, int notifier, - int status); -static long voice_resample_callback(void *cb_data, float **data); -static int voice_resample(MCPXAPUState *d, uint16_t v, float samples[][2], - int requested_num, float rate); -static void voice_reset_filters(MCPXAPUState *d, uint16_t v); -static void voice_process(MCPXAPUState *d, - float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME], - float sample_buf[NUM_SAMPLES_PER_FRAME][2], - 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); static void update_irq(MCPXAPUState *d); static void sleep_ns(int64_t ns); @@ -98,30 +64,6 @@ static int mcpx_apu_post_load(void *opaque, int version_id); static void mcpx_apu_register(void); static void *mcpx_apu_frame_thread(void *arg); - - - -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); -} - - - static void mcpx_apu_update_dsp_preference(MCPXAPUState *d) { static int last_known_preference = -1; @@ -143,41 +85,6 @@ static void mcpx_apu_update_dsp_preference(MCPXAPUState *d) last_known_preference = g_config.audio.use_dsp; } -static float clampf(float v, float min, float max) -{ - if (v < min) { - return min; - } else if (v > max) { - return max; - } else { - return v; - } -} - -static float attenuate(uint16_t vol) -{ - vol &= 0xFFF; - return (vol == 0xFFF) ? 0.0 : powf(10.0f, vol/(64.0 * -20.0f)); -} - -static uint32_t voice_get_mask(MCPXAPUState *d, uint16_t voice_handle, - hwaddr offset, uint32_t mask) -{ - hwaddr voice = d->regs[NV_PAPU_VPVADDR] + voice_handle * NV_PAVS_SIZE; - return (ldl_le_phys(&address_space_memory, voice + offset) & mask) >> - ctz32(mask); -} - -static void voice_set_mask(MCPXAPUState *d, uint16_t voice_handle, - hwaddr offset, uint32_t mask, uint32_t val) -{ - hwaddr voice = d->regs[NV_PAPU_VPVADDR] - + voice_handle * NV_PAVS_SIZE; - uint32_t v = ldl_le_phys(&address_space_memory, voice + offset) & ~mask; - stl_le_phys(&address_space_memory, voice + offset, - v | ((val << ctz32(mask)) & mask)); -} - static void update_irq(MCPXAPUState *d) { if (d->regs[NV_PAPU_FECTL] & NV_PAPU_FECTL_FEMETHMODE_TRAPPED) { @@ -258,552 +165,6 @@ static const MemoryRegionOps mcpx_apu_mmio_ops = { .write = mcpx_apu_write, }; -static void voice_off(MCPXAPUState *d, uint16_t v) -{ - voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, - NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE, 0); - - bool stream = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, - NV_PAVS_VOICE_CFG_FMT_DATA_TYPE); - int notifier = MCPX_HW_NOTIFIER_SSLA_DONE; - if (stream) { - assert(v < MCPX_HW_MAX_VOICES); - assert(d->vp.ssl[v].ssl_index <= 1); - notifier += d->vp.ssl[v].ssl_index; - } - set_notify_status(d, v, notifier, NV1BA0_NOTIFICATION_STATUS_DONE_SUCCESS); -} - -static void voice_lock(MCPXAPUState *d, uint16_t v, bool lock) -{ - assert(v < MCPX_HW_MAX_VOICES); - qemu_spin_lock(&d->vp.voice_spinlocks[v]); - uint64_t mask = 1LL << (v % 64); - if (lock) { - d->vp.voice_locked[v / 64] |= mask; - } else { - d->vp.voice_locked[v / 64] &= ~mask; - } - qemu_spin_unlock(&d->vp.voice_spinlocks[v]); - qemu_cond_broadcast(&d->cond); -} - -static bool is_voice_locked(MCPXAPUState *d, uint16_t v) -{ - assert(v < MCPX_HW_MAX_VOICES); - uint64_t mask = 1LL << (v % 64); - return (qatomic_read(&d->vp.voice_locked[v / 64]) & mask) != 0; -} - -static void set_hrir_coeff_tar(MCPXAPUState *d, int channel, int coeff_idx, - int8_t value) -{ - int entry = d->vp.hrtf.current_entry; - d->vp.hrtf.entries[entry].hrir[channel][coeff_idx] = int8_to_float(value); -} - -static void fe_method(MCPXAPUState *d, uint32_t method, uint32_t argument) -{ - unsigned int slot; - - trace_mcpx_apu_method(method, argument); - - //assert((d->regs[NV_PAPU_FECTL] & NV_PAPU_FECTL_FEMETHMODE) == 0); - - d->regs[NV_PAPU_FEDECMETH] = method; - d->regs[NV_PAPU_FEDECPARAM] = argument; - unsigned int selected_handle, list; - switch (method) { - case NV1BA0_PIO_VOICE_LOCK: - voice_lock(d, d->regs[NV_PAPU_FECV], argument & 1); - break; - case NV1BA0_PIO_SET_ANTECEDENT_VOICE: - d->regs[NV_PAPU_FEAV] = argument; - break; - case NV1BA0_PIO_VOICE_ON: { - selected_handle = argument & NV1BA0_PIO_VOICE_ON_HANDLE; - DPRINTF("VOICE %d ON\n", selected_handle); - - bool locked = is_voice_locked(d, selected_handle); - if (!locked) { - voice_lock(d, selected_handle, true); - } - - list = GET_MASK(d->regs[NV_PAPU_FEAV], NV_PAPU_FEAV_LST); - if (list != NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_INHERIT) { - /* voice is added to the top of the selected list */ - unsigned int top_reg = voice_list_regs[list - 1].top; - voice_set_mask(d, selected_handle, NV_PAVS_VOICE_TAR_PITCH_LINK, - NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE, - d->regs[top_reg]); - d->regs[top_reg] = selected_handle; - } else { - unsigned int antecedent_voice = - GET_MASK(d->regs[NV_PAPU_FEAV], NV_PAPU_FEAV_VALUE); - /* voice is added after the antecedent voice */ - assert(antecedent_voice != 0xFFFF); - - uint32_t next_handle = voice_get_mask( - d, antecedent_voice, NV_PAVS_VOICE_TAR_PITCH_LINK, - NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE); - voice_set_mask(d, selected_handle, NV_PAVS_VOICE_TAR_PITCH_LINK, - NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE, - next_handle); - voice_set_mask(d, antecedent_voice, NV_PAVS_VOICE_TAR_PITCH_LINK, - NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE, - selected_handle); - } - - // FIXME: Should set CBO here? - voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_OFFSET, - NV_PAVS_VOICE_PAR_OFFSET_CBO, 0); - d->vp.ssl[selected_handle].ssl_seg = 0; // FIXME: verify this - d->vp.ssl[selected_handle].ssl_index = 0; // FIXME: verify this - - unsigned int ea_start = GET_MASK(argument, NV1BA0_PIO_VOICE_ON_ENVA); - voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_STATE, - NV_PAVS_VOICE_PAR_STATE_EACUR, ea_start); - if (ea_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_DELAY) { - uint16_t delay_time = - voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENV0, - NV_PAVS_VOICE_CFG_ENV0_EA_DELAYTIME); - voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, - NV_PAVS_VOICE_CUR_ECNT_EACOUNT, delay_time * 16); - } else if (ea_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_ATTACK) { - voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, - NV_PAVS_VOICE_CUR_ECNT_EACOUNT, 0); - } else if (ea_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_HOLD) { - uint16_t hold_time = - voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENVA, - NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME); - voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, - NV_PAVS_VOICE_CUR_ECNT_EACOUNT, hold_time * 16); - } - // FIXME: Will count be overwritten in other cases too? - - unsigned int ef_start = GET_MASK(argument, NV1BA0_PIO_VOICE_ON_ENVF); - voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_STATE, - NV_PAVS_VOICE_PAR_STATE_EFCUR, ef_start); - if (ef_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_DELAY) { - uint16_t delay_time = - voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENV1, - NV_PAVS_VOICE_CFG_ENV0_EA_DELAYTIME); - voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, - NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, delay_time * 16); - } else if (ef_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_ATTACK) { - voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, - NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, 0); - } else if (ef_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_HOLD) { - uint16_t hold_time = - voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENVF, - NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME); - voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, - NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, hold_time * 16); - } - // FIXME: Will count be overwritten in other cases too? - - voice_reset_filters(d, selected_handle); - voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_STATE, - NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE, 1); - - if (!locked) { - voice_lock(d, selected_handle, false); - } - - break; - } - case NV1BA0_PIO_VOICE_RELEASE: { - selected_handle = argument & NV1BA0_PIO_VOICE_ON_HANDLE; - - // FIXME: What if already in release? Restart envelope? - // FIXME: Should release count ascend or descend? - - bool locked = is_voice_locked(d, selected_handle); - if (!locked) { - voice_lock(d, selected_handle, true); - } - - uint16_t rr; - rr = voice_get_mask(d, selected_handle, NV_PAVS_VOICE_TAR_LFO_ENV, - NV_PAVS_VOICE_TAR_LFO_ENV_EA_RELEASERATE); - voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, - NV_PAVS_VOICE_CUR_ECNT_EACOUNT, rr * 16); - voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_STATE, - NV_PAVS_VOICE_PAR_STATE_EACUR, - NV_PAVS_VOICE_PAR_STATE_EFCUR_RELEASE); - - rr = voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_MISC, - NV_PAVS_VOICE_CFG_MISC_EF_RELEASERATE); - voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, - NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, rr * 16); - voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_STATE, - NV_PAVS_VOICE_PAR_STATE_EFCUR, - NV_PAVS_VOICE_PAR_STATE_EFCUR_RELEASE); - - if (!locked) { - voice_lock(d, selected_handle, false); - } - - break; - } - case NV1BA0_PIO_VOICE_OFF: - voice_off(d, argument & NV1BA0_PIO_VOICE_OFF_HANDLE); - break; - case NV1BA0_PIO_VOICE_PAUSE: - voice_set_mask(d, argument & NV1BA0_PIO_VOICE_PAUSE_HANDLE, - NV_PAVS_VOICE_PAR_STATE, NV_PAVS_VOICE_PAR_STATE_PAUSED, - (argument & NV1BA0_PIO_VOICE_PAUSE_ACTION) != 0); - break; - case NV1BA0_PIO_SET_CURRENT_HRTF_ENTRY: { - int handle = GET_MASK(argument, NV1BA0_PIO_SET_CURRENT_HRTF_ENTRY_HANDLE); - d->vp.hrtf.current_entry = handle; - break; - } - case NV1BA0_PIO_SET_CURRENT_VOICE: - d->regs[NV_PAPU_FECV] = argument; - break; - case NV1BA0_PIO_SET_VOICE_CFG_VBIN: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_VBIN, - 0xFFFFFFFF, argument); - break; - case NV1BA0_PIO_SET_VOICE_CFG_FMT: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_FMT, - 0xFFFFFFFF, argument); - break; - case NV1BA0_PIO_SET_VOICE_CFG_ENV0: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_ENV0, - 0xFFFFFFFF, argument); - break; - case NV1BA0_PIO_SET_VOICE_CFG_ENVA: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_ENVA, - 0xFFFFFFFF, argument); - break; - case NV1BA0_PIO_SET_VOICE_CFG_ENV1: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_ENV1, - 0xFFFFFFFF, argument); - break; - case NV1BA0_PIO_SET_VOICE_CFG_ENVF: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_ENVF, - 0xFFFFFFFF, argument); - break; - case NV1BA0_PIO_SET_VOICE_CFG_MISC: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_MISC, - 0xFFFFFFFF, argument); - break; - case NV1BA0_PIO_SET_VOICE_TAR_HRTF: { - int handle = GET_MASK(argument, NV1BA0_PIO_SET_VOICE_TAR_HRTF_HANDLE); - int current_voice = d->regs[NV_PAPU_FECV]; - voice_set_mask(d, current_voice, NV_PAVS_VOICE_CFG_HRTF_TARGET, - NV_PAVS_VOICE_CFG_HRTF_TARGET_HANDLE, handle); - if (current_voice < MCPX_HW_MAX_3D_VOICES && - handle != HRTF_NULL_HANDLE) { - // FIXME: Xbox software seems to reliably set voice HRTF handles - // after updating filter parameters, however it may be possible to - // update parameter targets for an active voice. - assert(handle < HRTF_ENTRY_COUNT); - hrtf_filter_set_target_params(&d->vp.filters[current_voice].hrtf, - d->vp.hrtf.entries[handle].hrir, - d->vp.hrtf.entries[handle].itd); - } - break; - } - case NV1BA0_PIO_SET_VOICE_TAR_VOLA: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_VOLA, - 0xFFFFFFFF, argument); - break; - case NV1BA0_PIO_SET_VOICE_TAR_VOLB: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_VOLB, - 0xFFFFFFFF, argument); - break; - case NV1BA0_PIO_SET_VOICE_TAR_VOLC: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_VOLC, - 0xFFFFFFFF, argument); - break; - case NV1BA0_PIO_SET_VOICE_LFO_ENV: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_LFO_ENV, - 0xFFFFFFFF, argument); - break; - case NV1BA0_PIO_SET_VOICE_TAR_FCA: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_FCA, - 0xFFFFFFFF, argument); - break; - case NV1BA0_PIO_SET_VOICE_TAR_FCB: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_FCB, - 0xFFFFFFFF, argument); - break; - case NV1BA0_PIO_SET_VOICE_TAR_PITCH: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_PITCH_LINK, - NV_PAVS_VOICE_TAR_PITCH_LINK_PITCH, - (argument & NV1BA0_PIO_SET_VOICE_TAR_PITCH_STEP) >> 16); - break; - case NV1BA0_PIO_SET_VOICE_CFG_BUF_BASE: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CUR_PSL_START, - NV_PAVS_VOICE_CUR_PSL_START_BA, argument); - break; - case NV1BA0_PIO_SET_VOICE_CFG_BUF_LBO: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CUR_PSH_SAMPLE, - NV_PAVS_VOICE_CUR_PSH_SAMPLE_LBO, argument); - break; - case NV1BA0_PIO_SET_VOICE_BUF_CBO: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_PAR_OFFSET, - NV_PAVS_VOICE_PAR_OFFSET_CBO, argument); - break; - case NV1BA0_PIO_SET_VOICE_CFG_BUF_EBO: - voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_PAR_NEXT, - NV_PAVS_VOICE_PAR_NEXT_EBO, argument); - break; - case NV1BA0_PIO_SET_HRIR ... NV1BA0_PIO_SET_HRIR_X - 1: { - assert(d->vp.hrtf.current_entry < HRTF_ENTRY_COUNT); - slot = (method - NV1BA0_PIO_SET_HRIR) / 4; - int8_t left0 = GET_MASK(argument, NV1BA0_PIO_SET_HRIR_LEFT0); - int8_t right0 = GET_MASK(argument, NV1BA0_PIO_SET_HRIR_RIGHT0); - int8_t left1 = GET_MASK(argument, NV1BA0_PIO_SET_HRIR_LEFT1); - int8_t right1 = GET_MASK(argument, NV1BA0_PIO_SET_HRIR_RIGHT1); - int coeff_idx = slot * 2; - set_hrir_coeff_tar(d, 0, coeff_idx, left0); - set_hrir_coeff_tar(d, 1, coeff_idx, right0); - coeff_idx += 1; - set_hrir_coeff_tar(d, 0, coeff_idx, left1); - set_hrir_coeff_tar(d, 1, coeff_idx, right1); - break; - } - case NV1BA0_PIO_SET_HRIR_X: { - assert(d->vp.hrtf.current_entry < HRTF_ENTRY_COUNT); - int8_t left30 = GET_MASK(argument, NV1BA0_PIO_SET_HRIR_X_LEFT30); - int8_t right30 = GET_MASK(argument, NV1BA0_PIO_SET_HRIR_X_RIGHT30); - int16_t itd = GET_MASK(argument, NV1BA0_PIO_SET_HRIR_X_ITD); - set_hrir_coeff_tar(d, 0, 30, left30); - set_hrir_coeff_tar(d, 1, 30, right30); - d->vp.hrtf.entries[d->vp.hrtf.current_entry].itd = s6p9_to_float(itd); - break; - } - case NV1BA0_PIO_SET_CURRENT_INBUF_SGE: - d->inbuf_sge_handle = argument & NV1BA0_PIO_SET_CURRENT_INBUF_SGE_HANDLE; - break; - case NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET: { - // FIXME: Is there an upper limit for the SGE table size? - // FIXME: NV_PAPU_VPSGEADDR is probably bad, as outbuf SGE use the same - // handle range (or that is also wrong) - hwaddr sge_address = - d->regs[NV_PAPU_VPSGEADDR] + d->inbuf_sge_handle * 8; - stl_le_phys(&address_space_memory, sge_address, - argument & - NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET_PARAMETER); - DPRINTF("Wrote inbuf SGE[0x%X] = 0x%08X\n", d->inbuf_sge_handle, - argument & NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET_PARAMETER); - break; - } - CASE_4(NV1BA0_PIO_SET_OUTBUF_BA, 8): // 8 byte pitch, 4 entries -#ifdef DEBUG_MCPX - slot = (method - NV1BA0_PIO_SET_OUTBUF_BA) / 8; - //FIXME: Use NV1BA0_PIO_SET_OUTBUF_BA_ADDRESS = 0x007FFF00 ? - DPRINTF("outbuf_ba[%d]: 0x%08X\n", slot, argument); -#endif - //assert(false); //FIXME: Enable assert! no idea what this reg does - break; - CASE_4(NV1BA0_PIO_SET_OUTBUF_LEN, 8): // 8 byte pitch, 4 entries -#ifdef DEBUG_MCPX - slot = (method - NV1BA0_PIO_SET_OUTBUF_LEN) / 8; - //FIXME: Use NV1BA0_PIO_SET_OUTBUF_LEN_VALUE = 0x007FFF00 ? - DPRINTF("outbuf_len[%d]: 0x%08X\n", slot, argument); -#endif - //assert(false); //FIXME: Enable assert! no idea what this reg does - break; - case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE: - d->outbuf_sge_handle = - argument & NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_HANDLE; - break; - case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET: { - // FIXME: Is there an upper limit for the SGE table size? - // FIXME: NV_PAPU_VPSGEADDR is probably bad, as inbuf SGE use the same - // handle range (or that is also wrong) - // NV_PAPU_EPFADDR EP outbufs - // NV_PAPU_GPFADDR GP outbufs - // But how does it know which outbuf is being written?! - hwaddr sge_address = - d->regs[NV_PAPU_VPSGEADDR] + d->outbuf_sge_handle * 8; - stl_le_phys(&address_space_memory, sge_address, - argument & - NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET_PARAMETER); - DPRINTF("Wrote outbuf SGE[0x%X] = 0x%08X\n", d->outbuf_sge_handle, - argument & NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET_PARAMETER); - break; - } - case NV1BA0_PIO_SET_VOICE_SSL_A: { - int ssl = 0; - int current_voice = d->regs[NV_PAPU_FECV]; - assert(current_voice < MCPX_HW_MAX_VOICES); - d->vp.ssl[current_voice].base[ssl] = - GET_MASK(argument, NV1BA0_PIO_SET_VOICE_SSL_A_BASE); - d->vp.ssl[current_voice].count[ssl] = - GET_MASK(argument, NV1BA0_PIO_SET_VOICE_SSL_A_COUNT); - // d->vp.ssl[current_voice].ssl_index = 0; - DPRINTF("SSL%c Base = %x, Count = %d\n", 'A' + ssl, - d->vp.ssl[current_voice].base[ssl], - d->vp.ssl[current_voice].count[ssl]); - break; - } - // FIXME: Refactor into above - case NV1BA0_PIO_SET_VOICE_SSL_B: { - int ssl = 1; - int current_voice = d->regs[NV_PAPU_FECV]; - assert(current_voice < MCPX_HW_MAX_VOICES); - d->vp.ssl[current_voice].base[ssl] = - GET_MASK(argument, NV1BA0_PIO_SET_VOICE_SSL_A_BASE); - d->vp.ssl[current_voice].count[ssl] = - GET_MASK(argument, NV1BA0_PIO_SET_VOICE_SSL_A_COUNT); - // d->vp.ssl[current_voice].ssl_index = 0; - DPRINTF("SSL%c Base = %x, Count = %d\n", 'A' + ssl, - d->vp.ssl[current_voice].base[ssl], - d->vp.ssl[current_voice].count[ssl]); - break; - } - case NV1BA0_PIO_SET_CURRENT_SSL: { - assert((argument & 0x3f) == 0); - assert(argument < (MCPX_HW_MAX_SSL_PRDS*NV_PSGE_SIZE)); - d->vp.ssl_base_page = argument; - break; - } - case NV1BA0_PIO_SET_SSL_SEGMENT_OFFSET ... - NV1BA0_PIO_SET_SSL_SEGMENT_LENGTH+8*64-1: { - // 64 offset/base pairs relative to segment base - // FIXME: Entries are 64b, assuming they are stored - // like this <[offset,length],...> - assert((method & 0x3) == 0); - hwaddr addr = d->regs[NV_PAPU_VPSSLADDR] - + (d->vp.ssl_base_page * 8) - + (method - NV1BA0_PIO_SET_SSL_SEGMENT_OFFSET); - stl_le_phys(&address_space_memory, addr, argument); - DPRINTF(" ssl_segment[%x + %x].%s = %x\n", - d->vp.ssl_base_page, - (method - NV1BA0_PIO_SET_SSL_SEGMENT_OFFSET)/8, - method & 4 ? "length" : "offset", - argument); - break; - } - case NV1BA0_PIO_SET_HRTF_SUBMIXES: - d->vp.hrtf_submix[0] = (argument >> 0) & 0x1f; - d->vp.hrtf_submix[1] = (argument >> 8) & 0x1f; - d->vp.hrtf_submix[2] = (argument >> 16) & 0x1f; - d->vp.hrtf_submix[3] = (argument >> 24) & 0x1f; - break; - case NV1BA0_PIO_SET_HRTF_HEADROOM: - d->vp.hrtf_headroom = argument & NV1BA0_PIO_SET_HRTF_HEADROOM_AMOUNT; - break; - case NV1BA0_PIO_SET_SUBMIX_HEADROOM ... - NV1BA0_PIO_SET_SUBMIX_HEADROOM+4*(NUM_MIXBINS-1): - assert((method & 3) == 0); - slot = (method-NV1BA0_PIO_SET_SUBMIX_HEADROOM)/4; - d->vp.submix_headroom[slot] = - argument & NV1BA0_PIO_SET_SUBMIX_HEADROOM_AMOUNT; - break; - case SE2FE_IDLE_VOICE: - if (d->regs[NV_PAPU_FETFORCE1] & NV_PAPU_FETFORCE1_SE2FE_IDLE_VOICE) { - d->regs[NV_PAPU_FECTL] &= ~NV_PAPU_FECTL_FEMETHMODE; - d->regs[NV_PAPU_FECTL] |= NV_PAPU_FECTL_FEMETHMODE_TRAPPED; - d->regs[NV_PAPU_FECTL] &= ~NV_PAPU_FECTL_FETRAPREASON; - d->regs[NV_PAPU_FECTL] |= NV_PAPU_FECTL_FETRAPREASON_REQUESTED; - DPRINTF("idle voice %d\n", argument); - d->set_irq = true; - } else { - assert(false); - } - break; - default: - assert(false); - break; - } -} - -static uint64_t vp_read(void *opaque, hwaddr addr, unsigned int size) -{ - DPRINTF("mcpx apu VP: read [0x%" HWADDR_PRIx "] (%s)\n", addr, - get_method_str(addr)); - - switch (addr) { - case NV1BA0_PIO_FREE: - /* we don't simulate the queue for now, - * pretend to always be empty */ - return 0x80; - default: - break; - } - - return 0; -} - -static void vp_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size) -{ - MCPXAPUState *d = opaque; - - DPRINTF("mcpx apu VP: [0x%" HWADDR_PRIx "] %s = 0x%lx\n", addr, - get_method_str(addr), val); - - switch (addr) { - case NV1BA0_PIO_SET_ANTECEDENT_VOICE: - case NV1BA0_PIO_VOICE_LOCK: - case NV1BA0_PIO_VOICE_ON: - case NV1BA0_PIO_VOICE_RELEASE: - case NV1BA0_PIO_VOICE_OFF: - case NV1BA0_PIO_VOICE_PAUSE: - case NV1BA0_PIO_SET_CURRENT_HRTF_ENTRY: - case NV1BA0_PIO_SET_CURRENT_VOICE: - case NV1BA0_PIO_SET_VOICE_CFG_VBIN: - case NV1BA0_PIO_SET_VOICE_CFG_FMT: - case NV1BA0_PIO_SET_VOICE_CFG_ENV0: - case NV1BA0_PIO_SET_VOICE_CFG_ENVA: - case NV1BA0_PIO_SET_VOICE_CFG_ENV1: - case NV1BA0_PIO_SET_VOICE_CFG_ENVF: - case NV1BA0_PIO_SET_VOICE_CFG_MISC: - case NV1BA0_PIO_SET_VOICE_TAR_HRTF: - case NV1BA0_PIO_SET_VOICE_TAR_VOLA: - case NV1BA0_PIO_SET_VOICE_TAR_VOLB: - case NV1BA0_PIO_SET_VOICE_TAR_VOLC: - case NV1BA0_PIO_SET_VOICE_LFO_ENV: - case NV1BA0_PIO_SET_VOICE_TAR_FCA: - case NV1BA0_PIO_SET_VOICE_TAR_FCB: - case NV1BA0_PIO_SET_VOICE_TAR_PITCH: - case NV1BA0_PIO_SET_VOICE_CFG_BUF_BASE: - case NV1BA0_PIO_SET_VOICE_CFG_BUF_LBO: - case NV1BA0_PIO_SET_VOICE_BUF_CBO: - case NV1BA0_PIO_SET_VOICE_CFG_BUF_EBO: - case NV1BA0_PIO_SET_HRIR ... NV1BA0_PIO_SET_HRIR_X - 1: - case NV1BA0_PIO_SET_HRIR_X: - case NV1BA0_PIO_SET_CURRENT_INBUF_SGE: - case NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET: - CASE_4(NV1BA0_PIO_SET_OUTBUF_BA, 8): // 8 byte pitch, 4 entries - CASE_4(NV1BA0_PIO_SET_OUTBUF_LEN, 8): // 8 byte pitch, 4 entries - case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE: - case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET: - case NV1BA0_PIO_SET_CURRENT_SSL: - case NV1BA0_PIO_SET_SSL_SEGMENT_OFFSET ... - NV1BA0_PIO_SET_SSL_SEGMENT_LENGTH+8*64-1: - case NV1BA0_PIO_SET_VOICE_SSL_A: - case NV1BA0_PIO_SET_VOICE_SSL_B: - case NV1BA0_PIO_SET_HRTF_SUBMIXES: - case NV1BA0_PIO_SET_HRTF_HEADROOM: - case NV1BA0_PIO_SET_SUBMIX_HEADROOM ... - NV1BA0_PIO_SET_SUBMIX_HEADROOM+4*(NUM_MIXBINS-1): - /* TODO: these should instead be queueing up fe commands */ - fe_method(d, addr, val); - break; - - case NV1BA0_PIO_GET_VOICE_POSITION: - case NV1BA0_PIO_SET_CONTEXT_DMA_NOTIFY: - case NV1BA0_PIO_SET_CURRENT_SSL_CONTEXT_DMA: - DPRINTF("unhandled method: %" HWADDR_PRIx " = %" HWADDR_PRIx "\n", addr, - val); - assert(0); - default: - break; - } -} - -static const MemoryRegionOps vp_ops = { - .read = vp_read, - .write = vp_write, -}; - static void scatter_gather_rw(MCPXAPUState *d, hwaddr sge_base, unsigned int max_sge, uint8_t *ptr, uint32_t addr, size_t len, bool dir) @@ -1196,1168 +557,6 @@ static const MemoryRegionOps ep_ops = { .write = ep_write, }; -static hwaddr get_data_ptr(hwaddr sge_base, unsigned int max_sge, uint32_t addr) -{ - unsigned int entry = addr / TARGET_PAGE_SIZE; - assert(entry <= max_sge); - uint32_t prd_address = - ldl_le_phys(&address_space_memory, sge_base + entry * 4 * 2); - // uint32_t prd_control = - // ldl_le_phys(&address_space_memory, sge_base + entry * 4 * 2 + 4); - DPRINTF("Addr: 0x%08X, control: 0x%08X\n", prd_address, prd_control); - return prd_address + addr % TARGET_PAGE_SIZE; -} - -static float voice_step_envelope(MCPXAPUState *d, uint16_t v, uint32_t reg_0, - uint32_t reg_a, uint32_t rr_reg, uint32_t rr_mask, - uint32_t lvl_reg, uint32_t lvl_mask, - uint32_t count_mask, uint32_t cur_mask) -{ - uint8_t cur = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask); - switch (cur) { - case NV_PAVS_VOICE_PAR_STATE_EFCUR_OFF: - voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, 0); - voice_set_mask(d, v, lvl_reg, lvl_mask, 0xFF); - return 1.0f; - case NV_PAVS_VOICE_PAR_STATE_EFCUR_DELAY: { - uint16_t count = - voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask); - voice_set_mask(d, v, lvl_reg, lvl_mask, 0x00); // FIXME: Confirm this? - - if (count == 0) { - cur++; - voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur); - count = 0; - } else { - count--; - } - voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count); - return 0.0f; - } - case NV_PAVS_VOICE_PAR_STATE_EFCUR_ATTACK: { - uint16_t count = - voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask); - uint16_t attack_rate = - voice_get_mask(d, v, reg_0, NV_PAVS_VOICE_CFG_ENV0_EA_ATTACKRATE); - - float value; - if (attack_rate == 0) { - // FIXME: [division by zero] - // Got crackling sound in hardware for amplitude env. - value = 255.0f; - } else { - if (count <= (attack_rate * 16)) { - value = (count * 0xFF) / (attack_rate * 16); - } else { - // FIXME: Overflow in hardware - // The actual value seems to overflow, but not sure how - value = 255.0f; - } - } - voice_set_mask(d, v, lvl_reg, lvl_mask, value); - // FIXME: Comparison could also be the other way around?! Test please. - if (count == (attack_rate * 16)) { - cur++; - voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur); - uint16_t hold_time = - voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME); - count = hold_time * 16; // FIXME: Skip next phase if count is 0? - // [other instances too] - } else { - count++; - } - voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count); - return value / 255.0f; - } - case NV_PAVS_VOICE_PAR_STATE_EFCUR_HOLD: { - uint16_t count = - voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask); - voice_set_mask(d, v, lvl_reg, lvl_mask, 0xFF); - - if (count == 0) { - cur++; - voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur); - uint16_t decay_rate = voice_get_mask( - d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_DECAYRATE); - count = decay_rate * 16; - } else { - count--; - } - voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count); - return 1.0f; - } - case NV_PAVS_VOICE_PAR_STATE_EFCUR_DECAY: { - uint16_t count = - voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask); - uint16_t decay_rate = - voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_DECAYRATE); - uint8_t sustain_level = - voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_SUSTAINLEVEL); - - // FIXME: Decay should return a value no less than sustain - float value; - if (decay_rate == 0) { - value = 0.0f; - } else { - // FIXME: This formula and threshold is not accurate, but I can't - // get it any better for now - value = 255.0f * powf(0.99988799f, (decay_rate * 16 - count) * - 4096 / decay_rate); - } - if (value <= (sustain_level + 0.2f) || (value > 255.0f)) { - // FIXME: Should we still update lvl? - cur++; - voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur); - } else { - count--; - voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count); - voice_set_mask(d, v, lvl_reg, lvl_mask, value); - } - return value / 255.0f; - } - case NV_PAVS_VOICE_PAR_STATE_EFCUR_SUSTAIN: { - uint8_t sustain_level = - voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_SUSTAINLEVEL); - voice_set_mask( - d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, - 0x00); // FIXME: is this only set to 0 once or forced to zero? - voice_set_mask(d, v, lvl_reg, lvl_mask, sustain_level); - return sustain_level / 255.0f; - } - case NV_PAVS_VOICE_PAR_STATE_EFCUR_RELEASE: { - uint16_t count = - voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask); - uint16_t release_rate = voice_get_mask(d, v, rr_reg, rr_mask); - - if (release_rate == 0) { - count = 0; - } - - float value = 0; - if (count == 0) { - voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, ++cur); - } else { - // FIXME: Appears to be an exponential but unsure about actual - // curve; performing standard decay of current level to T60 over the - // release interval which seems about right. - // FIXME: Based on sustain level or just decay of current level? - // FIXME: Update level? A very similar, alternative decay function - // (probably what the hw actually does): y(t)=2^(-10t), which would - // permit simpler attenuation more efficiently and update level on - // each round. - float pos = clampf(1 - count / (release_rate * 16.0), 0, 1); - uint8_t lvl = voice_get_mask(d, v, lvl_reg, lvl_mask); - value = powf(M_E, -6.91*pos)*lvl; - count--; // FIXME: Should release count ascend or descend? - voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count); - } - - return value / 255.0f; - } - case NV_PAVS_VOICE_PAR_STATE_EFCUR_FORCE_RELEASE: - if (count_mask == NV_PAVS_VOICE_CUR_ECNT_EACOUNT) { - voice_off(d, v); - } - return 0.0f; - default: - fprintf(stderr, "Unknown envelope state 0x%x\n", cur); - assert(false); - return 0.0f; - } -} - -static void set_notify_status(MCPXAPUState *d, uint32_t v, int notifier, - int status) -{ - hwaddr notify_offset = d->regs[NV_PAPU_FENADDR]; - notify_offset += 16 * (MCPX_HW_NOTIFIER_BASE_OFFSET + - v * MCPX_HW_NOTIFIER_COUNT + notifier); - notify_offset += 15; // Final byte is status, same for all notifiers - - // FIXME: Check notify enable - // FIXME: Set NV1BA0_NOTIFICATION_STATUS_IN_PROGRESS when appropriate - stb_phys(&address_space_memory, notify_offset, status); - - // FIXME: Refactor this out of here - // FIXME: Actually provied current envelope state - stb_phys(&address_space_memory, notify_offset - 1, 1); - - qatomic_or(&d->regs[NV_PAPU_ISTS], - NV_PAPU_ISTS_FEVINTSTS | NV_PAPU_ISTS_FENINTSTS); - d->set_irq = true; -} - -static long voice_resample_callback(void *cb_data, float **data) -{ - MCPXAPUVoiceFilter *filter = cb_data; - uint16_t v = filter->voice; - assert(v < MCPX_HW_MAX_VOICES); - MCPXAPUState *d = container_of(filter, MCPXAPUState, vp.filters[v]); - - int sample_count = 0; - while (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) { - break; - } - int count = voice_get_samples( - d, v, (float(*)[2]) & filter->resample_buf[2 * sample_count], - NUM_SAMPLES_PER_FRAME - sample_count); - if (count < 0) { - break; - } - sample_count += count; - } - - if (sample_count < NUM_SAMPLES_PER_FRAME) { - /* Starvation causes SRC hang on repeated calls. Provide silence. */ - memset(&filter->resample_buf[2*sample_count], 0, - 2*(NUM_SAMPLES_PER_FRAME-sample_count)*sizeof(float)); - sample_count = NUM_SAMPLES_PER_FRAME; - } - - *data = filter->resample_buf; - return sample_count; -} - -static int voice_resample(MCPXAPUState *d, uint16_t v, float samples[][2], - int requested_num, float rate) -{ - assert(v < MCPX_HW_MAX_VOICES); - MCPXAPUVoiceFilter *filter = &d->vp.filters[v]; - - if (filter->resampler == NULL) { - filter->voice = v; - int err; - - /* Note: Using a sinc based resampler for quality. Unsure about - * hardware's actual interpolation method; it could just be linear, in - * which case using this resampler is overkill, but quality is good - * so use it for now. - */ - // FIXME: Don't do 2ch resampling if this is a mono voice - filter->resampler = src_callback_new(&voice_resample_callback, - SRC_SINC_FASTEST, 2, &err, filter); - if (filter->resampler == NULL) { - fprintf(stderr, "src error: %s\n", src_strerror(err)); - assert(0); - } - } - - int count = src_callback_read(filter->resampler, rate, requested_num, - (float *)samples); - if (count == -1) { - DPRINTF("resample error\n"); - } - if (count != requested_num) { - DPRINTF("resample returned fewer than expected: %d\n", count); - - if (count == 0) - return -1; - } - - return count; -} - -static void voice_reset_filters(MCPXAPUState *d, uint16_t v) -{ - assert(v < MCPX_HW_MAX_VOICES); - memset(&d->vp.filters[v].svf, 0, sizeof(d->vp.filters[v].svf)); - if (d->vp.filters[v].resampler) { - src_reset(d->vp.filters[v].resampler); - } -} - -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 get_voice_bin_src_dst(MCPXAPUState *d, int v, - uint32_t *src, uint32_t *dst, uint32_t *clr) -{ - uint32_t src_v = 0; - uint32_t dst_v = 0; - uint32_t clr_v = 0; - - bool multipass = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, - NV_PAVS_VOICE_CFG_FMT_MULTIPASS); - if (multipass) { - int mp_bin = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, - NV_PAVS_VOICE_CFG_FMT_MULTIPASS_BIN); - bool clear_mix = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, - NV_PAVS_VOICE_CFG_FMT_CLEAR_MIX); - src_v |= (1 << mp_bin); - if (clear_mix) { - clr_v |= (1 << mp_bin); - } - } - - int bin[8]; - if (v < MCPX_HW_MAX_3D_VOICES) { - bin[0] = d->vp.hrtf_submix[0]; - bin[1] = d->vp.hrtf_submix[1]; - bin[2] = d->vp.hrtf_submix[2]; - bin[3] = d->vp.hrtf_submix[3]; - } else { - bin[0] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, - NV_PAVS_VOICE_CFG_VBIN_V0BIN); - bin[1] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, - NV_PAVS_VOICE_CFG_VBIN_V1BIN); - bin[2] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, - NV_PAVS_VOICE_CFG_VBIN_V2BIN); - bin[3] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, - NV_PAVS_VOICE_CFG_VBIN_V3BIN); - } - bin[4] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, - NV_PAVS_VOICE_CFG_VBIN_V4BIN); - bin[5] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, - NV_PAVS_VOICE_CFG_VBIN_V5BIN); - bin[6] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, - NV_PAVS_VOICE_CFG_FMT_V6BIN); - bin[7] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, - NV_PAVS_VOICE_CFG_FMT_V7BIN); - - for (int i = 0; i < 8; i++) { - dst_v |= 1 << bin[i]; - } - - if (src) { - *src = src_v; - } - if (dst) { - *dst = dst_v; - } - if (clr) { - *clr = clr_v; - } -} - -static void *voice_worker_thread(void *arg) -{ - MCPXAPUState *d = arg; - VoiceWorkDispatch *vwd = &d->vp.voice_work_dispatch; - - rcu_register_thread(); - qemu_mutex_lock(&vwd->lock); - - int worker_id = ctz64(vwd->workers_pending); - VoiceWorker *self = &d->vp.voice_work_dispatch.workers[worker_id]; - self->queue_len = 0; - - do { - int64_t start_time = qemu_clock_get_us(QEMU_CLOCK_REALTIME); - g_dbg.vp.workers[worker_id].num_voices = self->queue_len; - - if (self->queue_len) { - qemu_mutex_unlock(&vwd->lock); - - // Process queued voices - memset(self->mixbins, 0, sizeof(self->mixbins)); - if (d->mon == MCPX_APU_DEBUG_MON_VP) { - memset(self->sample_buf, 0, sizeof(self->sample_buf)); - } - for (int i = 0; i < self->queue_len; i++) { - voice_process(d, self->mixbins, self->sample_buf, - self->queue[i].voice, self->queue[i].list); - } - - qemu_mutex_lock(&vwd->lock); - - // Add voice contributions - for (int b = 0; b < NUM_MIXBINS; b++) { - for (int s = 0; s < NUM_SAMPLES_PER_FRAME; s++) { - vwd->mixbins[b][s] += self->mixbins[b][s]; - } - } - if (d->mon == MCPX_APU_DEBUG_MON_VP) { - for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) { - d->vp.sample_buf[i][0] += self->sample_buf[i][0]; - d->vp.sample_buf[i][1] += self->sample_buf[i][1]; - } - } - - self->queue_len = 0; - } - - vwd->workers_pending &= ~(1 << worker_id); - if (!vwd->workers_pending) { - qemu_cond_signal(&vwd->work_finished); - } - - int64_t end_time = qemu_clock_get_us(QEMU_CLOCK_REALTIME); - g_dbg.vp.workers[worker_id].time_us = end_time - start_time; - - qemu_cond_wait(&vwd->work_pending, &vwd->lock); - } while (!vwd->workers_should_exit); - - rcu_unregister_thread(); - return NULL; -} - -static void voice_work_acquire_voice_lock_for_processing(MCPXAPUState *d, int v) -{ - qemu_spin_lock(&d->vp.voice_spinlocks[v]); - while (is_voice_locked(d, v)) { - /* Stall until voice is available */ - qemu_spin_unlock(&d->vp.voice_spinlocks[v]); - qemu_cond_wait(&d->cond, &d->lock); - qemu_spin_lock(&d->vp.voice_spinlocks[v]); - } -} - -static void voice_work_enqueue(MCPXAPUState *d, int v, int list) -{ - VoiceWorkDispatch *vwd = &d->vp.voice_work_dispatch; - - assert(vwd->queue_len < ARRAY_SIZE(vwd->queue)); - vwd->queue[vwd->queue_len++] = (VoiceWorkItem){ - .voice = v, - .list = list, - }; - - voice_work_acquire_voice_lock_for_processing(d, v); -} - -static void voice_work_release_voice_locks(MCPXAPUState *d) -{ - VoiceWorkDispatch *vwd = &d->vp.voice_work_dispatch; - - for (int i = 0; i < vwd->queue_len; i++) { - qemu_spin_unlock(&d->vp.voice_spinlocks[vwd->queue[i].voice]); - } -} - -static void voice_work_schedule(MCPXAPUState *d) -{ - VoiceWorkDispatch *vwd = &d->vp.voice_work_dispatch; - int next_worker_to_schedule = 0; - bool group = false; - uint32_t dirty = 0; - - for (int i = 0; i < vwd->queue_len; i++) { - uint32_t src, dst, clr; - get_voice_bin_src_dst(d, vwd->queue[i].voice, &src, &dst, &clr); - - // TODO: To simplify submix scheduling, we make a few assumptions based - // on Xbox software observations. However, the configurability of - // multipass sources suggests the hardware may not be so strict. We'll - // defer making this more robust for now. - // - // We currently assume that: - // - // - MP bin is constant - assert(!src || (src == MULTIPASS_BIN_MASK)); - // - // - MP voice always clears MP bin - assert(!src || (clr == MULTIPASS_BIN_MASK)); - // - // - MP source voices are ordered consecutively in voice lists - assert(src || (dst & MULTIPASS_BIN_MASK) || - !(dirty & MULTIPASS_BIN_MASK)); - - if ((dst & MULTIPASS_BIN_MASK) & ~dirty) { - group = true; - } - - // Assign voice to worker - VoiceWorker *worker = &vwd->workers[next_worker_to_schedule]; - worker->queue[worker->queue_len++] = vwd->queue[i]; - vwd->workers_pending |= 1 << next_worker_to_schedule; - - dirty = (dirty & ~clr) | dst; - if (clr & MULTIPASS_BIN_MASK) { - group = false; - } - - if (!group) { - next_worker_to_schedule = - (next_worker_to_schedule + 1) % NUM_VOICE_WORKERS; - } - } -} - -static void -voice_work_dispatch(MCPXAPUState *d, - float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME]) -{ - VoiceWorkDispatch *vwd = &d->vp.voice_work_dispatch; - - int64_t start_time = qemu_clock_get_us(QEMU_CLOCK_REALTIME); - - qemu_mutex_lock(&vwd->lock); - - if (vwd->queue_len) { - memset(vwd->mixbins, 0, sizeof(vwd->mixbins)); - - // Signal workers and wait for completion - voice_work_schedule(d); - qemu_cond_broadcast(&vwd->work_pending); - qemu_cond_wait(&vwd->work_finished, &vwd->lock); - assert(!vwd->workers_pending); - voice_work_release_voice_locks(d); - vwd->queue_len = 0; - - // Add voice contributions - for (int b = 0; b < NUM_MIXBINS; b++) { - for (int s = 0; s < NUM_SAMPLES_PER_FRAME; s++) { - mixbins[b][s] += vwd->mixbins[b][s]; - } - } - } - - int64_t end_time = qemu_clock_get_us(QEMU_CLOCK_REALTIME); - g_dbg.vp.total_worker_time_us = end_time - start_time; - - qemu_mutex_unlock(&vwd->lock); -} - -static void voice_work_init(MCPXAPUState *d) -{ - VoiceWorkDispatch *vwd = &d->vp.voice_work_dispatch; - - vwd->workers_should_exit = false; - vwd->workers_pending = 0; - vwd->queue_len = 0; - - qemu_mutex_init(&vwd->lock); - qemu_mutex_lock(&vwd->lock); - qemu_cond_init(&vwd->work_pending); - qemu_cond_init(&vwd->work_finished); - for (int i = 0; i < NUM_VOICE_WORKERS; i++) { - vwd->workers_pending |= 1 << i; - qemu_thread_create(&vwd->workers[i].thread, "mcpx.voice_worker", - voice_worker_thread, d, QEMU_THREAD_JOINABLE); - } - qemu_cond_wait(&vwd->work_finished, &vwd->lock); - assert(!vwd->workers_pending); - qemu_mutex_unlock(&vwd->lock); -} - -static void voice_work_finalize(MCPXAPUState *d) -{ - VoiceWorkDispatch *vwd = &d->vp.voice_work_dispatch; - - qemu_mutex_lock(&vwd->lock); - vwd->workers_should_exit = true; - qemu_cond_broadcast(&vwd->work_pending); - qemu_mutex_unlock(&vwd->lock); - for (int i = 0; i < NUM_VOICE_WORKERS; i++) { - qemu_thread_join(&vwd->workers[i].thread); - } -} - -static void voice_process(MCPXAPUState *d, - float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME], - float sample_buf[NUM_SAMPLES_PER_FRAME][2], - uint16_t v, int voice_list) -{ - assert(v < MCPX_HW_MAX_VOICES); - bool stereo = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, - NV_PAVS_VOICE_CFG_FMT_STEREO); - unsigned int channels = stereo ? 2 : 1; - bool paused = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE, - NV_PAVS_VOICE_PAR_STATE_PAUSED); - - struct McpxApuDebugVoice *dbg = &g_dbg.vp.v[v]; - dbg->active = true; - dbg->stereo = stereo; - dbg->paused = paused; - - if (paused) { - return; - } - - float ef_value = voice_step_envelope( - d, v, NV_PAVS_VOICE_CFG_ENV1, NV_PAVS_VOICE_CFG_ENVF, - NV_PAVS_VOICE_CFG_MISC, NV_PAVS_VOICE_CFG_MISC_EF_RELEASERATE, - NV_PAVS_VOICE_PAR_NEXT, NV_PAVS_VOICE_PAR_NEXT_EFLVL, - NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, NV_PAVS_VOICE_PAR_STATE_EFCUR); - assert(ef_value >= 0.0f); - assert(ef_value <= 1.0f); - int16_t p = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_PITCH_LINK, - NV_PAVS_VOICE_TAR_PITCH_LINK_PITCH); - int8_t ps = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_ENV0, - NV_PAVS_VOICE_CFG_ENV0_EF_PITCHSCALE); - float rate = 1.0 / powf(2.0f, (p + ps * 32 * ef_value) / 4096.0f); - dbg->rate = rate; - - float ea_value = voice_step_envelope( - d, v, NV_PAVS_VOICE_CFG_ENV0, NV_PAVS_VOICE_CFG_ENVA, - NV_PAVS_VOICE_TAR_LFO_ENV, NV_PAVS_VOICE_TAR_LFO_ENV_EA_RELEASERATE, - NV_PAVS_VOICE_PAR_OFFSET, NV_PAVS_VOICE_PAR_OFFSET_EALVL, - NV_PAVS_VOICE_CUR_ECNT_EACOUNT, NV_PAVS_VOICE_PAR_STATE_EACUR); - assert(ea_value >= 0.0f); - assert(ea_value <= 1.0f); - - float samples[NUM_SAMPLES_PER_FRAME][2] = { 0 }; - - 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 active = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE, - NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE); - if (!active) { - return; - } - - int bin[8]; - bin[0] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, - NV_PAVS_VOICE_CFG_VBIN_V0BIN); - bin[1] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, - NV_PAVS_VOICE_CFG_VBIN_V1BIN); - bin[2] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, - NV_PAVS_VOICE_CFG_VBIN_V2BIN); - bin[3] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, - NV_PAVS_VOICE_CFG_VBIN_V3BIN); - bin[4] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, - NV_PAVS_VOICE_CFG_VBIN_V4BIN); - bin[5] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, - NV_PAVS_VOICE_CFG_VBIN_V5BIN); - bin[6] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, - NV_PAVS_VOICE_CFG_FMT_V6BIN); - bin[7] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, - NV_PAVS_VOICE_CFG_FMT_V7BIN); - - if (v < MCPX_HW_MAX_3D_VOICES) { - bin[0] = d->vp.hrtf_submix[0]; - bin[1] = d->vp.hrtf_submix[1]; - bin[2] = d->vp.hrtf_submix[2]; - bin[3] = d->vp.hrtf_submix[3]; - } - - uint16_t vol[8]; - vol[0] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA, - NV_PAVS_VOICE_TAR_VOLA_VOLUME0); - vol[1] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA, - NV_PAVS_VOICE_TAR_VOLA_VOLUME1); - vol[2] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB, - NV_PAVS_VOICE_TAR_VOLB_VOLUME2); - vol[3] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB, - NV_PAVS_VOICE_TAR_VOLB_VOLUME3); - vol[4] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC, - NV_PAVS_VOICE_TAR_VOLC_VOLUME4); - vol[5] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC, - NV_PAVS_VOICE_TAR_VOLC_VOLUME5); - - vol[6] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC, - NV_PAVS_VOICE_TAR_VOLC_VOLUME6_B11_8) << 8; - vol[6] |= voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB, - NV_PAVS_VOICE_TAR_VOLB_VOLUME6_B7_4) << 4; - vol[6] |= voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA, - NV_PAVS_VOICE_TAR_VOLA_VOLUME6_B3_0); - vol[7] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC, - NV_PAVS_VOICE_TAR_VOLC_VOLUME7_B11_8) << 8; - vol[7] |= voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB, - NV_PAVS_VOICE_TAR_VOLB_VOLUME7_B7_4) << 4; - vol[7] |= voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA, - NV_PAVS_VOICE_TAR_VOLA_VOLUME7_B3_0); - - // FIXME: If phase negations means to flip the signal upside down - // we should modify volume of bin6 and bin7 here. - - for (int i = 0; i < 8; i++) { - dbg->bin[i] = bin[i]; - dbg->vol[i] = vol[i]; - } - - if (voice_should_mute(v)) { - return; - } - - int fmode = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_MISC, - NV_PAVS_VOICE_CFG_MISC_FMODE); - - // FIXME: Move to function - bool lpf = false; - if (v < MCPX_HW_MAX_3D_VOICES) { - /* 1:DLS2+I3DL2 2:ParaEQ+I3DL2 3:I3DL2 */ - lpf = (fmode == 1); - } else { - /* 0:Bypass 1:DLS2 2:ParaEQ 3(Mono):DLS2+ParaEQ 3(Stereo):Bypass */ - lpf = stereo ? (fmode == 1) : (fmode & 1); - } - if (lpf) { - for (int ch = 0; ch < 2; ch++) { - // FIXME: Cutoff modulation via NV_PAVS_VOICE_CFG_ENV1_EF_FCSCALE - int16_t fc = voice_get_mask( - d, v, NV_PAVS_VOICE_TAR_FCA + (ch % channels) * 4, - NV_PAVS_VOICE_TAR_FCA_FC0); - float fc_f = clampf(pow(2, fc / 4096.0), 0.003906f, 1.0f); - uint16_t q = voice_get_mask( - d, v, NV_PAVS_VOICE_TAR_FCA + (ch % channels) * 4, - NV_PAVS_VOICE_TAR_FCA_FC1); - float q_f = clampf(q / (1.0 * 0x8000), 0.079407f, 1.0f); - sv_filter *filter = &d->vp.filters[v].svf[ch]; - setup_svf(filter, fc_f, q_f, F_LP); - for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) { - samples[i][ch] = run_svf(filter, samples[i][ch]); - samples[i][ch] = fmin(fmax(samples[i][ch], -1.0), 1.0); - } - } - } - - if (v < MCPX_HW_MAX_3D_VOICES && g_config.audio.hrtf) { - uint16_t hrtf_handle = - voice_get_mask(d, v, NV_PAVS_VOICE_CFG_HRTF_TARGET, - NV_PAVS_VOICE_CFG_HRTF_TARGET_HANDLE); - if (hrtf_handle != HRTF_NULL_HANDLE) { - hrtf_filter_process(&d->vp.filters[v].hrtf, samples, samples); - } - } - - // FIXME: ParaEQ - - for (int b = 0; b < 8; b++) { - float g = ea_value; - float hr; - if ((v < MCPX_HW_MAX_3D_VOICES) && (b < 4)) { - // FIXME: Not sure if submix/voice headroom factor in for HRTF - hr = 1 << d->vp.hrtf_headroom; - } else { - hr = 1 << d->vp.submix_headroom[bin[b]]; - } - g *= attenuate(vol[b])/hr; - for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) { - mixbins[bin[b]][i] += g*samples[i][b % channels]; - } - } - - if (d->mon == MCPX_APU_DEBUG_MON_VP) { - /* 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); - } - g *= ea_value; - for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) { - sample_buf[i][0] += g*samples[i][0]; - sample_buf[i][1] += g*samples[i][1]; - } - } -} - -static int voice_get_samples(MCPXAPUState *d, uint32_t v, float samples[][2], - int num_samples_requested) -{ - assert(v < MCPX_HW_MAX_VOICES); - bool stereo = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, - NV_PAVS_VOICE_CFG_FMT_STEREO); - unsigned int channels = stereo ? 2 : 1; - unsigned int sample_size = voice_get_mask( - d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE); - unsigned int container_sizes[4] = { 1, 2, 0, 4 }; /* B8, B16, ADPCM, B32 */ - unsigned int container_size_index = voice_get_mask( - d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE); - unsigned int container_size = container_sizes[container_size_index]; - bool stream = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, - NV_PAVS_VOICE_CFG_FMT_DATA_TYPE); - bool paused = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE, - NV_PAVS_VOICE_PAR_STATE_PAUSED); - 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); - unsigned int samples_per_block = - 1 + voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, - NV_PAVS_VOICE_CFG_FMT_SAMPLES_PER_BLOCK); - bool persist = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, - NV_PAVS_VOICE_CFG_FMT_PERSIST); - bool multipass = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, - NV_PAVS_VOICE_CFG_FMT_MULTIPASS); - 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; - int count = 0; - int seg_len = 0; - int seg_cs = 0; - int seg_spb = 0; - int seg_s = 0; - hwaddr segment_offset = 0; - uint32_t segment_length = 0; - size_t block_size; - - int adpcm_block_index = -1; - uint32_t adpcm_block[36*2/4]; - int16_t adpcm_decoded[65*2]; // FIXME: Move out of here - - // FIXME: Only update if necessary - 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 = samples_per_block; - dbg->persist = persist; - dbg->multipass = multipass; - dbg->linked = linked; - - // This is probably cleared when the first sample is played - // FIXME: How will this behave if CBO > EBO on first play? - // FIXME: How will this behave if paused? - voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, - NV_PAVS_VOICE_PAR_STATE_NEW_VOICE, 0); - - if (paused) { - return -1; - } - - if (stream) { - if (!persist) { - // FIXME: Confirm. Unsure if this should wait until end of SSL or - // terminate immediately. Definitely not before end of envelope. - int eacur = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE, - NV_PAVS_VOICE_PAR_STATE_EACUR); - if (eacur < NV_PAVS_VOICE_PAR_STATE_EFCUR_RELEASE) { - DPRINTF("Voice %d envelope not in release state (%d) and " - "persist is not set. Ending stream now!\n", - v, eacur); - voice_off(d, v); - return -1; - } - } - - DPRINTF("**** STREAMING (%d) ****\n", v); - assert(!loop); - - ssl_index = d->vp.ssl[v].ssl_index; - ssl_seg = d->vp.ssl[v].ssl_seg; - page = d->vp.ssl[v].base[ssl_index] + ssl_seg; - count = d->vp.ssl[v].count[ssl_index]; - - // Check to see if the stream has ended - if (count == 0) { - DPRINTF("Stream has ended\n"); - voice_set_mask(d, v, NV_PAVS_VOICE_PAR_OFFSET, - NV_PAVS_VOICE_PAR_OFFSET_CBO, 0); - d->vp.ssl[v].ssl_seg = 0; - if (!persist) { - d->vp.ssl[v].ssl_index = 0; - voice_off(d, v); - } else { - set_notify_status( - d, v, MCPX_HW_NOTIFIER_SSLA_DONE + d->vp.ssl[v].ssl_index, - NV1BA0_NOTIFICATION_STATUS_DONE_SUCCESS); - } - return -1; - } - - hwaddr addr = d->regs[NV_PAPU_VPSSLADDR] + page * 8; - segment_offset = ldl_le_phys(&address_space_memory, addr); - segment_length = ldl_le_phys(&address_space_memory, addr + 4); - assert(segment_offset != 0); - assert(segment_length != 0); - seg_len = (segment_length >> 0) & 0xffff; - seg_cs = (segment_length >> 16) & 3; - seg_spb = (segment_length >> 18) & 0x1f; - seg_s = (segment_length >> 23) & 1; - assert(seg_cs == container_size_index); - assert((seg_spb + 1) == samples_per_block); - assert(seg_s == stereo); - container_size_index = seg_cs; - if (seg_cs == NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_ADPCM) { - sample_size = NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S24; - } - - assert(seg_len > 0); - ebo = seg_len - 1; // FIXME: Confirm seg_len-1 is last valid sample index - - DPRINTF("Segment: SSL%c[%d]\n", 'A' + ssl_index, ssl_seg); - DPRINTF("Page: %x\n", page); - DPRINTF("Count: %d\n", count); - DPRINTF("Segment offset: 0x%" HWADDR_PRIx "\n", segment_offset); - DPRINTF("Segment length: %x\n", segment_length); - DPRINTF("...len = 0x%x\n", seg_len); - DPRINTF("...cs = %d (%s)\n", seg_cs, container_size_str[seg_cs]); - DPRINTF("...spb = %d\n", seg_spb); - DPRINTF("...s = %d (%s)\n", seg_s, seg_s ? "stereo" : "mono"); - } else { - DPRINTF("**** BUFFER (%d) ****\n", v); - } - - bool adpcm = - (container_size_index == NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_ADPCM); - - if (adpcm) { - block_size = 36; - DPRINTF("ADPCM:\n"); - } else { - assert(container_size_index < 4); - assert(sample_size < 4); - block_size = container_size; - DPRINTF("PCM:\n"); - DPRINTF(" Container Size: %s\n", - container_size_str[container_size_index]); - DPRINTF(" Sample Size: %s\n", sample_size_str[sample_size]); - } - - DPRINTF("CBO=%d EBO=%d\n", cbo, ebo); - - block_size *= samples_per_block; - - // FIXME: Restructure this loop - int sample_count = 0; - for (; (sample_count < num_samples_requested) && (cbo <= ebo); - sample_count++, cbo++) { - if (adpcm) { - unsigned int block_index = cbo / ADPCM_SAMPLES_PER_BLOCK; - unsigned int block_position = cbo % ADPCM_SAMPLES_PER_BLOCK; - if (adpcm_block_index != block_index) { - uint32_t linear_addr = block_index * block_size; - if (stream) { - hwaddr addr = segment_offset + linear_addr; - int max_seg_byte = (seg_len >> 6) * block_size; - assert(linear_addr + block_size <= max_seg_byte); - memcpy(adpcm_block, &d->ram_ptr[addr], - block_size); // FIXME: Use idiomatic DMA function - } else { - linear_addr += ba; - for (unsigned int word_index = 0; - word_index < (9 * samples_per_block); word_index++) { - hwaddr addr = get_data_ptr(d->regs[NV_PAPU_VPSGEADDR], - 0xFFFFFFFF, linear_addr); - adpcm_block[word_index] = - ldl_le_phys(&address_space_memory, addr); - linear_addr += 4; - } - } - adpcm_decode_block(adpcm_decoded, (uint8_t *)adpcm_block, - block_size, channels); - adpcm_block_index = block_index; - } - - samples[sample_count][0] = - int16_to_float(adpcm_decoded[block_position * channels]); - if (stereo) { - samples[sample_count][1] = int16_to_float( - adpcm_decoded[block_position * channels + 1]); - } - } else { - // FIXME: Handle reading accross pages?! - - hwaddr addr; - if (stream) { - addr = segment_offset + cbo * block_size; - } else { - uint32_t linear_addr = ba + cbo * block_size; - addr = get_data_ptr(d->regs[NV_PAPU_VPSGEADDR], 0xFFFFFFFF, - linear_addr); - } - - for (unsigned int channel = 0; channel < channels; channel++) { - uint32_t ival; - float fval; - switch (sample_size) { - case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_U8: - ival = ldub_phys(&address_space_memory, addr); - fval = uint8_to_float(ival & 0xff); - break; - case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S16: - ival = lduw_le_phys(&address_space_memory, addr); - fval = int16_to_float(ival & 0xffff); - break; - case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S24: - ival = ldl_le_phys(&address_space_memory, addr); - fval = int24_to_float(ival); - break; - case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S32: - ival = ldl_le_phys(&address_space_memory, addr); - fval = int32_to_float(ival); - break; - default: - assert(false); - break; - } - samples[sample_count][channel] = fval; - addr += container_size; - } - } - - if (!stereo) { - samples[sample_count][1] = samples[sample_count][0]; - } - } - - if (cbo >= ebo) { - if (stream) { - d->vp.ssl[v].ssl_seg += 1; - cbo = 0; - if (d->vp.ssl[v].ssl_seg < d->vp.ssl[v].count[ssl_index]) { - DPRINTF("SSL%c[%d]\n", 'A' + ssl_index, d->vp.ssl[v].ssl_seg); - } else { - int next_index = (ssl_index + 1) % 2; - DPRINTF("SSL%c\n", 'A' + next_index); - d->vp.ssl[v].ssl_index = next_index; - d->vp.ssl[v].ssl_seg = 0; - set_notify_status(d, v, MCPX_HW_NOTIFIER_SSLA_DONE + ssl_index, - NV1BA0_NOTIFICATION_STATUS_DONE_SUCCESS); - } - } else { - if (loop) { - cbo = lbo; - } else { - cbo = ebo; - voice_off(d, v); - DPRINTF("end of buffer!\n"); - } - } - } - - voice_set_mask(d, v, NV_PAVS_VOICE_PAR_OFFSET, - NV_PAVS_VOICE_PAR_OFFSET_CBO, cbo); - return sample_count; -} - static void se_frame(MCPXAPUState *d) { mcpx_apu_update_dsp_preference(d); @@ -2398,53 +597,7 @@ static void se_frame(MCPXAPUState *d) /* Buffer for all mixbins for this frame */ float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME] = { 0 }; - memset(d->vp.sample_buf, 0, sizeof(d->vp.sample_buf)); - - /* Process all voices, mixing each into the affected MIXBINs */ - for (int list = 0; list < 3; list++) { - hwaddr top, current, next; - top = voice_list_regs[list].top; - current = voice_list_regs[list].current; - next = voice_list_regs[list].next; - - d->regs[current] = d->regs[top]; - DPRINTF("list %d current voice %d\n", list, d->regs[current]); - - for (int i = 0; d->regs[current] != 0xFFFF; i++) { - /* Make sure not to get stuck... */ - if (i >= MCPX_HW_MAX_VOICES) { - DPRINTF("Voice list contains invalid entry!\n"); - break; - } - - uint16_t v = d->regs[current]; - d->regs[next] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_PITCH_LINK, - NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE); - if (!voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE, - NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE)) { - fe_method(d, SE2FE_IDLE_VOICE, v); - } else { - voice_work_enqueue(d, v, list); - } - d->regs[current] = d->regs[next]; - } - } - voice_work_dispatch(d, mixbins); - - if (d->mon == MCPX_APU_DEBUG_MON_VP) { - /* Mix all voices together to hear any audible voice */ - int16_t isamp[NUM_SAMPLES_PER_FRAME * 2]; - src_float_to_short_array((float *)d->vp.sample_buf, isamp, - NUM_SAMPLES_PER_FRAME * 2); - int off = (d->ep_frame_div % 8) * NUM_SAMPLES_PER_FRAME; - for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) { - d->apu_fifo_output[off + i][0] += isamp[2*i]; - d->apu_fifo_output[off + i][1] += isamp[2*i+1]; - } - - memset(d->vp.sample_buf, 0, sizeof(d->vp.sample_buf)); - memset(mixbins, 0, sizeof(mixbins)); - } + mcpx_apu_vp_frame(d, mixbins); /* Write VP results to the GP DSP MIXBUF */ for (int mixbin = 0; mixbin < NUM_MIXBINS; mixbin++) { @@ -2603,7 +756,7 @@ static void mcpx_apu_exitfn(PCIDevice *dev) d->exiting = true; qemu_cond_broadcast(&d->cond); qemu_thread_join(&d->apu_thread); - voice_work_finalize(d); + mcpx_apu_vp_finalize(d); } static void mcpx_apu_reset(MCPXAPUState *d) @@ -2611,15 +764,7 @@ static void mcpx_apu_reset(MCPXAPUState *d) qemu_mutex_lock(&d->lock); // FIXME: Can fail if thread is pegged, add flag memset(d->regs, 0, sizeof(d->regs)); - d->vp.ssl_base_page = 0; - d->vp.hrtf_headroom = 0; - memset(d->vp.ssl, 0, sizeof(d->vp.ssl)); - memset(d->vp.hrtf_submix, 0, sizeof(d->vp.hrtf_submix)); - memset(d->vp.submix_headroom, 0, sizeof(d->vp.submix_headroom)); - memset(d->vp.voice_locked, 0, sizeof(d->vp.voice_locked)); - for (int v = 0; v < ARRAY_SIZE(d->vp.filters); v++) { - hrtf_filter_init(&d->vp.filters[v].hrtf); - } + mcpx_apu_vp_reset(d); // FIXME: Reset DSP state memset(d->gp.dsp->core.pram_opcache, 0, @@ -2927,9 +1072,6 @@ void mcpx_apu_init(PCIBus *bus, int devfn, MemoryRegion *ram) SDL_PauseAudioDevice(sdl_audio_dev, 0); qemu_spin_init(&d->vp.out_buf_lock); - for (int i = 0; i < MCPX_HW_MAX_VOICES; i++) { - qemu_spin_init(&d->vp.voice_spinlocks[i]); - } fifo8_create(&d->vp.out_buf, 3 * (256 * 2 * 2)); qemu_mutex_init(&d->lock); @@ -2941,7 +1083,7 @@ void mcpx_apu_init(PCIBus *bus, int devfn, MemoryRegion *ram) */ mcpx_apu_update_dsp_preference(d); - voice_work_init(d); + mcpx_apu_vp_init(d); qemu_thread_create(&d->apu_thread, "mcpx.apu_thread", mcpx_apu_frame_thread, d, QEMU_THREAD_JOINABLE); } diff --git a/hw/xbox/mcpx/apu_int.h b/hw/xbox/mcpx/apu_int.h index a4d499d657..f4ec7811b7 100644 --- a/hw/xbox/mcpx/apu_int.h +++ b/hw/xbox/mcpx/apu_int.h @@ -45,7 +45,6 @@ #include "apu.h" #include "apu_regs.h" #include "apu_debug.h" -#include "adpcm.h" #include "svf.h" #include "fpconv.h" #include "hrtf.h" @@ -196,14 +195,6 @@ typedef struct MCPXAPUState { int16_t apu_fifo_output[256][2]; // 1 EP frame (0x400 bytes), 8 buffered } MCPXAPUState; -static const struct { - hwaddr top, current, next; -} voice_list_regs[] = { - { NV_PAPU_TVL2D, NV_PAPU_CVL2D, NV_PAPU_NVL2D }, // 2D - { NV_PAPU_TVL3D, NV_PAPU_CVL3D, NV_PAPU_NVL3D }, // 3D - { NV_PAPU_TVLMP, NV_PAPU_CVLMP, NV_PAPU_NVLMP }, // MP -}; - extern MCPXAPUState *g_state; // Used via debug handlers extern struct McpxApuDebug g_dbg, g_dbg_cache; extern int g_dbg_voice_monitor; @@ -212,4 +203,11 @@ extern uint64_t g_dbg_muted_voices[4]; void mcpx_debug_begin_frame(void); void mcpx_debug_end_frame(void); +extern const MemoryRegionOps vp_ops; + +void mcpx_apu_vp_init(MCPXAPUState *d); +void mcpx_apu_vp_finalize(MCPXAPUState *d); +void mcpx_apu_vp_frame(MCPXAPUState *d, float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME]); +void mcpx_apu_vp_reset(MCPXAPUState *d); + #endif diff --git a/hw/xbox/mcpx/meson.build b/hw/xbox/mcpx/meson.build index c1d2ab65d0..13ec11ec25 100644 --- a/hw/xbox/mcpx/meson.build +++ b/hw/xbox/mcpx/meson.build @@ -5,6 +5,7 @@ mcpx_ss.add(sdl, libsamplerate, dsp, files( 'apu.c', 'aci.c', 'debug.c', + 'vp.c' )) specific_ss.add_all(mcpx_ss) \ No newline at end of file diff --git a/hw/xbox/mcpx/vp.c b/hw/xbox/mcpx/vp.c new file mode 100644 index 0000000000..28b7ad9f78 --- /dev/null +++ b/hw/xbox/mcpx/vp.c @@ -0,0 +1,1907 @@ +/* + * QEMU MCPX Audio Processing Unit implementation + * + * Copyright (c) 2012 espes + * Copyright (c) 2018-2019 Jannik Vogel + * Copyright (c) 2019-2025 Matt Borgerson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "apu_int.h" +#include "adpcm.h" + +static const struct { + hwaddr top, current, next; +} voice_list_regs[] = { + { NV_PAPU_TVL2D, NV_PAPU_CVL2D, NV_PAPU_NVL2D }, // 2D + { NV_PAPU_TVL3D, NV_PAPU_CVL3D, NV_PAPU_NVL3D }, // 3D + { NV_PAPU_TVLMP, NV_PAPU_CVLMP, NV_PAPU_NVLMP }, // MP +}; + +static float clampf(float v, float min, float max); +static float attenuate(uint16_t vol); + +static bool voice_should_mute(uint16_t v); +static uint32_t voice_get_mask(MCPXAPUState *d, uint16_t voice_handle, + hwaddr offset, uint32_t mask); +static void voice_set_mask(MCPXAPUState *d, uint16_t voice_handle, + hwaddr offset, uint32_t mask, uint32_t val); +static void voice_off(MCPXAPUState *d, uint16_t v); +static void voice_lock(MCPXAPUState *d, uint16_t v, bool lock); +static bool is_voice_locked(MCPXAPUState *d, uint16_t v); +static void fe_method(MCPXAPUState *d, uint32_t method, uint32_t argument); +static uint64_t vp_read(void *opaque, hwaddr addr, unsigned int size); +static void vp_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size); +static float voice_step_envelope(MCPXAPUState *d, uint16_t v, + uint32_t reg_0, uint32_t reg_a, + uint32_t rr_reg, uint32_t rr_mask, + uint32_t lvl_reg, uint32_t lvl_mask, + uint32_t count_mask, uint32_t cur_mask); +static hwaddr get_data_ptr(hwaddr sge_base, unsigned int max_sge, + uint32_t addr); +static void set_notify_status(MCPXAPUState *d, uint32_t v, int notifier, + int status); +static long voice_resample_callback(void *cb_data, float **data); +static int voice_resample(MCPXAPUState *d, uint16_t v, float samples[][2], + int requested_num, float rate); +static void voice_reset_filters(MCPXAPUState *d, uint16_t v); +static void voice_process(MCPXAPUState *d, + float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME], + float sample_buf[NUM_SAMPLES_PER_FRAME][2], + uint16_t v, int voice_list); +static int voice_get_samples(MCPXAPUState *d, uint32_t v, float samples[][2], + int num_samples_requested); + + +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); +} + +static float clampf(float v, float min, float max) +{ + if (v < min) { + return min; + } else if (v > max) { + return max; + } else { + return v; + } +} + +static float attenuate(uint16_t vol) +{ + vol &= 0xFFF; + return (vol == 0xFFF) ? 0.0 : powf(10.0f, vol/(64.0 * -20.0f)); +} + +static uint32_t voice_get_mask(MCPXAPUState *d, uint16_t voice_handle, + hwaddr offset, uint32_t mask) +{ + hwaddr voice = d->regs[NV_PAPU_VPVADDR] + voice_handle * NV_PAVS_SIZE; + return (ldl_le_phys(&address_space_memory, voice + offset) & mask) >> + ctz32(mask); +} + +static void voice_set_mask(MCPXAPUState *d, uint16_t voice_handle, + hwaddr offset, uint32_t mask, uint32_t val) +{ + hwaddr voice = d->regs[NV_PAPU_VPVADDR] + + voice_handle * NV_PAVS_SIZE; + uint32_t v = ldl_le_phys(&address_space_memory, voice + offset) & ~mask; + stl_le_phys(&address_space_memory, voice + offset, + v | ((val << ctz32(mask)) & mask)); +} + +static void voice_off(MCPXAPUState *d, uint16_t v) +{ + voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, + NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE, 0); + + bool stream = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, + NV_PAVS_VOICE_CFG_FMT_DATA_TYPE); + int notifier = MCPX_HW_NOTIFIER_SSLA_DONE; + if (stream) { + assert(v < MCPX_HW_MAX_VOICES); + assert(d->vp.ssl[v].ssl_index <= 1); + notifier += d->vp.ssl[v].ssl_index; + } + set_notify_status(d, v, notifier, NV1BA0_NOTIFICATION_STATUS_DONE_SUCCESS); +} + +static void voice_lock(MCPXAPUState *d, uint16_t v, bool lock) +{ + assert(v < MCPX_HW_MAX_VOICES); + qemu_spin_lock(&d->vp.voice_spinlocks[v]); + uint64_t mask = 1LL << (v % 64); + if (lock) { + d->vp.voice_locked[v / 64] |= mask; + } else { + d->vp.voice_locked[v / 64] &= ~mask; + } + qemu_spin_unlock(&d->vp.voice_spinlocks[v]); + qemu_cond_broadcast(&d->cond); +} + +static bool is_voice_locked(MCPXAPUState *d, uint16_t v) +{ + assert(v < MCPX_HW_MAX_VOICES); + uint64_t mask = 1LL << (v % 64); + return (qatomic_read(&d->vp.voice_locked[v / 64]) & mask) != 0; +} + +static void set_hrir_coeff_tar(MCPXAPUState *d, int channel, int coeff_idx, + int8_t value) +{ + int entry = d->vp.hrtf.current_entry; + d->vp.hrtf.entries[entry].hrir[channel][coeff_idx] = int8_to_float(value); +} + +static void fe_method(MCPXAPUState *d, uint32_t method, uint32_t argument) +{ + unsigned int slot; + + trace_mcpx_apu_method(method, argument); + + //assert((d->regs[NV_PAPU_FECTL] & NV_PAPU_FECTL_FEMETHMODE) == 0); + + d->regs[NV_PAPU_FEDECMETH] = method; + d->regs[NV_PAPU_FEDECPARAM] = argument; + unsigned int selected_handle, list; + switch (method) { + case NV1BA0_PIO_VOICE_LOCK: + voice_lock(d, d->regs[NV_PAPU_FECV], argument & 1); + break; + case NV1BA0_PIO_SET_ANTECEDENT_VOICE: + d->regs[NV_PAPU_FEAV] = argument; + break; + case NV1BA0_PIO_VOICE_ON: { + selected_handle = argument & NV1BA0_PIO_VOICE_ON_HANDLE; + DPRINTF("VOICE %d ON\n", selected_handle); + + bool locked = is_voice_locked(d, selected_handle); + if (!locked) { + voice_lock(d, selected_handle, true); + } + + list = GET_MASK(d->regs[NV_PAPU_FEAV], NV_PAPU_FEAV_LST); + if (list != NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_INHERIT) { + /* voice is added to the top of the selected list */ + unsigned int top_reg = voice_list_regs[list - 1].top; + voice_set_mask(d, selected_handle, NV_PAVS_VOICE_TAR_PITCH_LINK, + NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE, + d->regs[top_reg]); + d->regs[top_reg] = selected_handle; + } else { + unsigned int antecedent_voice = + GET_MASK(d->regs[NV_PAPU_FEAV], NV_PAPU_FEAV_VALUE); + /* voice is added after the antecedent voice */ + assert(antecedent_voice != 0xFFFF); + + uint32_t next_handle = voice_get_mask( + d, antecedent_voice, NV_PAVS_VOICE_TAR_PITCH_LINK, + NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE); + voice_set_mask(d, selected_handle, NV_PAVS_VOICE_TAR_PITCH_LINK, + NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE, + next_handle); + voice_set_mask(d, antecedent_voice, NV_PAVS_VOICE_TAR_PITCH_LINK, + NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE, + selected_handle); + } + + // FIXME: Should set CBO here? + voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_OFFSET, + NV_PAVS_VOICE_PAR_OFFSET_CBO, 0); + d->vp.ssl[selected_handle].ssl_seg = 0; // FIXME: verify this + d->vp.ssl[selected_handle].ssl_index = 0; // FIXME: verify this + + unsigned int ea_start = GET_MASK(argument, NV1BA0_PIO_VOICE_ON_ENVA); + voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_STATE, + NV_PAVS_VOICE_PAR_STATE_EACUR, ea_start); + if (ea_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_DELAY) { + uint16_t delay_time = + voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENV0, + NV_PAVS_VOICE_CFG_ENV0_EA_DELAYTIME); + voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, + NV_PAVS_VOICE_CUR_ECNT_EACOUNT, delay_time * 16); + } else if (ea_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_ATTACK) { + voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, + NV_PAVS_VOICE_CUR_ECNT_EACOUNT, 0); + } else if (ea_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_HOLD) { + uint16_t hold_time = + voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENVA, + NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME); + voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, + NV_PAVS_VOICE_CUR_ECNT_EACOUNT, hold_time * 16); + } + // FIXME: Will count be overwritten in other cases too? + + unsigned int ef_start = GET_MASK(argument, NV1BA0_PIO_VOICE_ON_ENVF); + voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_STATE, + NV_PAVS_VOICE_PAR_STATE_EFCUR, ef_start); + if (ef_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_DELAY) { + uint16_t delay_time = + voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENV1, + NV_PAVS_VOICE_CFG_ENV0_EA_DELAYTIME); + voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, + NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, delay_time * 16); + } else if (ef_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_ATTACK) { + voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, + NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, 0); + } else if (ef_start == NV_PAVS_VOICE_PAR_STATE_EFCUR_HOLD) { + uint16_t hold_time = + voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENVF, + NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME); + voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, + NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, hold_time * 16); + } + // FIXME: Will count be overwritten in other cases too? + + voice_reset_filters(d, selected_handle); + voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_STATE, + NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE, 1); + + if (!locked) { + voice_lock(d, selected_handle, false); + } + + break; + } + case NV1BA0_PIO_VOICE_RELEASE: { + selected_handle = argument & NV1BA0_PIO_VOICE_ON_HANDLE; + + // FIXME: What if already in release? Restart envelope? + // FIXME: Should release count ascend or descend? + + bool locked = is_voice_locked(d, selected_handle); + if (!locked) { + voice_lock(d, selected_handle, true); + } + + uint16_t rr; + rr = voice_get_mask(d, selected_handle, NV_PAVS_VOICE_TAR_LFO_ENV, + NV_PAVS_VOICE_TAR_LFO_ENV_EA_RELEASERATE); + voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, + NV_PAVS_VOICE_CUR_ECNT_EACOUNT, rr * 16); + voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_STATE, + NV_PAVS_VOICE_PAR_STATE_EACUR, + NV_PAVS_VOICE_PAR_STATE_EFCUR_RELEASE); + + rr = voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_MISC, + NV_PAVS_VOICE_CFG_MISC_EF_RELEASERATE); + voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, + NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, rr * 16); + voice_set_mask(d, selected_handle, NV_PAVS_VOICE_PAR_STATE, + NV_PAVS_VOICE_PAR_STATE_EFCUR, + NV_PAVS_VOICE_PAR_STATE_EFCUR_RELEASE); + + if (!locked) { + voice_lock(d, selected_handle, false); + } + + break; + } + case NV1BA0_PIO_VOICE_OFF: + voice_off(d, argument & NV1BA0_PIO_VOICE_OFF_HANDLE); + break; + case NV1BA0_PIO_VOICE_PAUSE: + voice_set_mask(d, argument & NV1BA0_PIO_VOICE_PAUSE_HANDLE, + NV_PAVS_VOICE_PAR_STATE, NV_PAVS_VOICE_PAR_STATE_PAUSED, + (argument & NV1BA0_PIO_VOICE_PAUSE_ACTION) != 0); + break; + case NV1BA0_PIO_SET_CURRENT_HRTF_ENTRY: { + int handle = GET_MASK(argument, NV1BA0_PIO_SET_CURRENT_HRTF_ENTRY_HANDLE); + d->vp.hrtf.current_entry = handle; + break; + } + case NV1BA0_PIO_SET_CURRENT_VOICE: + d->regs[NV_PAPU_FECV] = argument; + break; + case NV1BA0_PIO_SET_VOICE_CFG_VBIN: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_VBIN, + 0xFFFFFFFF, argument); + break; + case NV1BA0_PIO_SET_VOICE_CFG_FMT: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_FMT, + 0xFFFFFFFF, argument); + break; + case NV1BA0_PIO_SET_VOICE_CFG_ENV0: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_ENV0, + 0xFFFFFFFF, argument); + break; + case NV1BA0_PIO_SET_VOICE_CFG_ENVA: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_ENVA, + 0xFFFFFFFF, argument); + break; + case NV1BA0_PIO_SET_VOICE_CFG_ENV1: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_ENV1, + 0xFFFFFFFF, argument); + break; + case NV1BA0_PIO_SET_VOICE_CFG_ENVF: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_ENVF, + 0xFFFFFFFF, argument); + break; + case NV1BA0_PIO_SET_VOICE_CFG_MISC: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CFG_MISC, + 0xFFFFFFFF, argument); + break; + case NV1BA0_PIO_SET_VOICE_TAR_HRTF: { + int handle = GET_MASK(argument, NV1BA0_PIO_SET_VOICE_TAR_HRTF_HANDLE); + int current_voice = d->regs[NV_PAPU_FECV]; + voice_set_mask(d, current_voice, NV_PAVS_VOICE_CFG_HRTF_TARGET, + NV_PAVS_VOICE_CFG_HRTF_TARGET_HANDLE, handle); + if (current_voice < MCPX_HW_MAX_3D_VOICES && + handle != HRTF_NULL_HANDLE) { + // FIXME: Xbox software seems to reliably set voice HRTF handles + // after updating filter parameters, however it may be possible to + // update parameter targets for an active voice. + assert(handle < HRTF_ENTRY_COUNT); + hrtf_filter_set_target_params(&d->vp.filters[current_voice].hrtf, + d->vp.hrtf.entries[handle].hrir, + d->vp.hrtf.entries[handle].itd); + } + break; + } + case NV1BA0_PIO_SET_VOICE_TAR_VOLA: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_VOLA, + 0xFFFFFFFF, argument); + break; + case NV1BA0_PIO_SET_VOICE_TAR_VOLB: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_VOLB, + 0xFFFFFFFF, argument); + break; + case NV1BA0_PIO_SET_VOICE_TAR_VOLC: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_VOLC, + 0xFFFFFFFF, argument); + break; + case NV1BA0_PIO_SET_VOICE_LFO_ENV: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_LFO_ENV, + 0xFFFFFFFF, argument); + break; + case NV1BA0_PIO_SET_VOICE_TAR_FCA: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_FCA, + 0xFFFFFFFF, argument); + break; + case NV1BA0_PIO_SET_VOICE_TAR_FCB: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_FCB, + 0xFFFFFFFF, argument); + break; + case NV1BA0_PIO_SET_VOICE_TAR_PITCH: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_TAR_PITCH_LINK, + NV_PAVS_VOICE_TAR_PITCH_LINK_PITCH, + (argument & NV1BA0_PIO_SET_VOICE_TAR_PITCH_STEP) >> 16); + break; + case NV1BA0_PIO_SET_VOICE_CFG_BUF_BASE: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CUR_PSL_START, + NV_PAVS_VOICE_CUR_PSL_START_BA, argument); + break; + case NV1BA0_PIO_SET_VOICE_CFG_BUF_LBO: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_CUR_PSH_SAMPLE, + NV_PAVS_VOICE_CUR_PSH_SAMPLE_LBO, argument); + break; + case NV1BA0_PIO_SET_VOICE_BUF_CBO: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_PAR_OFFSET, + NV_PAVS_VOICE_PAR_OFFSET_CBO, argument); + break; + case NV1BA0_PIO_SET_VOICE_CFG_BUF_EBO: + voice_set_mask(d, d->regs[NV_PAPU_FECV], NV_PAVS_VOICE_PAR_NEXT, + NV_PAVS_VOICE_PAR_NEXT_EBO, argument); + break; + case NV1BA0_PIO_SET_HRIR ... NV1BA0_PIO_SET_HRIR_X - 1: { + assert(d->vp.hrtf.current_entry < HRTF_ENTRY_COUNT); + slot = (method - NV1BA0_PIO_SET_HRIR) / 4; + int8_t left0 = GET_MASK(argument, NV1BA0_PIO_SET_HRIR_LEFT0); + int8_t right0 = GET_MASK(argument, NV1BA0_PIO_SET_HRIR_RIGHT0); + int8_t left1 = GET_MASK(argument, NV1BA0_PIO_SET_HRIR_LEFT1); + int8_t right1 = GET_MASK(argument, NV1BA0_PIO_SET_HRIR_RIGHT1); + int coeff_idx = slot * 2; + set_hrir_coeff_tar(d, 0, coeff_idx, left0); + set_hrir_coeff_tar(d, 1, coeff_idx, right0); + coeff_idx += 1; + set_hrir_coeff_tar(d, 0, coeff_idx, left1); + set_hrir_coeff_tar(d, 1, coeff_idx, right1); + break; + } + case NV1BA0_PIO_SET_HRIR_X: { + assert(d->vp.hrtf.current_entry < HRTF_ENTRY_COUNT); + int8_t left30 = GET_MASK(argument, NV1BA0_PIO_SET_HRIR_X_LEFT30); + int8_t right30 = GET_MASK(argument, NV1BA0_PIO_SET_HRIR_X_RIGHT30); + int16_t itd = GET_MASK(argument, NV1BA0_PIO_SET_HRIR_X_ITD); + set_hrir_coeff_tar(d, 0, 30, left30); + set_hrir_coeff_tar(d, 1, 30, right30); + d->vp.hrtf.entries[d->vp.hrtf.current_entry].itd = s6p9_to_float(itd); + break; + } + case NV1BA0_PIO_SET_CURRENT_INBUF_SGE: + d->inbuf_sge_handle = argument & NV1BA0_PIO_SET_CURRENT_INBUF_SGE_HANDLE; + break; + case NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET: { + // FIXME: Is there an upper limit for the SGE table size? + // FIXME: NV_PAPU_VPSGEADDR is probably bad, as outbuf SGE use the same + // handle range (or that is also wrong) + hwaddr sge_address = + d->regs[NV_PAPU_VPSGEADDR] + d->inbuf_sge_handle * 8; + stl_le_phys(&address_space_memory, sge_address, + argument & + NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET_PARAMETER); + DPRINTF("Wrote inbuf SGE[0x%X] = 0x%08X\n", d->inbuf_sge_handle, + argument & NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET_PARAMETER); + break; + } + CASE_4(NV1BA0_PIO_SET_OUTBUF_BA, 8): // 8 byte pitch, 4 entries +#ifdef DEBUG_MCPX + slot = (method - NV1BA0_PIO_SET_OUTBUF_BA) / 8; + //FIXME: Use NV1BA0_PIO_SET_OUTBUF_BA_ADDRESS = 0x007FFF00 ? + DPRINTF("outbuf_ba[%d]: 0x%08X\n", slot, argument); +#endif + //assert(false); //FIXME: Enable assert! no idea what this reg does + break; + CASE_4(NV1BA0_PIO_SET_OUTBUF_LEN, 8): // 8 byte pitch, 4 entries +#ifdef DEBUG_MCPX + slot = (method - NV1BA0_PIO_SET_OUTBUF_LEN) / 8; + //FIXME: Use NV1BA0_PIO_SET_OUTBUF_LEN_VALUE = 0x007FFF00 ? + DPRINTF("outbuf_len[%d]: 0x%08X\n", slot, argument); +#endif + //assert(false); //FIXME: Enable assert! no idea what this reg does + break; + case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE: + d->outbuf_sge_handle = + argument & NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_HANDLE; + break; + case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET: { + // FIXME: Is there an upper limit for the SGE table size? + // FIXME: NV_PAPU_VPSGEADDR is probably bad, as inbuf SGE use the same + // handle range (or that is also wrong) + // NV_PAPU_EPFADDR EP outbufs + // NV_PAPU_GPFADDR GP outbufs + // But how does it know which outbuf is being written?! + hwaddr sge_address = + d->regs[NV_PAPU_VPSGEADDR] + d->outbuf_sge_handle * 8; + stl_le_phys(&address_space_memory, sge_address, + argument & + NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET_PARAMETER); + DPRINTF("Wrote outbuf SGE[0x%X] = 0x%08X\n", d->outbuf_sge_handle, + argument & NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET_PARAMETER); + break; + } + case NV1BA0_PIO_SET_VOICE_SSL_A: { + int ssl = 0; + int current_voice = d->regs[NV_PAPU_FECV]; + assert(current_voice < MCPX_HW_MAX_VOICES); + d->vp.ssl[current_voice].base[ssl] = + GET_MASK(argument, NV1BA0_PIO_SET_VOICE_SSL_A_BASE); + d->vp.ssl[current_voice].count[ssl] = + GET_MASK(argument, NV1BA0_PIO_SET_VOICE_SSL_A_COUNT); + // d->vp.ssl[current_voice].ssl_index = 0; + DPRINTF("SSL%c Base = %x, Count = %d\n", 'A' + ssl, + d->vp.ssl[current_voice].base[ssl], + d->vp.ssl[current_voice].count[ssl]); + break; + } + // FIXME: Refactor into above + case NV1BA0_PIO_SET_VOICE_SSL_B: { + int ssl = 1; + int current_voice = d->regs[NV_PAPU_FECV]; + assert(current_voice < MCPX_HW_MAX_VOICES); + d->vp.ssl[current_voice].base[ssl] = + GET_MASK(argument, NV1BA0_PIO_SET_VOICE_SSL_A_BASE); + d->vp.ssl[current_voice].count[ssl] = + GET_MASK(argument, NV1BA0_PIO_SET_VOICE_SSL_A_COUNT); + // d->vp.ssl[current_voice].ssl_index = 0; + DPRINTF("SSL%c Base = %x, Count = %d\n", 'A' + ssl, + d->vp.ssl[current_voice].base[ssl], + d->vp.ssl[current_voice].count[ssl]); + break; + } + case NV1BA0_PIO_SET_CURRENT_SSL: { + assert((argument & 0x3f) == 0); + assert(argument < (MCPX_HW_MAX_SSL_PRDS*NV_PSGE_SIZE)); + d->vp.ssl_base_page = argument; + break; + } + case NV1BA0_PIO_SET_SSL_SEGMENT_OFFSET ... + NV1BA0_PIO_SET_SSL_SEGMENT_LENGTH+8*64-1: { + // 64 offset/base pairs relative to segment base + // FIXME: Entries are 64b, assuming they are stored + // like this <[offset,length],...> + assert((method & 0x3) == 0); + hwaddr addr = d->regs[NV_PAPU_VPSSLADDR] + + (d->vp.ssl_base_page * 8) + + (method - NV1BA0_PIO_SET_SSL_SEGMENT_OFFSET); + stl_le_phys(&address_space_memory, addr, argument); + DPRINTF(" ssl_segment[%x + %x].%s = %x\n", + d->vp.ssl_base_page, + (method - NV1BA0_PIO_SET_SSL_SEGMENT_OFFSET)/8, + method & 4 ? "length" : "offset", + argument); + break; + } + case NV1BA0_PIO_SET_HRTF_SUBMIXES: + d->vp.hrtf_submix[0] = (argument >> 0) & 0x1f; + d->vp.hrtf_submix[1] = (argument >> 8) & 0x1f; + d->vp.hrtf_submix[2] = (argument >> 16) & 0x1f; + d->vp.hrtf_submix[3] = (argument >> 24) & 0x1f; + break; + case NV1BA0_PIO_SET_HRTF_HEADROOM: + d->vp.hrtf_headroom = argument & NV1BA0_PIO_SET_HRTF_HEADROOM_AMOUNT; + break; + case NV1BA0_PIO_SET_SUBMIX_HEADROOM ... + NV1BA0_PIO_SET_SUBMIX_HEADROOM+4*(NUM_MIXBINS-1): + assert((method & 3) == 0); + slot = (method-NV1BA0_PIO_SET_SUBMIX_HEADROOM)/4; + d->vp.submix_headroom[slot] = + argument & NV1BA0_PIO_SET_SUBMIX_HEADROOM_AMOUNT; + break; + case SE2FE_IDLE_VOICE: + if (d->regs[NV_PAPU_FETFORCE1] & NV_PAPU_FETFORCE1_SE2FE_IDLE_VOICE) { + d->regs[NV_PAPU_FECTL] &= ~NV_PAPU_FECTL_FEMETHMODE; + d->regs[NV_PAPU_FECTL] |= NV_PAPU_FECTL_FEMETHMODE_TRAPPED; + d->regs[NV_PAPU_FECTL] &= ~NV_PAPU_FECTL_FETRAPREASON; + d->regs[NV_PAPU_FECTL] |= NV_PAPU_FECTL_FETRAPREASON_REQUESTED; + DPRINTF("idle voice %d\n", argument); + d->set_irq = true; + } else { + assert(false); + } + break; + default: + assert(false); + break; + } +} + +static uint64_t vp_read(void *opaque, hwaddr addr, unsigned int size) +{ + DPRINTF("mcpx apu VP: read [0x%" HWADDR_PRIx "] (%s)\n", addr, + get_method_str(addr)); + + switch (addr) { + case NV1BA0_PIO_FREE: + /* we don't simulate the queue for now, + * pretend to always be empty */ + return 0x80; + default: + break; + } + + return 0; +} + +static void vp_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size) +{ + MCPXAPUState *d = opaque; + + DPRINTF("mcpx apu VP: [0x%" HWADDR_PRIx "] %s = 0x%lx\n", addr, + get_method_str(addr), val); + + switch (addr) { + case NV1BA0_PIO_SET_ANTECEDENT_VOICE: + case NV1BA0_PIO_VOICE_LOCK: + case NV1BA0_PIO_VOICE_ON: + case NV1BA0_PIO_VOICE_RELEASE: + case NV1BA0_PIO_VOICE_OFF: + case NV1BA0_PIO_VOICE_PAUSE: + case NV1BA0_PIO_SET_CURRENT_HRTF_ENTRY: + case NV1BA0_PIO_SET_CURRENT_VOICE: + case NV1BA0_PIO_SET_VOICE_CFG_VBIN: + case NV1BA0_PIO_SET_VOICE_CFG_FMT: + case NV1BA0_PIO_SET_VOICE_CFG_ENV0: + case NV1BA0_PIO_SET_VOICE_CFG_ENVA: + case NV1BA0_PIO_SET_VOICE_CFG_ENV1: + case NV1BA0_PIO_SET_VOICE_CFG_ENVF: + case NV1BA0_PIO_SET_VOICE_CFG_MISC: + case NV1BA0_PIO_SET_VOICE_TAR_HRTF: + case NV1BA0_PIO_SET_VOICE_TAR_VOLA: + case NV1BA0_PIO_SET_VOICE_TAR_VOLB: + case NV1BA0_PIO_SET_VOICE_TAR_VOLC: + case NV1BA0_PIO_SET_VOICE_LFO_ENV: + case NV1BA0_PIO_SET_VOICE_TAR_FCA: + case NV1BA0_PIO_SET_VOICE_TAR_FCB: + case NV1BA0_PIO_SET_VOICE_TAR_PITCH: + case NV1BA0_PIO_SET_VOICE_CFG_BUF_BASE: + case NV1BA0_PIO_SET_VOICE_CFG_BUF_LBO: + case NV1BA0_PIO_SET_VOICE_BUF_CBO: + case NV1BA0_PIO_SET_VOICE_CFG_BUF_EBO: + case NV1BA0_PIO_SET_HRIR ... NV1BA0_PIO_SET_HRIR_X - 1: + case NV1BA0_PIO_SET_HRIR_X: + case NV1BA0_PIO_SET_CURRENT_INBUF_SGE: + case NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET: + CASE_4(NV1BA0_PIO_SET_OUTBUF_BA, 8): // 8 byte pitch, 4 entries + CASE_4(NV1BA0_PIO_SET_OUTBUF_LEN, 8): // 8 byte pitch, 4 entries + case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE: + case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET: + case NV1BA0_PIO_SET_CURRENT_SSL: + case NV1BA0_PIO_SET_SSL_SEGMENT_OFFSET ... + NV1BA0_PIO_SET_SSL_SEGMENT_LENGTH+8*64-1: + case NV1BA0_PIO_SET_VOICE_SSL_A: + case NV1BA0_PIO_SET_VOICE_SSL_B: + case NV1BA0_PIO_SET_HRTF_SUBMIXES: + case NV1BA0_PIO_SET_HRTF_HEADROOM: + case NV1BA0_PIO_SET_SUBMIX_HEADROOM ... + NV1BA0_PIO_SET_SUBMIX_HEADROOM+4*(NUM_MIXBINS-1): + /* TODO: these should instead be queueing up fe commands */ + fe_method(d, addr, val); + break; + + case NV1BA0_PIO_GET_VOICE_POSITION: + case NV1BA0_PIO_SET_CONTEXT_DMA_NOTIFY: + case NV1BA0_PIO_SET_CURRENT_SSL_CONTEXT_DMA: + DPRINTF("unhandled method: %" HWADDR_PRIx " = %" HWADDR_PRIx "\n", addr, + val); + assert(0); + default: + break; + } +} + +const MemoryRegionOps vp_ops = { + .read = vp_read, + .write = vp_write, +}; + +static hwaddr get_data_ptr(hwaddr sge_base, unsigned int max_sge, uint32_t addr) +{ + unsigned int entry = addr / TARGET_PAGE_SIZE; + assert(entry <= max_sge); + uint32_t prd_address = + ldl_le_phys(&address_space_memory, sge_base + entry * 4 * 2); + // uint32_t prd_control = + // ldl_le_phys(&address_space_memory, sge_base + entry * 4 * 2 + 4); + DPRINTF("Addr: 0x%08X, control: 0x%08X\n", prd_address, prd_control); + return prd_address + addr % TARGET_PAGE_SIZE; +} + +static float voice_step_envelope(MCPXAPUState *d, uint16_t v, uint32_t reg_0, + uint32_t reg_a, uint32_t rr_reg, uint32_t rr_mask, + uint32_t lvl_reg, uint32_t lvl_mask, + uint32_t count_mask, uint32_t cur_mask) +{ + uint8_t cur = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask); + switch (cur) { + case NV_PAVS_VOICE_PAR_STATE_EFCUR_OFF: + voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, 0); + voice_set_mask(d, v, lvl_reg, lvl_mask, 0xFF); + return 1.0f; + case NV_PAVS_VOICE_PAR_STATE_EFCUR_DELAY: { + uint16_t count = + voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask); + voice_set_mask(d, v, lvl_reg, lvl_mask, 0x00); // FIXME: Confirm this? + + if (count == 0) { + cur++; + voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur); + count = 0; + } else { + count--; + } + voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count); + return 0.0f; + } + case NV_PAVS_VOICE_PAR_STATE_EFCUR_ATTACK: { + uint16_t count = + voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask); + uint16_t attack_rate = + voice_get_mask(d, v, reg_0, NV_PAVS_VOICE_CFG_ENV0_EA_ATTACKRATE); + + float value; + if (attack_rate == 0) { + // FIXME: [division by zero] + // Got crackling sound in hardware for amplitude env. + value = 255.0f; + } else { + if (count <= (attack_rate * 16)) { + value = (count * 0xFF) / (attack_rate * 16); + } else { + // FIXME: Overflow in hardware + // The actual value seems to overflow, but not sure how + value = 255.0f; + } + } + voice_set_mask(d, v, lvl_reg, lvl_mask, value); + // FIXME: Comparison could also be the other way around?! Test please. + if (count == (attack_rate * 16)) { + cur++; + voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur); + uint16_t hold_time = + voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME); + count = hold_time * 16; // FIXME: Skip next phase if count is 0? + // [other instances too] + } else { + count++; + } + voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count); + return value / 255.0f; + } + case NV_PAVS_VOICE_PAR_STATE_EFCUR_HOLD: { + uint16_t count = + voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask); + voice_set_mask(d, v, lvl_reg, lvl_mask, 0xFF); + + if (count == 0) { + cur++; + voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur); + uint16_t decay_rate = voice_get_mask( + d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_DECAYRATE); + count = decay_rate * 16; + } else { + count--; + } + voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count); + return 1.0f; + } + case NV_PAVS_VOICE_PAR_STATE_EFCUR_DECAY: { + uint16_t count = + voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask); + uint16_t decay_rate = + voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_DECAYRATE); + uint8_t sustain_level = + voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_SUSTAINLEVEL); + + // FIXME: Decay should return a value no less than sustain + float value; + if (decay_rate == 0) { + value = 0.0f; + } else { + // FIXME: This formula and threshold is not accurate, but I can't + // get it any better for now + value = 255.0f * powf(0.99988799f, (decay_rate * 16 - count) * + 4096 / decay_rate); + } + if (value <= (sustain_level + 0.2f) || (value > 255.0f)) { + // FIXME: Should we still update lvl? + cur++; + voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur); + } else { + count--; + voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count); + voice_set_mask(d, v, lvl_reg, lvl_mask, value); + } + return value / 255.0f; + } + case NV_PAVS_VOICE_PAR_STATE_EFCUR_SUSTAIN: { + uint8_t sustain_level = + voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_SUSTAINLEVEL); + voice_set_mask( + d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, + 0x00); // FIXME: is this only set to 0 once or forced to zero? + voice_set_mask(d, v, lvl_reg, lvl_mask, sustain_level); + return sustain_level / 255.0f; + } + case NV_PAVS_VOICE_PAR_STATE_EFCUR_RELEASE: { + uint16_t count = + voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask); + uint16_t release_rate = voice_get_mask(d, v, rr_reg, rr_mask); + + if (release_rate == 0) { + count = 0; + } + + float value = 0; + if (count == 0) { + voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, ++cur); + } else { + // FIXME: Appears to be an exponential but unsure about actual + // curve; performing standard decay of current level to T60 over the + // release interval which seems about right. + // FIXME: Based on sustain level or just decay of current level? + // FIXME: Update level? A very similar, alternative decay function + // (probably what the hw actually does): y(t)=2^(-10t), which would + // permit simpler attenuation more efficiently and update level on + // each round. + float pos = clampf(1 - count / (release_rate * 16.0), 0, 1); + uint8_t lvl = voice_get_mask(d, v, lvl_reg, lvl_mask); + value = powf(M_E, -6.91*pos)*lvl; + count--; // FIXME: Should release count ascend or descend? + voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count); + } + + return value / 255.0f; + } + case NV_PAVS_VOICE_PAR_STATE_EFCUR_FORCE_RELEASE: + if (count_mask == NV_PAVS_VOICE_CUR_ECNT_EACOUNT) { + voice_off(d, v); + } + return 0.0f; + default: + fprintf(stderr, "Unknown envelope state 0x%x\n", cur); + assert(false); + return 0.0f; + } +} + +static void set_notify_status(MCPXAPUState *d, uint32_t v, int notifier, + int status) +{ + hwaddr notify_offset = d->regs[NV_PAPU_FENADDR]; + notify_offset += 16 * (MCPX_HW_NOTIFIER_BASE_OFFSET + + v * MCPX_HW_NOTIFIER_COUNT + notifier); + notify_offset += 15; // Final byte is status, same for all notifiers + + // FIXME: Check notify enable + // FIXME: Set NV1BA0_NOTIFICATION_STATUS_IN_PROGRESS when appropriate + stb_phys(&address_space_memory, notify_offset, status); + + // FIXME: Refactor this out of here + // FIXME: Actually provied current envelope state + stb_phys(&address_space_memory, notify_offset - 1, 1); + + qatomic_or(&d->regs[NV_PAPU_ISTS], + NV_PAPU_ISTS_FEVINTSTS | NV_PAPU_ISTS_FENINTSTS); + d->set_irq = true; +} + +static long voice_resample_callback(void *cb_data, float **data) +{ + MCPXAPUVoiceFilter *filter = cb_data; + uint16_t v = filter->voice; + assert(v < MCPX_HW_MAX_VOICES); + MCPXAPUState *d = container_of(filter, MCPXAPUState, vp.filters[v]); + + int sample_count = 0; + while (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) { + break; + } + int count = voice_get_samples( + d, v, (float(*)[2]) & filter->resample_buf[2 * sample_count], + NUM_SAMPLES_PER_FRAME - sample_count); + if (count < 0) { + break; + } + sample_count += count; + } + + if (sample_count < NUM_SAMPLES_PER_FRAME) { + /* Starvation causes SRC hang on repeated calls. Provide silence. */ + memset(&filter->resample_buf[2*sample_count], 0, + 2*(NUM_SAMPLES_PER_FRAME-sample_count)*sizeof(float)); + sample_count = NUM_SAMPLES_PER_FRAME; + } + + *data = filter->resample_buf; + return sample_count; +} + +static int voice_resample(MCPXAPUState *d, uint16_t v, float samples[][2], + int requested_num, float rate) +{ + assert(v < MCPX_HW_MAX_VOICES); + MCPXAPUVoiceFilter *filter = &d->vp.filters[v]; + + if (filter->resampler == NULL) { + filter->voice = v; + int err; + + /* Note: Using a sinc based resampler for quality. Unsure about + * hardware's actual interpolation method; it could just be linear, in + * which case using this resampler is overkill, but quality is good + * so use it for now. + */ + // FIXME: Don't do 2ch resampling if this is a mono voice + filter->resampler = src_callback_new(&voice_resample_callback, + SRC_SINC_FASTEST, 2, &err, filter); + if (filter->resampler == NULL) { + fprintf(stderr, "src error: %s\n", src_strerror(err)); + assert(0); + } + } + + int count = src_callback_read(filter->resampler, rate, requested_num, + (float *)samples); + if (count == -1) { + DPRINTF("resample error\n"); + } + if (count != requested_num) { + DPRINTF("resample returned fewer than expected: %d\n", count); + + if (count == 0) + return -1; + } + + return count; +} + +static void voice_reset_filters(MCPXAPUState *d, uint16_t v) +{ + assert(v < MCPX_HW_MAX_VOICES); + memset(&d->vp.filters[v].svf, 0, sizeof(d->vp.filters[v].svf)); + if (d->vp.filters[v].resampler) { + src_reset(d->vp.filters[v].resampler); + } +} + +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 get_voice_bin_src_dst(MCPXAPUState *d, int v, + uint32_t *src, uint32_t *dst, uint32_t *clr) +{ + uint32_t src_v = 0; + uint32_t dst_v = 0; + uint32_t clr_v = 0; + + bool multipass = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, + NV_PAVS_VOICE_CFG_FMT_MULTIPASS); + if (multipass) { + int mp_bin = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, + NV_PAVS_VOICE_CFG_FMT_MULTIPASS_BIN); + bool clear_mix = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, + NV_PAVS_VOICE_CFG_FMT_CLEAR_MIX); + src_v |= (1 << mp_bin); + if (clear_mix) { + clr_v |= (1 << mp_bin); + } + } + + int bin[8]; + if (v < MCPX_HW_MAX_3D_VOICES) { + bin[0] = d->vp.hrtf_submix[0]; + bin[1] = d->vp.hrtf_submix[1]; + bin[2] = d->vp.hrtf_submix[2]; + bin[3] = d->vp.hrtf_submix[3]; + } else { + bin[0] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, + NV_PAVS_VOICE_CFG_VBIN_V0BIN); + bin[1] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, + NV_PAVS_VOICE_CFG_VBIN_V1BIN); + bin[2] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, + NV_PAVS_VOICE_CFG_VBIN_V2BIN); + bin[3] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, + NV_PAVS_VOICE_CFG_VBIN_V3BIN); + } + bin[4] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, + NV_PAVS_VOICE_CFG_VBIN_V4BIN); + bin[5] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, + NV_PAVS_VOICE_CFG_VBIN_V5BIN); + bin[6] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, + NV_PAVS_VOICE_CFG_FMT_V6BIN); + bin[7] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, + NV_PAVS_VOICE_CFG_FMT_V7BIN); + + for (int i = 0; i < 8; i++) { + dst_v |= 1 << bin[i]; + } + + if (src) { + *src = src_v; + } + if (dst) { + *dst = dst_v; + } + if (clr) { + *clr = clr_v; + } +} + +static void *voice_worker_thread(void *arg) +{ + MCPXAPUState *d = arg; + VoiceWorkDispatch *vwd = &d->vp.voice_work_dispatch; + + rcu_register_thread(); + qemu_mutex_lock(&vwd->lock); + + int worker_id = ctz64(vwd->workers_pending); + VoiceWorker *self = &d->vp.voice_work_dispatch.workers[worker_id]; + self->queue_len = 0; + + do { + int64_t start_time = qemu_clock_get_us(QEMU_CLOCK_REALTIME); + g_dbg.vp.workers[worker_id].num_voices = self->queue_len; + + if (self->queue_len) { + qemu_mutex_unlock(&vwd->lock); + + // Process queued voices + memset(self->mixbins, 0, sizeof(self->mixbins)); + if (d->mon == MCPX_APU_DEBUG_MON_VP) { + memset(self->sample_buf, 0, sizeof(self->sample_buf)); + } + for (int i = 0; i < self->queue_len; i++) { + voice_process(d, self->mixbins, self->sample_buf, + self->queue[i].voice, self->queue[i].list); + } + + qemu_mutex_lock(&vwd->lock); + + // Add voice contributions + for (int b = 0; b < NUM_MIXBINS; b++) { + for (int s = 0; s < NUM_SAMPLES_PER_FRAME; s++) { + vwd->mixbins[b][s] += self->mixbins[b][s]; + } + } + if (d->mon == MCPX_APU_DEBUG_MON_VP) { + for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) { + d->vp.sample_buf[i][0] += self->sample_buf[i][0]; + d->vp.sample_buf[i][1] += self->sample_buf[i][1]; + } + } + + self->queue_len = 0; + } + + vwd->workers_pending &= ~(1 << worker_id); + if (!vwd->workers_pending) { + qemu_cond_signal(&vwd->work_finished); + } + + int64_t end_time = qemu_clock_get_us(QEMU_CLOCK_REALTIME); + g_dbg.vp.workers[worker_id].time_us = end_time - start_time; + + qemu_cond_wait(&vwd->work_pending, &vwd->lock); + } while (!vwd->workers_should_exit); + + rcu_unregister_thread(); + return NULL; +} + +static void voice_work_acquire_voice_lock_for_processing(MCPXAPUState *d, int v) +{ + qemu_spin_lock(&d->vp.voice_spinlocks[v]); + while (is_voice_locked(d, v)) { + /* Stall until voice is available */ + qemu_spin_unlock(&d->vp.voice_spinlocks[v]); + qemu_cond_wait(&d->cond, &d->lock); + qemu_spin_lock(&d->vp.voice_spinlocks[v]); + } +} + +static void voice_work_enqueue(MCPXAPUState *d, int v, int list) +{ + VoiceWorkDispatch *vwd = &d->vp.voice_work_dispatch; + + assert(vwd->queue_len < ARRAY_SIZE(vwd->queue)); + vwd->queue[vwd->queue_len++] = (VoiceWorkItem){ + .voice = v, + .list = list, + }; + + voice_work_acquire_voice_lock_for_processing(d, v); +} + +static void voice_work_release_voice_locks(MCPXAPUState *d) +{ + VoiceWorkDispatch *vwd = &d->vp.voice_work_dispatch; + + for (int i = 0; i < vwd->queue_len; i++) { + qemu_spin_unlock(&d->vp.voice_spinlocks[vwd->queue[i].voice]); + } +} + +static void voice_work_schedule(MCPXAPUState *d) +{ + VoiceWorkDispatch *vwd = &d->vp.voice_work_dispatch; + int next_worker_to_schedule = 0; + bool group = false; + uint32_t dirty = 0; + + for (int i = 0; i < vwd->queue_len; i++) { + uint32_t src, dst, clr; + get_voice_bin_src_dst(d, vwd->queue[i].voice, &src, &dst, &clr); + + // TODO: To simplify submix scheduling, we make a few assumptions based + // on Xbox software observations. However, the configurability of + // multipass sources suggests the hardware may not be so strict. We'll + // defer making this more robust for now. + // + // We currently assume that: + // + // - MP bin is constant + assert(!src || (src == MULTIPASS_BIN_MASK)); + // + // - MP voice always clears MP bin + assert(!src || (clr == MULTIPASS_BIN_MASK)); + // + // - MP source voices are ordered consecutively in voice lists + assert(src || (dst & MULTIPASS_BIN_MASK) || + !(dirty & MULTIPASS_BIN_MASK)); + + if ((dst & MULTIPASS_BIN_MASK) & ~dirty) { + group = true; + } + + // Assign voice to worker + VoiceWorker *worker = &vwd->workers[next_worker_to_schedule]; + worker->queue[worker->queue_len++] = vwd->queue[i]; + vwd->workers_pending |= 1 << next_worker_to_schedule; + + dirty = (dirty & ~clr) | dst; + if (clr & MULTIPASS_BIN_MASK) { + group = false; + } + + if (!group) { + next_worker_to_schedule = + (next_worker_to_schedule + 1) % NUM_VOICE_WORKERS; + } + } +} + +static void +voice_work_dispatch(MCPXAPUState *d, + float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME]) +{ + VoiceWorkDispatch *vwd = &d->vp.voice_work_dispatch; + + int64_t start_time = qemu_clock_get_us(QEMU_CLOCK_REALTIME); + + qemu_mutex_lock(&vwd->lock); + + if (vwd->queue_len) { + memset(vwd->mixbins, 0, sizeof(vwd->mixbins)); + + // Signal workers and wait for completion + voice_work_schedule(d); + qemu_cond_broadcast(&vwd->work_pending); + qemu_cond_wait(&vwd->work_finished, &vwd->lock); + assert(!vwd->workers_pending); + voice_work_release_voice_locks(d); + vwd->queue_len = 0; + + // Add voice contributions + for (int b = 0; b < NUM_MIXBINS; b++) { + for (int s = 0; s < NUM_SAMPLES_PER_FRAME; s++) { + mixbins[b][s] += vwd->mixbins[b][s]; + } + } + } + + int64_t end_time = qemu_clock_get_us(QEMU_CLOCK_REALTIME); + g_dbg.vp.total_worker_time_us = end_time - start_time; + + qemu_mutex_unlock(&vwd->lock); +} + +static void voice_work_init(MCPXAPUState *d) +{ + VoiceWorkDispatch *vwd = &d->vp.voice_work_dispatch; + + vwd->workers_should_exit = false; + vwd->workers_pending = 0; + vwd->queue_len = 0; + + qemu_mutex_init(&vwd->lock); + qemu_mutex_lock(&vwd->lock); + qemu_cond_init(&vwd->work_pending); + qemu_cond_init(&vwd->work_finished); + for (int i = 0; i < NUM_VOICE_WORKERS; i++) { + vwd->workers_pending |= 1 << i; + qemu_thread_create(&vwd->workers[i].thread, "mcpx.voice_worker", + voice_worker_thread, d, QEMU_THREAD_JOINABLE); + } + qemu_cond_wait(&vwd->work_finished, &vwd->lock); + assert(!vwd->workers_pending); + qemu_mutex_unlock(&vwd->lock); +} + +static void voice_work_finalize(MCPXAPUState *d) +{ + VoiceWorkDispatch *vwd = &d->vp.voice_work_dispatch; + + qemu_mutex_lock(&vwd->lock); + vwd->workers_should_exit = true; + qemu_cond_broadcast(&vwd->work_pending); + qemu_mutex_unlock(&vwd->lock); + for (int i = 0; i < NUM_VOICE_WORKERS; i++) { + qemu_thread_join(&vwd->workers[i].thread); + } +} + +static void voice_process(MCPXAPUState *d, + float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME], + float sample_buf[NUM_SAMPLES_PER_FRAME][2], + uint16_t v, int voice_list) +{ + assert(v < MCPX_HW_MAX_VOICES); + bool stereo = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, + NV_PAVS_VOICE_CFG_FMT_STEREO); + unsigned int channels = stereo ? 2 : 1; + bool paused = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE, + NV_PAVS_VOICE_PAR_STATE_PAUSED); + + struct McpxApuDebugVoice *dbg = &g_dbg.vp.v[v]; + dbg->active = true; + dbg->stereo = stereo; + dbg->paused = paused; + + if (paused) { + return; + } + + float ef_value = voice_step_envelope( + d, v, NV_PAVS_VOICE_CFG_ENV1, NV_PAVS_VOICE_CFG_ENVF, + NV_PAVS_VOICE_CFG_MISC, NV_PAVS_VOICE_CFG_MISC_EF_RELEASERATE, + NV_PAVS_VOICE_PAR_NEXT, NV_PAVS_VOICE_PAR_NEXT_EFLVL, + NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, NV_PAVS_VOICE_PAR_STATE_EFCUR); + assert(ef_value >= 0.0f); + assert(ef_value <= 1.0f); + int16_t p = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_PITCH_LINK, + NV_PAVS_VOICE_TAR_PITCH_LINK_PITCH); + int8_t ps = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_ENV0, + NV_PAVS_VOICE_CFG_ENV0_EF_PITCHSCALE); + float rate = 1.0 / powf(2.0f, (p + ps * 32 * ef_value) / 4096.0f); + dbg->rate = rate; + + float ea_value = voice_step_envelope( + d, v, NV_PAVS_VOICE_CFG_ENV0, NV_PAVS_VOICE_CFG_ENVA, + NV_PAVS_VOICE_TAR_LFO_ENV, NV_PAVS_VOICE_TAR_LFO_ENV_EA_RELEASERATE, + NV_PAVS_VOICE_PAR_OFFSET, NV_PAVS_VOICE_PAR_OFFSET_EALVL, + NV_PAVS_VOICE_CUR_ECNT_EACOUNT, NV_PAVS_VOICE_PAR_STATE_EACUR); + assert(ea_value >= 0.0f); + assert(ea_value <= 1.0f); + + float samples[NUM_SAMPLES_PER_FRAME][2] = { 0 }; + + 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 active = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE, + NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE); + if (!active) { + return; + } + + int bin[8]; + bin[0] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, + NV_PAVS_VOICE_CFG_VBIN_V0BIN); + bin[1] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, + NV_PAVS_VOICE_CFG_VBIN_V1BIN); + bin[2] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, + NV_PAVS_VOICE_CFG_VBIN_V2BIN); + bin[3] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, + NV_PAVS_VOICE_CFG_VBIN_V3BIN); + bin[4] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, + NV_PAVS_VOICE_CFG_VBIN_V4BIN); + bin[5] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, + NV_PAVS_VOICE_CFG_VBIN_V5BIN); + bin[6] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, + NV_PAVS_VOICE_CFG_FMT_V6BIN); + bin[7] = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, + NV_PAVS_VOICE_CFG_FMT_V7BIN); + + if (v < MCPX_HW_MAX_3D_VOICES) { + bin[0] = d->vp.hrtf_submix[0]; + bin[1] = d->vp.hrtf_submix[1]; + bin[2] = d->vp.hrtf_submix[2]; + bin[3] = d->vp.hrtf_submix[3]; + } + + uint16_t vol[8]; + vol[0] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA, + NV_PAVS_VOICE_TAR_VOLA_VOLUME0); + vol[1] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA, + NV_PAVS_VOICE_TAR_VOLA_VOLUME1); + vol[2] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB, + NV_PAVS_VOICE_TAR_VOLB_VOLUME2); + vol[3] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB, + NV_PAVS_VOICE_TAR_VOLB_VOLUME3); + vol[4] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC, + NV_PAVS_VOICE_TAR_VOLC_VOLUME4); + vol[5] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC, + NV_PAVS_VOICE_TAR_VOLC_VOLUME5); + + vol[6] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC, + NV_PAVS_VOICE_TAR_VOLC_VOLUME6_B11_8) << 8; + vol[6] |= voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB, + NV_PAVS_VOICE_TAR_VOLB_VOLUME6_B7_4) << 4; + vol[6] |= voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA, + NV_PAVS_VOICE_TAR_VOLA_VOLUME6_B3_0); + vol[7] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC, + NV_PAVS_VOICE_TAR_VOLC_VOLUME7_B11_8) << 8; + vol[7] |= voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB, + NV_PAVS_VOICE_TAR_VOLB_VOLUME7_B7_4) << 4; + vol[7] |= voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA, + NV_PAVS_VOICE_TAR_VOLA_VOLUME7_B3_0); + + // FIXME: If phase negations means to flip the signal upside down + // we should modify volume of bin6 and bin7 here. + + for (int i = 0; i < 8; i++) { + dbg->bin[i] = bin[i]; + dbg->vol[i] = vol[i]; + } + + if (voice_should_mute(v)) { + return; + } + + int fmode = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_MISC, + NV_PAVS_VOICE_CFG_MISC_FMODE); + + // FIXME: Move to function + bool lpf = false; + if (v < MCPX_HW_MAX_3D_VOICES) { + /* 1:DLS2+I3DL2 2:ParaEQ+I3DL2 3:I3DL2 */ + lpf = (fmode == 1); + } else { + /* 0:Bypass 1:DLS2 2:ParaEQ 3(Mono):DLS2+ParaEQ 3(Stereo):Bypass */ + lpf = stereo ? (fmode == 1) : (fmode & 1); + } + if (lpf) { + for (int ch = 0; ch < 2; ch++) { + // FIXME: Cutoff modulation via NV_PAVS_VOICE_CFG_ENV1_EF_FCSCALE + int16_t fc = voice_get_mask( + d, v, NV_PAVS_VOICE_TAR_FCA + (ch % channels) * 4, + NV_PAVS_VOICE_TAR_FCA_FC0); + float fc_f = clampf(pow(2, fc / 4096.0), 0.003906f, 1.0f); + uint16_t q = voice_get_mask( + d, v, NV_PAVS_VOICE_TAR_FCA + (ch % channels) * 4, + NV_PAVS_VOICE_TAR_FCA_FC1); + float q_f = clampf(q / (1.0 * 0x8000), 0.079407f, 1.0f); + sv_filter *filter = &d->vp.filters[v].svf[ch]; + setup_svf(filter, fc_f, q_f, F_LP); + for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) { + samples[i][ch] = run_svf(filter, samples[i][ch]); + samples[i][ch] = fmin(fmax(samples[i][ch], -1.0), 1.0); + } + } + } + + if (v < MCPX_HW_MAX_3D_VOICES && g_config.audio.hrtf) { + uint16_t hrtf_handle = + voice_get_mask(d, v, NV_PAVS_VOICE_CFG_HRTF_TARGET, + NV_PAVS_VOICE_CFG_HRTF_TARGET_HANDLE); + if (hrtf_handle != HRTF_NULL_HANDLE) { + hrtf_filter_process(&d->vp.filters[v].hrtf, samples, samples); + } + } + + // FIXME: ParaEQ + + for (int b = 0; b < 8; b++) { + float g = ea_value; + float hr; + if ((v < MCPX_HW_MAX_3D_VOICES) && (b < 4)) { + // FIXME: Not sure if submix/voice headroom factor in for HRTF + hr = 1 << d->vp.hrtf_headroom; + } else { + hr = 1 << d->vp.submix_headroom[bin[b]]; + } + g *= attenuate(vol[b])/hr; + for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) { + mixbins[bin[b]][i] += g*samples[i][b % channels]; + } + } + + if (d->mon == MCPX_APU_DEBUG_MON_VP) { + /* 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); + } + g *= ea_value; + for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) { + sample_buf[i][0] += g*samples[i][0]; + sample_buf[i][1] += g*samples[i][1]; + } + } +} + +static int voice_get_samples(MCPXAPUState *d, uint32_t v, float samples[][2], + int num_samples_requested) +{ + assert(v < MCPX_HW_MAX_VOICES); + bool stereo = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, + NV_PAVS_VOICE_CFG_FMT_STEREO); + unsigned int channels = stereo ? 2 : 1; + unsigned int sample_size = voice_get_mask( + d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE); + unsigned int container_sizes[4] = { 1, 2, 0, 4 }; /* B8, B16, ADPCM, B32 */ + unsigned int container_size_index = voice_get_mask( + d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE); + unsigned int container_size = container_sizes[container_size_index]; + bool stream = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, + NV_PAVS_VOICE_CFG_FMT_DATA_TYPE); + bool paused = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE, + NV_PAVS_VOICE_PAR_STATE_PAUSED); + 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); + unsigned int samples_per_block = + 1 + voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, + NV_PAVS_VOICE_CFG_FMT_SAMPLES_PER_BLOCK); + bool persist = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, + NV_PAVS_VOICE_CFG_FMT_PERSIST); + bool multipass = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, + NV_PAVS_VOICE_CFG_FMT_MULTIPASS); + 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; + int count = 0; + int seg_len = 0; + int seg_cs = 0; + int seg_spb = 0; + int seg_s = 0; + hwaddr segment_offset = 0; + uint32_t segment_length = 0; + size_t block_size; + + int adpcm_block_index = -1; + uint32_t adpcm_block[36*2/4]; + int16_t adpcm_decoded[65*2]; // FIXME: Move out of here + + // FIXME: Only update if necessary + 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 = samples_per_block; + dbg->persist = persist; + dbg->multipass = multipass; + dbg->linked = linked; + + // This is probably cleared when the first sample is played + // FIXME: How will this behave if CBO > EBO on first play? + // FIXME: How will this behave if paused? + voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, + NV_PAVS_VOICE_PAR_STATE_NEW_VOICE, 0); + + if (paused) { + return -1; + } + + if (stream) { + if (!persist) { + // FIXME: Confirm. Unsure if this should wait until end of SSL or + // terminate immediately. Definitely not before end of envelope. + int eacur = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE, + NV_PAVS_VOICE_PAR_STATE_EACUR); + if (eacur < NV_PAVS_VOICE_PAR_STATE_EFCUR_RELEASE) { + DPRINTF("Voice %d envelope not in release state (%d) and " + "persist is not set. Ending stream now!\n", + v, eacur); + voice_off(d, v); + return -1; + } + } + + DPRINTF("**** STREAMING (%d) ****\n", v); + assert(!loop); + + ssl_index = d->vp.ssl[v].ssl_index; + ssl_seg = d->vp.ssl[v].ssl_seg; + page = d->vp.ssl[v].base[ssl_index] + ssl_seg; + count = d->vp.ssl[v].count[ssl_index]; + + // Check to see if the stream has ended + if (count == 0) { + DPRINTF("Stream has ended\n"); + voice_set_mask(d, v, NV_PAVS_VOICE_PAR_OFFSET, + NV_PAVS_VOICE_PAR_OFFSET_CBO, 0); + d->vp.ssl[v].ssl_seg = 0; + if (!persist) { + d->vp.ssl[v].ssl_index = 0; + voice_off(d, v); + } else { + set_notify_status( + d, v, MCPX_HW_NOTIFIER_SSLA_DONE + d->vp.ssl[v].ssl_index, + NV1BA0_NOTIFICATION_STATUS_DONE_SUCCESS); + } + return -1; + } + + hwaddr addr = d->regs[NV_PAPU_VPSSLADDR] + page * 8; + segment_offset = ldl_le_phys(&address_space_memory, addr); + segment_length = ldl_le_phys(&address_space_memory, addr + 4); + assert(segment_offset != 0); + assert(segment_length != 0); + seg_len = (segment_length >> 0) & 0xffff; + seg_cs = (segment_length >> 16) & 3; + seg_spb = (segment_length >> 18) & 0x1f; + seg_s = (segment_length >> 23) & 1; + assert(seg_cs == container_size_index); + assert((seg_spb + 1) == samples_per_block); + assert(seg_s == stereo); + container_size_index = seg_cs; + if (seg_cs == NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_ADPCM) { + sample_size = NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S24; + } + + assert(seg_len > 0); + ebo = seg_len - 1; // FIXME: Confirm seg_len-1 is last valid sample index + + DPRINTF("Segment: SSL%c[%d]\n", 'A' + ssl_index, ssl_seg); + DPRINTF("Page: %x\n", page); + DPRINTF("Count: %d\n", count); + DPRINTF("Segment offset: 0x%" HWADDR_PRIx "\n", segment_offset); + DPRINTF("Segment length: %x\n", segment_length); + DPRINTF("...len = 0x%x\n", seg_len); + DPRINTF("...cs = %d (%s)\n", seg_cs, container_size_str[seg_cs]); + DPRINTF("...spb = %d\n", seg_spb); + DPRINTF("...s = %d (%s)\n", seg_s, seg_s ? "stereo" : "mono"); + } else { + DPRINTF("**** BUFFER (%d) ****\n", v); + } + + bool adpcm = + (container_size_index == NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_ADPCM); + + if (adpcm) { + block_size = 36; + DPRINTF("ADPCM:\n"); + } else { + assert(container_size_index < 4); + assert(sample_size < 4); + block_size = container_size; + DPRINTF("PCM:\n"); + DPRINTF(" Container Size: %s\n", + container_size_str[container_size_index]); + DPRINTF(" Sample Size: %s\n", sample_size_str[sample_size]); + } + + DPRINTF("CBO=%d EBO=%d\n", cbo, ebo); + + block_size *= samples_per_block; + + // FIXME: Restructure this loop + int sample_count = 0; + for (; (sample_count < num_samples_requested) && (cbo <= ebo); + sample_count++, cbo++) { + if (adpcm) { + unsigned int block_index = cbo / ADPCM_SAMPLES_PER_BLOCK; + unsigned int block_position = cbo % ADPCM_SAMPLES_PER_BLOCK; + if (adpcm_block_index != block_index) { + uint32_t linear_addr = block_index * block_size; + if (stream) { + hwaddr addr = segment_offset + linear_addr; + int max_seg_byte = (seg_len >> 6) * block_size; + assert(linear_addr + block_size <= max_seg_byte); + memcpy(adpcm_block, &d->ram_ptr[addr], + block_size); // FIXME: Use idiomatic DMA function + } else { + linear_addr += ba; + for (unsigned int word_index = 0; + word_index < (9 * samples_per_block); word_index++) { + hwaddr addr = get_data_ptr(d->regs[NV_PAPU_VPSGEADDR], + 0xFFFFFFFF, linear_addr); + adpcm_block[word_index] = + ldl_le_phys(&address_space_memory, addr); + linear_addr += 4; + } + } + adpcm_decode_block(adpcm_decoded, (uint8_t *)adpcm_block, + block_size, channels); + adpcm_block_index = block_index; + } + + samples[sample_count][0] = + int16_to_float(adpcm_decoded[block_position * channels]); + if (stereo) { + samples[sample_count][1] = int16_to_float( + adpcm_decoded[block_position * channels + 1]); + } + } else { + // FIXME: Handle reading accross pages?! + + hwaddr addr; + if (stream) { + addr = segment_offset + cbo * block_size; + } else { + uint32_t linear_addr = ba + cbo * block_size; + addr = get_data_ptr(d->regs[NV_PAPU_VPSGEADDR], 0xFFFFFFFF, + linear_addr); + } + + for (unsigned int channel = 0; channel < channels; channel++) { + uint32_t ival; + float fval; + switch (sample_size) { + case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_U8: + ival = ldub_phys(&address_space_memory, addr); + fval = uint8_to_float(ival & 0xff); + break; + case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S16: + ival = lduw_le_phys(&address_space_memory, addr); + fval = int16_to_float(ival & 0xffff); + break; + case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S24: + ival = ldl_le_phys(&address_space_memory, addr); + fval = int24_to_float(ival); + break; + case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S32: + ival = ldl_le_phys(&address_space_memory, addr); + fval = int32_to_float(ival); + break; + default: + assert(false); + break; + } + samples[sample_count][channel] = fval; + addr += container_size; + } + } + + if (!stereo) { + samples[sample_count][1] = samples[sample_count][0]; + } + } + + if (cbo >= ebo) { + if (stream) { + d->vp.ssl[v].ssl_seg += 1; + cbo = 0; + if (d->vp.ssl[v].ssl_seg < d->vp.ssl[v].count[ssl_index]) { + DPRINTF("SSL%c[%d]\n", 'A' + ssl_index, d->vp.ssl[v].ssl_seg); + } else { + int next_index = (ssl_index + 1) % 2; + DPRINTF("SSL%c\n", 'A' + next_index); + d->vp.ssl[v].ssl_index = next_index; + d->vp.ssl[v].ssl_seg = 0; + set_notify_status(d, v, MCPX_HW_NOTIFIER_SSLA_DONE + ssl_index, + NV1BA0_NOTIFICATION_STATUS_DONE_SUCCESS); + } + } else { + if (loop) { + cbo = lbo; + } else { + cbo = ebo; + voice_off(d, v); + DPRINTF("end of buffer!\n"); + } + } + } + + voice_set_mask(d, v, NV_PAVS_VOICE_PAR_OFFSET, + NV_PAVS_VOICE_PAR_OFFSET_CBO, cbo); + return sample_count; +} + +void mcpx_apu_vp_frame(MCPXAPUState *d, float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME]) +{ + memset(d->vp.sample_buf, 0, sizeof(d->vp.sample_buf)); + + /* Process all voices, mixing each into the affected MIXBINs */ + for (int list = 0; list < 3; list++) { + hwaddr top, current, next; + top = voice_list_regs[list].top; + current = voice_list_regs[list].current; + next = voice_list_regs[list].next; + + d->regs[current] = d->regs[top]; + DPRINTF("list %d current voice %d\n", list, d->regs[current]); + + for (int i = 0; d->regs[current] != 0xFFFF; i++) { + /* Make sure not to get stuck... */ + if (i >= MCPX_HW_MAX_VOICES) { + DPRINTF("Voice list contains invalid entry!\n"); + break; + } + + uint16_t v = d->regs[current]; + d->regs[next] = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_PITCH_LINK, + NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE); + if (!voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE, + NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE)) { + fe_method(d, SE2FE_IDLE_VOICE, v); + } else { + voice_work_enqueue(d, v, list); + } + d->regs[current] = d->regs[next]; + } + } + voice_work_dispatch(d, mixbins); + + if (d->mon == MCPX_APU_DEBUG_MON_VP) { + /* Mix all voices together to hear any audible voice */ + int16_t isamp[NUM_SAMPLES_PER_FRAME * 2]; + src_float_to_short_array((float *)d->vp.sample_buf, isamp, + NUM_SAMPLES_PER_FRAME * 2); + int off = (d->ep_frame_div % 8) * NUM_SAMPLES_PER_FRAME; + for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) { + d->apu_fifo_output[off + i][0] += isamp[2*i]; + d->apu_fifo_output[off + i][1] += isamp[2*i+1]; + } + + memset(d->vp.sample_buf, 0, sizeof(d->vp.sample_buf)); + memset(mixbins, 0, sizeof(float[32][32])); + } +} + +void mcpx_apu_vp_init(MCPXAPUState *d) +{ + for (int i = 0; i < MCPX_HW_MAX_VOICES; i++) { + qemu_spin_init(&d->vp.voice_spinlocks[i]); + } + + voice_work_init(d); +} + +void mcpx_apu_vp_finalize(MCPXAPUState *d) +{ + voice_work_finalize(d); +} + +void mcpx_apu_vp_reset(MCPXAPUState *d) +{ + d->vp.ssl_base_page = 0; + d->vp.hrtf_headroom = 0; + memset(d->vp.ssl, 0, sizeof(d->vp.ssl)); + memset(d->vp.hrtf_submix, 0, sizeof(d->vp.hrtf_submix)); + memset(d->vp.submix_headroom, 0, sizeof(d->vp.submix_headroom)); + memset(d->vp.voice_locked, 0, sizeof(d->vp.voice_locked)); + for (int v = 0; v < ARRAY_SIZE(d->vp.filters); v++) { + hrtf_filter_init(&d->vp.filters[v].hrtf); + } +} \ No newline at end of file