Project Andio

This commit is contained in:
Kelebek1 2022-07-16 23:48:45 +01:00
parent 6e36f4d230
commit 458da8a948
270 changed files with 33712 additions and 8445 deletions

View file

@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include "audio_core/common/common.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
/**
* Represents one channel for mixing a voice.
*/
class VoiceChannelResource {
public:
struct InParameter {
/* 0x00 */ u32 id;
/* 0x04 */ std::array<f32, MaxMixBuffers> mix_volumes;
/* 0x64 */ bool in_use;
/* 0x65 */ char unk65[0xB];
};
static_assert(sizeof(InParameter) == 0x70,
"VoiceChannelResource::InParameter has the wrong size!");
explicit VoiceChannelResource(u32 id_) : id{id_} {}
/// Current volume for each mix buffer
std::array<f32, MaxMixBuffers> mix_volumes{};
/// Previous volume for each mix buffer
std::array<f32, MaxMixBuffers> prev_mix_volumes{};
/// Id of this resource
const u32 id;
/// Is this resource in use?
bool in_use{};
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <ranges>
#include "audio_core/renderer/voice/voice_context.h"
namespace AudioCore::AudioRenderer {
VoiceState& VoiceContext::GetDspSharedState(const u32 index) {
if (index >= dsp_states.size()) {
LOG_ERROR(Service_Audio, "Invalid voice dsp state index {:04X}", index);
}
return dsp_states[index];
}
VoiceChannelResource& VoiceContext::GetChannelResource(const u32 index) {
if (index >= channel_resources.size()) {
LOG_ERROR(Service_Audio, "Invalid voice channel resource index {:04X}", index);
}
return channel_resources[index];
}
void VoiceContext::Initialize(std::span<VoiceInfo*> sorted_voice_infos_,
std::span<VoiceInfo> voice_infos_,
std::span<VoiceChannelResource> voice_channel_resources_,
std::span<VoiceState> cpu_states_, std::span<VoiceState> dsp_states_,
const u32 voice_count_) {
sorted_voice_info = sorted_voice_infos_;
voices = voice_infos_;
channel_resources = voice_channel_resources_;
cpu_states = cpu_states_;
dsp_states = dsp_states_;
voice_count = voice_count_;
active_count = 0;
}
VoiceInfo* VoiceContext::GetSortedInfo(const u32 index) {
if (index >= sorted_voice_info.size()) {
LOG_ERROR(Service_Audio, "Invalid voice sorted info index {:04X}", index);
}
return sorted_voice_info[index];
}
VoiceInfo& VoiceContext::GetInfo(const u32 index) {
if (index >= voices.size()) {
LOG_ERROR(Service_Audio, "Invalid voice info index {:04X}", index);
}
return voices[index];
}
VoiceState& VoiceContext::GetState(const u32 index) {
if (index >= cpu_states.size()) {
LOG_ERROR(Service_Audio, "Invalid voice cpu state index {:04X}", index);
}
return cpu_states[index];
}
u32 VoiceContext::GetCount() const {
return voice_count;
}
u32 VoiceContext::GetActiveCount() const {
return active_count;
}
void VoiceContext::SetActiveCount(const u32 active_count_) {
active_count = active_count_;
}
void VoiceContext::SortInfo() {
for (u32 i = 0; i < voice_count; i++) {
sorted_voice_info[i] = &voices[i];
}
std::ranges::sort(sorted_voice_info, [](const VoiceInfo* a, const VoiceInfo* b) {
return a->priority != b->priority ? a->priority < b->priority
: a->sort_order < b->sort_order;
});
}
void VoiceContext::UpdateStateByDspShared() {
std::memcpy(cpu_states.data(), dsp_states.data(), voice_count * sizeof(VoiceState));
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,126 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "audio_core/renderer/voice/voice_channel_resource.h"
#include "audio_core/renderer/voice/voice_info.h"
#include "audio_core/renderer/voice/voice_state.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
/**
* Contains all voices, with utility functions for managing them.
*/
class VoiceContext {
public:
/**
* Get the AudioRenderer state for a given index
*
* @param index - State index to get.
* @return The requested voice state.
*/
VoiceState& GetDspSharedState(u32 index);
/**
* Get the channel resource for a given index
*
* @param index - Resource index to get.
* @return The requested voice resource.
*/
VoiceChannelResource& GetChannelResource(u32 index);
/**
* Initialize the voice context.
*
* @param sorted_voice_infos - Workbuffer for the sorted voices.
* @param voice_infos - Workbuffer for the voices.
* @param voice_channel_resources - Workbuffer for the voice channel resources.
* @param cpu_states - Workbuffer for the host-side voice states.
* @param dsp_states - Workbuffer for the AudioRenderer-side voice states.
* @param voice_count - The number of voices in each workbuffer.
*/
void Initialize(std::span<VoiceInfo*> sorted_voice_infos, std::span<VoiceInfo> voice_infos,
std::span<VoiceChannelResource> voice_channel_resources,
std::span<VoiceState> cpu_states, std::span<VoiceState> dsp_states,
u32 voice_count);
/**
* Get a sorted voice with the given index.
*
* @param index - The sorted voice index to get.
* @return The sorted voice.
*/
VoiceInfo* GetSortedInfo(u32 index);
/**
* Get a voice with the given index.
*
* @param index - The voice index to get.
* @return The voice.
*/
VoiceInfo& GetInfo(u32 index);
/**
* Get a host voice state with the given index.
*
* @param index - The host voice state index to get.
* @return The voice state.
*/
VoiceState& GetState(u32 index);
/**
* Get the maximum number of voices.
* Not all voices in the buffers may be in use, see GetActiveCount.
*
* @return The maximum number of voices.
*/
u32 GetCount() const;
/**
* Get the number of active voices.
* Can be less than or equal to the maximum number of voices.
*
* @return The number of active voices.
*/
u32 GetActiveCount() const;
/**
* Set the number of active voices.
* Can be less than or equal to the maximum number of voices.
*
* @param active_count - The new number of active voices.
*/
void SetActiveCount(u32 active_count);
/**
* Sort all voices. Results are available via GetSortedInfo.
* Voices are sorted descendingly, according to priority, and then sort order.
*/
void SortInfo();
/**
* Update all voice states, copying AudioRenderer-side states to host-side states.
*/
void UpdateStateByDspShared();
private:
/// Sorted voices
std::span<VoiceInfo*> sorted_voice_info{};
/// Voices
std::span<VoiceInfo> voices{};
/// Channel resources
std::span<VoiceChannelResource> channel_resources{};
/// Host-side voice states
std::span<VoiceState> cpu_states{};
/// AudioRenderer-side voice states
std::span<VoiceState> dsp_states{};
/// Maximum number of voices
u32 voice_count{};
/// Number of active voices
u32 active_count{};
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,408 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/memory/pool_mapper.h"
#include "audio_core/renderer/voice/voice_context.h"
#include "audio_core/renderer/voice/voice_info.h"
#include "audio_core/renderer/voice/voice_state.h"
namespace AudioCore::AudioRenderer {
VoiceInfo::VoiceInfo() {
Initialize();
}
void VoiceInfo::Initialize() {
in_use = false;
is_new = false;
id = 0;
node_id = 0;
current_play_state = ServerPlayState::Stopped;
src_quality = SrcQuality::Medium;
priority = LowestVoicePriority;
sample_format = SampleFormat::Invalid;
sample_rate = 0;
channel_count = 0;
wave_buffer_count = 0;
wave_buffer_index = 0;
pitch = 0.0f;
volume = 0.0f;
prev_volume = 0.0f;
mix_id = UnusedMixId;
splitter_id = UnusedSplitterId;
biquads = {};
biquad_initialized = {};
voice_dropped = false;
data_unmapped = false;
buffer_unmapped = false;
flush_buffer_count = 0;
data_address.Setup(0, 0);
for (auto& wavebuffer : wavebuffers) {
wavebuffer.Initialize();
}
}
bool VoiceInfo::ShouldUpdateParameters(const InParameter& params) const {
return data_address.GetCpuAddr() != params.src_data_address ||
data_address.GetSize() != params.src_data_size || data_unmapped;
}
void VoiceInfo::UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
in_use = params.in_use;
id = params.id;
node_id = params.node_id;
UpdatePlayState(params.play_state);
UpdateSrcQuality(params.src_quality);
priority = params.priority;
sort_order = params.sort_order;
sample_rate = params.sample_rate;
sample_format = params.sample_format;
channel_count = static_cast<s8>(params.channel_count);
pitch = params.pitch;
volume = params.volume;
biquads = params.biquads;
wave_buffer_count = params.wave_buffer_count;
wave_buffer_index = params.wave_buffer_index;
if (behavior.IsFlushVoiceWaveBuffersSupported()) {
flush_buffer_count += params.flush_buffer_count;
}
mix_id = params.mix_id;
if (behavior.IsSplitterSupported()) {
splitter_id = params.splitter_id;
} else {
splitter_id = UnusedSplitterId;
}
channel_resource_ids = params.channel_resource_ids;
flags &= u16(~0b11);
if (behavior.IsVoicePlayedSampleCountResetAtLoopPointSupported()) {
flags |= u16(params.flags.IsVoicePlayedSampleCountResetAtLoopPointSupported);
}
if (behavior.IsVoicePitchAndSrcSkippedSupported()) {
flags |= u16(params.flags.IsVoicePitchAndSrcSkippedSupported);
}
if (params.clear_voice_drop) {
voice_dropped = false;
}
if (ShouldUpdateParameters(params)) {
data_unmapped = !pool_mapper.TryAttachBuffer(error_info, data_address,
params.src_data_address, params.src_data_size);
} else {
error_info.error_code = ResultSuccess;
error_info.address = CpuAddr(0);
}
}
void VoiceInfo::UpdatePlayState(const PlayState state) {
last_play_state = current_play_state;
switch (state) {
case PlayState::Started:
current_play_state = ServerPlayState::Started;
break;
case PlayState::Stopped:
if (current_play_state != ServerPlayState::Stopped) {
current_play_state = ServerPlayState::RequestStop;
}
break;
case PlayState::Paused:
current_play_state = ServerPlayState::Paused;
break;
default:
LOG_ERROR(Service_Audio, "Invalid input play state {}", static_cast<u32>(state));
break;
}
}
void VoiceInfo::UpdateSrcQuality(const SrcQuality quality) {
switch (quality) {
case SrcQuality::Medium:
src_quality = quality;
break;
case SrcQuality::High:
src_quality = quality;
break;
case SrcQuality::Low:
src_quality = quality;
break;
default:
LOG_ERROR(Service_Audio, "Invalid input src quality {}", static_cast<u32>(quality));
break;
}
}
void VoiceInfo::UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
[[maybe_unused]] u32 error_count, const InParameter& params,
std::span<VoiceState*> voice_states,
const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
if (params.is_new) {
for (size_t i = 0; i < wavebuffers.size(); i++) {
wavebuffers[i].Initialize();
}
for (s8 channel = 0; channel < static_cast<s8>(params.channel_count); channel++) {
voice_states[channel]->wave_buffer_valid.fill(false);
}
}
for (u32 i = 0; i < MaxWaveBuffers; i++) {
UpdateWaveBuffer(error_infos[i], wavebuffers[i], params.wave_buffer_internal[i],
params.sample_format, voice_states[0]->wave_buffer_valid[i], pool_mapper,
behavior);
}
}
void VoiceInfo::UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info,
WaveBuffer& wave_buffer,
const WaveBufferInternal& wave_buffer_internal,
const SampleFormat sample_format_, const bool valid,
const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
if (!valid && wave_buffer.sent_to_DSP && wave_buffer.buffer_address.GetCpuAddr() != 0) {
pool_mapper.ForceUnmapPointer(wave_buffer.buffer_address);
wave_buffer.buffer_address.Setup(0, 0);
}
if (!ShouldUpdateWaveBuffer(wave_buffer_internal)) {
return;
}
switch (sample_format_) {
case SampleFormat::PcmInt16: {
constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmInt16)};
if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size ||
wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) {
LOG_ERROR(Service_Audio, "Invalid PCM16 start/end wavebuffer sizes!");
error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
error_info[0].address = wave_buffer_internal.address;
return;
}
} break;
case SampleFormat::PcmFloat: {
constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmFloat)};
if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size ||
wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) {
LOG_ERROR(Service_Audio, "Invalid PCMFloat start/end wavebuffer sizes!");
error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
error_info[0].address = wave_buffer_internal.address;
return;
}
} break;
case SampleFormat::Adpcm: {
const auto start_frame{wave_buffer_internal.start_offset / 14};
auto start_extra{wave_buffer_internal.start_offset % 14 == 0
? 0
: (wave_buffer_internal.start_offset % 14) / 2 + 1 +
((wave_buffer_internal.start_offset % 14) % 2)};
const auto start{start_frame * 8 + start_extra};
const auto end_frame{wave_buffer_internal.end_offset / 14};
const auto end_extra{wave_buffer_internal.end_offset % 14 == 0
? 0
: (wave_buffer_internal.end_offset % 14) / 2 + 1 +
((wave_buffer_internal.end_offset % 14) % 2)};
const auto end{end_frame * 8 + end_extra};
if (start > static_cast<s64>(wave_buffer_internal.size) ||
end > static_cast<s64>(wave_buffer_internal.size)) {
LOG_ERROR(Service_Audio, "Invalid ADPCM start/end wavebuffer sizes!");
error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
error_info[0].address = wave_buffer_internal.address;
return;
}
} break;
default:
break;
}
if (wave_buffer_internal.start_offset < 0 || wave_buffer_internal.end_offset < 0) {
LOG_ERROR(Service_Audio, "Invalid input start/end wavebuffer sizes!");
error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
error_info[0].address = wave_buffer_internal.address;
return;
}
wave_buffer.start_offset = wave_buffer_internal.start_offset;
wave_buffer.end_offset = wave_buffer_internal.end_offset;
wave_buffer.loop = wave_buffer_internal.loop;
wave_buffer.stream_ended = wave_buffer_internal.stream_ended;
wave_buffer.sent_to_DSP = false;
wave_buffer.loop_start_offset = wave_buffer_internal.loop_start;
wave_buffer.loop_end_offset = wave_buffer_internal.loop_end;
wave_buffer.loop_count = wave_buffer_internal.loop_count;
buffer_unmapped =
!pool_mapper.TryAttachBuffer(error_info[0], wave_buffer.buffer_address,
wave_buffer_internal.address, wave_buffer_internal.size);
if (sample_format_ == SampleFormat::Adpcm && behavior.IsAdpcmLoopContextBugFixed() &&
wave_buffer_internal.context_address != 0) {
buffer_unmapped = !pool_mapper.TryAttachBuffer(error_info[1], wave_buffer.context_address,
wave_buffer_internal.context_address,
wave_buffer_internal.context_size) ||
data_unmapped;
} else {
wave_buffer.context_address.Setup(0, 0);
}
}
bool VoiceInfo::ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const {
return !wave_buffer_internal.sent_to_DSP || buffer_unmapped;
}
void VoiceInfo::WriteOutStatus(OutStatus& out_status, const InParameter& params,
std::span<VoiceState*> voice_states) {
if (params.is_new) {
is_new = true;
}
if (params.is_new || is_new) {
out_status.played_sample_count = 0;
out_status.wave_buffers_consumed = 0;
out_status.voice_dropped = false;
} else {
out_status.played_sample_count = voice_states[0]->played_sample_count;
out_status.wave_buffers_consumed = voice_states[0]->wave_buffers_consumed;
out_status.voice_dropped = voice_dropped;
}
}
bool VoiceInfo::ShouldSkip() const {
return !in_use || wave_buffer_count == 0 || data_unmapped || buffer_unmapped || voice_dropped;
}
bool VoiceInfo::HasAnyConnection() const {
return mix_id != UnusedMixId || splitter_id != UnusedSplitterId;
}
void VoiceInfo::FlushWaveBuffers(const u32 flush_count, std::span<VoiceState*> voice_states,
const s8 channel_count_) {
auto wave_index{wave_buffer_index};
for (size_t i = 0; i < flush_count; i++) {
wavebuffers[wave_index].sent_to_DSP = true;
for (s8 j = 0; j < channel_count_; j++) {
auto voice_state{voice_states[j]};
if (voice_state->wave_buffer_index == wave_index) {
voice_state->wave_buffer_index =
(voice_state->wave_buffer_index + 1) % MaxWaveBuffers;
voice_state->wave_buffers_consumed++;
}
voice_state->wave_buffer_valid[wave_index] = false;
}
wave_index = (wave_index + 1) % MaxWaveBuffers;
}
}
bool VoiceInfo::UpdateParametersForCommandGeneration(std::span<VoiceState*> voice_states) {
if (flush_buffer_count > 0) {
FlushWaveBuffers(flush_buffer_count, voice_states, channel_count);
flush_buffer_count = 0;
}
switch (current_play_state) {
case ServerPlayState::Started:
for (u32 i = 0; i < MaxWaveBuffers; i++) {
if (!wavebuffers[i].sent_to_DSP) {
for (s8 channel = 0; channel < channel_count; channel++) {
voice_states[channel]->wave_buffer_valid[i] = true;
}
wavebuffers[i].sent_to_DSP = true;
}
}
was_playing = false;
for (u32 i = 0; i < MaxWaveBuffers; i++) {
if (voice_states[0]->wave_buffer_valid[i]) {
return true;
}
}
break;
case ServerPlayState::Stopped:
case ServerPlayState::Paused:
for (auto& wavebuffer : wavebuffers) {
if (!wavebuffer.sent_to_DSP) {
wavebuffer.buffer_address.GetReference(true);
wavebuffer.context_address.GetReference(true);
}
}
if (sample_format == SampleFormat::Adpcm && data_address.GetCpuAddr() != 0) {
data_address.GetReference(true);
}
was_playing = last_play_state == ServerPlayState::Started;
break;
case ServerPlayState::RequestStop:
for (u32 i = 0; i < MaxWaveBuffers; i++) {
wavebuffers[i].sent_to_DSP = true;
for (s8 channel = 0; channel < channel_count; channel++) {
if (voice_states[channel]->wave_buffer_valid[i]) {
voice_states[channel]->wave_buffer_index =
(voice_states[channel]->wave_buffer_index + 1) % MaxWaveBuffers;
voice_states[channel]->wave_buffers_consumed++;
}
voice_states[channel]->wave_buffer_valid[i] = false;
}
}
for (s8 channel = 0; channel < channel_count; channel++) {
voice_states[channel]->offset = 0;
voice_states[channel]->played_sample_count = 0;
voice_states[channel]->adpcm_context = {};
voice_states[channel]->sample_history.fill(0);
voice_states[channel]->fraction = 0;
}
current_play_state = ServerPlayState::Stopped;
was_playing = last_play_state == ServerPlayState::Started;
break;
}
return was_playing;
}
bool VoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
std::array<VoiceState*, MaxChannels> voice_states{};
if (is_new) {
ResetResources(voice_context);
prev_volume = volume;
is_new = false;
}
for (s8 channel = 0; channel < channel_count; channel++) {
voice_states[channel] = &voice_context.GetDspSharedState(channel_resource_ids[channel]);
}
return UpdateParametersForCommandGeneration(voice_states);
}
void VoiceInfo::ResetResources(VoiceContext& voice_context) const {
for (s8 channel = 0; channel < channel_count; channel++) {
auto& state{voice_context.GetDspSharedState(channel_resource_ids[channel])};
state = {};
auto& channel_resource{voice_context.GetChannelResource(channel_resource_ids[channel])};
channel_resource.prev_mix_volumes = channel_resource.mix_volumes;
}
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,378 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <bitset>
#include "audio_core/common/common.h"
#include "audio_core/common/wave_buffer.h"
#include "audio_core/renderer/behavior/behavior_info.h"
#include "audio_core/renderer/memory/address_info.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
class PoolMapper;
class VoiceContext;
struct VoiceState;
/**
* Represents one voice. Voices are essentially noises, and they can be further mixed and have
* effects applied to them, but voices are the basis of all sounds.
*/
class VoiceInfo {
public:
enum class ServerPlayState {
Started,
Stopped,
RequestStop,
Paused,
};
struct Flags {
u8 IsVoicePlayedSampleCountResetAtLoopPointSupported : 1;
u8 IsVoicePitchAndSrcSkippedSupported : 1;
};
/**
* A wavebuffer contains information on the data source buffers.
*/
struct WaveBuffer {
void Copy(WaveBufferVersion1& other) {
other.buffer = buffer_address.GetReference(true);
other.buffer_size = buffer_address.GetSize();
other.start_offset = start_offset;
other.end_offset = end_offset;
other.loop = loop;
other.stream_ended = stream_ended;
if (context_address.GetCpuAddr()) {
other.context = context_address.GetReference(true);
other.context_size = context_address.GetSize();
} else {
other.context = CpuAddr(0);
other.context_size = 0;
}
}
void Copy(WaveBufferVersion2& other) {
other.buffer = buffer_address.GetReference(true);
other.buffer_size = buffer_address.GetSize();
other.start_offset = start_offset;
other.end_offset = end_offset;
other.loop_start_offset = loop_start_offset;
other.loop_end_offset = loop_end_offset;
other.loop = loop;
other.loop_count = loop_count;
other.stream_ended = stream_ended;
if (context_address.GetCpuAddr()) {
other.context = context_address.GetReference(true);
other.context_size = context_address.GetSize();
} else {
other.context = CpuAddr(0);
other.context_size = 0;
}
}
void Initialize() {
buffer_address.Setup(0, 0);
context_address.Setup(0, 0);
start_offset = 0;
end_offset = 0;
loop = false;
stream_ended = false;
sent_to_DSP = true;
loop_start_offset = 0;
loop_end_offset = 0;
loop_count = 0;
}
/// Game memory address of the wavebuffer data
AddressInfo buffer_address{0, 0};
/// Context for decoding, used for ADPCM
AddressInfo context_address{0, 0};
/// Starting offset for the wavebuffer
u32 start_offset{};
/// Ending offset the wavebuffer
u32 end_offset{};
/// Should this wavebuffer loop?
bool loop{};
/// Has this wavebuffer ended?
bool stream_ended{};
/// Has this wavebuffer been sent to the AudioRenderer?
bool sent_to_DSP{true};
/// Starting offset when looping, can differ from start_offset
u32 loop_start_offset{};
/// Ending offset when looping, can differ from end_offset
u32 loop_end_offset{};
/// Number of times to loop this wavebuffer
s32 loop_count{};
};
struct WaveBufferInternal {
/* 0x00 */ CpuAddr address;
/* 0x08 */ u64 size;
/* 0x10 */ s32 start_offset;
/* 0x14 */ s32 end_offset;
/* 0x18 */ bool loop;
/* 0x19 */ bool stream_ended;
/* 0x1A */ bool sent_to_DSP;
/* 0x1C */ s32 loop_count;
/* 0x20 */ CpuAddr context_address;
/* 0x28 */ u64 context_size;
/* 0x30 */ u32 loop_start;
/* 0x34 */ u32 loop_end;
};
static_assert(sizeof(WaveBufferInternal) == 0x38,
"VoiceInfo::WaveBufferInternal has the wrong size!");
struct BiquadFilterParameter {
/* 0x00 */ bool enabled;
/* 0x02 */ std::array<s16, 3> b;
/* 0x08 */ std::array<s16, 2> a;
};
static_assert(sizeof(BiquadFilterParameter) == 0xC,
"VoiceInfo::BiquadFilterParameter has the wrong size!");
struct InParameter {
/* 0x000 */ u32 id;
/* 0x004 */ u32 node_id;
/* 0x008 */ bool is_new;
/* 0x009 */ bool in_use;
/* 0x00A */ PlayState play_state;
/* 0x00B */ SampleFormat sample_format;
/* 0x00C */ u32 sample_rate;
/* 0x010 */ s32 priority;
/* 0x014 */ s32 sort_order;
/* 0x018 */ u32 channel_count;
/* 0x01C */ f32 pitch;
/* 0x020 */ f32 volume;
/* 0x024 */ std::array<BiquadFilterParameter, MaxBiquadFilters> biquads;
/* 0x03C */ u32 wave_buffer_count;
/* 0x040 */ u16 wave_buffer_index;
/* 0x042 */ char unk042[0x6];
/* 0x048 */ CpuAddr src_data_address;
/* 0x050 */ u64 src_data_size;
/* 0x058 */ u32 mix_id;
/* 0x05C */ u32 splitter_id;
/* 0x060 */ std::array<WaveBufferInternal, MaxWaveBuffers> wave_buffer_internal;
/* 0x140 */ std::array<u32, MaxChannels> channel_resource_ids;
/* 0x158 */ bool clear_voice_drop;
/* 0x159 */ u8 flush_buffer_count;
/* 0x15A */ char unk15A[0x2];
/* 0x15C */ Flags flags;
/* 0x15D */ char unk15D[0x1];
/* 0x15E */ SrcQuality src_quality;
/* 0x15F */ char unk15F[0x11];
};
static_assert(sizeof(InParameter) == 0x170, "VoiceInfo::InParameter has the wrong size!");
struct OutStatus {
/* 0x00 */ u64 played_sample_count;
/* 0x08 */ u32 wave_buffers_consumed;
/* 0x0C */ bool voice_dropped;
};
static_assert(sizeof(OutStatus) == 0x10, "OutStatus::InParameter has the wrong size!");
VoiceInfo();
/**
* Initialize this voice.
*/
void Initialize();
/**
* Does this voice ned an update?
*
* @param params - Input parametetrs to check matching.
* @return True if this voice needs an update, otherwise false.
*/
bool ShouldUpdateParameters(const InParameter& params) const;
/**
* Update the parameters of this voice.
*
* @param error_info - Output error code.
* @param params - Input parametters to udpate from.
* @param pool_mapper - Used to map buffers.
* @param behavior - behavior to check supported features.
*/
void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
const PoolMapper& pool_mapper, const BehaviorInfo& behavior);
/**
* Update the current play state.
*
* @param state - New play state for this voice.
*/
void UpdatePlayState(PlayState state);
/**
* Update the current sample rate conversion quality.
*
* @param quality - New quality.
*/
void UpdateSrcQuality(SrcQuality quality);
/**
* Update all wavebuffers.
*
* @param error_infos - Output 2D array of errors, 2 per wavebuffer.
* @param error_count - Number of errors provided. Unused.
* @param params - Input parametters to be used for the update.
* @param voice_states - The voice states for each channel in this voice to be updated.
* @param pool_mapper - Used to map the wavebuffers.
* @param behavior - Used to check for supported features.
*/
void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
u32 error_count, const InParameter& params,
std::span<VoiceState*> voice_states, const PoolMapper& pool_mapper,
const BehaviorInfo& behavior);
/**
* Update a wavebuffer.
*
* @param error_infos - Output array of errors.
* @param wave_buffer - The wavebuffer to be updated.
* @param wave_buffer_internal - Input parametters to be used for the update.
* @param sample_format - Sample format of the wavebuffer.
* @param valid - Is this wavebuffer valid?
* @param pool_mapper - Used to map the wavebuffers.
* @param behavior - Used to check for supported features.
*/
void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer,
const WaveBufferInternal& wave_buffer_internal,
SampleFormat sample_format, bool valid, const PoolMapper& pool_mapper,
const BehaviorInfo& behavior);
/**
* Check if the input wavebuffer needs an update.
*
* @param wave_buffer_internal - Input wavebuffer parameters to check.
* @return True if the given wavebuffer needs an update, otherwise false.
*/
bool ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const;
/**
* Write the number of played samples, number of consumed wavebuffers and if this voice was
* dropped, to the given out_status.
*
* @param out_status - Output status to be written to.
* @param in_params - Input parameters to check if the wavebuffer is new.
* @param voice_states - Current host voice states for this voice, source of the output.
*/
void WriteOutStatus(OutStatus& out_status, const InParameter& in_params,
std::span<VoiceState*> voice_states);
/**
* Check if this voice should be skipped for command generation.
* Checks various things such as usage state, whether data is mapped etc.
*
* @return True if this voice should not be generated, otherwise false.
*/
bool ShouldSkip() const;
/**
* Check if this voice has any mixing connections.
*
* @return True if this voice participes in mixing, otherwise false.
*/
bool HasAnyConnection() const;
/**
* Flush flush_count wavebuffers, marking them as consumed.
*
* @param flush_count - Number of wavebuffers to flush.
* @param voice_states - Voice states for these wavebuffers.
* @param channel_count - Number of active channels.
*/
void FlushWaveBuffers(u32 flush_count, std::span<VoiceState*> voice_states, s8 channel_count);
/**
* Update this voice's parameters on command generation,
* updating voice states and flushing if needed.
*
* @param voice_states - Voice states for these wavebuffers.
* @return True if this voice should be generated, otherwise false.
*/
bool UpdateParametersForCommandGeneration(std::span<VoiceState*> voice_states);
/**
* Update this voice on command generation.
*
* @param voice_states - Voice states for these wavebuffers.
* @return True if this voice should be generated, otherwise false.
*/
bool UpdateForCommandGeneration(VoiceContext& voice_context);
/**
* Reset the AudioRenderer-side voice states, and the channel resources for this voice.
*
* @param voice_context - Context from which to get the resources.
*/
void ResetResources(VoiceContext& voice_context) const;
/// Is this voice in use?
bool in_use{};
/// Is this voice new?
bool is_new{};
/// Was this voice last playing? Used for depopping
bool was_playing{};
/// Sample format of the wavebuffers in this voice
SampleFormat sample_format{};
/// Sample rate of the wavebuffers in this voice
u32 sample_rate{};
/// Number of channels in this voice
s8 channel_count{};
/// Id of this voice
u32 id{};
/// Node id of this voice
u32 node_id{};
/// Mix id this voice is mixed to
u32 mix_id{};
/// Play state of this voice
ServerPlayState current_play_state{ServerPlayState::Stopped};
/// Last play state of this voice
ServerPlayState last_play_state{ServerPlayState::Started};
/// Priority of this voice, lower is higher
s32 priority{};
/// Sort order of this voice, used when same priority
s32 sort_order{};
/// Pitch of this voice (for sample rate conversion)
f32 pitch{};
/// Current volume of this voice
f32 volume{};
/// Previous volume of this voice
f32 prev_volume{};
/// Biquad filters for generating filter commands on this voice
std::array<BiquadFilterParameter, MaxBiquadFilters> biquads{};
/// Number of active wavebuffers
u32 wave_buffer_count{};
/// Current playing wavebuffer index
u16 wave_buffer_index{};
/// Flags controlling decode behavior
u16 flags{};
/// Game memory for ADPCM coefficients
AddressInfo data_address{0, 0};
/// Wavebuffers
std::array<WaveBuffer, MaxWaveBuffers> wavebuffers{};
/// Channel resources for this voice
std::array<u32, MaxChannels> channel_resource_ids{};
/// Splitter id this voice is connected with
s32 splitter_id{UnusedSplitterId};
/// Sample rate conversion quality
SrcQuality src_quality{SrcQuality::Medium};
/// Was this voice dropped due to limited time?
bool voice_dropped{};
/// Is this voice's coefficient (data_address) unmapped?
bool data_unmapped{};
/// Is this voice's buffers (wavebuffer data and ADPCM context) unmapped?
bool buffer_unmapped{};
/// Initialisation state of the biquads
std::array<bool, MaxBiquadFilters> biquad_initialized{};
/// Number of wavebuffers to flush
u8 flush_buffer_count{};
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include "audio_core/common/common.h"
#include "common/common_types.h"
#include "common/fixed_point.h"
namespace AudioCore::AudioRenderer {
/**
* Holds a state for a voice. One is kept host-side, and one is used by the AudioRenderer,
* host-side is updated on the next iteration.
*/
struct VoiceState {
/**
* State of the voice's biquad filter.
*/
struct BiquadFilterState {
Common::FixedPoint<50, 14> s0;
Common::FixedPoint<50, 14> s1;
Common::FixedPoint<50, 14> s2;
Common::FixedPoint<50, 14> s3;
};
/**
* Context for ADPCM decoding.
*/
struct AdpcmContext {
u16 header;
s16 yn0;
s16 yn1;
};
/// Number of samples played
u64 played_sample_count;
/// Current offset from the starting offset
u32 offset;
/// Currently active wavebuffer index
u32 wave_buffer_index;
/// Array of which wavebuffers are currently valid
std::array<bool, MaxWaveBuffers> wave_buffer_valid;
/// Number of wavebuffers consumed, given back to the game
u32 wave_buffers_consumed;
/// History of samples, used for rate conversion
std::array<s16, MaxWaveBuffers * 2> sample_history;
/// Current read fraction, used for resampling
Common::FixedPoint<49, 15> fraction;
/// Current adpcm context
AdpcmContext adpcm_context;
/// Current biquad states, used when filtering
std::array<std::array<BiquadFilterState, MaxBiquadFilters>, MaxBiquadFilters> biquad_states;
/// Previous samples
std::array<s32, MaxMixBuffers> previous_samples;
/// Unused
u32 external_context_size;
/// Unused
bool external_context_enabled;
/// Was this voice dropped?
bool voice_dropped;
/// Number of times the wavebuffer has looped
s32 loop_count;
};
} // namespace AudioCore::AudioRenderer