summaryrefslogtreecommitdiffstats
path: root/src/audio_core/renderer/voice/voice_info.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/audio_core/renderer/voice/voice_info.cpp408
1 files changed, 408 insertions, 0 deletions
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