/* RetroArch - A frontend for libretro. * Copyright (C) 2014-2016 - Ali Bouhlel * Copyright (C) 2016 - FIX94 * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along with RetroArch. * If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include "wiiu/wiiu_dbg.h" #include "wiiu/system/memory.h" #include "wiiu/multivoice.h" #include "audio/audio_driver.h" #include "configuration.h" #include "performance_counters.h" #include "runloop.h" typedef struct { AXMVoice* mvoice; uint16_t* buffer_l; uint16_t* buffer_r; bool nonblocking; uint32_t pos; uint32_t written; OSSpinLock spinlock; } ax_audio_t; //4096 samples main buffer, 85ms total #define AX_AUDIO_COUNT_SHIFT 12u #define AX_AUDIO_COUNT (1u << AX_AUDIO_COUNT_SHIFT) #define AX_AUDIO_COUNT_MASK (AX_AUDIO_COUNT - 1u) #define AX_AUDIO_SIZE (AX_AUDIO_COUNT << 1u) #define AX_AUDIO_SIZE_MASK (AX_AUDIO_SIZE - 1u) #define AX_AUDIO_SAMPLE_COUNT 144 //3ms #define AX_AUDIO_SAMPLE_MIN (AX_AUDIO_SAMPLE_COUNT * 6) //18ms #define AX_AUDIO_SAMPLE_LOAD (AX_AUDIO_SAMPLE_COUNT * 8) //24ms #define AX_AUDIO_RATE 48000 //#define ax_audio_ticks_to_samples(ticks) (((ticks) * 64) / 82875) //#define ax_audio_samples_to_ticks(samples) (((samples) * 82875) / 64) AXResult ax_aux_callback(void* data, ax_audio_t* ax) { OSUninterruptibleSpinLock_Acquire(&ax->spinlock); //buffer underrun, stop playback to let if fill up if(ax->written < AX_AUDIO_SAMPLE_MIN) AXSetMultiVoiceState(ax->mvoice, AX_VOICE_STATE_STOPPED); //make sure to update written value if voice is running if(AXIsMultiVoiceRunning(ax->mvoice)) ax->written -= (ax->written < AX_AUDIO_SAMPLE_COUNT ? ax->written : AX_AUDIO_SAMPLE_COUNT); OSUninterruptibleSpinLock_Release(&ax->spinlock); return AX_RESULT_SUCCESS; } static void* ax_audio_init(const char* device, unsigned rate, unsigned latency) { ax_audio_t* ax = (ax_audio_t*)calloc(1, sizeof(ax_audio_t)); if (!ax) return NULL; AXInitParams init = {AX_INIT_RENDERER_48KHZ, 0, 0}; AXInitWithParams(&init); u16 setup_buf[0x30] = {0}; setup_buf[0x25] = 2; //we request 2 channels AXAcquireMultiVoice(31, NULL, 0, setup_buf, &ax->mvoice); if (!ax->mvoice || ax->mvoice->channels != 2) { free(ax); return NULL; } ax->buffer_l = MEM1_alloc(AX_AUDIO_SIZE, 0x100); ax->buffer_r = MEM1_alloc(AX_AUDIO_SIZE, 0x100); memset(ax->buffer_l,0,AX_AUDIO_SIZE); memset(ax->buffer_r,0,AX_AUDIO_SIZE); DCFlushRange(ax->buffer_l,AX_AUDIO_SIZE); DCFlushRange(ax->buffer_r,AX_AUDIO_SIZE); //shared by both voices AXVoiceOffsets offsets[2]; offsets[0].currentOffset = 0; offsets[0].loopOffset = 0; offsets[0].endOffset = AX_AUDIO_COUNT - 1; offsets[0].loopingEnabled = AX_VOICE_LOOP_ENABLED; offsets[0].dataType = AX_VOICE_FORMAT_LPCM16; memcpy(&offsets[1], &offsets[0], sizeof(AXVoiceOffsets)); //different buffers per voice offsets[0].data = ax->buffer_l; offsets[1].data = ax->buffer_r; AXSetMultiVoiceOffsets(ax->mvoice, offsets); AXSetMultiVoiceSrcType(ax->mvoice, AX_VOICE_SRC_TYPE_NONE); AXSetMultiVoiceSrcRatio(ax->mvoice, 1.0f); AXVoiceVeData ve = {0xF000, 0}; AXSetMultiVoiceVe(ax->mvoice, &ve); AXSetMultiVoiceDeviceMix(ax->mvoice, AX_DEVICE_TYPE_DRC, 0, 0, 0x8000, 0); AXSetMultiVoiceDeviceMix(ax->mvoice, AX_DEVICE_TYPE_TV, 0, 0, 0x8000, 0); AXSetMultiVoiceState(ax->mvoice, AX_VOICE_STATE_STOPPED); ax->pos = 0; ax->written = 0; config_get_ptr()->audio.out_rate = AX_AUDIO_RATE; AXRegisterAuxCallback(AX_DEVICE_TYPE_DRC, 0, 0, (AXAuxCallback)ax_aux_callback, ax); OSInitSpinLock(&ax->spinlock); return ax; } static void ax_audio_free(void* data) { ax_audio_t* ax = (ax_audio_t*)data; AXRegisterAuxCallback(AX_DEVICE_TYPE_DRC, 0, 0, NULL, NULL); AXFreeMultiVoice(ax->mvoice); MEM1_free(ax->buffer_l); MEM1_free(ax->buffer_r); free(ax); AXQuit(); } static bool ax_audio_stop(void* data) { ax_audio_t* ax = (ax_audio_t*)data; AXSetMultiVoiceState(ax->mvoice, AX_VOICE_STATE_STOPPED); return true; } static bool ax_audio_start(void* data) { ax_audio_t* ax = (ax_audio_t*)data; /* Prevents restarting audio when the menu * is toggled off on shutdown */ if (runloop_ctl(RUNLOOP_CTL_IS_SHUTDOWN, NULL)) return true; //set back to playing on enough buffered data if(ax->written > AX_AUDIO_SAMPLE_MIN) AXSetMultiVoiceState(ax->mvoice, AX_VOICE_STATE_PLAYING); return true; } static ssize_t ax_audio_write(void* data, const void* buf, size_t size) { static struct retro_perf_counter ax_audio_write_perf = {0}; ax_audio_t* ax = (ax_audio_t*)data; const uint16_t* src = buf; int i; if(!size || (size & 0x3)) return 0; int count = size >> 2; if(count > AX_AUDIO_COUNT) count = AX_AUDIO_COUNT; size_t countAvail = AX_AUDIO_COUNT - ax->written; if (ax->nonblocking) { if(countAvail < AX_AUDIO_SAMPLE_COUNT) return 0; if(count > countAvail) count = countAvail; } else if(countAvail < count) { //sync, wait for free memory do { OSYieldThread(); countAvail = AX_AUDIO_COUNT - ax->written; } while(AXIsMultiVoiceRunning(ax->mvoice) && countAvail < count); } performance_counter_init(&ax_audio_write_perf, "ax_audio_write"); performance_counter_start(&ax_audio_write_perf); //write in new data size_t startPos = ax->pos; int flushP2needed = 0; int flushP2 = 0; for (i = 0; i < (count << 1); i += 2) { ax->buffer_l[ax->pos] = src[i]; ax->buffer_r[ax->pos] = src[i + 1]; ax->pos++; ax->pos &= AX_AUDIO_COUNT_MASK; //wrapped around, make sure to store cache if(ax->pos == 0) { flushP2needed = 1; flushP2 = ((count << 1) - i); DCStoreRangeNoSync(ax->buffer_l+startPos, (AX_AUDIO_COUNT-startPos) << 1); DCStoreRangeNoSync(ax->buffer_r+startPos, (AX_AUDIO_COUNT-startPos) << 1); } } //standard cache store case if(!flushP2needed) { DCStoreRangeNoSync(ax->buffer_l+startPos, count << 1); DCStoreRange(ax->buffer_r+startPos, count << 1); } //store the rest after wrap else if(flushP2 > 0) { DCStoreRangeNoSync(ax->buffer_l, flushP2); DCStoreRange(ax->buffer_r, flushP2); } //add in new audio data OSUninterruptibleSpinLock_Acquire(&ax->spinlock); ax->written += count; OSUninterruptibleSpinLock_Release(&ax->spinlock); //possibly buffer underrun if(!AXIsMultiVoiceRunning(ax->mvoice)) { //checks if it can be started ax_audio_start(ax); } performance_counter_stop(&ax_audio_write_perf); return (count << 2); } static bool ax_audio_alive(void* data) { ax_audio_t* ax = (ax_audio_t*)data; return AXIsMultiVoiceRunning(ax->mvoice); } static void ax_audio_set_nonblock_state(void* data, bool state) { ax_audio_t* ax = (ax_audio_t*)data; if (ax) { ax->nonblocking = state; } } static bool ax_audio_use_float(void* data) { (void)data; return false; } static size_t ax_audio_write_avail(void* data) { ax_audio_t* ax = (ax_audio_t*)data; size_t ret = AX_AUDIO_COUNT - ax->written; return (ret < AX_AUDIO_SAMPLE_COUNT ? 0 : ret); } static size_t ax_audio_buffer_size(void* data) { (void)data; return AX_AUDIO_COUNT; } audio_driver_t audio_ax = { ax_audio_init, ax_audio_write, ax_audio_stop, ax_audio_start, ax_audio_alive, ax_audio_set_nonblock_state, ax_audio_free, ax_audio_use_float, "AX", NULL, NULL, // ax_audio_write_avail, // ax_audio_buffer_size };