summaryrefslogtreecommitdiffstats
path: root/src/audio_core/renderer/voice
diff options
context:
space:
mode:
authorKelebek1 <eeeedddccc@hotmail.co.uk>2022-07-17 00:48:45 +0200
committerKelebek1 <eeeedddccc@hotmail.co.uk>2022-07-22 02:11:32 +0200
commit458da8a94877677f086f06cdeecf959ec4283a33 (patch)
tree583166d77602ad90a0d552f37de8729ad80fd6c1 /src/audio_core/renderer/voice
parentMerge pull request #8598 from Link4565/recv-dontwait (diff)
downloadyuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.gz
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.bz2
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.lz
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.xz
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.zst
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.zip
Diffstat (limited to 'src/audio_core/renderer/voice')
-rw-r--r--src/audio_core/renderer/voice/voice_channel_resource.h38
-rw-r--r--src/audio_core/renderer/voice/voice_context.cpp86
-rw-r--r--src/audio_core/renderer/voice/voice_context.h126
-rw-r--r--src/audio_core/renderer/voice/voice_info.cpp408
-rw-r--r--src/audio_core/renderer/voice/voice_info.h378
-rw-r--r--src/audio_core/renderer/voice/voice_state.h70
6 files changed, 1106 insertions, 0 deletions
diff --git a/src/audio_core/renderer/voice/voice_channel_resource.h b/src/audio_core/renderer/voice/voice_channel_resource.h
new file mode 100644
index 000000000..26ab4ccce
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_channel_resource.h
@@ -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
diff --git a/src/audio_core/renderer/voice/voice_context.cpp b/src/audio_core/renderer/voice/voice_context.cpp
new file mode 100644
index 000000000..eafb51b01
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_context.cpp
@@ -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
diff --git a/src/audio_core/renderer/voice/voice_context.h b/src/audio_core/renderer/voice/voice_context.h
new file mode 100644
index 000000000..43b677154
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_context.h
@@ -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
diff --git a/src/audio_core/renderer/voice/voice_info.cpp b/src/audio_core/renderer/voice/voice_info.cpp
new file mode 100644
index 000000000..1849eeb57
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_info.cpp
@@ -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
diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h
new file mode 100644
index 000000000..896723e0c
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_info.h
@@ -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
diff --git a/src/audio_core/renderer/voice/voice_state.h b/src/audio_core/renderer/voice/voice_state.h
new file mode 100644
index 000000000..d5497e2fb
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_state.h
@@ -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