cen64/ai/controller.c
queueRAM d7339013a6 Don't make any OpenAL calls if -noaudio option is used.
Also returns error if some OpenAL initialization fails
during ai_context_create().

Fixes #68
2017-05-17 11:54:07 -05:00

326 lines
8.9 KiB
C

//
// ai/controller.c: Audio interface controller.
//
// CEN64: Cycle-Accurate Nintendo 64 Emulator.
// Copyright (C) 2015, Tyler J. Stachecki.
//
// This file is subject to the terms and conditions defined in
// 'LICENSE', which is part of this source code package.
//
#include "common.h"
#include "ai/context.h"
#include "ai/controller.h"
#include "bus/address.h"
#include "bus/controller.h"
#include "ri/controller.h"
#include "rsp/rsp.h"
#include "vr4300/interface.h"
#define NTSC_DAC_FREQ 48681812 // 48.681812MHz
#ifdef DEBUG_MMIO_REGISTER_ACCESS
const char *ai_register_mnemonics[NUM_AI_REGISTERS] = {
#define X(reg) #reg,
#include "ai/registers.md"
#undef X
};
#endif
static void ai_dma(struct ai_controller *ai);
static const uint8_t *byteswap_audio_buffer(const uint8_t *input,
uint8_t *output, uint32_t length);
// Advances the controller by one clock cycle.
void ai_cycle_(struct ai_controller *ai) {
// DMA engine is finishing up with one entry.
if (ai->fifo_count > 0) {
struct bus_controller *bus;
memcpy(&bus, ai, sizeof(bus));
signal_rcp_interrupt(bus->vr4300, MI_INTR_AI);
ai->fifo_ri ^= 0x1;
ai->regs[AI_STATUS_REG] &= ~0xC0000001;
ai->fifo_count--;
if (ai->fifo_count > 0) {
ai->regs[AI_STATUS_REG] = 0x40000000;
ai_dma(ai);
}
}
}
// Performs an (instantaneous) DMA.
void ai_dma(struct ai_controller *ai) {
struct bus_controller *bus;
// Shove things into the audio context, slide the window.
memcpy(&bus, ai, sizeof(bus));
if (ai->fifo[ai->fifo_ri].length > 0) {
unsigned freq = (double) NTSC_DAC_FREQ / (ai->regs[AI_DACRATE_REG] + 1);
unsigned samples = ai->fifo[ai->fifo_ri].length / 4;
// Shovel things into the audio context.
if (ai->no_output)
ai->counter = (62500000.0 / freq) * samples;
else {
ALuint buffer;
ALint val;
alGetSourcei(ai->ctx.source, AL_BUFFERS_PROCESSED, &val);
// XXX: Most games pick one frequency and stick with it.
// Instead of paying garbage, try to dynamically switch
// the frequency of the buffers that OpenAL is using.
//
// This will result in pops and other unpleasant things
// when the frequency changes underneath us, but it still
// seems to sound better than what we had before.
if (ai->ctx.cur_frequency != freq) {
if (val == 0) {
printf("OpenAL: Switching context buffer frequency to: %u\n", freq);
ai_switch_frequency(&ai->ctx, freq);
}
}
// Grab any buffers that have been processed (for reuse).
if (val) {
alSourceUnqueueBuffers(ai->ctx.source, val,
ai->ctx.buffers + ai->ctx.unqueued_buffers);
ai->ctx.unqueued_buffers += val;
}
// Need to raise an interrupt when the DMA engine
// is doing the last 8-byte data bus transfer...
// This dodgy, since the RCP's audio is more or less a realtime
// (and CEN64 has *not* a realtime system, nor does it have a
// resampler that can fudge the audio around non-perfectly-timed
// INTs). So, for now, throw interrupts a little early to prevent
// popping.
switch (ai->ctx.unqueued_buffers) {
// We are ahead of where we want to be. Keep spinning
// until OpenAL has some free buffers for us.
case 0:
case 1:
ai->counter = (62500000.0 / freq) * samples;
break;
// One unprocessed buffer, one going. Try to throw the
// INT almost immediately when the current buffer finshes.
case 2:
ai->counter = (62500000.0 / freq) * (samples - 10);
break;
// No buffers, we should throw an INT almost immediately?
case 3:
ai->counter = 1;
break;
}
if (ai->ctx.unqueued_buffers > 0) {
cen64_align(uint8_t buf[0x40000], 16);
uint32_t length = ai->fifo[ai->fifo_ri].length;
uint8_t *input = bus->ri->ram + ai->fifo[ai->fifo_ri].address;
const uint8_t *buf_ptr = byteswap_audio_buffer(input, buf, length);
ai->ctx.unqueued_buffers--;
buffer = ai->ctx.buffers[ai->ctx.unqueued_buffers];
alBufferData(buffer, AL_FORMAT_STEREO16, buf_ptr, length, freq);
alSourceQueueBuffers(ai->ctx.source, 1, &buffer);
}
alGetSourcei(ai->ctx.source, AL_SOURCE_STATE, &val);
if (val != AL_PLAYING)
alSourcePlay(ai->ctx.source);
}
}
// If the length was zero, just interrupt now?
else {
signal_rcp_interrupt(bus->vr4300, MI_INTR_AI);
ai->fifo_ri ^= 0x1;
ai->regs[AI_STATUS_REG] &= ~0xC0000001;
ai->fifo_count--;
if (ai->fifo_count > 0) {
ai->regs[AI_STATUS_REG] |= 0x40000000;
ai->counter = 1;
}
}
}
// Initializes the AI.
int ai_init(struct ai_controller *ai,
struct bus_controller *bus, bool no_interface) {
ai->bus = bus;
ai->no_output = no_interface;
if (!no_interface) {
alGetError();
if (ai_context_create(&ai->ctx)) {
ai->no_output = 1;
return 1;
}
}
return 0;
}
// Reads a word from the AI MMIO register space.
int read_ai_regs(void *opaque, uint32_t address, uint32_t *word) {
struct ai_controller *ai = (struct ai_controller *) opaque;
unsigned offset = address - AI_REGS_BASE_ADDRESS;
enum ai_register reg = (offset >> 2);
*word = ai->regs[reg];
debug_mmio_read(ai, ai_register_mnemonics[reg], *word);
if (reg == AI_LEN_REG) {
*word = 0;
if (ai->regs[AI_STATUS_REG] & 0x80000001)
*word = ai->regs[AI_LEN_REG];
else if (ai->regs[AI_STATUS_REG] & 0x40000000) {
// TODO
}
}
return 0;
}
// Writes a word to the AI MMIO register space.
int write_ai_regs(void *opaque, uint32_t address, uint32_t word, uint32_t dqm) {
struct ai_controller *ai = (struct ai_controller *) opaque;
unsigned offset = address - AI_REGS_BASE_ADDRESS;
enum ai_register reg = (offset >> 2);
debug_mmio_write(ai, ai_register_mnemonics[reg], word, dqm);
if (reg == AI_DRAM_ADDR_REG)
ai->regs[AI_DRAM_ADDR_REG] = word & 0xFFFFF8;
else if (reg == AI_LEN_REG) {
ai->regs[AI_LEN_REG] = word & 0x3FFF8;
if (ai->fifo_count == 2)
return 0;
// Fill the next FIFO entry in the DMA engine.
ai->fifo[ai->fifo_wi].address = ai->regs[AI_DRAM_ADDR_REG];
ai->fifo[ai->fifo_wi].length = ai->regs[AI_LEN_REG];
ai->fifo_wi ^= 0x1;
ai->fifo_count++;
if (ai->fifo_count == 2)
ai->regs[AI_STATUS_REG] |= 0x80000001U;
// If we're not DMA-ing already, start DMA engine.
if (!(ai->regs[AI_STATUS_REG] & 0x40000000U)) {
ai->regs[AI_STATUS_REG] = 0x40000000;
ai_dma(ai);
}
}
else if (reg == AI_STATUS_REG) {
struct bus_controller *bus;
memcpy(&bus, ai, sizeof(bus));
clear_rcp_interrupt(bus->vr4300, MI_INTR_AI);
}
else if (reg == AI_DACRATE_REG) {
ai->regs[AI_DACRATE_REG] = word & 0x3FFF;
ai->ctx.frequency = (double) NTSC_DAC_FREQ / (ai->regs[AI_DACRATE_REG] + 1);
}
else if (reg == AI_BITRATE_REG)
ai->regs[AI_BITRATE_REG] = word & 0xF;
else
ai->regs[reg] = word;
return 0;
}
const uint8_t *byteswap_audio_buffer(const uint8_t *input,
uint8_t *output, uint32_t length) {
uint32_t i = 0;
#ifdef __SSE2__
#ifdef __SSSE3__
static const uint32_t byteswap_key[] = {
0x02030001, 0x06070405, 0x0A0B0809, 0x0E0F0C0D};
const __m128i key = _mm_load_si128((__m128i*) byteswap_key);
#endif
const uint8_t *ret_buf;
uintptr_t input_ptr;
memcpy(&input_ptr, &input, sizeof(input_ptr));
// Align input buffer to 16 bytes (since transfers are 8 bytes).
if (input_ptr & 0x8) {
__m128i samples = _mm_loadl_epi64((__m128i*) input);
#ifdef __SSSE3__
samples = _mm_shuffle_epi8(samples, key);
#else
__m128i swapleft = _mm_slli_epi16(samples, 8);
__m128i swapright = _mm_srli_epi16(samples, 8);
samples = _mm_or_si128(swapleft, swapright);
#endif
_mm_storel_epi64((__m128i*) (output + sizeof(uint64_t)), samples);
output += sizeof(uint64_t);
i += sizeof(uint64_t);
}
ret_buf = output;
// Byteswap the majority of the buffer just our 16-byte algorithm.
while ((length - i) >= sizeof(__m128i)) {
__m128i samples = _mm_load_si128((__m128i*) (input + i));
#ifdef __SSSE3__
samples = _mm_shuffle_epi8(samples, key);
#else
__m128i swapleft = _mm_slli_epi16(samples, 8);
__m128i swapright = _mm_srli_epi16(samples, 8);
samples = _mm_or_si128(swapleft, swapright);
#endif
_mm_store_si128((__m128i*) (output + i), samples);
i += sizeof(samples);
}
// Finish last (8 byte) segment if one exists.
if (length != i) {
__m128i samples = _mm_loadl_epi64((__m128i*) (input + i));
#ifdef __SSSE3__
samples = _mm_shuffle_epi8(samples, key);
#else
__m128i swapleft = _mm_slli_epi16(samples, 8);
__m128i swapright = _mm_srli_epi16(samples, 8);
samples = _mm_or_si128(swapleft, swapright);
#endif
_mm_storel_epi64((__m128i*) (output + i), samples);
}
return ret_buf;
#else
#error "Unimplemented byteswap_audio_buffer!"
#endif
}