mirror of https://github.com/xemu-project/xemu.git
2810 lines
102 KiB
C
2810 lines
102 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include <math.h>
|
|
#include <samplerate.h>
|
|
#include <SDL.h>
|
|
#include "hw/hw.h"
|
|
#include "hw/pci/pci.h"
|
|
#include "hw/pci/pci_device.h"
|
|
#include "cpu.h"
|
|
#include "migration/vmstate.h"
|
|
#include "qemu/main-loop.h"
|
|
#include "sysemu/runstate.h"
|
|
#include "audio/audio.h"
|
|
#include "qemu/fifo8.h"
|
|
#include "ui/xemu-settings.h"
|
|
|
|
#include "trace.h"
|
|
#include "dsp/dsp.h"
|
|
#include "dsp/dsp_dma.h"
|
|
#include "dsp/dsp_cpu.h"
|
|
#include "dsp/dsp_state.h"
|
|
#include "apu.h"
|
|
#include "apu_regs.h"
|
|
#include "apu_debug.h"
|
|
#include "adpcm.h"
|
|
#include "svf.h"
|
|
#include "fpconv.h"
|
|
|
|
#define GET_MASK(v, mask) (((v) & (mask)) >> ctz32(mask))
|
|
|
|
#define SET_MASK(v, mask, val) \
|
|
do { \
|
|
(v) &= ~(mask); \
|
|
(v) |= ((val) << ctz32(mask)) & (mask); \
|
|
} while (0)
|
|
|
|
#define CASE_4(v, step) \
|
|
case (v): \
|
|
case (v)+(step): \
|
|
case (v)+(step)*2: \
|
|
case (v)+(step)*3
|
|
|
|
// #define DEBUG_MCPX
|
|
|
|
#ifdef DEBUG_MCPX
|
|
#define DPRINTF(fmt, ...) \
|
|
do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
|
|
#else
|
|
#define DPRINTF(fmt, ...) \
|
|
do { } while (0)
|
|
#endif
|
|
|
|
#define MCPX_APU_DEVICE(obj) \
|
|
OBJECT_CHECK(MCPXAPUState, (obj), "mcpx-apu")
|
|
|
|
typedef struct MCPXAPUVPSSLData {
|
|
uint32_t base[MCPX_HW_SSLS_PER_VOICE];
|
|
uint8_t count[MCPX_HW_SSLS_PER_VOICE];
|
|
int ssl_index;
|
|
int ssl_seg;
|
|
} MCPXAPUVPSSLData;
|
|
|
|
typedef struct MCPXAPUVoiceFilter {
|
|
uint16_t voice;
|
|
float resample_buf[NUM_SAMPLES_PER_FRAME * 2];
|
|
SRC_STATE *resampler;
|
|
sv_filter svf[2];
|
|
} MCPXAPUVoiceFilter;
|
|
|
|
typedef struct MCPXAPUState {
|
|
/*< private >*/
|
|
PCIDevice parent_obj;
|
|
/*< public >*/
|
|
|
|
bool exiting;
|
|
bool set_irq;
|
|
|
|
QemuThread apu_thread;
|
|
QemuMutex lock;
|
|
QemuCond cond;
|
|
|
|
MemoryRegion *ram;
|
|
uint8_t *ram_ptr;
|
|
MemoryRegion mmio;
|
|
|
|
/* Setup Engine */
|
|
struct {
|
|
} se;
|
|
|
|
/* Voice Processor */
|
|
struct {
|
|
MemoryRegion mmio;
|
|
MCPXAPUVoiceFilter filters[MCPX_HW_MAX_VOICES];
|
|
QemuSpin out_buf_lock;
|
|
Fifo8 out_buf;
|
|
|
|
// FIXME: Where are these stored?
|
|
int ssl_base_page;
|
|
MCPXAPUVPSSLData ssl[MCPX_HW_MAX_VOICES];
|
|
uint8_t hrtf_headroom;
|
|
uint8_t hrtf_submix[4];
|
|
uint8_t submix_headroom[NUM_MIXBINS];
|
|
float sample_buf[NUM_SAMPLES_PER_FRAME][2];
|
|
uint64_t voice_locked[4];
|
|
QemuSpin voice_spinlocks[MCPX_HW_MAX_VOICES];
|
|
} vp;
|
|
|
|
/* Global Processor */
|
|
struct {
|
|
bool realtime;
|
|
MemoryRegion mmio;
|
|
DSPState *dsp;
|
|
uint32_t regs[0x10000];
|
|
} gp;
|
|
|
|
/* Encode Processor */
|
|
struct {
|
|
bool realtime;
|
|
MemoryRegion mmio;
|
|
DSPState *dsp;
|
|
uint32_t regs[0x10000];
|
|
} ep;
|
|
|
|
uint32_t regs[0x20000];
|
|
|
|
uint32_t inbuf_sge_handle; //FIXME: Where is this stored?
|
|
uint32_t outbuf_sge_handle; //FIXME: Where is this stored?
|
|
|
|
int mon;
|
|
int ep_frame_div;
|
|
int sleep_acc;
|
|
int frame_count;
|
|
int64_t frame_count_time;
|
|
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
|
|
};
|
|
|
|
static MCPXAPUState *g_state; // Used via debug handlers
|
|
static struct McpxApuDebug g_dbg, g_dbg_cache;
|
|
static int g_dbg_voice_monitor = -1;
|
|
static uint64_t g_dbg_muted_voices[4];
|
|
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 void mcpx_debug_begin_frame(void);
|
|
static void mcpx_debug_end_frame(void);
|
|
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);
|
|
static void gp_scratch_rw(void *opaque, uint8_t *ptr, uint32_t addr, size_t len,
|
|
bool dir);
|
|
static void ep_scratch_rw(void *opaque, uint8_t *ptr, uint32_t addr, size_t len,
|
|
bool dir);
|
|
static uint32_t circular_scatter_gather_rw(MCPXAPUState *d, hwaddr sge_base,
|
|
unsigned int max_sge, uint8_t *ptr,
|
|
uint32_t base, uint32_t end,
|
|
uint32_t cur, size_t len, bool dir);
|
|
static void gp_fifo_rw(void *opaque, uint8_t *ptr, unsigned int index,
|
|
size_t len, bool dir);
|
|
static bool ep_sink_samples(MCPXAPUState *d, uint8_t *ptr, size_t len);
|
|
static void ep_fifo_rw(void *opaque, uint8_t *ptr, unsigned int index,
|
|
size_t len, bool dir);
|
|
static void proc_rst_write(DSPState *dsp, uint32_t oldval, uint32_t val);
|
|
static uint64_t gp_read(void *opaque, hwaddr addr, unsigned int size);
|
|
static void gp_write(void *opaque, hwaddr addr, uint64_t val,
|
|
unsigned int size);
|
|
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],
|
|
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);
|
|
static void mcpx_vp_out_cb(void *opaque, uint8_t *stream, int free_b);
|
|
static void mcpx_apu_realize(PCIDevice *dev, Error **errp);
|
|
static void mcpx_apu_exitfn(PCIDevice *dev);
|
|
static void mcpx_apu_reset(MCPXAPUState *d);
|
|
static void mcpx_apu_vm_state_change(void *opaque, bool running, RunState state);
|
|
static int mcpx_apu_post_save(void *opaque);
|
|
static int mcpx_apu_pre_load(void *opaque);
|
|
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);
|
|
|
|
|
|
const struct McpxApuDebug *mcpx_apu_get_debug_info(void)
|
|
{
|
|
return &g_dbg_cache;
|
|
}
|
|
|
|
static void mcpx_debug_begin_frame(void)
|
|
{
|
|
for (int i = 0; i < MCPX_HW_MAX_VOICES; i++) {
|
|
g_dbg.vp.v[i].active = false;
|
|
g_dbg.vp.v[i].multipass_dst_voice = 0xFFFF;
|
|
}
|
|
}
|
|
|
|
static void mcpx_debug_end_frame(void)
|
|
{
|
|
g_dbg_cache = g_dbg;
|
|
}
|
|
|
|
void mcpx_apu_debug_set_gp_realtime_enabled(bool run)
|
|
{
|
|
g_state->gp.realtime = run;
|
|
}
|
|
|
|
void mcpx_apu_debug_set_ep_realtime_enabled(bool run)
|
|
{
|
|
g_state->ep.realtime = run;
|
|
}
|
|
|
|
int mcpx_apu_debug_get_monitor(void)
|
|
{
|
|
return g_state->mon;
|
|
}
|
|
|
|
void mcpx_apu_debug_set_monitor(int new_mon)
|
|
{
|
|
g_state->mon = new_mon;
|
|
}
|
|
|
|
void mcpx_apu_debug_isolate_voice(uint16_t v)
|
|
{
|
|
g_dbg_voice_monitor = v;
|
|
}
|
|
|
|
void mcpx_apu_debug_clear_isolations(void)
|
|
{
|
|
g_dbg_voice_monitor = -1;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
bool mcpx_apu_debug_is_muted(uint16_t v)
|
|
{
|
|
assert(v < MCPX_HW_MAX_VOICES);
|
|
return g_dbg_muted_voices[v / 64] & (1LL << (v % 64));
|
|
}
|
|
|
|
void mcpx_apu_debug_toggle_mute(uint16_t v)
|
|
{
|
|
assert(v < MCPX_HW_MAX_VOICES);
|
|
g_dbg_muted_voices[v / 64] ^= (1LL << (v % 64));
|
|
}
|
|
|
|
static void mcpx_apu_update_dsp_preference(MCPXAPUState *d)
|
|
{
|
|
static int last_known_preference = -1;
|
|
|
|
if (last_known_preference == (int)g_config.audio.use_dsp) {
|
|
return;
|
|
}
|
|
|
|
if (g_config.audio.use_dsp) {
|
|
d->mon = MCPX_APU_DEBUG_MON_GP_OR_EP;
|
|
d->gp.realtime = true;
|
|
d->ep.realtime = true;
|
|
} else {
|
|
d->mon = MCPX_APU_DEBUG_MON_VP;
|
|
d->gp.realtime = false;
|
|
d->ep.realtime = false;
|
|
}
|
|
|
|
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) {
|
|
qatomic_or(&d->regs[NV_PAPU_ISTS], NV_PAPU_ISTS_FETINTSTS);
|
|
}
|
|
if ((d->regs[NV_PAPU_IEN] & NV_PAPU_ISTS_GINTSTS) &&
|
|
((d->regs[NV_PAPU_ISTS] & ~NV_PAPU_ISTS_GINTSTS) &
|
|
d->regs[NV_PAPU_IEN])) {
|
|
qatomic_or(&d->regs[NV_PAPU_ISTS], NV_PAPU_ISTS_GINTSTS);
|
|
// fprintf(stderr, "mcpx irq raise ien=%08x ists=%08x\n",
|
|
// d->regs[NV_PAPU_IEN], d->regs[NV_PAPU_ISTS]);
|
|
pci_irq_assert(PCI_DEVICE(d));
|
|
} else {
|
|
qatomic_and(&d->regs[NV_PAPU_ISTS], ~NV_PAPU_ISTS_GINTSTS);
|
|
// fprintf(stderr, "mcpx irq lower ien=%08x ists=%08x\n",
|
|
// d->regs[NV_PAPU_IEN], d->regs[NV_PAPU_ISTS]);
|
|
pci_irq_deassert(PCI_DEVICE(d));
|
|
}
|
|
}
|
|
|
|
static uint64_t mcpx_apu_read(void *opaque, hwaddr addr, unsigned int size)
|
|
{
|
|
MCPXAPUState *d = opaque;
|
|
|
|
uint64_t r = 0;
|
|
switch (addr) {
|
|
case NV_PAPU_XGSCNT:
|
|
r = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 100; //???
|
|
break;
|
|
default:
|
|
if (addr < 0x20000) {
|
|
r = qatomic_read(&d->regs[addr]);
|
|
}
|
|
break;
|
|
}
|
|
|
|
trace_mcpx_apu_reg_read(addr, size, r);
|
|
return r;
|
|
}
|
|
|
|
static void mcpx_apu_write(void *opaque, hwaddr addr, uint64_t val,
|
|
unsigned int size)
|
|
{
|
|
MCPXAPUState *d = opaque;
|
|
|
|
trace_mcpx_apu_reg_write(addr, size, val);
|
|
|
|
switch (addr) {
|
|
case NV_PAPU_ISTS:
|
|
/* the bits of the interrupts to clear are written */
|
|
qatomic_and(&d->regs[NV_PAPU_ISTS], ~val);
|
|
update_irq(d);
|
|
qemu_cond_broadcast(&d->cond);
|
|
break;
|
|
case NV_PAPU_FECTL:
|
|
case NV_PAPU_SECTL:
|
|
qatomic_set(&d->regs[addr], val);
|
|
qemu_cond_broadcast(&d->cond);
|
|
break;
|
|
case NV_PAPU_FEMEMDATA:
|
|
/* 'magic write'
|
|
* This value is expected to be written to FEMEMADDR on completion of
|
|
* something to do with notifies. Just do it now :/ */
|
|
stl_le_phys(&address_space_memory, d->regs[NV_PAPU_FEMEMADDR], val);
|
|
// fprintf(stderr, "MAGIC WRITE\n");
|
|
qatomic_set(&d->regs[addr], val);
|
|
break;
|
|
default:
|
|
if (addr < 0x20000) {
|
|
qatomic_set(&d->regs[addr], val);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const MemoryRegionOps mcpx_apu_mmio_ops = {
|
|
.read = mcpx_apu_read,
|
|
.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 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_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_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_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_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_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_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)
|
|
{
|
|
unsigned int page_entry = addr / TARGET_PAGE_SIZE;
|
|
unsigned int offset_in_page = addr % TARGET_PAGE_SIZE;
|
|
unsigned int bytes_to_copy = TARGET_PAGE_SIZE - offset_in_page;
|
|
|
|
while (len > 0) {
|
|
assert(page_entry <= max_sge);
|
|
|
|
uint32_t prd_address = ldl_le_phys(&address_space_memory,
|
|
sge_base + page_entry * 8 + 0);
|
|
// uint32_t prd_control = ldl_le_phys(&address_space_memory,
|
|
// sge_base + page_entry * 8 + 4);
|
|
|
|
hwaddr paddr = prd_address + offset_in_page;
|
|
|
|
if (bytes_to_copy > len) {
|
|
bytes_to_copy = len;
|
|
}
|
|
|
|
assert(paddr + bytes_to_copy < memory_region_size(d->ram));
|
|
|
|
if (dir) {
|
|
memcpy(&d->ram_ptr[paddr], ptr, bytes_to_copy);
|
|
memory_region_set_dirty(d->ram, paddr, bytes_to_copy);
|
|
} else {
|
|
memcpy(ptr, &d->ram_ptr[paddr], bytes_to_copy);
|
|
}
|
|
|
|
ptr += bytes_to_copy;
|
|
len -= bytes_to_copy;
|
|
|
|
/* After the first iteration, we are page aligned */
|
|
page_entry += 1;
|
|
bytes_to_copy = TARGET_PAGE_SIZE;
|
|
offset_in_page = 0;
|
|
}
|
|
}
|
|
|
|
static void gp_scratch_rw(void *opaque, uint8_t *ptr, uint32_t addr, size_t len,
|
|
bool dir)
|
|
{
|
|
MCPXAPUState *d = opaque;
|
|
// fprintf(stderr, "GP %s scratch 0x%x bytes (0x%x words) at %x (0x%x words)\n", dir ? "writing to" : "reading from", len, len/4, addr, addr/4);
|
|
scatter_gather_rw(d, d->regs[NV_PAPU_GPSADDR], d->regs[NV_PAPU_GPSMAXSGE],
|
|
ptr, addr, len, dir);
|
|
}
|
|
|
|
static void ep_scratch_rw(void *opaque, uint8_t *ptr, uint32_t addr, size_t len,
|
|
bool dir)
|
|
{
|
|
MCPXAPUState *d = opaque;
|
|
// fprintf(stderr, "EP %s scratch 0x%x bytes (0x%x words) at %x (0x%x words)\n", dir ? "writing to" : "reading from", len, len/4, addr, addr/4);
|
|
scatter_gather_rw(d, d->regs[NV_PAPU_EPSADDR], d->regs[NV_PAPU_EPSMAXSGE],
|
|
ptr, addr, len, dir);
|
|
}
|
|
|
|
static uint32_t circular_scatter_gather_rw(MCPXAPUState *d, hwaddr sge_base,
|
|
unsigned int max_sge, uint8_t *ptr,
|
|
uint32_t base, uint32_t end,
|
|
uint32_t cur, size_t len, bool dir)
|
|
{
|
|
while (len > 0) {
|
|
unsigned int bytes_to_copy = end - cur;
|
|
|
|
if (bytes_to_copy > len) {
|
|
bytes_to_copy = len;
|
|
}
|
|
|
|
DPRINTF("circular scatter gather %s in range 0x%x - 0x%x at 0x%x of "
|
|
"length 0x%x / 0x%lx bytes\n",
|
|
dir ? "write" : "read", base, end, cur, bytes_to_copy, len);
|
|
|
|
assert((cur >= base) && ((cur + bytes_to_copy) <= end));
|
|
scatter_gather_rw(d, sge_base, max_sge, ptr, cur, bytes_to_copy, dir);
|
|
|
|
ptr += bytes_to_copy;
|
|
len -= bytes_to_copy;
|
|
|
|
/* After the first iteration we might have to wrap */
|
|
cur += bytes_to_copy;
|
|
if (cur >= end) {
|
|
assert(cur == end);
|
|
cur = base;
|
|
}
|
|
}
|
|
|
|
return cur;
|
|
}
|
|
|
|
static void gp_fifo_rw(void *opaque, uint8_t *ptr, unsigned int index,
|
|
size_t len, bool dir)
|
|
{
|
|
MCPXAPUState *d = opaque;
|
|
uint32_t base;
|
|
uint32_t end;
|
|
hwaddr cur_reg;
|
|
if (dir) {
|
|
assert(index < GP_OUTPUT_FIFO_COUNT);
|
|
base = GET_MASK(d->regs[NV_PAPU_GPOFBASE0 + 0x10 * index],
|
|
NV_PAPU_GPOFBASE0_VALUE);
|
|
end = GET_MASK(d->regs[NV_PAPU_GPOFEND0 + 0x10 * index],
|
|
NV_PAPU_GPOFEND0_VALUE);
|
|
cur_reg = NV_PAPU_GPOFCUR0 + 0x10 * index;
|
|
} else {
|
|
assert(index < GP_INPUT_FIFO_COUNT);
|
|
base = GET_MASK(d->regs[NV_PAPU_GPIFBASE0 + 0x10 * index],
|
|
NV_PAPU_GPOFBASE0_VALUE);
|
|
end = GET_MASK(d->regs[NV_PAPU_GPIFEND0 + 0x10 * index],
|
|
NV_PAPU_GPOFEND0_VALUE);
|
|
cur_reg = NV_PAPU_GPIFCUR0 + 0x10 * index;
|
|
}
|
|
|
|
uint32_t cur = GET_MASK(d->regs[cur_reg], NV_PAPU_GPOFCUR0_VALUE);
|
|
|
|
// fprintf(stderr, "GP %s fifo #%d, base = %x, end = %x, cur = %x, len = %x\n",
|
|
// dir ? "writing to" : "reading from", index,
|
|
// base, end, cur, len);
|
|
|
|
/* DSP hangs if current >= end; but forces current >= base */
|
|
assert(cur < end);
|
|
if (cur < base) {
|
|
cur = base;
|
|
}
|
|
|
|
cur = circular_scatter_gather_rw(d,
|
|
d->regs[NV_PAPU_GPFADDR], d->regs[NV_PAPU_GPFMAXSGE],
|
|
ptr, base, end, cur, len, dir);
|
|
|
|
SET_MASK(d->regs[cur_reg], NV_PAPU_GPOFCUR0_VALUE, cur);
|
|
}
|
|
|
|
static bool ep_sink_samples(MCPXAPUState *d, uint8_t *ptr, size_t len)
|
|
{
|
|
if (d->mon == MCPX_APU_DEBUG_MON_AC97) {
|
|
return false;
|
|
} else if ((d->mon == MCPX_APU_DEBUG_MON_EP) ||
|
|
(d->mon == MCPX_APU_DEBUG_MON_GP_OR_EP)) {
|
|
assert(len == sizeof(d->apu_fifo_output));
|
|
memcpy(d->apu_fifo_output, ptr, len);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void ep_fifo_rw(void *opaque, uint8_t *ptr, unsigned int index,
|
|
size_t len, bool dir)
|
|
{
|
|
MCPXAPUState *d = opaque;
|
|
uint32_t base;
|
|
uint32_t end;
|
|
hwaddr cur_reg;
|
|
if (dir) {
|
|
assert(index < EP_OUTPUT_FIFO_COUNT);
|
|
base = GET_MASK(d->regs[NV_PAPU_EPOFBASE0 + 0x10 * index],
|
|
NV_PAPU_GPOFBASE0_VALUE);
|
|
end = GET_MASK(d->regs[NV_PAPU_EPOFEND0 + 0x10 * index],
|
|
NV_PAPU_GPOFEND0_VALUE);
|
|
cur_reg = NV_PAPU_EPOFCUR0 + 0x10 * index;
|
|
} else {
|
|
assert(index < EP_INPUT_FIFO_COUNT);
|
|
base = GET_MASK(d->regs[NV_PAPU_EPIFBASE0 + 0x10 * index],
|
|
NV_PAPU_GPOFBASE0_VALUE);
|
|
end = GET_MASK(d->regs[NV_PAPU_EPIFEND0 + 0x10 * index],
|
|
NV_PAPU_GPOFEND0_VALUE);
|
|
cur_reg = NV_PAPU_EPIFCUR0 + 0x10 * index;
|
|
}
|
|
|
|
uint32_t cur = GET_MASK(d->regs[cur_reg], NV_PAPU_GPOFCUR0_VALUE);
|
|
|
|
// fprintf(stderr, "EP %s fifo #%d, base = %x, end = %x, cur = %x, len = %x\n",
|
|
// dir ? "writing to" : "reading from", index,
|
|
// base, end, cur, len);
|
|
|
|
if (dir && index == 0) {
|
|
bool did_sink = ep_sink_samples(d, ptr, len);
|
|
if (did_sink) {
|
|
/* Since we are sinking, push silence out */
|
|
assert(len <= sizeof(ep_silence));
|
|
ptr = (uint8_t*)ep_silence;
|
|
}
|
|
}
|
|
|
|
/* DSP hangs if current >= end; but forces current >= base */
|
|
if (cur >= end) {
|
|
cur = cur % (end - base);
|
|
}
|
|
if (cur < base) {
|
|
cur = base;
|
|
}
|
|
|
|
cur = circular_scatter_gather_rw(d,
|
|
d->regs[NV_PAPU_EPFADDR], d->regs[NV_PAPU_EPFMAXSGE],
|
|
ptr, base, end, cur, len, dir);
|
|
|
|
SET_MASK(d->regs[cur_reg], NV_PAPU_GPOFCUR0_VALUE, cur);
|
|
}
|
|
|
|
static void proc_rst_write(DSPState *dsp, uint32_t oldval, uint32_t val)
|
|
{
|
|
if (!(val & NV_PAPU_GPRST_GPRST) || !(val & NV_PAPU_GPRST_GPDSPRST)) {
|
|
dsp_reset(dsp);
|
|
} else if (
|
|
(!(oldval & NV_PAPU_GPRST_GPRST) || !(oldval & NV_PAPU_GPRST_GPDSPRST))
|
|
&& ((val & NV_PAPU_GPRST_GPRST) && (val & NV_PAPU_GPRST_GPDSPRST))) {
|
|
dsp_bootstrap(dsp);
|
|
}
|
|
}
|
|
|
|
/* Global Processor - programmable DSP */
|
|
static uint64_t gp_read(void *opaque, hwaddr addr, unsigned int size)
|
|
{
|
|
MCPXAPUState *d = opaque;
|
|
|
|
assert(size == 4);
|
|
assert(addr % 4 == 0);
|
|
|
|
uint64_t r = 0;
|
|
switch (addr) {
|
|
case NV_PAPU_GPXMEM ... NV_PAPU_GPXMEM + 0x1000 * 4 - 1: {
|
|
uint32_t xaddr = (addr - NV_PAPU_GPXMEM) / 4;
|
|
r = dsp_read_memory(d->gp.dsp, 'X', xaddr);
|
|
// fprintf(stderr, "read GP NV_PAPU_GPXMEM [%x] -> %x\n", xaddr, r);
|
|
break;
|
|
}
|
|
case NV_PAPU_GPMIXBUF ... NV_PAPU_GPMIXBUF + 0x400 * 4 - 1: {
|
|
uint32_t xaddr = (addr - NV_PAPU_GPMIXBUF) / 4;
|
|
r = dsp_read_memory(d->gp.dsp, 'X', GP_DSP_MIXBUF_BASE + xaddr);
|
|
// fprintf(stderr, "read GP NV_PAPU_GPMIXBUF [%x] -> %x\n", xaddr, r);
|
|
break;
|
|
}
|
|
case NV_PAPU_GPYMEM ... NV_PAPU_GPYMEM + 0x800 * 4 - 1: {
|
|
uint32_t yaddr = (addr - NV_PAPU_GPYMEM) / 4;
|
|
r = dsp_read_memory(d->gp.dsp, 'Y', yaddr);
|
|
// fprintf(stderr, "read GP NV_PAPU_GPYMEM [%x] -> %x\n", yaddr, r);
|
|
break;
|
|
}
|
|
case NV_PAPU_GPPMEM ... NV_PAPU_GPPMEM + 0x1000 * 4 - 1: {
|
|
uint32_t paddr = (addr - NV_PAPU_GPPMEM) / 4;
|
|
r = dsp_read_memory(d->gp.dsp, 'P', paddr);
|
|
// fprintf(stderr, "read GP NV_PAPU_GPPMEM [%x] -> %x\n", paddr, r);
|
|
break;
|
|
}
|
|
default:
|
|
r = d->gp.regs[addr];
|
|
break;
|
|
}
|
|
DPRINTF("mcpx apu GP: read [0x%" HWADDR_PRIx "] -> 0x%lx\n", addr, r);
|
|
|
|
return r;
|
|
}
|
|
|
|
static void gp_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size)
|
|
{
|
|
MCPXAPUState *d = opaque;
|
|
|
|
qemu_mutex_lock(&d->lock);
|
|
|
|
assert(size == 4);
|
|
assert(addr % 4 == 0);
|
|
|
|
DPRINTF("mcpx apu GP: [0x%" HWADDR_PRIx "] = 0x%lx\n", addr, val);
|
|
|
|
switch (addr) {
|
|
case NV_PAPU_GPXMEM ... NV_PAPU_GPXMEM + 0x1000 * 4 - 1: {
|
|
uint32_t xaddr = (addr - NV_PAPU_GPXMEM) / 4;
|
|
// fprintf(stderr, "gp write xmem %x = %x\n", xaddr, val);
|
|
dsp_write_memory(d->gp.dsp, 'X', xaddr, val);
|
|
break;
|
|
}
|
|
case NV_PAPU_GPMIXBUF ... NV_PAPU_GPMIXBUF + 0x400 * 4 - 1: {
|
|
uint32_t xaddr = (addr - NV_PAPU_GPMIXBUF) / 4;
|
|
// fprintf(stderr, "gp write xmixbuf %x = %x\n", xaddr, val);
|
|
dsp_write_memory(d->gp.dsp, 'X', GP_DSP_MIXBUF_BASE + xaddr, val);
|
|
break;
|
|
}
|
|
case NV_PAPU_GPYMEM ... NV_PAPU_GPYMEM + 0x800 * 4 - 1: {
|
|
uint32_t yaddr = (addr - NV_PAPU_GPYMEM) / 4;
|
|
// fprintf(stderr, "gp write ymem %x = %x\n", yaddr, val);
|
|
dsp_write_memory(d->gp.dsp, 'Y', yaddr, val);
|
|
break;
|
|
}
|
|
case NV_PAPU_GPPMEM ... NV_PAPU_GPPMEM + 0x1000 * 4 - 1: {
|
|
uint32_t paddr = (addr - NV_PAPU_GPPMEM) / 4;
|
|
// fprintf(stderr, "gp write pmem %x = %x\n", paddr, val);
|
|
dsp_write_memory(d->gp.dsp, 'P', paddr, val);
|
|
break;
|
|
}
|
|
case NV_PAPU_GPRST:
|
|
proc_rst_write(d->gp.dsp, d->gp.regs[NV_PAPU_GPRST], val);
|
|
d->gp.regs[NV_PAPU_GPRST] = val;
|
|
break;
|
|
default:
|
|
d->gp.regs[addr] = val;
|
|
break;
|
|
}
|
|
|
|
qemu_mutex_unlock(&d->lock);
|
|
}
|
|
|
|
static const MemoryRegionOps gp_ops = {
|
|
.read = gp_read,
|
|
.write = gp_write,
|
|
};
|
|
|
|
/* Encode Processor - encoding DSP */
|
|
static uint64_t ep_read(void *opaque, hwaddr addr, unsigned int size)
|
|
{
|
|
MCPXAPUState *d = opaque;
|
|
|
|
assert(size == 4);
|
|
assert(addr % 4 == 0);
|
|
|
|
uint64_t r = 0;
|
|
switch (addr) {
|
|
case NV_PAPU_EPXMEM ... NV_PAPU_EPXMEM + 0xC00 * 4 - 1: {
|
|
uint32_t xaddr = (addr - NV_PAPU_EPXMEM) / 4;
|
|
r = dsp_read_memory(d->ep.dsp, 'X', xaddr);
|
|
// fprintf(stderr, "read EP NV_PAPU_EPXMEM [%x] -> %x\n", xaddr, r);
|
|
break;
|
|
}
|
|
case NV_PAPU_EPYMEM ... NV_PAPU_EPYMEM + 0x100 * 4 - 1: {
|
|
uint32_t yaddr = (addr - NV_PAPU_EPYMEM) / 4;
|
|
r = dsp_read_memory(d->ep.dsp, 'Y', yaddr);
|
|
// fprintf(stderr, "read EP NV_PAPU_EPYMEM [%x] -> %x\n", yaddr, r);
|
|
break;
|
|
}
|
|
case NV_PAPU_EPPMEM ... NV_PAPU_EPPMEM + 0x1000 * 4 - 1: {
|
|
uint32_t paddr = (addr - NV_PAPU_EPPMEM) / 4;
|
|
r = dsp_read_memory(d->ep.dsp, 'P', paddr);
|
|
// fprintf(stderr, "read EP NV_PAPU_EPPMEM [%x] -> %x\n", paddr, r);
|
|
break;
|
|
}
|
|
default:
|
|
r = d->ep.regs[addr];
|
|
break;
|
|
}
|
|
DPRINTF("mcpx apu EP: read [0x%" HWADDR_PRIx "] -> 0x%lx\n", addr, r);
|
|
|
|
return r;
|
|
}
|
|
|
|
static void ep_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size)
|
|
{
|
|
MCPXAPUState *d = opaque;
|
|
|
|
qemu_mutex_lock(&d->lock);
|
|
|
|
assert(size == 4);
|
|
assert(addr % 4 == 0);
|
|
|
|
DPRINTF("mcpx apu EP: [0x%" HWADDR_PRIx "] = 0x%lx\n", addr, val);
|
|
|
|
switch (addr) {
|
|
case NV_PAPU_EPXMEM ... NV_PAPU_EPXMEM + 0xC00 * 4 - 1: {
|
|
uint32_t xaddr = (addr - NV_PAPU_EPXMEM) / 4;
|
|
dsp_write_memory(d->ep.dsp, 'X', xaddr, val);
|
|
// fprintf(stderr, "ep write xmem %x = %x\n", xaddr, val);
|
|
break;
|
|
}
|
|
case NV_PAPU_EPYMEM ... NV_PAPU_EPYMEM + 0x100 * 4 - 1: {
|
|
uint32_t yaddr = (addr - NV_PAPU_EPYMEM) / 4;
|
|
dsp_write_memory(d->ep.dsp, 'Y', yaddr, val);
|
|
// fprintf(stderr, "ep write ymem %x = %x\n", yaddr, val);
|
|
break;
|
|
}
|
|
case NV_PAPU_EPPMEM ... NV_PAPU_EPPMEM + 0x1000 * 4 - 1: {
|
|
uint32_t paddr = (addr - NV_PAPU_EPPMEM) / 4;
|
|
// fprintf(stderr, "ep write pmem %x = %x\n", paddr, val);
|
|
dsp_write_memory(d->ep.dsp, 'P', paddr, val);
|
|
break;
|
|
}
|
|
case NV_PAPU_EPRST:
|
|
proc_rst_write(d->ep.dsp, d->ep.regs[NV_PAPU_EPRST], val);
|
|
d->ep.regs[NV_PAPU_EPRST] = val;
|
|
d->ep_frame_div = 0; /* FIXME: Still unsure about frame sync */
|
|
break;
|
|
default:
|
|
d->ep.regs[addr] = val;
|
|
break;
|
|
}
|
|
|
|
qemu_mutex_unlock(&d->lock);
|
|
}
|
|
|
|
static const MemoryRegionOps ep_ops = {
|
|
.read = ep_read,
|
|
.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 voice_process(MCPXAPUState *d,
|
|
float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME],
|
|
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 < 64) {
|
|
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 < 64) {
|
|
/* 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// FIXME: ParaEQ
|
|
|
|
for (int b = 0; b < 8; b++) {
|
|
float g = ea_value;
|
|
float hr;
|
|
if ((v < 64) && (b < 4)) {
|
|
// FIXME: Not sure if submix/voice headroom factor in for HRTF
|
|
// Note: Attenuate extra 6dB to simulate HRTF
|
|
hr = 1 << (d->vp.hrtf_headroom + 1);
|
|
} 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++) {
|
|
d->vp.sample_buf[i][0] += g*samples[i][0];
|
|
d->vp.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);
|
|
mcpx_debug_begin_frame();
|
|
g_dbg.gp_realtime = d->gp.realtime;
|
|
g_dbg.ep_realtime = d->ep.realtime;
|
|
|
|
qemu_spin_lock(&d->vp.out_buf_lock);
|
|
int num_bytes_free = fifo8_num_free(&d->vp.out_buf);
|
|
qemu_spin_unlock(&d->vp.out_buf_lock);
|
|
|
|
/* A rudimentary calculation to determine approximately how taxed the APU
|
|
* thread is, by measuring how much time we spend waiting for FIFO to drain
|
|
* versus working on building frames.
|
|
* =1: thread is not sleeping and likely falling behind realtime
|
|
* <1: thread is able to complete work on time
|
|
*/
|
|
if (num_bytes_free < sizeof(d->apu_fifo_output)) {
|
|
int64_t sleep_start = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
|
|
qemu_cond_wait(&d->cond, &d->lock);
|
|
int64_t sleep_end = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
|
|
d->sleep_acc += (sleep_end - sleep_start);
|
|
return;
|
|
}
|
|
int64_t now = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
|
|
if (now - d->frame_count_time >= 1000) {
|
|
g_dbg.frames_processed = d->frame_count;
|
|
float t = 1.0f - ((double)d->sleep_acc /
|
|
(double)((now - d->frame_count_time) * 1000));
|
|
g_dbg.utilization = t;
|
|
|
|
d->frame_count_time = now;
|
|
d->frame_count = 0;
|
|
d->sleep_acc = 0;
|
|
}
|
|
d->frame_count++;
|
|
|
|
/* 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 {
|
|
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]);
|
|
}
|
|
voice_process(d, mixbins, v, list);
|
|
qemu_spin_unlock(&d->vp.voice_spinlocks[v]);
|
|
}
|
|
d->regs[current] = d->regs[next];
|
|
}
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
/* Write VP results to the GP DSP MIXBUF */
|
|
for (int mixbin = 0; mixbin < NUM_MIXBINS; mixbin++) {
|
|
uint32_t base = GP_DSP_MIXBUF_BASE + mixbin * NUM_SAMPLES_PER_FRAME;
|
|
for (int sample = 0; sample < NUM_SAMPLES_PER_FRAME; sample++) {
|
|
dsp_write_memory(d->gp.dsp, 'X', base + sample,
|
|
float_to_24b(mixbins[mixbin][sample]));
|
|
}
|
|
}
|
|
|
|
bool ep_enabled = (d->ep.regs[NV_PAPU_EPRST] & NV_PAPU_GPRST_GPRST) &&
|
|
(d->ep.regs[NV_PAPU_EPRST] & NV_PAPU_GPRST_GPDSPRST);
|
|
|
|
/* Run GP */
|
|
if ((d->gp.regs[NV_PAPU_GPRST] & NV_PAPU_GPRST_GPRST) &&
|
|
(d->gp.regs[NV_PAPU_GPRST] & NV_PAPU_GPRST_GPDSPRST)) {
|
|
dsp_start_frame(d->gp.dsp);
|
|
d->gp.dsp->core.is_idle = false;
|
|
d->gp.dsp->core.cycle_count = 0;
|
|
do {
|
|
dsp_run(d->gp.dsp, 1000);
|
|
} while (!d->gp.dsp->core.is_idle && d->gp.realtime);
|
|
g_dbg.gp.cycles = d->gp.dsp->core.cycle_count;
|
|
|
|
if ((d->mon == MCPX_APU_DEBUG_MON_GP) ||
|
|
(d->mon == MCPX_APU_DEBUG_MON_GP_OR_EP && !ep_enabled)) {
|
|
int off = (d->ep_frame_div % 8) * NUM_SAMPLES_PER_FRAME;
|
|
for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) {
|
|
uint32_t l = dsp_read_memory(d->gp.dsp, 'X', 0x1400 + i);
|
|
d->apu_fifo_output[off + i][0] = l >> 8;
|
|
uint32_t r =
|
|
dsp_read_memory(d->gp.dsp, 'X', 0x1400 + 1 * 0x20 + i);
|
|
d->apu_fifo_output[off + i][1] = r >> 8;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Run EP */
|
|
if ((d->ep.regs[NV_PAPU_EPRST] & NV_PAPU_GPRST_GPRST) &&
|
|
(d->ep.regs[NV_PAPU_EPRST] & NV_PAPU_GPRST_GPDSPRST)) {
|
|
if (d->ep_frame_div % 8 == 0) {
|
|
dsp_start_frame(d->ep.dsp);
|
|
d->ep.dsp->core.is_idle = false;
|
|
d->ep.dsp->core.cycle_count = 0;
|
|
do {
|
|
dsp_run(d->ep.dsp, 1000);
|
|
} while (!d->ep.dsp->core.is_idle && d->ep.realtime);
|
|
g_dbg.ep.cycles = d->ep.dsp->core.cycle_count;
|
|
}
|
|
}
|
|
|
|
if ((d->ep_frame_div + 1) % 8 == 0) {
|
|
#if 0
|
|
FILE *fd = fopen("ep.pcm", "a+");
|
|
assert(fd != NULL);
|
|
fwrite(d->apu_fifo_output, sizeof(d->apu_fifo_output), 1, fd);
|
|
fclose(fd);
|
|
#endif
|
|
|
|
if (0 <= g_config.audio.volume_limit && g_config.audio.volume_limit < 1) {
|
|
float f = pow(g_config.audio.volume_limit, M_E);
|
|
for (int i = 0; i < 256; i++) {
|
|
d->apu_fifo_output[i][0] *= f;
|
|
d->apu_fifo_output[i][1] *= f;
|
|
}
|
|
}
|
|
|
|
qemu_spin_lock(&d->vp.out_buf_lock);
|
|
num_bytes_free = fifo8_num_free(&d->vp.out_buf);
|
|
assert(num_bytes_free >= sizeof(d->apu_fifo_output));
|
|
fifo8_push_all(&d->vp.out_buf, (uint8_t *)d->apu_fifo_output,
|
|
sizeof(d->apu_fifo_output));
|
|
qemu_spin_unlock(&d->vp.out_buf_lock);
|
|
memset(d->apu_fifo_output, 0, sizeof(d->apu_fifo_output));
|
|
}
|
|
|
|
d->ep_frame_div++;
|
|
|
|
mcpx_debug_end_frame();
|
|
}
|
|
|
|
/* Note: only supports millisecond resolution on Windows */
|
|
static void sleep_ns(int64_t ns)
|
|
{
|
|
#ifndef _WIN32
|
|
struct timespec sleep_delay, rem_delay;
|
|
sleep_delay.tv_sec = ns / 1000000000LL;
|
|
sleep_delay.tv_nsec = ns % 1000000000LL;
|
|
nanosleep(&sleep_delay, &rem_delay);
|
|
#else
|
|
Sleep(ns / SCALE_MS);
|
|
#endif
|
|
}
|
|
|
|
static void mcpx_vp_out_cb(void *opaque, uint8_t *stream, int free_b)
|
|
{
|
|
MCPXAPUState *s = MCPX_APU_DEVICE(opaque);
|
|
|
|
if (!runstate_is_running()) {
|
|
memset(stream, 0, free_b);
|
|
return;
|
|
}
|
|
|
|
int avail = 0;
|
|
while (avail < free_b) {
|
|
qemu_spin_lock(&s->vp.out_buf_lock);
|
|
avail = fifo8_num_used(&s->vp.out_buf);
|
|
qemu_spin_unlock(&s->vp.out_buf_lock);
|
|
if (avail < free_b) {
|
|
sleep_ns(1000000);
|
|
qemu_cond_broadcast(&s->cond);
|
|
}
|
|
}
|
|
|
|
int to_copy = MIN(free_b, avail);
|
|
while (to_copy > 0) {
|
|
uint32_t chunk_len = 0;
|
|
qemu_spin_lock(&s->vp.out_buf_lock);
|
|
chunk_len = fifo8_pop_buf(&s->vp.out_buf, stream, to_copy);
|
|
assert(chunk_len <= to_copy);
|
|
qemu_spin_unlock(&s->vp.out_buf_lock);
|
|
stream += chunk_len;
|
|
to_copy -= chunk_len;
|
|
}
|
|
|
|
qemu_cond_broadcast(&s->cond);
|
|
}
|
|
|
|
static void mcpx_apu_realize(PCIDevice *dev, Error **errp)
|
|
{
|
|
MCPXAPUState *d = MCPX_APU_DEVICE(dev);
|
|
|
|
dev->config[PCI_INTERRUPT_PIN] = 0x01;
|
|
|
|
memory_region_init_io(&d->mmio, OBJECT(dev), &mcpx_apu_mmio_ops, d,
|
|
"mcpx-apu-mmio", 0x80000);
|
|
|
|
memory_region_init_io(&d->vp.mmio, OBJECT(dev), &vp_ops, d,
|
|
"mcpx-apu-vp", 0x10000);
|
|
memory_region_add_subregion(&d->mmio, 0x20000, &d->vp.mmio);
|
|
|
|
memory_region_init_io(&d->gp.mmio, OBJECT(dev), &gp_ops, d,
|
|
"mcpx-apu-gp", 0x10000);
|
|
memory_region_add_subregion(&d->mmio, 0x30000, &d->gp.mmio);
|
|
|
|
memory_region_init_io(&d->ep.mmio, OBJECT(dev), &ep_ops, d,
|
|
"mcpx-apu-ep", 0x10000);
|
|
memory_region_add_subregion(&d->mmio, 0x50000, &d->ep.mmio);
|
|
|
|
pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);
|
|
}
|
|
|
|
static void mcpx_apu_exitfn(PCIDevice *dev)
|
|
{
|
|
MCPXAPUState *d = MCPX_APU_DEVICE(dev);
|
|
d->exiting = true;
|
|
qemu_cond_broadcast(&d->cond);
|
|
qemu_thread_join(&d->apu_thread);
|
|
}
|
|
|
|
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));
|
|
|
|
// FIXME: Reset DSP state
|
|
memset(d->gp.dsp->core.pram_opcache, 0,
|
|
sizeof(d->gp.dsp->core.pram_opcache));
|
|
memset(d->ep.dsp->core.pram_opcache, 0,
|
|
sizeof(d->ep.dsp->core.pram_opcache));
|
|
d->set_irq = false;
|
|
qemu_cond_signal(&d->cond);
|
|
qemu_mutex_unlock(&d->lock);
|
|
}
|
|
|
|
// Note: This is handled as a VM state change and not as a `pre_save` callback
|
|
// because we want to halt the FIFO before any VM state is saved/restored to
|
|
// avoid corruption.
|
|
static void mcpx_apu_vm_state_change(void *opaque, bool running, RunState state)
|
|
{
|
|
MCPXAPUState *d = opaque;
|
|
|
|
if (state == RUN_STATE_SAVE_VM) {
|
|
qemu_mutex_lock(&d->lock);
|
|
}
|
|
}
|
|
|
|
static int mcpx_apu_post_save(void *opaque)
|
|
{
|
|
MCPXAPUState *d = opaque;
|
|
qemu_cond_signal(&d->cond);
|
|
qemu_mutex_unlock(&d->lock);
|
|
return 0;
|
|
}
|
|
|
|
static int mcpx_apu_pre_load(void *opaque)
|
|
{
|
|
MCPXAPUState *d = opaque;
|
|
mcpx_apu_reset(d);
|
|
qemu_mutex_lock(&d->lock);
|
|
return 0;
|
|
}
|
|
|
|
static int mcpx_apu_post_load(void *opaque, int version_id)
|
|
{
|
|
MCPXAPUState *d = opaque;
|
|
qemu_cond_signal(&d->cond);
|
|
qemu_mutex_unlock(&d->lock);
|
|
return 0;
|
|
}
|
|
|
|
static void mcpx_apu_reset_hold(Object *obj, ResetType type)
|
|
{
|
|
MCPXAPUState *d = MCPX_APU_DEVICE(obj);
|
|
mcpx_apu_reset(d);
|
|
}
|
|
|
|
const VMStateDescription vmstate_vp_dsp_dma_state = {
|
|
.name = "mcpx-apu/dsp-state/dma",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT32(configuration, DSPDMAState),
|
|
VMSTATE_UINT32(control, DSPDMAState),
|
|
VMSTATE_UINT32(start_block, DSPDMAState),
|
|
VMSTATE_UINT32(next_block, DSPDMAState),
|
|
VMSTATE_BOOL(error, DSPDMAState),
|
|
VMSTATE_BOOL(eol, DSPDMAState),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
const VMStateDescription vmstate_vp_dsp_core_state = {
|
|
.name = "mcpx-apu/dsp-state/core",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
// FIXME: Remove unnecessary fields
|
|
VMSTATE_UINT16(instr_cycle, dsp_core_t),
|
|
VMSTATE_UINT32(pc, dsp_core_t),
|
|
VMSTATE_UINT32_ARRAY(registers, dsp_core_t, DSP_REG_MAX),
|
|
VMSTATE_UINT32_2DARRAY(stack, dsp_core_t, 2, 16),
|
|
VMSTATE_UINT32_ARRAY(xram, dsp_core_t, DSP_XRAM_SIZE),
|
|
VMSTATE_UINT32_ARRAY(yram, dsp_core_t, DSP_YRAM_SIZE),
|
|
VMSTATE_UINT32_ARRAY(pram, dsp_core_t, DSP_PRAM_SIZE),
|
|
VMSTATE_UINT32_ARRAY(mixbuffer, dsp_core_t, DSP_MIXBUFFER_SIZE),
|
|
VMSTATE_UINT32_ARRAY(periph, dsp_core_t, DSP_PERIPH_SIZE),
|
|
VMSTATE_UINT32(loop_rep, dsp_core_t),
|
|
VMSTATE_UINT32(pc_on_rep, dsp_core_t),
|
|
VMSTATE_UINT16(interrupt_state, dsp_core_t),
|
|
VMSTATE_UINT16(interrupt_instr_fetch, dsp_core_t),
|
|
VMSTATE_UINT16(interrupt_save_pc, dsp_core_t),
|
|
VMSTATE_UINT16(interrupt_counter, dsp_core_t),
|
|
VMSTATE_UINT16(interrupt_ipl_to_raise, dsp_core_t),
|
|
VMSTATE_UINT16(interrupt_pipeline_count, dsp_core_t),
|
|
VMSTATE_INT16_ARRAY(interrupt_ipl, dsp_core_t, 12),
|
|
VMSTATE_UINT16_ARRAY(interrupt_is_pending, dsp_core_t, 12),
|
|
VMSTATE_UINT32(num_inst, dsp_core_t),
|
|
VMSTATE_UINT32(cur_inst_len, dsp_core_t),
|
|
VMSTATE_UINT32(cur_inst, dsp_core_t),
|
|
VMSTATE_UNUSED(1),
|
|
VMSTATE_UINT32(disasm_memory_ptr, dsp_core_t),
|
|
VMSTATE_BOOL(exception_debugging, dsp_core_t),
|
|
VMSTATE_UINT32(disasm_prev_inst_pc, dsp_core_t),
|
|
VMSTATE_BOOL(disasm_is_looping, dsp_core_t),
|
|
VMSTATE_UINT32(disasm_cur_inst, dsp_core_t),
|
|
VMSTATE_UINT16(disasm_cur_inst_len, dsp_core_t),
|
|
VMSTATE_UINT32_ARRAY(disasm_registers_save, dsp_core_t, 64),
|
|
// #ifdef DSP_DISASM_REG_PC
|
|
// VMSTATE_UINT32(pc_save, dsp_core_t),
|
|
// #endif
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
const VMStateDescription vmstate_vp_dsp_state = {
|
|
.name = "mcpx-apu/dsp-state",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_STRUCT(core, DSPState, 1, vmstate_vp_dsp_core_state, dsp_core_t),
|
|
VMSTATE_STRUCT(dma, DSPState, 1, vmstate_vp_dsp_dma_state, DSPDMAState),
|
|
VMSTATE_INT32(save_cycles, DSPState),
|
|
VMSTATE_UINT32(interrupts, DSPState),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
|
|
const VMStateDescription vmstate_vp_ssl_data = {
|
|
.name = "mcpx_apu_voice_data",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT32_ARRAY(base, MCPXAPUVPSSLData, MCPX_HW_SSLS_PER_VOICE),
|
|
VMSTATE_UINT8_ARRAY(count, MCPXAPUVPSSLData, MCPX_HW_SSLS_PER_VOICE),
|
|
VMSTATE_INT32(ssl_index, MCPXAPUVPSSLData),
|
|
VMSTATE_INT32(ssl_seg, MCPXAPUVPSSLData),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static const VMStateDescription vmstate_mcpx_apu = {
|
|
.name = "mcpx-apu",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.post_save = mcpx_apu_post_save,
|
|
.pre_load = mcpx_apu_pre_load,
|
|
.post_load = mcpx_apu_post_load,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_PCI_DEVICE(parent_obj, MCPXAPUState),
|
|
VMSTATE_STRUCT_POINTER(gp.dsp, MCPXAPUState, vmstate_vp_dsp_state,
|
|
DSPState),
|
|
VMSTATE_UINT32_ARRAY(gp.regs, MCPXAPUState, 0x10000),
|
|
VMSTATE_STRUCT_POINTER(ep.dsp, MCPXAPUState, vmstate_vp_dsp_state,
|
|
DSPState),
|
|
VMSTATE_UINT32_ARRAY(ep.regs, MCPXAPUState, 0x10000),
|
|
VMSTATE_UINT32_ARRAY(regs, MCPXAPUState, 0x20000),
|
|
VMSTATE_UINT32(inbuf_sge_handle, MCPXAPUState),
|
|
VMSTATE_UINT32(outbuf_sge_handle, MCPXAPUState),
|
|
VMSTATE_STRUCT_ARRAY(vp.ssl, MCPXAPUState, MCPX_HW_MAX_VOICES, 1,
|
|
vmstate_vp_ssl_data, MCPXAPUVPSSLData),
|
|
VMSTATE_INT32(vp.ssl_base_page, MCPXAPUState),
|
|
VMSTATE_UINT8_ARRAY(vp.hrtf_submix, MCPXAPUState, 4),
|
|
VMSTATE_UINT8(vp.hrtf_headroom, MCPXAPUState),
|
|
VMSTATE_UINT8_ARRAY(vp.submix_headroom, MCPXAPUState, NUM_MIXBINS),
|
|
VMSTATE_UINT64_ARRAY(vp.voice_locked, MCPXAPUState, 4),
|
|
VMSTATE_END_OF_LIST()
|
|
},
|
|
};
|
|
|
|
static void mcpx_apu_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
ResettableClass *rc = RESETTABLE_CLASS(klass);
|
|
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
|
|
|
|
k->vendor_id = PCI_VENDOR_ID_NVIDIA;
|
|
k->device_id = PCI_DEVICE_ID_NVIDIA_MCPX_APU;
|
|
k->revision = 177;
|
|
k->class_id = PCI_CLASS_MULTIMEDIA_AUDIO;
|
|
k->realize = mcpx_apu_realize;
|
|
k->exit = mcpx_apu_exitfn;
|
|
|
|
rc->phases.hold = mcpx_apu_reset_hold;
|
|
|
|
dc->desc = "MCPX Audio Processing Unit";
|
|
dc->vmsd = &vmstate_mcpx_apu;
|
|
}
|
|
|
|
static const TypeInfo mcpx_apu_info = {
|
|
.name = "mcpx-apu",
|
|
.parent = TYPE_PCI_DEVICE,
|
|
.instance_size = sizeof(MCPXAPUState),
|
|
.class_init = mcpx_apu_class_init,
|
|
.interfaces =
|
|
(InterfaceInfo[]){
|
|
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
|
|
{},
|
|
},
|
|
};
|
|
|
|
static void mcpx_apu_register(void)
|
|
{
|
|
type_register_static(&mcpx_apu_info);
|
|
}
|
|
type_init(mcpx_apu_register);
|
|
|
|
static void *mcpx_apu_frame_thread(void *arg)
|
|
{
|
|
MCPXAPUState *d = MCPX_APU_DEVICE(arg);
|
|
qemu_mutex_lock(&d->lock);
|
|
while (!qatomic_read(&d->exiting)) {
|
|
int xcntmode = GET_MASK(qatomic_read(&d->regs[NV_PAPU_SECTL]),
|
|
NV_PAPU_SECTL_XCNTMODE);
|
|
uint32_t fectl = qatomic_read(&d->regs[NV_PAPU_FECTL]);
|
|
if (xcntmode == NV_PAPU_SECTL_XCNTMODE_OFF ||
|
|
(fectl & NV_PAPU_FECTL_FEMETHMODE_TRAPPED) ||
|
|
(fectl & NV_PAPU_FECTL_FEMETHMODE_HALTED)) {
|
|
d->set_irq = true;
|
|
}
|
|
|
|
if (d->set_irq) {
|
|
qemu_mutex_unlock(&d->lock);
|
|
bql_lock();
|
|
update_irq(d);
|
|
bql_unlock();
|
|
qemu_mutex_lock(&d->lock);
|
|
d->set_irq = false;
|
|
}
|
|
|
|
xcntmode = GET_MASK(qatomic_read(&d->regs[NV_PAPU_SECTL]),
|
|
NV_PAPU_SECTL_XCNTMODE);
|
|
fectl = qatomic_read(&d->regs[NV_PAPU_FECTL]);
|
|
if (xcntmode == NV_PAPU_SECTL_XCNTMODE_OFF ||
|
|
(fectl & NV_PAPU_FECTL_FEMETHMODE_TRAPPED) ||
|
|
(fectl & NV_PAPU_FECTL_FEMETHMODE_HALTED)) {
|
|
qemu_cond_wait(&d->cond, &d->lock);
|
|
continue;
|
|
}
|
|
se_frame((void *)d);
|
|
}
|
|
qemu_mutex_unlock(&d->lock);
|
|
return NULL;
|
|
}
|
|
|
|
void mcpx_apu_init(PCIBus *bus, int devfn, MemoryRegion *ram)
|
|
{
|
|
PCIDevice *dev = pci_create_simple(bus, devfn, "mcpx-apu");
|
|
MCPXAPUState *d = MCPX_APU_DEVICE(dev);
|
|
|
|
g_state = d;
|
|
|
|
d->ram = ram;
|
|
d->ram_ptr = memory_region_get_ram_ptr(d->ram);
|
|
|
|
d->gp.dsp = dsp_init(d, gp_scratch_rw, gp_fifo_rw);
|
|
for (int i = 0; i < DSP_PRAM_SIZE; i++) {
|
|
d->gp.dsp->core.pram[i] = 0xCACACACA;
|
|
}
|
|
memset(d->gp.dsp->core.pram_opcache, 0,
|
|
sizeof(d->gp.dsp->core.pram_opcache));
|
|
d->gp.dsp->is_gp = true;
|
|
d->gp.dsp->core.is_gp = true;
|
|
d->gp.dsp->core.is_idle = false;
|
|
d->gp.dsp->core.cycle_count = 0;
|
|
|
|
d->ep.dsp = dsp_init(d, ep_scratch_rw, ep_fifo_rw);
|
|
for (int i = 0; i < DSP_PRAM_SIZE; i++) {
|
|
d->ep.dsp->core.pram[i] = 0xCACACACA;
|
|
}
|
|
memset(d->ep.dsp->core.pram_opcache, 0,
|
|
sizeof(d->ep.dsp->core.pram_opcache));
|
|
for (int i = 0; i < DSP_XRAM_SIZE; i++) {
|
|
d->ep.dsp->core.xram[i] = 0xCACACACA;
|
|
}
|
|
for (int i = 0; i < DSP_YRAM_SIZE; i++) {
|
|
d->ep.dsp->core.yram[i] = 0xCACACACA;
|
|
}
|
|
d->ep.dsp->is_gp = false;
|
|
d->ep.dsp->core.is_gp = false;
|
|
d->ep.dsp->core.is_idle = false;
|
|
d->ep.dsp->core.cycle_count = 0;
|
|
|
|
d->set_irq = false;
|
|
d->exiting = false;
|
|
|
|
struct SDL_AudioSpec sdl_audio_spec = {
|
|
.freq = 48000,
|
|
.format = AUDIO_S16LSB,
|
|
.channels = 2,
|
|
.samples = 512,
|
|
.callback = mcpx_vp_out_cb,
|
|
.userdata = d,
|
|
};
|
|
|
|
if (SDL_Init(SDL_INIT_AUDIO) < 0) {
|
|
fprintf(stderr, "Failed to initialize SDL audio subsystem: %s\n", SDL_GetError());
|
|
exit(1);
|
|
}
|
|
|
|
SDL_AudioDeviceID sdl_audio_dev;
|
|
sdl_audio_dev = SDL_OpenAudioDevice(NULL, 0, &sdl_audio_spec, NULL, 0);
|
|
if (sdl_audio_dev == 0) {
|
|
fprintf(stderr, "SDL_OpenAudioDevice failed: %s\n", SDL_GetError());
|
|
assert(!"SDL_OpenAudioDevice failed");
|
|
exit(1);
|
|
}
|
|
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);
|
|
qemu_cond_init(&d->cond);
|
|
qemu_add_vm_change_state_handler(mcpx_apu_vm_state_change, d);
|
|
|
|
/* Until DSP is more performant, a switch to decide whether or not we should
|
|
* use the full audio pipeline or not.
|
|
*/
|
|
mcpx_apu_update_dsp_preference(d);
|
|
|
|
qemu_thread_create(&d->apu_thread, "mcpx.apu_thread", mcpx_apu_frame_thread,
|
|
d, QEMU_THREAD_JOINABLE);
|
|
}
|