From 772c839c8cc0abe4a2373a4dd7279e4c946e9d3d Mon Sep 17 00:00:00 2001 From: fincs Date: Mon, 13 Aug 2018 16:31:50 +0200 Subject: [PATCH] Add audio/ ("AudioDriver"), a high level wrapper around audren --- nx/Makefile | 2 +- nx/include/switch.h | 2 + nx/include/switch/audio/driver.h | 144 ++++++++++++++++++ nx/source/audio/driver.c | 143 ++++++++++++++++++ nx/source/audio/driver_internal.h | 70 +++++++++ nx/source/audio/mempool.c | 77 ++++++++++ nx/source/audio/mix_object.c | 40 +++++ nx/source/audio/sink.c | 33 +++++ nx/source/audio/voice.c | 236 ++++++++++++++++++++++++++++++ 9 files changed, 746 insertions(+), 1 deletion(-) create mode 100644 nx/include/switch/audio/driver.h create mode 100644 nx/source/audio/driver.c create mode 100644 nx/source/audio/driver_internal.h create mode 100644 nx/source/audio/mempool.c create mode 100644 nx/source/audio/mix_object.c create mode 100644 nx/source/audio/sink.c create mode 100644 nx/source/audio/voice.c diff --git a/nx/Makefile b/nx/Makefile index fe00f807..d3abb3c4 100644 --- a/nx/Makefile +++ b/nx/Makefile @@ -24,7 +24,7 @@ VERSION := $(LIBNX_MAJOR).$(LIBNX_MINOR).$(LIBNX_PATCH) #--------------------------------------------------------------------------------- TARGET := nx #BUILD := build -SOURCES := source/arm source/runtime source/kernel source/runtime/devices source/services source/gfx source/gfx/ioctl source/runtime/util/utf +SOURCES := source/arm source/kernel source/services source/gfx source/gfx/ioctl source/audio source/runtime source/runtime/devices source/runtime/util/utf DATA := data INCLUDES := include external/bsd/include diff --git a/nx/include/switch.h b/nx/include/switch.h index 266ffba8..893d02b5 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -73,6 +73,8 @@ extern "C" { #include "switch/gfx/nvioctl.h" #include "switch/gfx/nvgfx.h" +#include "switch/audio/driver.h" + #include "switch/runtime/env.h" #include "switch/runtime/nxlink.h" diff --git a/nx/include/switch/audio/driver.h b/nx/include/switch/audio/driver.h new file mode 100644 index 00000000..a6e84c79 --- /dev/null +++ b/nx/include/switch/audio/driver.h @@ -0,0 +1,144 @@ +/** + * @file driver.h + * @brief Audio driver (audren wrapper). + * @author fincs + * @copyright libnx Authors + */ +#pragma once +#include "../services/audren.h" + +typedef struct AudioDriverEtc AudioDriverEtc; + +typedef struct { + AudioDriverEtc* etc; + AudioRendererConfig config; + AudioRendererMemPoolInfoIn* in_mempools; + AudioRendererChannelInfoIn* in_channels; + AudioRendererVoiceInfoIn* in_voices; + AudioRendererMixInfoIn* in_mixes; + AudioRendererSinkInfoIn* in_sinks; +} AudioDriver; + +Result audrvCreate(AudioDriver* d, const AudioRendererConfig* config, int num_final_mix_channels); +Result audrvUpdate(AudioDriver* d); +void audrvClose(AudioDriver* d); + +//----------------------------------------------------------------------------- + +int audrvMemPoolAdd(AudioDriver* d, void* buffer, size_t size); +bool audrvMemPoolRemove(AudioDriver* d, int id); +bool audrvMemPoolAttach(AudioDriver* d, int id); +bool audrvMemPoolDetach(AudioDriver* d, int id); + +//----------------------------------------------------------------------------- + +typedef enum { + AudioDriverWaveBufState_Free, + AudioDriverWaveBufState_Waiting, + AudioDriverWaveBufState_Queued, + AudioDriverWaveBufState_Playing, + AudioDriverWaveBufState_Done, +} AudioDriverWaveBufState; + +typedef struct AudioDriverWaveBuf AudioDriverWaveBuf; + +struct AudioDriverWaveBuf { + union { + s16* data_pcm16; + u8* data_adpcm; + const void* data_raw; + }; + u64 size; + s32 start_sample_offset; + s32 end_sample_offset; + const void* context_addr; + u64 context_sz; + AudioDriverWaveBufState state : 8; + bool is_looping; + u32 sequence_id; + AudioDriverWaveBuf* next; +}; + +bool audrvVoiceInit(AudioDriver* d, int id, int num_channels, PcmFormat format, int sample_rate); +void audrvVoiceDrop(AudioDriver* d, int id); +void audrvVoiceStop(AudioDriver* d, int id); +bool audrvVoiceIsPlaying(AudioDriver* d, int id); +bool audrvVoiceAddWaveBuf(AudioDriver* d, int id, AudioDriverWaveBuf* wavebuf); +u32 audrvVoiceGetWaveBufSeq(AudioDriver* d, int id); +u32 audrvVoiceGetPlayedSampleCount(AudioDriver* d, int id); +u32 audrvVoiceGetVoiceDropsCount(AudioDriver* d, int id); +void audrvVoiceSetBiquadFilter(AudioDriver* d, int id, int biquad_id, float a0, float a1, float a2, float b0, float b1, float b2); + +static inline void audrvVoiceSetExtraParams(AudioDriver* d, int id, const void* params, size_t params_size) +{ + d->in_voices[id].extra_params_ptr = params; + d->in_voices[id].extra_params_sz = params_size; +} + +static inline void audrvVoiceSetDestinationMix(AudioDriver* d, int id, int mix_id) +{ + d->in_voices[id].dest_mix_id = mix_id; + d->in_voices[id].dest_splitter_id = AUDREN_UNUSED_SPLITTER_ID; +} + +static inline void audrvVoiceSetMixFactor(AudioDriver* d, int id, float factor, int src_channel_id, int dest_channel_id) +{ + int channel_id = d->in_voices[id].channel_ids[src_channel_id]; + d->in_channels[channel_id].mix[dest_channel_id] = factor; +} + +static inline void audrvVoiceSetVolume(AudioDriver* d, int id, float volume) +{ + d->in_voices[id].volume = volume; +} + +static inline void audrvVoiceSetPitch(AudioDriver* d, int id, float pitch) +{ + d->in_voices[id].pitch = pitch; +} + +static inline void audrvVoiceSetPriority(AudioDriver* d, int id, int priority) +{ + d->in_voices[id].priority = priority; +} + +static inline void audrvVoiceClearBiquadFilter(AudioDriver* d, int id, int biquad_id) +{ + d->in_voices[id].biquads[biquad_id].enable = false; +} + +static inline void audrvVoiceSetPaused(AudioDriver* d, int id, bool paused) +{ + d->in_voices[id].state = paused ? AudioRendererVoicePlayState_Paused : AudioRendererVoicePlayState_Started; +} + +static inline void audrvVoiceStart(AudioDriver* d, int id) +{ + audrvVoiceSetPaused(d, id, false); +} + +//----------------------------------------------------------------------------- + +int audrvMixAdd(AudioDriver* d, int sample_rate, int num_channels); +void audrvMixRemove(AudioDriver* d, int id); + +static inline void audrvMixSetDestinationMix(AudioDriver* d, int id, int mix_id) +{ + d->in_mixes[id].dest_mix_id = mix_id; + d->in_mixes[id].dest_splitter_id = AUDREN_UNUSED_SPLITTER_ID; +} + +static inline void audrvMixSetMixFactor(AudioDriver* d, int id, float factor, int src_channel_id, int dest_channel_id) +{ + d->in_mixes[id].mix[src_channel_id][dest_channel_id] = factor; +} + +static inline void audrvMixSetVolume(AudioDriver* d, int id, float volume) +{ + d->in_mixes[id].volume = volume; +} + +//----------------------------------------------------------------------------- + +int audrvDeviceSinkAdd(AudioDriver* d, const char* device_name, int num_channels, const u8* channel_ids); +void audrvSinkRemove(AudioDriver* d, int id); diff --git a/nx/source/audio/driver.c b/nx/source/audio/driver.c new file mode 100644 index 00000000..b8cb2f18 --- /dev/null +++ b/nx/source/audio/driver.c @@ -0,0 +1,143 @@ +#include "driver_internal.h" + +static inline void _audrvInitConfig(AudioDriver* d, int num_final_mix_channels) +{ + memset(d->etc->in_buf, 0, d->etc->in_buf_size); + AudioRendererUpdateDataHeader* in_hdr = (AudioRendererUpdateDataHeader*)d->etc->in_buf; + in_hdr->revision = audrenGetRevision(); + in_hdr->behavior_sz = sizeof(AudioRendererBehaviorInfoIn); + in_hdr->mempools_sz = sizeof(AudioRendererMemPoolInfoIn) * d->etc->mempool_count; + in_hdr->channels_sz = sizeof(AudioRendererChannelInfoIn) * d->config.num_voices; + in_hdr->voices_sz = sizeof(AudioRendererVoiceInfoIn) * d->config.num_voices; + in_hdr->mixes_sz = sizeof(AudioRendererMixInfoIn) * d->config.num_mix_objs; + in_hdr->sinks_sz = sizeof(AudioRendererSinkInfoIn) * d->config.num_sinks; + in_hdr->perfmgr_sz = sizeof(AudioRendererPerformanceBufferInfoIn); + in_hdr->total_sz = d->etc->in_buf_size; + + d->etc->in_behavior = (AudioRendererBehaviorInfoIn*)(in_hdr+1); + d->etc->in_behavior->revision = audrenGetRevision(); + d->etc->in_behavior->flags = 0; + + d->in_mempools = (AudioRendererMemPoolInfoIn*)(d->etc->in_behavior+1); + for (int i = 0; i < d->etc->mempool_count; i ++) { + d->in_mempools[i].state = AudioRendererMemPoolState_Released; + d->etc->mempools[i].next_free = i != d->etc->mempool_count-1 ? i+1 : -1; + } + + d->in_channels = (AudioRendererChannelInfoIn*)(d->in_mempools+d->etc->mempool_count); + d->in_voices = (AudioRendererVoiceInfoIn*)(d->in_channels+d->config.num_voices); + for (int i = 0; i < d->config.num_voices; i ++) { + d->in_channels[i].id = i; + d->etc->voices[i].next_free_channel = i != d->config.num_voices-1 ? i+1 : -1; + } + + d->in_mixes = (AudioRendererMixInfoIn*)(d->in_voices+d->config.num_voices); + d->in_mixes[0].volume = 1.0f; + d->in_mixes[0].sample_rate = d->config.output_rate == AudioRendererOutputRate_32kHz ? 32000 : 48000; + d->in_mixes[0].buffer_count = num_final_mix_channels; + d->in_mixes[0].is_used = true; + d->in_mixes[0].mix_id = AUDREN_FINAL_MIX_ID; + d->in_mixes[0].node_id = AUDREN_NODEID(2,0,0); + d->in_mixes[0].dest_mix_id = AUDREN_UNUSED_MIX_ID; + d->in_mixes[0].dest_splitter_id = AUDREN_UNUSED_SPLITTER_ID; + if (d->config.num_mix_objs > 1) { + d->etc->first_free_mix = 1; + for (int i = 1; i < d->etc->first_free_mix; i ++) + d->etc->mixes[i].next_free = i != d->etc->first_free_mix-1 ? i+1 : -1; + } else { + d->etc->first_free_mix = -1; + } + + d->in_sinks = (AudioRendererSinkInfoIn*)(d->in_mixes+d->config.num_mix_objs); + for (int i = 0; i < d->config.num_sinks; i ++) + d->etc->sinks[i].next_free = i != d->config.num_sinks-1 ? i+1 : -1; + + d->etc->in_perfbuf = (AudioRendererPerformanceBufferInfoIn*)(d->in_sinks+d->config.num_sinks); + d->etc->in_perfbuf->detail_target = AUDREN_NODEID(15,0,0); // whatever? +} + +Result audrvCreate(AudioDriver* d, const AudioRendererConfig* config, int num_final_mix_channels) +{ + if (num_final_mix_channels < config->num_mix_buffers) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + + size_t etc_size = _audrvGetEtcSize(config); + memset(d, 0, sizeof(AudioDriver)); + + d->etc = (AudioDriverEtc*)malloc(etc_size); + if (!d->etc) + goto _error0; + + memset(d->etc, 0, etc_size); + d->config = *config; + d->etc->mempool_count = audrenGetMemPoolCount(config); + d->etc->free_channel_count = config->num_voices; + d->etc->free_mix_buffer_count = config->num_mix_buffers - num_final_mix_channels; + d->etc->first_used_voice = -1; + d->etc->mempools = (AudioDriverEtcMemPool*)(d->etc+1); + d->etc->voices = (AudioDriverEtcVoice*)(d->etc->mempools+d->etc->mempool_count); + d->etc->mixes = (AudioDriverEtcMix*)(d->etc->voices+config->num_voices); + d->etc->sinks = (AudioDriverEtcSink*)(d->etc->mixes+config->num_mix_objs); + + d->etc->out_buf_size = audrenGetOutputParamSize(config); + d->etc->out_buf = memalign(AUDREN_OUTPUT_PARAM_ALIGNMENT, d->etc->out_buf_size); + if (!d->etc->out_buf) + goto _error1; + + d->etc->in_buf_size = audrenGetInputParamSize(config); + d->etc->in_buf = memalign(AUDREN_INPUT_PARAM_ALIGNMENT, d->etc->in_buf_size); + if (!d->etc->in_buf) + goto _error2; + + _audrvInitConfig(d, num_final_mix_channels); + return 0; + +_error2: + free(d->etc->out_buf); +_error1: + free(d->etc); + d->etc = NULL; +_error0: + return MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); +} + +Result audrvUpdate(AudioDriver* d) +{ + for (int i = d->etc->first_used_voice, j = 0; i >= 0; i = d->etc->voices[i].next_used_voice, j++) + d->in_voices[i].sorting_order = j; + + Result rc = audrenRequestUpdateAudioRenderer(d->etc->in_buf, d->etc->in_buf_size, d->etc->out_buf, d->etc->out_buf_size, NULL, 0); + if (R_FAILED(rc)) + return rc; + + AudioRendererUpdateDataHeader* out_hdr = (AudioRendererUpdateDataHeader*)d->etc->out_buf; + + AudioRendererMemPoolInfoOut* out_mempools = (AudioRendererMemPoolInfoOut*)(out_hdr+1); + if (out_hdr->mempools_sz != d->etc->mempool_count*sizeof(AudioRendererMemPoolInfoOut)) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + + for (int i = 0; i < d->etc->mempool_count; i ++) + { + // todo: this is supposed to be more complex + AudioRendererMemPoolState new_state = out_mempools[i].new_state; + if (new_state != AudioRendererMemPoolState_Invalid) + d->in_mempools[i].state = new_state; + } + + AudioRendererVoiceInfoOut* out_voices = (AudioRendererVoiceInfoOut*)(out_mempools+d->etc->mempool_count); + if (out_hdr->voices_sz != d->config.num_voices*sizeof(AudioRendererVoiceInfoOut)) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + + for (int i = d->etc->first_used_voice; i >= 0; i = d->etc->voices[i].next_used_voice) + _audrvVoiceUpdate(d, i, &out_voices[i]); + + return 0; +} + +void audrvClose(AudioDriver* d) +{ + free(d->etc->in_buf); + free(d->etc->out_buf); + free(d->etc); + memset(d, 0, sizeof(AudioDriver)); +} diff --git a/nx/source/audio/driver_internal.h b/nx/source/audio/driver_internal.h new file mode 100644 index 00000000..b6435ca7 --- /dev/null +++ b/nx/source/audio/driver_internal.h @@ -0,0 +1,70 @@ +#pragma once +#include +#include +#include "types.h" +#include "result.h" +#include "arm/atomics.h" +#include "services/audren.h" +#include "audio/driver.h" + +typedef struct { + int next_free; + int padding; +} AudioDriverEtcMemPool; + +typedef struct { + int* prev_next_voice; + int next_free_channel; + int next_used_voice; + u16 node_counter; + u32 num_wavebufs_consumed; + u32 voice_drops_count; + u64 played_sample_count; + AudioDriverWaveBuf* first_wavebuf; + AudioDriverWaveBuf* waiting_wavebuf; + AudioDriverWaveBuf* last_wavebuf; +} AudioDriverEtcVoice; + +typedef struct { + int next_free; + int padding; +} AudioDriverEtcMix; + +typedef struct { + int next_free; + int padding; +} AudioDriverEtcSink; + +struct AudioDriverEtc { + int mempool_count; + int free_channel_count; + int free_mix_buffer_count; + int first_used_voice; + int first_free_mempool; + int first_free_channel; + int first_free_mix; + int first_free_sink; + AudioDriverEtcMemPool* mempools; + AudioDriverEtcVoice* voices; + AudioDriverEtcMix* mixes; + AudioDriverEtcSink* sinks; + AudioRendererBehaviorInfoIn* in_behavior; + AudioRendererPerformanceBufferInfoIn* in_perfbuf; + void* in_buf; + size_t in_buf_size; + void* out_buf; + size_t out_buf_size; +}; + +static inline size_t _audrvGetEtcSize(const AudioRendererConfig* config) +{ + size_t size = 0; + size += sizeof(AudioDriverEtc); + size += sizeof(AudioDriverEtcMemPool) * audrenGetMemPoolCount(config); + size += sizeof(AudioDriverEtcVoice) * config->num_voices; + size += sizeof(AudioDriverEtcMix) * config->num_mix_objs; + size += sizeof(AudioDriverEtcSink) * config->num_sinks; + return size; +} + +void _audrvVoiceUpdate(AudioDriver* d, int id, AudioRendererVoiceInfoOut* out_voice); diff --git a/nx/source/audio/mempool.c b/nx/source/audio/mempool.c new file mode 100644 index 00000000..975d5e78 --- /dev/null +++ b/nx/source/audio/mempool.c @@ -0,0 +1,77 @@ +#include "driver_internal.h" + +int audrvMemPoolAdd(AudioDriver* d, void* buffer, size_t size) +{ + if (!buffer || ((uintptr_t)buffer & (AUDREN_MEMPOOL_ALIGNMENT-1))) + return -1; + if (size & (AUDREN_MEMPOOL_ALIGNMENT-1)) + return -1; + + int id = d->etc->first_free_mempool; + if (id < 0) + return -1; + + d->etc->first_free_mempool = d->etc->mempools[id].next_free; + d->in_mempools[id].address = buffer; + d->in_mempools[id].size = size; + d->in_mempools[id].state = AudioRendererMemPoolState_New; + + return id; +} + +bool audrvMemPoolRemove(AudioDriver* d, int id) +{ + if (id < 0) + return false; + if (d->in_mempools[id].state == AudioRendererMemPoolState_Attached || d->in_mempools[id].state == AudioRendererMemPoolState_RequestDetach) + return false; + + d->etc->mempools[id].next_free = d->etc->first_free_mempool; + d->etc->first_free_mempool = id; + d->in_mempools[id].address = NULL; + d->in_mempools[id].size = 0; + d->in_mempools[id].state = AudioRendererMemPoolState_Released; + + return true; +} + +bool audrvMemPoolAttach(AudioDriver* d, int id) +{ + if (id < 0 || id >= d->etc->mempool_count) + return false; + + switch (d->in_mempools[id].state) { + case AudioRendererMemPoolState_New: + case AudioRendererMemPoolState_RequestAttach: + case AudioRendererMemPoolState_Detached: + d->in_mempools[id].state = AudioRendererMemPoolState_RequestAttach; + return true; + case AudioRendererMemPoolState_RequestDetach: + case AudioRendererMemPoolState_Attached: + d->in_mempools[id].state = AudioRendererMemPoolState_Attached; + return true; + default: + return false; + } +} + +bool audrvMemPoolDetach(AudioDriver* d, int id) +{ + if (id < 0 || id >= d->etc->mempool_count) + return false; + + switch (d->in_mempools[id].state) { + case AudioRendererMemPoolState_New: + return true; + case AudioRendererMemPoolState_RequestAttach: + case AudioRendererMemPoolState_Detached: + d->in_mempools[id].state = AudioRendererMemPoolState_Detached; + return true; + case AudioRendererMemPoolState_RequestDetach: + case AudioRendererMemPoolState_Attached: + d->in_mempools[id].state = AudioRendererMemPoolState_RequestDetach; + return true; + default: + return false; + } +} diff --git a/nx/source/audio/mix_object.c b/nx/source/audio/mix_object.c new file mode 100644 index 00000000..2dfb01fc --- /dev/null +++ b/nx/source/audio/mix_object.c @@ -0,0 +1,40 @@ +#include "driver_internal.h" + +int audrvMixAdd(AudioDriver* d, int sample_rate, int num_channels) +{ + if (num_channels < 1 || num_channels > 24) + return -1; + + if (d->etc->free_mix_buffer_count < num_channels) + return -1; + + int id = d->etc->first_free_mix; + if (id < 0) + return -1; + + d->etc->free_mix_buffer_count -= num_channels; + d->etc->first_free_mix = d->etc->mixes[id].next_free; + d->in_mixes[id].volume = 1.0f; + d->in_mixes[id].sample_rate = sample_rate; + d->in_mixes[id].buffer_count = num_channels; + d->in_mixes[id].is_used = true; + d->in_mixes[id].mix_id = id; + d->in_mixes[id].node_id = AUDREN_NODEID(2,id,0); + d->in_mixes[id].dest_mix_id = AUDREN_UNUSED_MIX_ID; + d->in_mixes[id].dest_splitter_id = AUDREN_UNUSED_SPLITTER_ID; + + return id; +} + +void audrvMixRemove(AudioDriver* d, int id) +{ + if (id <= AUDREN_FINAL_MIX_ID || id >= d->config.num_mix_objs) + return; + if (!d->in_mixes[id].is_used) + return; + + d->etc->free_mix_buffer_count += d->in_mixes[id].buffer_count; + d->etc->mixes[id].next_free = d->etc->first_free_mix; + d->etc->first_free_mix = id; + memset(&d->in_mixes[id], 0, sizeof(AudioRendererMixInfoIn)); +} diff --git a/nx/source/audio/sink.c b/nx/source/audio/sink.c new file mode 100644 index 00000000..dbfd2be0 --- /dev/null +++ b/nx/source/audio/sink.c @@ -0,0 +1,33 @@ +#include "driver_internal.h" + +int audrvDeviceSinkAdd(AudioDriver* d, const char* device_name, int num_channels, const u8* channel_ids) +{ + if (!device_name || num_channels < 1 || num_channels > 6 || !channel_ids) + return -1; + + int id = d->etc->first_free_sink; + if (id < 0) + return -1; + + d->etc->first_free_sink = d->etc->sinks[id].next_free; + d->in_sinks[id].type = AudioRendererSinkType_Device; + d->in_sinks[id].is_used = true; + d->in_sinks[id].node_id = AUDREN_NODEID(3,id,0); + strncpy(d->in_sinks[id].device_sink.name, device_name, sizeof(d->in_sinks[id].device_sink.name)-1); + d->in_sinks[id].device_sink.input_count = num_channels; + memcpy(d->in_sinks[id].device_sink.inputs, channel_ids, num_channels); + + return id; +} + +void audrvSinkRemove(AudioDriver* d, int id) +{ + if (id < 0 || id >= d->config.num_sinks) + return; + if (!d->in_sinks[id].is_used) + return; + + d->etc->sinks[id].next_free = d->etc->first_free_sink; + d->etc->first_free_sink = id; + memset(&d->in_sinks[id], 0, sizeof(AudioRendererSinkInfoIn)); +} diff --git a/nx/source/audio/voice.c b/nx/source/audio/voice.c new file mode 100644 index 00000000..f917f9bb --- /dev/null +++ b/nx/source/audio/voice.c @@ -0,0 +1,236 @@ +#include "driver_internal.h" + +bool audrvVoiceInit(AudioDriver* d, int id, int num_channels, PcmFormat format, int sample_rate) +{ + // Validation + if (id < 0 || id >= d->config.num_voices || num_channels < 1 || (format != PcmFormat_Int16 && format != PcmFormat_Adpcm)) + return false; + + // Drop the voice if it's used + if (d->in_voices[id].is_used) + audrvVoiceDrop(d, id); + + // Make sure there are enough free channels + if (d->etc->free_channel_count < num_channels) + return false; + + // Initialize the voice + d->in_voices[id].id = id; + d->in_voices[id].node_id = AUDREN_NODEID(1,id,d->etc->voices[id].node_counter++); + d->in_voices[id].is_new = true; + d->in_voices[id].is_used = true; + d->in_voices[id].state = AudioRendererVoicePlayState_Stopped; + d->in_voices[id].sample_format = format; + d->in_voices[id].sample_rate = sample_rate; + d->in_voices[id].channel_count = num_channels; + d->in_voices[id].pitch = 1.0f; + d->in_voices[id].volume = 1.0f; + d->in_voices[id].dest_mix_id = AUDREN_UNUSED_MIX_ID; + d->in_voices[id].dest_splitter_id = AUDREN_UNUSED_SPLITTER_ID; + for (int i = 0; i < 4; i ++) + d->in_voices[id].wavebufs[i].sent_to_server = true; + + // Add the voice to the list of used voices + int next_voice = d->etc->first_used_voice; + d->etc->voices[id].next_used_voice = next_voice; + d->etc->voices[id].prev_next_voice = &d->etc->first_used_voice; + d->etc->first_used_voice = id; + if (next_voice >= 0) + d->etc->voices[next_voice].prev_next_voice = &d->etc->voices[id].next_used_voice; + + // Allocate the channels + d->etc->free_channel_count -= num_channels; + for (int i = 0; i < num_channels; i ++) { + int channel_id = d->etc->first_free_channel; + d->etc->first_free_channel = d->etc->voices[channel_id].next_free_channel; + d->in_voices[id].channel_ids[i] = channel_id; + d->in_channels[channel_id].is_used = true; + memset(d->in_channels[channel_id].mix, 0, 24*sizeof(float)); + } + + return true; +} + +static void _audrvVoiceResetInternalState(AudioDriver* d, int id) +{ + // Clear state + d->etc->voices[id].num_wavebufs_consumed = 0; + d->etc->voices[id].voice_drops_count = 0; + d->etc->voices[id].played_sample_count = 0; + + // Clear wavebuf queue, flagging all wavebuf that were on it as 'done' + for (AudioDriverWaveBuf* wavebuf = d->etc->voices[id].first_wavebuf; wavebuf; wavebuf = wavebuf->next) + wavebuf->state = AudioDriverWaveBufState_Done; + d->etc->voices[id].first_wavebuf = NULL; + d->etc->voices[id].waiting_wavebuf = NULL; + d->etc->voices[id].last_wavebuf = NULL; +} + +void audrvVoiceDrop(AudioDriver* d, int id) +{ + // Release the channels + d->etc->free_channel_count += d->in_voices[id].channel_count; + for (int i = d->in_voices[id].channel_count-1; i >= 0; i --) + { + int channel_id = d->in_voices[id].channel_ids[i]; + d->in_channels[channel_id].is_used = false; + d->etc->voices[channel_id].next_free_channel = d->etc->first_free_channel; + d->etc->first_free_channel = channel_id; + } + + // Remove the voice from the list of used voices + int next_voice = d->etc->voices[id].next_used_voice; + *d->etc->voices[id].prev_next_voice = next_voice; + if (next_voice >= 0) + d->etc->voices[next_voice].prev_next_voice = d->etc->voices[id].prev_next_voice; + + // Clear out state + memset(&d->in_voices[id], 0, sizeof(AudioRendererVoiceInfoIn)); + _audrvVoiceResetInternalState(d, id); +} + +void audrvVoiceStop(AudioDriver* d, int id) +{ + // Reset voice state + d->in_voices[id].state = AudioRendererVoicePlayState_Stopped; + d->in_voices[id].is_new = true; + d->in_voices[id].wavebuf_count = 0; + d->in_voices[id].wavebuf_head = 0; + + // Reset wavebuf state + memset(d->in_voices[id].wavebufs, 0, 4*sizeof(AudioRendererWaveBuf)); + for (int i = 0; i < 4; i ++) + d->in_voices[id].wavebufs[i].sent_to_server = true; + + // Reset internal state + _audrvVoiceResetInternalState(d, id); +} + +bool audrvVoiceIsPlaying(AudioDriver* d, int id) +{ + return d->in_voices[id].state == AudioRendererVoicePlayState_Started && d->etc->voices[id].first_wavebuf; +} + +static void _audrvVoiceQueueWaveBufs(AudioDriver* d, int id) +{ + AudioDriverWaveBuf* wavebuf = d->etc->voices[id].waiting_wavebuf; + + while (wavebuf && d->in_voices[id].wavebuf_count < 4) { + int head = (d->in_voices[id].wavebuf_head + d->in_voices[id].wavebuf_count) & 3; + + wavebuf->state = AudioDriverWaveBufState_Queued; + wavebuf->sequence_id = d->etc->voices[id].num_wavebufs_consumed + d->in_voices[id].wavebuf_count; + d->in_voices[id].wavebufs[head].address = wavebuf->data_raw; + d->in_voices[id].wavebufs[head].size = wavebuf->size; + d->in_voices[id].wavebufs[head].start_sample_offset = wavebuf->start_sample_offset; + d->in_voices[id].wavebufs[head].end_sample_offset = wavebuf->end_sample_offset; + d->in_voices[id].wavebufs[head].is_looping = wavebuf->is_looping; + d->in_voices[id].wavebufs[head].end_of_stream = false; + d->in_voices[id].wavebufs[head].sent_to_server = false; + d->in_voices[id].wavebufs[head].context_addr = wavebuf->context_addr; + d->in_voices[id].wavebufs[head].context_sz = wavebuf->context_sz; + d->in_voices[id].wavebuf_count ++; + + wavebuf = wavebuf->next; + } + + d->etc->voices[id].waiting_wavebuf = wavebuf; +} + +bool audrvVoiceAddWaveBuf(AudioDriver* d, int id, AudioDriverWaveBuf* wavebuf) +{ + if (wavebuf->state != AudioDriverWaveBufState_Free && wavebuf->state != AudioDriverWaveBufState_Done) + return false; + if (!wavebuf->data_raw || ((uintptr_t)wavebuf->data_raw & (AUDREN_BUFFER_ALIGNMENT-1))) + return false; + if ((uintptr_t)wavebuf->context_addr & (AUDREN_BUFFER_ALIGNMENT-1)) + return false; + + wavebuf->state = AudioDriverWaveBufState_Waiting; + wavebuf->sequence_id = ~(u32)0; + wavebuf->next = NULL; + + if (d->etc->voices[id].first_wavebuf) + d->etc->voices[id].last_wavebuf->next = wavebuf; + else + d->etc->voices[id].first_wavebuf = wavebuf; + if (!d->etc->voices[id].waiting_wavebuf) + d->etc->voices[id].waiting_wavebuf = wavebuf; + d->etc->voices[id].last_wavebuf = wavebuf; + + _audrvVoiceQueueWaveBufs(d, id); + return true; +} + +u32 audrvVoiceGetWaveBufSeq(AudioDriver* d, int id) +{ + return d->etc->voices[id].num_wavebufs_consumed; +} + +u32 audrvVoiceGetPlayedSampleCount(AudioDriver* d, int id) +{ + return d->etc->voices[id].played_sample_count; +} + +u32 audrvVoiceGetVoiceDropsCount(AudioDriver* d, int id) +{ + return d->etc->voices[id].voice_drops_count; +} + +static inline s16 _audrvIirParamClamp(float param) +{ + float scaled = param*16384.0f; + s16 result = (s16)scaled; + if (scaled > 0x7FFF) + result = 0x7FFF; + else if (scaled < -0x8000) + result = -0x8000; + return result; +} + +void audrvVoiceSetBiquadFilter(AudioDriver* d, int id, int biquad_id, float a0, float a1, float a2, float b0, float b1, float b2) +{ + d->in_voices[id].biquads[biquad_id].enable = true; + d->in_voices[id].biquads[biquad_id].numerator[0] = _audrvIirParamClamp(b0 / a0); + d->in_voices[id].biquads[biquad_id].numerator[1] = _audrvIirParamClamp(b1 / a0); + d->in_voices[id].biquads[biquad_id].numerator[2] = _audrvIirParamClamp(b2 / a0); + d->in_voices[id].biquads[biquad_id].denominator[0] = _audrvIirParamClamp(a1 / a0); + d->in_voices[id].biquads[biquad_id].denominator[1] = _audrvIirParamClamp(a2 / a0); +} + +void _audrvVoiceUpdate(AudioDriver* d, int id, AudioRendererVoiceInfoOut* out_voice) +{ + // Update single-frame flags + d->in_voices[id].is_new = false; + for (int i = 0; i < 4; i ++) + d->in_voices[id].wavebufs[i].sent_to_server = true; + + // Copy state vars + d->etc->voices[id].played_sample_count = out_voice->played_sample_count; + d->etc->voices[id].voice_drops_count = out_voice->voice_drops_count; + + // Update wavebuf progress state + u32 num_wavebufs = out_voice->num_wavebufs_consumed - d->etc->voices[id].num_wavebufs_consumed; + if (num_wavebufs) { + d->in_voices[id].wavebuf_count -= num_wavebufs; + d->in_voices[id].wavebuf_head = (d->in_voices[id].wavebuf_head + num_wavebufs) & 3; + d->etc->voices[id].num_wavebufs_consumed = out_voice->num_wavebufs_consumed; + } + + // Dequeue wavebufs + AudioDriverWaveBuf* wavebuf = d->etc->voices[id].first_wavebuf; + for (u32 i = 0; wavebuf && i < num_wavebufs; i ++) { + wavebuf->state = AudioDriverWaveBufState_Done; + wavebuf = wavebuf->next; + } + + // Update status of current wavebuf (if exists) + if (wavebuf) { + wavebuf->state = AudioDriverWaveBufState_Playing; + d->etc->voices[id].first_wavebuf = wavebuf; + _audrvVoiceQueueWaveBufs(d, id); + } else { + d->etc->voices[id].first_wavebuf = NULL; + d->etc->voices[id].last_wavebuf = NULL; + } +}