mirror of
https://github.com/switchbrew/libnx.git
synced 2025-06-22 04:52:39 +02:00
Add audio/ ("AudioDriver"), a high level wrapper around audren
This commit is contained in:
parent
6fbf25fb62
commit
772c839c8c
@ -24,7 +24,7 @@ VERSION := $(LIBNX_MAJOR).$(LIBNX_MINOR).$(LIBNX_PATCH)
|
|||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
TARGET := nx
|
TARGET := nx
|
||||||
#BUILD := build
|
#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
|
DATA := data
|
||||||
INCLUDES := include external/bsd/include
|
INCLUDES := include external/bsd/include
|
||||||
|
|
||||||
|
@ -73,6 +73,8 @@ extern "C" {
|
|||||||
#include "switch/gfx/nvioctl.h"
|
#include "switch/gfx/nvioctl.h"
|
||||||
#include "switch/gfx/nvgfx.h"
|
#include "switch/gfx/nvgfx.h"
|
||||||
|
|
||||||
|
#include "switch/audio/driver.h"
|
||||||
|
|
||||||
#include "switch/runtime/env.h"
|
#include "switch/runtime/env.h"
|
||||||
#include "switch/runtime/nxlink.h"
|
#include "switch/runtime/nxlink.h"
|
||||||
|
|
||||||
|
144
nx/include/switch/audio/driver.h
Normal file
144
nx/include/switch/audio/driver.h
Normal file
@ -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);
|
143
nx/source/audio/driver.c
Normal file
143
nx/source/audio/driver.c
Normal file
@ -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));
|
||||||
|
}
|
70
nx/source/audio/driver_internal.h
Normal file
70
nx/source/audio/driver_internal.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <string.h>
|
||||||
|
#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);
|
77
nx/source/audio/mempool.c
Normal file
77
nx/source/audio/mempool.c
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
40
nx/source/audio/mix_object.c
Normal file
40
nx/source/audio/mix_object.c
Normal file
@ -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));
|
||||||
|
}
|
33
nx/source/audio/sink.c
Normal file
33
nx/source/audio/sink.c
Normal file
@ -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));
|
||||||
|
}
|
236
nx/source/audio/voice.c
Normal file
236
nx/source/audio/voice.c
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user