// 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(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(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(quality)); break; } } void VoiceInfo::UpdateWaveBuffers(std::span> error_infos, [[maybe_unused]] u32 error_count, const InParameter& params, std::span 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(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 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 static 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 static 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(wave_buffer_internal.size) || end > static_cast(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 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 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 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 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