diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 5ef38a337b..cb00ef60e8 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -12,16 +12,32 @@ add_library(audio_core STATIC buffer.h codec.cpp codec.h + command_generator.cpp + command_generator.h common.h + effect_context.cpp + effect_context.h + info_updater.cpp + info_updater.h + memory_pool.cpp + memory_pool.h + mix_context.cpp + mix_context.h null_sink.h sink.h + sink_context.cpp + sink_context.h sink_details.cpp sink_details.h sink_stream.h + splitter_context.cpp + splitter_context.h stream.cpp stream.h time_stretch.cpp time_stretch.h + voice_context.cpp + voice_context.h $<$:cubeb_sink.cpp cubeb_sink.h> ) diff --git a/src/audio_core/algorithm/interpolate.cpp b/src/audio_core/algorithm/interpolate.cpp index 49ab9d3e15..689a54508a 100644 --- a/src/audio_core/algorithm/interpolate.cpp +++ b/src/audio_core/algorithm/interpolate.cpp @@ -197,4 +197,36 @@ std::vector Interpolate(InterpolationState& state, std::vector input, return output; } +void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count) { + const std::array& lut = [pitch] { + if (pitch > 0xaaaa) { + return curve_lut0; + } + if (pitch <= 0x8000) { + return curve_lut1; + } + return curve_lut2; + }(); + + std::size_t index{}; + + for (std::size_t i = 0; i < sample_count; i++) { + const std::size_t lut_index{(static_cast(fraction) >> 8) * 4}; + const auto l0 = lut[lut_index + 0]; + const auto l1 = lut[lut_index + 1]; + const auto l2 = lut[lut_index + 2]; + const auto l3 = lut[lut_index + 3]; + + const auto s0 = static_cast(input[index]); + const auto s1 = static_cast(input[index + 1]); + const auto s2 = static_cast(input[index + 2]); + const auto s3 = static_cast(input[index + 3]); + + output[i] = (l0 * s0 + l1 * s1 + l2 * s2 + l3 * s3) >> 15; + fraction += pitch; + index += (fraction >> 15); + fraction &= 0x7fff; + } +} + } // namespace AudioCore diff --git a/src/audio_core/algorithm/interpolate.h b/src/audio_core/algorithm/interpolate.h index ab1a31754e..d534077afe 100644 --- a/src/audio_core/algorithm/interpolate.h +++ b/src/audio_core/algorithm/interpolate.h @@ -38,4 +38,7 @@ inline std::vector Interpolate(InterpolationState& state, std::vector return Interpolate(state, std::move(input), ratio); } +/// Nintendo Switchs DSP resampling algorithm. Based on a single channel +void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count); + } // namespace AudioCore diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp index d644526171..fbd87d5bff 100644 --- a/src/audio_core/audio_renderer.cpp +++ b/src/audio_core/audio_renderer.cpp @@ -2,90 +2,42 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include "audio_core/algorithm/interpolate.h" #include "audio_core/audio_out.h" #include "audio_core/audio_renderer.h" #include "audio_core/codec.h" #include "audio_core/common.h" +#include "audio_core/info_updater.h" +#include "audio_core/voice_context.h" #include "common/assert.h" #include "common/logging/log.h" #include "core/core.h" #include "core/hle/kernel/writable_event.h" #include "core/memory.h" +#include "core/settings.h" namespace AudioCore { - -constexpr u32 STREAM_SAMPLE_RATE{48000}; -constexpr u32 STREAM_NUM_CHANNELS{2}; -using VoiceChannelHolder = std::array; -class AudioRenderer::VoiceState { -public: - bool IsPlaying() const { - return is_in_use && info.play_state == PlayState::Started; - } - - const VoiceOutStatus& GetOutStatus() const { - return out_status; - } - - const VoiceInfo& GetInfo() const { - return info; - } - - VoiceInfo& GetInfo() { - return info; - } - - void SetWaveIndex(std::size_t index); - std::vector DequeueSamples(std::size_t sample_count, Core::Memory::Memory& memory, - const VoiceChannelHolder& voice_resources); - void UpdateState(); - void RefreshBuffer(Core::Memory::Memory& memory, const VoiceChannelHolder& voice_resources); - -private: - bool is_in_use{}; - bool is_refresh_pending{}; - std::size_t wave_index{}; - std::size_t offset{}; - Codec::ADPCMState adpcm_state{}; - InterpolationState interp_state{}; - std::vector samples; - VoiceOutStatus out_status{}; - VoiceInfo info{}; -}; - -class AudioRenderer::EffectState { -public: - const EffectOutStatus& GetOutStatus() const { - return out_status; - } - - const EffectInStatus& GetInfo() const { - return info; - } - - EffectInStatus& GetInfo() { - return info; - } - - void UpdateState(Core::Memory::Memory& memory); - -private: - EffectOutStatus out_status{}; - EffectInStatus info{}; -}; - AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, - AudioRendererParameter params, + AudioCommon::AudioRendererParameter params, std::shared_ptr buffer_event, std::size_t instance_number) - : worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count), - voice_resources(params.voice_count), effects(params.effect_count), memory{memory_} { + : worker_params{params}, buffer_event{buffer_event}, + memory_pool_info(params.effect_count + params.voice_count * 4), + voice_context(params.voice_count), effect_context(params.effect_count), mix_context(), + sink_context(params.sink_count), splitter_context(), + voices(params.voice_count), memory{memory_}, + command_generator(worker_params, voice_context, mix_context, splitter_context, memory), + temp_mix_buffer(AudioCommon::TOTAL_TEMP_MIX_SIZE) { behavior_info.SetUserRevision(params.revision); + splitter_context.Initialize(behavior_info, params.splitter_count, + params.num_splitter_send_channels); + mix_context.Initialize(behavior_info, params.submix_count + 1); audio_out = std::make_unique(); - stream = audio_out->OpenStream(core_timing, STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, - fmt::format("AudioRenderer-Instance{}", instance_number), - [=]() { buffer_event->Signal(); }); + stream = + audio_out->OpenStream(core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS, + fmt::format("AudioRenderer-Instance{}", instance_number), + [=]() { buffer_event->Signal(); }); audio_out->StartStream(stream); QueueMixedBuffer(0); @@ -111,355 +63,200 @@ Stream::State AudioRenderer::GetStreamState() const { return stream->GetState(); } -ResultVal> AudioRenderer::UpdateAudioRenderer(const std::vector& input_params) { - // Copy UpdateDataHeader struct - UpdateDataHeader config{}; - std::memcpy(&config, input_params.data(), sizeof(UpdateDataHeader)); - u32 memory_pool_count = worker_params.effect_count + (worker_params.voice_count * 4); - - if (!behavior_info.UpdateInput(input_params, sizeof(UpdateDataHeader))) { - LOG_ERROR(Audio, "Failed to update behavior info input parameters"); - return Audren::ERR_INVALID_PARAMETERS; - } - - // Copy MemoryPoolInfo structs - std::vector mem_pool_info(memory_pool_count); - std::memcpy(mem_pool_info.data(), - input_params.data() + sizeof(UpdateDataHeader) + config.behavior_size, - memory_pool_count * sizeof(MemoryPoolInfo)); - - // Copy voice resources - const std::size_t voice_resource_offset{sizeof(UpdateDataHeader) + config.behavior_size + - config.memory_pools_size}; - std::memcpy(voice_resources.data(), input_params.data() + voice_resource_offset, - sizeof(VoiceResourceInformation) * voice_resources.size()); - - // Copy VoiceInfo structs - std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size + - config.memory_pools_size + config.voice_resource_size}; - for (auto& voice : voices) { - std::memcpy(&voice.GetInfo(), input_params.data() + voice_offset, sizeof(VoiceInfo)); - voice_offset += sizeof(VoiceInfo); - } - - std::size_t effect_offset{sizeof(UpdateDataHeader) + config.behavior_size + - config.memory_pools_size + config.voice_resource_size + - config.voices_size}; - for (auto& effect : effects) { - std::memcpy(&effect.GetInfo(), input_params.data() + effect_offset, sizeof(EffectInStatus)); - effect_offset += sizeof(EffectInStatus); - } - - // Update memory pool state - std::vector memory_pool(memory_pool_count); - for (std::size_t index = 0; index < memory_pool.size(); ++index) { - if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) { - memory_pool[index].state = MemoryPoolStates::Attached; - } else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) { - memory_pool[index].state = MemoryPoolStates::Detached; - } - } - - // Update voices - for (auto& voice : voices) { - voice.UpdateState(); - if (!voice.GetInfo().is_in_use) { - continue; - } - if (voice.GetInfo().is_new) { - voice.SetWaveIndex(voice.GetInfo().wave_buffer_head); - } - } - - for (auto& effect : effects) { - effect.UpdateState(memory); - } - - // Release previous buffers and queue next ones for playback - ReleaseAndQueueBuffers(); - - // Copy output header - UpdateDataHeader response_data{worker_params}; - if (behavior_info.IsElapsedFrameCountSupported()) { - response_data.render_info = sizeof(RendererInfo); - response_data.total_size += sizeof(RendererInfo); - } - - std::vector output_params(response_data.total_size); - std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader)); - - // Copy output memory pool entries - std::memcpy(output_params.data() + sizeof(UpdateDataHeader), memory_pool.data(), - response_data.memory_pools_size); - - // Copy output voice status - std::size_t voice_out_status_offset{sizeof(UpdateDataHeader) + response_data.memory_pools_size}; - for (const auto& voice : voices) { - std::memcpy(output_params.data() + voice_out_status_offset, &voice.GetOutStatus(), - sizeof(VoiceOutStatus)); - voice_out_status_offset += sizeof(VoiceOutStatus); - } - - std::size_t effect_out_status_offset{ - sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size + - response_data.voice_resource_size}; - for (const auto& effect : effects) { - std::memcpy(output_params.data() + effect_out_status_offset, &effect.GetOutStatus(), - sizeof(EffectOutStatus)); - effect_out_status_offset += sizeof(EffectOutStatus); - } - - // Update behavior info output - const std::size_t behavior_out_status_offset{ - sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size + - response_data.effects_size + response_data.sinks_size + - response_data.performance_manager_size}; - - if (!behavior_info.UpdateOutput(output_params, behavior_out_status_offset)) { - LOG_ERROR(Audio, "Failed to update behavior info output parameters"); - return Audren::ERR_INVALID_PARAMETERS; - } - - if (behavior_info.IsElapsedFrameCountSupported()) { - const std::size_t renderer_info_offset{ - sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size + - response_data.effects_size + response_data.sinks_size + - response_data.performance_manager_size + response_data.behavior_size}; - RendererInfo renderer_info{}; - renderer_info.elasped_frame_count = elapsed_frame_count; - std::memcpy(output_params.data() + renderer_info_offset, &renderer_info, - sizeof(RendererInfo)); - } - - return MakeResult(output_params); -} - -void AudioRenderer::VoiceState::SetWaveIndex(std::size_t index) { - wave_index = index & 3; - is_refresh_pending = true; -} - -std::vector AudioRenderer::VoiceState::DequeueSamples( - std::size_t sample_count, Core::Memory::Memory& memory, - const VoiceChannelHolder& voice_resources) { - if (!IsPlaying()) { - return {}; - } - - if (is_refresh_pending) { - RefreshBuffer(memory, voice_resources); - } - - const std::size_t max_size{samples.size() - offset}; - const std::size_t dequeue_offset{offset}; - std::size_t size{sample_count * STREAM_NUM_CHANNELS}; - if (size > max_size) { - size = max_size; - } - - out_status.played_sample_count += size / STREAM_NUM_CHANNELS; - offset += size; - - const auto& wave_buffer{info.wave_buffer[wave_index]}; - if (offset == samples.size()) { - offset = 0; - - if (!wave_buffer.is_looping && wave_buffer.buffer_sz) { - SetWaveIndex(wave_index + 1); - } - - if (wave_buffer.buffer_sz) { - out_status.wave_buffer_consumed++; - } - - if (wave_buffer.end_of_stream || wave_buffer.buffer_sz == 0) { - info.play_state = PlayState::Paused; - } - } - - return {samples.begin() + dequeue_offset, samples.begin() + dequeue_offset + size}; -} - -void AudioRenderer::VoiceState::UpdateState() { - if (is_in_use && !info.is_in_use) { - // No longer in use, reset state - is_refresh_pending = true; - wave_index = 0; - offset = 0; - out_status = {}; - } - is_in_use = info.is_in_use; -} - -void AudioRenderer::VoiceState::RefreshBuffer(Core::Memory::Memory& memory, - const VoiceChannelHolder& voice_resources) { - const auto wave_buffer_address = info.wave_buffer[wave_index].buffer_addr; - const auto wave_buffer_size = info.wave_buffer[wave_index].buffer_sz; - std::vector new_samples(wave_buffer_size / sizeof(s16)); - memory.ReadBlock(wave_buffer_address, new_samples.data(), wave_buffer_size); - - switch (static_cast(info.sample_format)) { - case Codec::PcmFormat::Int16: { - // PCM16 is played as-is - break; - } - case Codec::PcmFormat::Adpcm: { - // Decode ADPCM to PCM16 - Codec::ADPCM_Coeff coeffs; - memory.ReadBlock(info.additional_params_addr, coeffs.data(), sizeof(Codec::ADPCM_Coeff)); - new_samples = Codec::DecodeADPCM(reinterpret_cast(new_samples.data()), - new_samples.size() * sizeof(s16), coeffs, adpcm_state); - break; - } - default: - UNIMPLEMENTED_MSG("Unimplemented sample_format={}", info.sample_format); - break; - } - - switch (info.channel_count) { - case 1: { - // 1 channel is upsampled to 2 channel - samples.resize(new_samples.size() * 2); - - for (std::size_t index = 0; index < new_samples.size(); ++index) { - auto sample = static_cast(new_samples[index]); - if (voice_resources[0]->in_use) { - sample *= voice_resources[0]->mix_volumes[0]; - } - - samples[index * 2] = static_cast(sample * info.volume); - samples[index * 2 + 1] = static_cast(sample * info.volume); - } - break; - } - case 2: { - // 2 channel is played as is - samples = std::move(new_samples); - const std::size_t sample_count = (samples.size() / 2); - for (std::size_t index = 0; index < sample_count; ++index) { - const std::size_t index_l = index * 2; - const std::size_t index_r = index * 2 + 1; - - auto sample_l = static_cast(samples[index_l]); - auto sample_r = static_cast(samples[index_r]); - - if (voice_resources[0]->in_use) { - sample_l *= voice_resources[0]->mix_volumes[0]; - } - - if (voice_resources[1]->in_use) { - sample_r *= voice_resources[1]->mix_volumes[1]; - } - - samples[index_l] = static_cast(sample_l * info.volume); - samples[index_r] = static_cast(sample_r * info.volume); - } - break; - } - case 6: { - samples.resize((new_samples.size() / 6) * 2); - const std::size_t sample_count = samples.size() / 2; - - for (std::size_t index = 0; index < sample_count; ++index) { - auto FL = static_cast(new_samples[index * 6]); - auto FR = static_cast(new_samples[index * 6 + 1]); - auto FC = static_cast(new_samples[index * 6 + 2]); - auto BL = static_cast(new_samples[index * 6 + 4]); - auto BR = static_cast(new_samples[index * 6 + 5]); - - if (voice_resources[0]->in_use) { - FL *= voice_resources[0]->mix_volumes[0]; - } - if (voice_resources[1]->in_use) { - FR *= voice_resources[1]->mix_volumes[1]; - } - if (voice_resources[2]->in_use) { - FC *= voice_resources[2]->mix_volumes[2]; - } - if (voice_resources[4]->in_use) { - BL *= voice_resources[4]->mix_volumes[4]; - } - if (voice_resources[5]->in_use) { - BR *= voice_resources[5]->mix_volumes[5]; - } - - samples[index * 2] = - static_cast((0.3694f * FL + 0.2612f * FC + 0.3694f * BL) * info.volume); - samples[index * 2 + 1] = - static_cast((0.3694f * FR + 0.2612f * FC + 0.3694f * BR) * info.volume); - } - break; - } - default: - UNIMPLEMENTED_MSG("Unimplemented channel_count={}", info.channel_count); - break; - } - - // Only interpolate when necessary, expensive. - if (GetInfo().sample_rate != STREAM_SAMPLE_RATE) { - samples = Interpolate(interp_state, std::move(samples), GetInfo().sample_rate, - STREAM_SAMPLE_RATE); - } - - is_refresh_pending = false; -} - -void AudioRenderer::EffectState::UpdateState(Core::Memory::Memory& memory) { - if (info.is_new) { - out_status.state = EffectStatus::New; - } else { - if (info.type == Effect::Aux) { - ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_info) == 0, - "Aux buffers tried to update"); - ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_info) == 0, - "Aux buffers tried to update"); - ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_base) == 0, - "Aux buffers tried to update"); - ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_base) == 0, - "Aux buffers tried to update"); - } - } -} - static constexpr s16 ClampToS16(s32 value) { return static_cast(std::clamp(value, -32768, 32767)); } +ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector& input_params, + std::vector& output_params) { + + InfoUpdater info_updater{input_params, output_params, behavior_info}; + + if (!info_updater.UpdateBehaviorInfo(behavior_info)) { + LOG_ERROR(Audio, "Failed to update behavior info input parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (!info_updater.UpdateMemoryPools(memory_pool_info)) { + LOG_ERROR(Audio, "Failed to update memory pool parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (!info_updater.UpdateVoiceChannelResources(voice_context)) { + LOG_ERROR(Audio, "Failed to update voice channel resource parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) { + LOG_ERROR(Audio, "Failed to update voice parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + // TODO(ogniK): Deal with stopped audio renderer but updates still taking place + if (!info_updater.UpdateEffects(effect_context, true)) { + LOG_ERROR(Audio, "Failed to update effect parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (behavior_info.IsSplitterSupported()) { + if (!info_updater.UpdateSplitterInfo(splitter_context)) { + LOG_ERROR(Audio, "Failed to update splitter parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + } + + auto mix_result = + info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count, splitter_context); + + if (mix_result.IsError()) { + LOG_ERROR(Audio, "Failed to update mix parameters"); + return mix_result; + } + + // TODO(ogniK): Sinks + if (!info_updater.UpdateSinks(sink_context)) { + LOG_ERROR(Audio, "Failed to update sink parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + // TODO(ogniK): Performance buffer + if (!info_updater.UpdatePerformanceBuffer()) { + LOG_ERROR(Audio, "Failed to update performance buffer parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (!info_updater.UpdateErrorInfo(behavior_info)) { + LOG_ERROR(Audio, "Failed to update error info"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (behavior_info.IsElapsedFrameCountSupported()) { + if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) { + LOG_ERROR(Audio, "Failed to update renderer info"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + } + // TODO(ogniK): Statistics + + if (!info_updater.WriteOutputHeader()) { + LOG_ERROR(Audio, "Failed to write output header"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + // TODO(ogniK): Check when all sections are implemented + + if (!info_updater.CheckConsumedSize()) { + LOG_ERROR(Audio, "Audio buffers were not consumed!"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + ReleaseAndQueueBuffers(); + + return RESULT_SUCCESS; +} + void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { - constexpr std::size_t BUFFER_SIZE{512}; + command_generator.PreCommand(); + // Clear mix buffers before our next operation + command_generator.ClearMixBuffers(); + + // If the splitter is not in use, sort our mixes + if (!splitter_context.UsingSplitter()) { + mix_context.SortInfo(); + } + // Sort our voices + voice_context.SortInfo(); + + // Handle samples + command_generator.GenerateVoiceCommands(); + command_generator.GenerateSubMixCommands(); + command_generator.GenerateFinalMixCommands(); + + command_generator.PostCommand(); + // Base sample size + std::size_t BUFFER_SIZE{worker_params.sample_count}; + // Samples std::vector buffer(BUFFER_SIZE * stream->GetNumChannels()); + // Make sure to clear our samples + std::memset(buffer.data(), 0, buffer.size() * sizeof(s16)); - for (auto& voice : voices) { - if (!voice.IsPlaying()) { - continue; - } - VoiceChannelHolder resources{}; - for (u32 channel = 0; channel < voice.GetInfo().channel_count; channel++) { - const auto channel_resource_id = voice.GetInfo().voice_channel_resource_ids[channel]; - resources[channel] = &voice_resources[channel_resource_id]; + if (sink_context.InUse()) { + const auto stream_channel_count = stream->GetNumChannels(); + const auto buffer_offsets = sink_context.OutputBuffers(); + const auto channel_count = buffer_offsets.size(); + const auto& final_mix = mix_context.GetFinalMixInfo(); + const auto& in_params = final_mix.GetInParams(); + std::vector mix_buffers(channel_count); + for (std::size_t i = 0; i < channel_count; i++) { + mix_buffers[i] = + command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]); } - std::size_t offset{}; - s64 samples_remaining{BUFFER_SIZE}; - while (samples_remaining > 0) { - const std::vector samples{ - voice.DequeueSamples(samples_remaining, memory, resources)}; + for (std::size_t i = 0; i < BUFFER_SIZE; i++) { + if (channel_count == 1) { + const auto sample = ClampToS16(mix_buffers[0][i]); + buffer[i * stream_channel_count + 0] = sample; + if (stream_channel_count > 1) { + buffer[i * stream_channel_count + 1] = sample; + } + if (stream_channel_count == 6) { + buffer[i * stream_channel_count + 2] = sample; + buffer[i * stream_channel_count + 4] = sample; + buffer[i * stream_channel_count + 5] = sample; + } + } else if (channel_count == 2) { + const auto l_sample = ClampToS16(mix_buffers[0][i]); + const auto r_sample = ClampToS16(mix_buffers[1][i]); + if (stream_channel_count == 0) { + buffer[i * stream_channel_count + 0] = l_sample; + } else if (stream_channel_count == 2) { + buffer[i * stream_channel_count + 0] = l_sample; + buffer[i * stream_channel_count + 1] = r_sample; + } else if (stream_channel_count == 6) { + buffer[i * stream_channel_count + 0] = l_sample; + buffer[i * stream_channel_count + 1] = r_sample; - if (samples.empty()) { - break; - } + buffer[i * stream_channel_count + 2] = + ClampToS16((static_cast(l_sample) + static_cast(r_sample)) / 2); - samples_remaining -= samples.size() / stream->GetNumChannels(); + buffer[i * stream_channel_count + 4] = l_sample; + buffer[i * stream_channel_count + 5] = r_sample; + } - for (const auto& sample : samples) { - const s32 buffer_sample{buffer[offset]}; - buffer[offset++] = - ClampToS16(buffer_sample + static_cast(sample * voice.GetInfo().volume)); + } else if (channel_count == 6) { + const auto fl_sample = ClampToS16(mix_buffers[0][i]); + const auto fr_sample = ClampToS16(mix_buffers[1][i]); + const auto fc_sample = ClampToS16(mix_buffers[2][i]); + const auto lf_sample = ClampToS16(mix_buffers[3][i]); + const auto bl_sample = ClampToS16(mix_buffers[4][i]); + const auto br_sample = ClampToS16(mix_buffers[5][i]); + + if (stream_channel_count == 1) { + buffer[i * stream_channel_count + 0] = fc_sample; + } else if (stream_channel_count == 2) { + buffer[i * stream_channel_count + 0] = + static_cast(0.3694f * static_cast(fl_sample) + + 0.2612f * static_cast(fc_sample) + + 0.3694f * static_cast(bl_sample)); + buffer[i * stream_channel_count + 1] = + static_cast(0.3694f * static_cast(fr_sample) + + 0.2612f * static_cast(fc_sample) + + 0.3694f * static_cast(br_sample)); + } else if (stream_channel_count == 6) { + buffer[i * stream_channel_count + 0] = fl_sample; + buffer[i * stream_channel_count + 1] = fr_sample; + buffer[i * stream_channel_count + 2] = fc_sample; + buffer[i * stream_channel_count + 3] = lf_sample; + buffer[i * stream_channel_count + 4] = bl_sample; + buffer[i * stream_channel_count + 5] = br_sample; + } } } } + audio_out->QueueBuffer(stream, tag, std::move(buffer)); elapsed_frame_count++; + voice_context.UpdateStateByDspShared(); } void AudioRenderer::ReleaseAndQueueBuffers() { diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h index f0b691a865..2bca795bab 100644 --- a/src/audio_core/audio_renderer.h +++ b/src/audio_core/audio_renderer.h @@ -9,8 +9,15 @@ #include #include "audio_core/behavior_info.h" +#include "audio_core/command_generator.h" #include "audio_core/common.h" +#include "audio_core/effect_context.h" +#include "audio_core/memory_pool.h" +#include "audio_core/mix_context.h" +#include "audio_core/sink_context.h" +#include "audio_core/splitter_context.h" #include "audio_core/stream.h" +#include "audio_core/voice_context.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" @@ -30,220 +37,25 @@ class Memory; } namespace AudioCore { +using DSPStateHolder = std::array; class AudioOut; -enum class PlayState : u8 { - Started = 0, - Stopped = 1, - Paused = 2, -}; - -enum class Effect : u8 { - None = 0, - Aux = 2, -}; - -enum class EffectStatus : u8 { - None = 0, - New = 1, -}; - -struct AudioRendererParameter { - u32_le sample_rate; - u32_le sample_count; - u32_le mix_buffer_count; - u32_le submix_count; - u32_le voice_count; - u32_le sink_count; - u32_le effect_count; - u32_le performance_frame_count; - u8 is_voice_drop_enabled; - u8 unknown_21; - u8 unknown_22; - u8 execution_mode; - u32_le splitter_count; - u32_le num_splitter_send_channels; - u32_le unknown_30; - u32_le revision; -}; -static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size"); - -enum class MemoryPoolStates : u32 { // Should be LE - Invalid = 0x0, - Unknown = 0x1, - RequestDetach = 0x2, - Detached = 0x3, - RequestAttach = 0x4, - Attached = 0x5, - Released = 0x6, -}; - -struct MemoryPoolEntry { - MemoryPoolStates state; - u32_le unknown_4; - u32_le unknown_8; - u32_le unknown_c; -}; -static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size"); - -struct MemoryPoolInfo { - u64_le pool_address; - u64_le pool_size; - MemoryPoolStates pool_state; - INSERT_PADDING_WORDS(3); // Unknown -}; -static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size"); -struct BiquadFilter { - u8 enable; - INSERT_PADDING_BYTES(1); - std::array numerator; - std::array denominator; -}; -static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size"); - -struct WaveBuffer { - u64_le buffer_addr; - u64_le buffer_sz; - s32_le start_sample_offset; - s32_le end_sample_offset; - u8 is_looping; - u8 end_of_stream; - u8 sent_to_server; - INSERT_PADDING_BYTES(5); - u64 context_addr; - u64 context_sz; - INSERT_PADDING_BYTES(8); -}; -static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size"); - -struct VoiceResourceInformation { - s32_le id{}; - std::array mix_volumes{}; - bool in_use{}; - INSERT_PADDING_BYTES(11); -}; -static_assert(sizeof(VoiceResourceInformation) == 0x70, "VoiceResourceInformation has wrong size"); - -struct VoiceInfo { - u32_le id; - u32_le node_id; - u8 is_new; - u8 is_in_use; - PlayState play_state; - u8 sample_format; - u32_le sample_rate; - u32_le priority; - u32_le sorting_order; - u32_le channel_count; - float_le pitch; - float_le volume; - std::array biquad_filter; - u32_le wave_buffer_count; - u32_le wave_buffer_head; - INSERT_PADDING_WORDS(1); - u64_le additional_params_addr; - u64_le additional_params_sz; - u32_le mix_id; - u32_le splitter_info_id; - std::array wave_buffer; - std::array voice_channel_resource_ids; - INSERT_PADDING_BYTES(24); -}; -static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size"); - -struct VoiceOutStatus { - u64_le played_sample_count; - u32_le wave_buffer_consumed; - u32_le voice_drops_count; -}; -static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size"); - -struct AuxInfo { - std::array input_mix_buffers; - std::array output_mix_buffers; - u32_le mix_buffer_count; - u32_le sample_rate; // Stored in the aux buffer currently - u32_le sample_count; - u64_le send_buffer_info; - u64_le send_buffer_base; - - u64_le return_buffer_info; - u64_le return_buffer_base; -}; -static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size"); - -struct EffectInStatus { - Effect type; - u8 is_new; - u8 is_enabled; - INSERT_PADDING_BYTES(1); - u32_le mix_id; - u64_le buffer_base; - u64_le buffer_sz; - s32_le priority; - INSERT_PADDING_BYTES(4); - union { - std::array raw; - AuxInfo aux_info; - }; -}; -static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size"); - -struct EffectOutStatus { - EffectStatus state; - INSERT_PADDING_BYTES(0xf); -}; -static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size"); - struct RendererInfo { u64_le elasped_frame_count{}; INSERT_PADDING_WORDS(2); }; static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size"); -struct UpdateDataHeader { - UpdateDataHeader() {} - - explicit UpdateDataHeader(const AudioRendererParameter& config) { - revision = Common::MakeMagic('R', 'E', 'V', '8'); // 9.2.0 Revision - behavior_size = 0xb0; - memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10; - voices_size = config.voice_count * 0x10; - voice_resource_size = 0x0; - effects_size = config.effect_count * 0x10; - mixes_size = 0x0; - sinks_size = config.sink_count * 0x20; - performance_manager_size = 0x10; - render_info = 0; - total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size + - effects_size + sinks_size + performance_manager_size; - } - - u32_le revision{}; - u32_le behavior_size{}; - u32_le memory_pools_size{}; - u32_le voices_size{}; - u32_le voice_resource_size{}; - u32_le effects_size{}; - u32_le mixes_size{}; - u32_le sinks_size{}; - u32_le performance_manager_size{}; - u32_le splitter_size{}; - u32_le render_info{}; - INSERT_PADDING_WORDS(4); - u32_le total_size{}; -}; -static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size"); - class AudioRenderer { public: AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, - AudioRendererParameter params, + AudioCommon::AudioRendererParameter params, std::shared_ptr buffer_event, std::size_t instance_number); ~AudioRenderer(); - ResultVal> UpdateAudioRenderer(const std::vector& input_params); + ResultCode UpdateAudioRenderer(const std::vector& input_params, + std::vector& output_params); void QueueMixedBuffer(Buffer::Tag tag); void ReleaseAndQueueBuffers(); u32 GetSampleRate() const; @@ -252,19 +64,23 @@ public: Stream::State GetStreamState() const; private: - class EffectState; - class VoiceState; BehaviorInfo behavior_info{}; - AudioRendererParameter worker_params; + AudioCommon::AudioRendererParameter worker_params; std::shared_ptr buffer_event; + std::vector memory_pool_info; + VoiceContext voice_context; + EffectContext effect_context; + MixContext mix_context; + SinkContext sink_context; + SplitterContext splitter_context; std::vector voices; - std::vector voice_resources; - std::vector effects; std::unique_ptr audio_out; StreamPtr stream; Core::Memory::Memory& memory; + CommandGenerator command_generator; std::size_t elapsed_frame_count{}; + std::vector temp_mix_buffer{}; }; } // namespace AudioCore diff --git a/src/audio_core/behavior_info.cpp b/src/audio_core/behavior_info.cpp index 94b7a3bf1c..b7cd8f17d8 100644 --- a/src/audio_core/behavior_info.cpp +++ b/src/audio_core/behavior_info.cpp @@ -9,39 +9,11 @@ namespace AudioCore { -BehaviorInfo::BehaviorInfo() : process_revision(CURRENT_PROCESS_REVISION) {} +BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {} BehaviorInfo::~BehaviorInfo() = default; -bool BehaviorInfo::UpdateInput(const std::vector& buffer, std::size_t offset) { - if (!CanConsumeBuffer(buffer.size(), offset, sizeof(InParams))) { - LOG_ERROR(Audio, "Buffer is an invalid size!"); - return false; - } - InParams params{}; - std::memcpy(¶ms, buffer.data() + offset, sizeof(InParams)); - - if (!IsValidRevision(params.revision)) { - LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", params.revision); - return false; - } - - if (user_revision != params.revision) { - LOG_ERROR(Audio, - "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}", - user_revision, params.revision); - return false; - } - - ClearError(); - UpdateFlags(params.flags); - - // TODO(ogniK): Check input params size when InfoUpdater is used - - return true; -} - bool BehaviorInfo::UpdateOutput(std::vector& buffer, std::size_t offset) { - if (!CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) { + if (!AudioCommon::CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) { LOG_ERROR(Audio, "Buffer is an invalid size!"); return false; } @@ -65,36 +37,69 @@ void BehaviorInfo::SetUserRevision(u32_le revision) { user_revision = revision; } +u32_le BehaviorInfo::GetUserRevision() const { + return user_revision; +} + +u32_le BehaviorInfo::GetProcessRevision() const { + return process_revision; +} + bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const { - return IsRevisionSupported(2, user_revision); + return AudioCommon::IsRevisionSupported(2, user_revision); } bool BehaviorInfo::IsSplitterSupported() const { - return IsRevisionSupported(2, user_revision); + return AudioCommon::IsRevisionSupported(2, user_revision); } bool BehaviorInfo::IsLongSizePreDelaySupported() const { - return IsRevisionSupported(3, user_revision); + return AudioCommon::IsRevisionSupported(3, user_revision); } bool BehaviorInfo::IsAudioRenererProcessingTimeLimit80PercentSupported() const { - return IsRevisionSupported(5, user_revision); + return AudioCommon::IsRevisionSupported(5, user_revision); } bool BehaviorInfo::IsAudioRenererProcessingTimeLimit75PercentSupported() const { - return IsRevisionSupported(4, user_revision); + return AudioCommon::IsRevisionSupported(4, user_revision); } bool BehaviorInfo::IsAudioRenererProcessingTimeLimit70PercentSupported() const { - return IsRevisionSupported(1, user_revision); + return AudioCommon::IsRevisionSupported(1, user_revision); } bool BehaviorInfo::IsElapsedFrameCountSupported() const { - return IsRevisionSupported(5, user_revision); + return AudioCommon::IsRevisionSupported(5, user_revision); } bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const { return (flags & 1) != 0; } +bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const { + return AudioCommon::IsRevisionSupported(5, user_revision); +} + +bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const { + return AudioCommon::IsRevisionSupported(5, user_revision); +} + +bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const { + return AudioCommon::IsRevisionSupported(5, user_revision); +} + +bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const { + return AudioCommon::IsRevisionSupported(7, user_revision); +} + +bool BehaviorInfo::IsSplitterBugFixed() const { + return AudioCommon::IsRevisionSupported(5, user_revision); +} + +void BehaviorInfo::CopyErrorInfo(BehaviorInfo::OutParams& dst) { + dst.error_count = static_cast(error_count); + std::memcpy(dst.errors.data(), errors.data(), sizeof(ErrorInfo) * dst.errors.size()); +} + } // namespace AudioCore diff --git a/src/audio_core/behavior_info.h b/src/audio_core/behavior_info.h index c5e91ab392..50948e8dff 100644 --- a/src/audio_core/behavior_info.h +++ b/src/audio_core/behavior_info.h @@ -14,30 +14,6 @@ namespace AudioCore { class BehaviorInfo { public: - explicit BehaviorInfo(); - ~BehaviorInfo(); - - bool UpdateInput(const std::vector& buffer, std::size_t offset); - bool UpdateOutput(std::vector& buffer, std::size_t offset); - - void ClearError(); - void UpdateFlags(u64_le dest_flags); - void SetUserRevision(u32_le revision); - - bool IsAdpcmLoopContextBugFixed() const; - bool IsSplitterSupported() const; - bool IsLongSizePreDelaySupported() const; - bool IsAudioRenererProcessingTimeLimit80PercentSupported() const; - bool IsAudioRenererProcessingTimeLimit75PercentSupported() const; - bool IsAudioRenererProcessingTimeLimit70PercentSupported() const; - bool IsElapsedFrameCountSupported() const; - bool IsMemoryPoolForceMappingEnabled() const; - -private: - u32_le process_revision{}; - u32_le user_revision{}; - u64_le flags{}; - struct ErrorInfo { u32_le result{}; INSERT_PADDING_WORDS(1); @@ -45,9 +21,6 @@ private: }; static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size"); - std::array errors{}; - std::size_t error_count{}; - struct InParams { u32_le revision{}; u32_le padding{}; @@ -61,6 +34,39 @@ private: INSERT_PADDING_BYTES(12); }; static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size"); + + explicit BehaviorInfo(); + ~BehaviorInfo(); + + bool UpdateOutput(std::vector& buffer, std::size_t offset); + + void ClearError(); + void UpdateFlags(u64_le dest_flags); + void SetUserRevision(u32_le revision); + u32_le GetUserRevision() const; + u32_le GetProcessRevision() const; + + bool IsAdpcmLoopContextBugFixed() const; + bool IsSplitterSupported() const; + bool IsLongSizePreDelaySupported() const; + bool IsAudioRenererProcessingTimeLimit80PercentSupported() const; + bool IsAudioRenererProcessingTimeLimit75PercentSupported() const; + bool IsAudioRenererProcessingTimeLimit70PercentSupported() const; + bool IsElapsedFrameCountSupported() const; + bool IsMemoryPoolForceMappingEnabled() const; + bool IsFlushVoiceWaveBuffersSupported() const; + bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const; + bool IsVoicePitchAndSrcSkippedSupported() const; + bool IsMixInParameterDirtyOnlyUpdateSupported() const; + bool IsSplitterBugFixed() const; + void CopyErrorInfo(OutParams& dst); + +private: + u32_le process_revision{}; + u32_le user_revision{}; + u64_le flags{}; + std::array errors{}; + std::size_t error_count{}; }; } // namespace AudioCore diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp new file mode 100644 index 0000000000..722f9b6c5e --- /dev/null +++ b/src/audio_core/command_generator.cpp @@ -0,0 +1,668 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/algorithm/interpolate.h" +#include "audio_core/command_generator.h" +#include "audio_core/mix_context.h" +#include "audio_core/voice_context.h" +#include "core/memory.h" + +namespace AudioCore { +namespace { +static constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00; +static constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL; + +template +void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) { + for (s32 i = 0; i < sample_count; i += N) { + for (std::size_t j = 0; j < N; j++) { + output[i + j] += + static_cast((static_cast(input[i + j]) * gain + 0x4000) >> 15); + } + } +} + +s32 ApplyMixRamp(s32* output, const s32* input, float gain, float delta, s32 sample_count) { + s32 x = 0; + for (s32 i = 0; i < sample_count; i++) { + x = static_cast(static_cast(input[i]) * gain); + output[i] += x; + gain += delta; + } + return x; +} + +void ApplyGain(s32* output, const s32* input, s32 gain, s32 delta, s32 sample_count) { + for (s32 i = 0; i < sample_count; i++) { + output[i] = static_cast((static_cast(input[i]) * gain + 0x4000) >> 15); + gain += delta; + } +} + +void ApplyGainWithoutDelta(s32* output, const s32* input, s32 gain, s32 sample_count) { + for (s32 i = 0; i < sample_count; i++) { + output[i] = static_cast((static_cast(input[i]) * gain + 0x4000) >> 15); + } +} + +} // namespace + +CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params, + VoiceContext& voice_context, MixContext& mix_context, + SplitterContext& splitter_context, Core::Memory::Memory& memory) + : worker_params(worker_params), voice_context(voice_context), mix_context(mix_context), + splitter_context(splitter_context), memory(memory), + mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) * + worker_params.sample_count), + sample_buffer(MIX_BUFFER_SIZE) {} +CommandGenerator::~CommandGenerator() = default; + +void CommandGenerator::ClearMixBuffers() { + std::memset(mix_buffer.data(), 0, mix_buffer.size() * sizeof(s32)); + std::memset(sample_buffer.data(), 0, sample_buffer.size() * sizeof(s32)); +} + +void CommandGenerator::GenerateVoiceCommands() { + if (dumping_frame) { + LOG_CRITICAL(Audio, "(DSP_TRACE) GenerateVoiceCommands"); + } + // Grab all our voices + const auto voice_count = voice_context.GetVoiceCount(); + for (std::size_t i = 0; i < voice_count; i++) { + auto& voice_info = voice_context.GetSortedInfo(i); + // Update voices and check if we should queue them + if (voice_info.ShouldSkip() || !voice_info.UpdateForCommandGeneration(voice_context)) { + continue; + } + + // Queue our voice + GenerateVoiceCommand(voice_info); + } + // Update our splitters + splitter_context.UpdateInternalState(); +} + +void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) { + auto& in_params = voice_info.GetInParams(); + const auto channel_count = in_params.channel_count; + + for (s32 channel = 0; channel < channel_count; channel++) { + const auto resource_id = in_params.voice_channel_resource_id[channel]; + auto& dsp_state = voice_context.GetDspSharedState(resource_id); + auto& channel_resource = voice_context.GetChannelResource(resource_id); + + // Decode our samples for our channel + GenerateDataSourceCommand(voice_info, dsp_state, channel); + + if (in_params.should_depop) { + in_params.last_volume = 0.0f; + } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER || + in_params.mix_id != AudioCommon::NO_MIX) { + // Apply a biquad filter if needed + GenerateBiquadFilterCommandForVoice(voice_info, dsp_state, + worker_params.mix_buffer_count, channel); + // Base voice volume ramping + GenerateVolumeRampCommand(in_params.last_volume, in_params.volume, channel, + in_params.node_id); + in_params.last_volume = in_params.volume; + + if (in_params.mix_id != AudioCommon::NO_MIX) { + // If we're using a mix id + auto& mix_info = mix_context.GetInfo(in_params.mix_id); + const auto& dest_mix_params = mix_info.GetInParams(); + + // Voice Mixing + GenerateVoiceMixCommand( + channel_resource.GetCurrentMixVolume(), channel_resource.GetLastMixVolume(), + dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count, + worker_params.mix_buffer_count + channel, in_params.node_id); + + // Update last mix volumes + channel_resource.UpdateLastMixVolumes(); + } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) { + s32 base = channel; + while (auto* destination_data = + GetDestinationData(in_params.splitter_info_id, base)) { + base += channel_count; + + if (!destination_data->IsConfigured()) { + continue; + } + if (destination_data->GetMixId() >= mix_context.GetCount()) { + continue; + } + + const auto& mix_info = mix_context.GetInfo(destination_data->GetMixId()); + const auto& dest_mix_params = mix_info.GetInParams(); + GenerateVoiceMixCommand( + destination_data->CurrentMixVolumes(), destination_data->LastMixVolumes(), + dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count, + worker_params.mix_buffer_count + channel, in_params.node_id); + destination_data->MarkDirty(); + } + } + } + + // Update biquad filter enabled states + for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) { + in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled; + } + } +} + +void CommandGenerator::GenerateSubMixCommands() { + const auto mix_count = mix_context.GetCount(); + for (std::size_t i = 0; i < mix_count; i++) { + auto& mix_info = mix_context.GetSortedInfo(i); + const auto& in_params = mix_info.GetInParams(); + if (!in_params.in_use || in_params.mix_id == AudioCommon::FINAL_MIX) { + continue; + } + GenerateSubMixCommand(mix_info); + } +} + +void CommandGenerator::GenerateFinalMixCommands() { + GenerateFinalMixCommand(); +} + +void CommandGenerator::PreCommand() { + if (dumping_frame) { + for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) { + const auto& base = splitter_context.GetInfo(i); + std::string graph = fmt::format("b[{}]", i); + auto* head = base.GetHead(); + while (head != nullptr) { + graph += fmt::format("->{}", head->GetMixId()); + head = head->GetNextDestination(); + } + LOG_CRITICAL(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph); + } + } +} + +void CommandGenerator::PostCommand() { + if (dumping_frame) { + dumping_frame = false; + } +} + +void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, + s32 channel) { + auto& in_params = voice_info.GetInParams(); + const auto depop = in_params.should_depop; + + if (in_params.mix_id != AudioCommon::NO_MIX) { + auto& mix_info = mix_context.GetInfo(in_params.mix_id); + // mix_info. + // TODO(ogniK): Depop to destination mix + } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) { + // TODO(ogniK): Depop to splitter + } + + if (!depop) { + switch (in_params.sample_format) { + case SampleFormat::Pcm16: + DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel, + worker_params.sample_rate, worker_params.sample_count, + in_params.node_id); + break; + case SampleFormat::Adpcm: + ASSERT(channel == 0 && in_params.channel_count == 1); + DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0, + worker_params.sample_rate, worker_params.sample_count, + in_params.node_id); + break; + default: + UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format); + } + } +} + +void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, + VoiceState& dsp_state, + s32 mix_buffer_count, s32 channel) { + for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) { + const auto& in_params = voice_info.GetInParams(); + auto& biquad_filter = in_params.biquad_filter[i]; + // Check if biquad filter is actually used + if (!biquad_filter.enabled) { + continue; + } + + // Reinitialize our biquad filter state if it was enabled previously + if (!in_params.was_biquad_filter_enabled[i]) { + std::memset(dsp_state.biquad_filter_state.data(), 0, + dsp_state.biquad_filter_state.size() * sizeof(s64)); + } + + // Generate biquad filter + GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter, dsp_state.biquad_filter_state, + mix_buffer_count + channel, mix_buffer_count + channel, + worker_params.sample_count, voice_info.GetInParams().node_id); + } +} + +void AudioCore::CommandGenerator::GenerateBiquadFilterCommand( + s32 mix_buffer, const BiquadFilterParameter& params, std::array& state, + std::size_t input_offset, std::size_t output_offset, s32 sample_count, s32 node_id) { + if (dumping_frame) { + LOG_CRITICAL(Audio, + "(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, " + "input_mix_buffer={}, output_mix_buffer={}", + node_id, input_offset, output_offset); + } + const auto* input = GetMixBuffer(input_offset); + auto* output = GetMixBuffer(output_offset); + + // Biquad filter parameters + const auto n0 = params.numerator[0]; + const auto n1 = params.numerator[1]; + const auto n2 = params.numerator[2]; + const auto d0 = params.denominator[0]; + const auto d1 = params.denominator[1]; + + // Biquad filter states + auto s0 = state[0]; + auto s1 = state[1]; + + constexpr s64 MIN = std::numeric_limits::min(); + constexpr s64 MAX = std::numeric_limits::max(); + + for (int i = 0; i < sample_count; ++i) { + const auto sample = static_cast(input[i]); + const auto f = (sample * n0 + s0 + 0x4000) >> 15; + const auto y = std::clamp(f, MIN, MAX); + s0 = sample * n1 + y * d0 + s1; + s1 = sample * n2 + y * d1; + output[i] = static_cast(y); + } + + state[0] = s0; + state[1] = s1; +} + +ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) { + if (splitter_id == AudioCommon::NO_SPLITTER) { + return nullptr; + } + return splitter_context.GetDestinationData(splitter_id, index); +} + +void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume, + s32 channel, s32 node_id) { + const auto last = static_cast(last_volume * 32768.0f); + const auto current = static_cast(current_volume * 32768.0f); + const auto delta = static_cast((static_cast(current) - static_cast(last)) / + static_cast(worker_params.sample_count)); + + if (dumping_frame) { + LOG_CRITICAL(Audio, + "(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, " + "last_volume={}, current_volume={}", + node_id, GetMixChannelBufferOffset(channel), + GetMixChannelBufferOffset(channel), last_volume, current_volume); + } + // Apply generic gain on samples + ApplyGain(GetChannelMixBuffer(channel), GetChannelMixBuffer(channel), last, delta, + worker_params.sample_count); +} + +void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes, + const MixVolumeBuffer& last_mix_volumes, + VoiceState& dsp_state, s32 mix_buffer_offset, + s32 mix_buffer_count, s32 voice_index, s32 node_id) { + // Loop all our mix buffers + for (s32 i = 0; i < mix_buffer_count; i++) { + if (last_mix_volumes[i] != 0.0f || mix_volumes[i] != 0.0f) { + const auto delta = static_cast((mix_volumes[i] - last_mix_volumes[i])) / + static_cast(worker_params.sample_count); + + if (dumping_frame) { + LOG_CRITICAL(Audio, + "(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, " + "output={}, last_volume={}, current_volume={}", + node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i], + mix_volumes[i]); + } + + dsp_state.previous_samples[i] = + ApplyMixRamp(GetMixBuffer(mix_buffer_offset + i), GetMixBuffer(voice_index), + last_mix_volumes[i], delta, worker_params.sample_count); + } else { + dsp_state.previous_samples[i] = 0; + } + } +} + +void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) { + if (dumping_frame) { + LOG_CRITICAL(Audio, "(DSP_TRACE) GenerateSubMixCommand"); + } + // TODO(ogniK): Depop + // TODO(ogniK): Effects + GenerateMixCommands(mix_info); +} + +void CommandGenerator::GenerateMixCommands(ServerMixInfo& mix_info) { + if (!mix_info.HasAnyConnection()) { + return; + } + const auto& in_params = mix_info.GetInParams(); + if (in_params.dest_mix_id != AudioCommon::NO_MIX) { + const auto& dest_mix = mix_context.GetInfo(in_params.dest_mix_id); + const auto& dest_in_params = dest_mix.GetInParams(); + + const auto buffer_count = in_params.buffer_count; + + for (s32 i = 0; i < buffer_count; i++) { + for (s32 j = 0; j < dest_in_params.buffer_count; j++) { + const auto mixed_volume = in_params.volume * in_params.mix_volume[i][j]; + if (mixed_volume != 0.0f) { + GenerateMixCommand(dest_in_params.buffer_offset + j, + in_params.buffer_offset + i, mixed_volume, + in_params.node_id); + } + } + } + } else if (in_params.splitter_id != AudioCommon::NO_SPLITTER) { + s32 base{}; + while (const auto* destination_data = GetDestinationData(in_params.splitter_id, base++)) { + if (!destination_data->IsConfigured()) { + continue; + } + + const auto& dest_mix = mix_context.GetInfo(destination_data->GetMixId()); + const auto& dest_in_params = dest_mix.GetInParams(); + const auto mix_index = (base - 1) % in_params.buffer_count + in_params.buffer_offset; + for (std::size_t i = 0; i < dest_in_params.buffer_count; i++) { + const auto mixed_volume = in_params.volume * destination_data->GetMixVolume(i); + if (mixed_volume != 0.0f) { + GenerateMixCommand(dest_in_params.buffer_offset + i, mix_index, mixed_volume, + in_params.node_id); + } + } + } + } +} + +void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, + float volume, s32 node_id) { + + if (dumping_frame) { + LOG_CRITICAL(Audio, + "(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}", + node_id, input_offset, output_offset, volume); + } + + auto* output = GetMixBuffer(output_offset); + const auto* input = GetMixBuffer(input_offset); + + const s32 gain = static_cast(volume * 32768.0f); + // Mix with loop unrolling + if (worker_params.sample_count % 4 == 0) { + ApplyMix<4>(output, input, gain, worker_params.sample_count); + } else if (worker_params.sample_count % 2 == 0) { + ApplyMix<2>(output, input, gain, worker_params.sample_count); + } else { + ApplyMix<1>(output, input, gain, worker_params.sample_count); + } +} + +void CommandGenerator::GenerateFinalMixCommand() { + if (dumping_frame) { + LOG_CRITICAL(Audio, "(DSP_TRACE) GenerateFinalMixCommand"); + } + // TODO(ogniK): Depop + // TODO(ogniK): Effects + auto& mix_info = mix_context.GetFinalMixInfo(); + const auto in_params = mix_info.GetInParams(); + for (s32 i = 0; i < in_params.buffer_count; i++) { + const s32 gain = static_cast(in_params.volume * 32768.0f); + if (dumping_frame) { + LOG_CRITICAL( + Audio, + "(DSP_TRACE) ApplyGainWithoutDelta node_id={}, input={}, output={}, volume={}", + in_params.node_id, in_params.buffer_offset + i, in_params.buffer_offset + i, + in_params.volume); + } + ApplyGainWithoutDelta(GetMixBuffer(in_params.buffer_offset + i), + GetMixBuffer(in_params.buffer_offset + i), gain, + worker_params.sample_count); + } +} + +s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, + s32 sample_count, s32 channel, std::size_t mix_offset) { + auto& in_params = voice_info.GetInParams(); + const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; + if (wave_buffer.buffer_address == 0) { + return 0; + } + if (wave_buffer.buffer_size == 0) { + return 0; + } + if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) { + return 0; + } + const auto samples_remaining = + (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset; + const auto start_offset = + ((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count) * + sizeof(s16); + const auto buffer_pos = wave_buffer.buffer_address + start_offset; + const auto samples_processed = std::min(sample_count, samples_remaining); + + if (in_params.channel_count == 1) { + std::vector buffer(samples_processed); + memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16)); + for (std::size_t i = 0; i < buffer.size(); i++) { + sample_buffer[mix_offset + i] = buffer[i]; + } + } else { + const auto channel_count = in_params.channel_count; + std::vector buffer(samples_processed * channel_count); + memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16)); + + for (std::size_t i = 0; i < samples_processed; i++) { + sample_buffer[mix_offset + i] = buffer[i * channel_count + channel]; + } + } + + return samples_processed; +} +s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, + s32 sample_count, s32 channel, std::size_t mix_offset) { + auto& in_params = voice_info.GetInParams(); + const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; + if (wave_buffer.buffer_address == 0) { + return 0; + } + if (wave_buffer.buffer_size == 0) { + return 0; + } + if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) { + return 0; + } + + const auto samples_remaining = + (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset; + const auto start_offset = + ((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count); + const auto buffer_pos = wave_buffer.buffer_address + start_offset; + + const auto samples_processed = std::min(sample_count, samples_remaining); + + if (start_offset > dsp_state.adpcm_samples.size()) { + dsp_state.adpcm_samples.clear(); + } + + // TODO(ogniK): Proper ADPCM streaming + if (dsp_state.adpcm_samples.empty()) { + Codec::ADPCM_Coeff coeffs; + memory.ReadBlock(in_params.additional_params_address, coeffs.data(), + sizeof(Codec::ADPCM_Coeff)); + std::vector buffer(wave_buffer.buffer_size); + memory.ReadBlock(wave_buffer.buffer_address, buffer.data(), buffer.size()); + dsp_state.adpcm_samples = + std::move(Codec::DecodeADPCM(buffer.data(), buffer.size(), coeffs, dsp_state.context)); + } + + for (std::size_t i = 0; i < samples_processed; i++) { + const auto sample_offset = i + start_offset; + sample_buffer[mix_offset + i] = + dsp_state.adpcm_samples[sample_offset * in_params.channel_count + channel]; + } + + return samples_processed; +} + +s32* CommandGenerator::GetMixBuffer(std::size_t index) { + return mix_buffer.data() + (index * worker_params.sample_count); +} + +const s32* CommandGenerator::GetMixBuffer(std::size_t index) const { + return mix_buffer.data() + (index * worker_params.sample_count); +} + +std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const { + return worker_params.mix_buffer_count + channel; +} + +s32* CommandGenerator::GetChannelMixBuffer(s32 channel) { + return GetMixBuffer(worker_params.mix_buffer_count + channel); +} + +const s32* CommandGenerator::GetChannelMixBuffer(s32 channel) const { + return GetMixBuffer(worker_params.mix_buffer_count + channel); +} + +void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, + VoiceState& dsp_state, s32 channel, + s32 target_sample_rate, s32 sample_count, + s32 node_id) { + auto& in_params = voice_info.GetInParams(); + if (dumping_frame) { + LOG_CRITICAL(Audio, + "(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, " + "format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}", + node_id, channel, in_params.sample_format, sample_count, in_params.sample_rate, + in_params.mix_id, in_params.splitter_info_id); + } + ASSERT_OR_EXECUTE(output != nullptr, { return; }); + + const auto resample_rate = static_cast( + static_cast(in_params.sample_rate) / static_cast(target_sample_rate) * + static_cast(static_cast(in_params.pitch * 32768.0f))); + auto* output_base = output; + if ((dsp_state.fraction + sample_count * resample_rate) > (SCALED_MIX_BUFFER_SIZE - 4ULL)) { + return; + } + + auto min_required_samples = + std::min(static_cast(SCALED_MIX_BUFFER_SIZE) - dsp_state.fraction, resample_rate); + if (min_required_samples >= sample_count) { + min_required_samples = sample_count; + } + + std::size_t temp_mix_offset{}; + bool is_buffer_completed{false}; + auto samples_remaining = sample_count; + while (samples_remaining > 0 && !is_buffer_completed) { + const auto samples_to_output = std::min(samples_remaining, min_required_samples); + const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15; + + if (!in_params.behavior_flags.is_pitch_and_src_skipped) { + // Append sample histtory for resampler + for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) { + sample_buffer[temp_mix_offset + i] = dsp_state.sample_history[i]; + } + temp_mix_offset += 4; + } + + s32 samples_read{}; + while (samples_read < samples_to_read) { + const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; + // No more data can be read + if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) { + is_buffer_completed = true; + break; + } + + if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 && + wave_buffer.context_address != 0 && wave_buffer.context_size != 0) { + // TODO(ogniK): ADPCM loop context + } + + s32 samples_decoded{0}; + switch (in_params.sample_format) { + case SampleFormat::Pcm16: + samples_decoded = DecodePcm16(voice_info, dsp_state, samples_to_read - samples_read, + channel, temp_mix_offset); + break; + case SampleFormat::Adpcm: + samples_decoded = DecodeAdpcm(voice_info, dsp_state, samples_to_read - samples_read, + channel, temp_mix_offset); + break; + default: + UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format); + } + + temp_mix_offset += samples_decoded; + samples_read += samples_decoded; + dsp_state.offset += samples_decoded; + dsp_state.played_sample_count += samples_decoded; + + if (dsp_state.offset >= + (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) || + samples_decoded == 0) { + // Reset our sample offset + dsp_state.offset = 0; + if (wave_buffer.is_looping) { + if (samples_decoded == 0) { + // End of our buffer + is_buffer_completed = true; + break; + } + + if (in_params.behavior_flags.is_played_samples_reset_at_loop_point.Value()) { + dsp_state.played_sample_count = 0; + } + } else { + if (in_params.sample_format == SampleFormat::Adpcm) { + // TODO(ogniK): Remove this when ADPCM streaming implemented + dsp_state.adpcm_samples.clear(); + } + + // Update our wave buffer states + dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false; + dsp_state.wave_buffer_consumed++; + dsp_state.wave_buffer_index = + (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; + if (wave_buffer.end_of_stream) { + dsp_state.played_sample_count = 0; + } + } + } + } + + if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) { + // No need to resample + memcpy(output, sample_buffer.data(), samples_read * sizeof(s32)); + } else { + std::memset(sample_buffer.data() + temp_mix_offset, 0, + sizeof(s32) * (samples_to_read - samples_read)); + AudioCore::Resample(output, sample_buffer.data(), resample_rate, dsp_state.fraction, + samples_to_output); + // Resample + for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) { + dsp_state.sample_history[i] = sample_buffer[samples_to_read + i]; + } + } + output += samples_to_output; + samples_remaining -= samples_to_output; + } +} + +} // namespace AudioCore diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h new file mode 100644 index 0000000000..e0d7510fc1 --- /dev/null +++ b/src/audio_core/command_generator.h @@ -0,0 +1,83 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "audio_core/common.h" +#include "audio_core/voice_context.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Core::Memory { +class Memory; +} + +namespace AudioCore { +class MixContext; +class SplitterContext; +class ServerSplitterDestinationData; +class ServerMixInfo; +using MixVolumeBuffer = std::array; +class CommandGenerator { +public: + explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params, + VoiceContext& voice_context, MixContext& mix_context, + SplitterContext& splitter_context, Core::Memory::Memory& memory); + ~CommandGenerator(); + + void ClearMixBuffers(); + void GenerateVoiceCommands(); + void GenerateVoiceCommand(ServerVoiceInfo& voice_info); + void GenerateSubMixCommands(); + void GenerateFinalMixCommands(); + void PreCommand(); + void PostCommand(); + + s32* GetChannelMixBuffer(s32 channel); + const s32* GetChannelMixBuffer(s32 channel) const; + s32* GetMixBuffer(std::size_t index); + const s32* GetMixBuffer(std::size_t index) const; + std::size_t GetMixChannelBufferOffset(s32 channel) const; + +private: + void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel); + void GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state, + s32 mix_buffer_count, s32 channel); + void GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel, + s32 node_id); + void GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes, + const MixVolumeBuffer& last_mix_volumes, VoiceState& dsp_state, + s32 mix_buffer_offset, s32 mix_buffer_count, s32 voice_index, + s32 node_id); + void GenerateSubMixCommand(ServerMixInfo& mix_info); + void GenerateMixCommands(ServerMixInfo& mix_info); + void GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, float volume, + s32 node_id); + void GenerateFinalMixCommand(); + void GenerateBiquadFilterCommand(s32 mix_buffer, const BiquadFilterParameter& params, + std::array& state, std::size_t input_offset, + std::size_t output_offset, s32 sample_count, s32 node_id); + void GenerateDepopPrepareCommand(VoiceState& dsp_state); + ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index); + + // DSP Code + s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, + s32 channel, std::size_t mix_offset); + s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, + s32 channel, std::size_t mix_offset); + void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state, + s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id); + void Resample(s32* output, s32* input, s32 pitch, s32& fraction, s32 sample_count); + + AudioCommon::AudioRendererParameter& worker_params; + VoiceContext& voice_context; + MixContext& mix_context; + SplitterContext& splitter_context; + Core::Memory::Memory& memory; + std::vector mix_buffer{}; + std::vector sample_buffer{}; + bool dumping_frame{false}; +}; +} // namespace AudioCore diff --git a/src/audio_core/common.h b/src/audio_core/common.h index 7bb145c53f..0731d3eb3e 100644 --- a/src/audio_core/common.h +++ b/src/audio_core/common.h @@ -8,13 +8,29 @@ #include "common/swap.h" #include "core/hle/result.h" -namespace AudioCore { +namespace AudioCommon { namespace Audren { constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41}; -} +constexpr ResultCode ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43}; +} // namespace Audren constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8'); constexpr std::size_t MAX_MIX_BUFFERS = 24; +constexpr std::size_t MAX_BIQUAD_FILTERS = 2; +constexpr std::size_t MAX_CHANNEL_COUNT = 6; +constexpr std::size_t MAX_WAVE_BUFFERS = 4; +constexpr std::size_t MAX_SAMPLE_HISTORY = 4; +constexpr u32 STREAM_SAMPLE_RATE = 48000; +constexpr u32 STREAM_NUM_CHANNELS = 6; +constexpr s32 NO_SPLITTER = -1; +constexpr s32 NO_MIX = 0x7fffffff; +constexpr s32 NO_FINAL_MIX = std::numeric_limits::min(); +constexpr s32 FINAL_MIX = 0; +constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant +// Any size checks seem to take the sample history into account +// and our const ends up being 0x3f04, the 4 bytes are most +// likely the sample history +constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY; static constexpr u32 VersionFromRevision(u32_le rev) { // "REV7" -> 7 @@ -45,4 +61,46 @@ static constexpr bool CanConsumeBuffer(std::size_t size, std::size_t offset, std return true; } -} // namespace AudioCore +struct UpdateDataSizes { + u32_le behavior{}; + u32_le memory_pool{}; + u32_le voice{}; + u32_le voice_channel_resource{}; + u32_le effect{}; + u32_le mixer{}; + u32_le sink{}; + u32_le performance{}; + u32_le splitter{}; + u32_le render_info{}; + INSERT_PADDING_WORDS(4); +}; +static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size"); + +struct UpdateDataHeader { + u32_le revision{}; + UpdateDataSizes size{}; + u32_le total_size{}; +}; +static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size"); + +struct AudioRendererParameter { + u32_le sample_rate; + u32_le sample_count; + u32_le mix_buffer_count; + u32_le submix_count; + u32_le voice_count; + u32_le sink_count; + u32_le effect_count; + u32_le performance_frame_count; + u8 is_voice_drop_enabled; + u8 unknown_21; + u8 unknown_22; + u8 execution_mode; + u32_le splitter_count; + u32_le num_splitter_send_channels; + u32_le unknown_30; + u32_le revision; +}; +static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size"); + +} // namespace AudioCommon diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index 41bf5cd4df..cbd6c56dac 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -23,14 +23,24 @@ class CubebSinkStream final : public SinkStream { public: CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, const std::string& name) - : ctx{ctx}, num_channels{std::min(num_channels_, 2u)}, time_stretch{sample_rate, + : ctx{ctx}, num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate, num_channels} { cubeb_stream_params params{}; params.rate = sample_rate; params.channels = num_channels; params.format = CUBEB_SAMPLE_S16NE; - params.layout = num_channels == 1 ? CUBEB_LAYOUT_MONO : CUBEB_LAYOUT_STEREO; + switch (num_channels) { + case 1: + params.layout = CUBEB_LAYOUT_MONO; + break; + case 2: + params.layout = CUBEB_LAYOUT_STEREO; + break; + case 6: + params.layout = CUBEB_LAYOUT_3F2_LFE; + break; + } u32 minimum_latency{}; if (cubeb_get_min_latency(ctx, ¶ms, &minimum_latency) != CUBEB_OK) { diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp new file mode 100644 index 0000000000..c42e71c1c7 --- /dev/null +++ b/src/audio_core/effect_context.cpp @@ -0,0 +1,39 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/effect_context.h" + +namespace AudioCore { +EffectContext::EffectContext(std::size_t effect_count) : effect_count(effect_count) { + for (std::size_t i = 0; i < effect_count; i++) { + effects.push_back(std::make_unique()); + } +} +EffectContext::~EffectContext() = default; + +std::size_t EffectContext::GetCount() const { + return effect_count; +} + +EffectBase* EffectContext::GetInfo(std::size_t i) { + return effects.at(i).get(); +} + +EffectStubbed::EffectStubbed() : EffectBase::EffectBase() {} +EffectStubbed::~EffectStubbed() = default; + +void EffectStubbed::Update(EffectInfo::InParams& in_params) { + if (in_params.is_new) { + usage = UsageStatus::New; + } +} + +EffectBase::EffectBase() = default; +EffectBase::~EffectBase() = default; + +UsageStatus EffectBase::GetUsage() const { + return usage; +} + +} // namespace AudioCore diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h new file mode 100644 index 0000000000..09aedf385a --- /dev/null +++ b/src/audio_core/effect_context.h @@ -0,0 +1,114 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include "audio_core/common.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace AudioCore { +enum class EffectType : u8 { + Invalid = 0, + BufferMixer = 1, + Aux = 2, + Delay = 3, + Reverb = 4, + I3dl2Reverb = 5, + BiquadFilter = 6, +}; + +enum class UsageStatus : u8 { + Invalid = 0, + New = 1, + Initialized = 2, + Used = 3, + Removed = 4, +}; + +struct BufferMixerParams { + std::array input{}; + std::array output{}; + std::array volume{}; + s32_le count{}; +}; +static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size"); + +struct AuxInfo { + std::array input_mix_buffers{}; + std::array output_mix_buffers{}; + u32_le count{}; + s32_le sample_rate{}; + s32_le sample_count{}; + s32_le mix_buffer_count{}; + u64_le send_buffer_info{}; + u64_le send_buffer_base{}; + + u64_le return_buffer_info{}; + u64_le return_buffer_base{}; +}; +static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size"); + +class EffectInfo { +public: + struct InParams { + EffectType type{}; + u8 is_new{}; + u8 is_enabled{}; + INSERT_PADDING_BYTES(1); + s32_le mix_id{}; + u64_le buffer_address{}; + u64_le buffer_size{}; + s32_le priority{}; + INSERT_PADDING_BYTES(4); + union { + std::array raw; + }; + }; + static_assert(sizeof(EffectInfo::InParams) == 0xc0, "InParams is an invalid size"); + + struct OutParams { + UsageStatus status{}; + INSERT_PADDING_BYTES(15); + }; + static_assert(sizeof(EffectInfo::OutParams) == 0x10, "OutParams is an invalid size"); +}; + +class EffectBase { +public: + EffectBase(); + ~EffectBase(); + + virtual void Update(EffectInfo::InParams& in_params) = 0; + UsageStatus GetUsage() const; + +protected: + UsageStatus usage{UsageStatus::Invalid}; +}; + +class EffectStubbed : public EffectBase { +public: + explicit EffectStubbed(); + ~EffectStubbed(); + + void Update(EffectInfo::InParams& in_params) override; +}; + +class EffectContext { +public: + explicit EffectContext(std::size_t effect_count); + ~EffectContext(); + + std::size_t GetCount() const; + EffectBase* GetInfo(std::size_t i); + +private: + std::size_t effect_count{}; + std::vector> effects; +}; +} // namespace AudioCore diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp new file mode 100644 index 0000000000..286aa03211 --- /dev/null +++ b/src/audio_core/info_updater.cpp @@ -0,0 +1,515 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/behavior_info.h" +#include "audio_core/effect_context.h" +#include "audio_core/info_updater.h" +#include "audio_core/memory_pool.h" +#include "audio_core/mix_context.h" +#include "audio_core/sink_context.h" +#include "audio_core/splitter_context.h" +#include "audio_core/voice_context.h" +#include "common/logging/log.h" + +namespace AudioCore { + +InfoUpdater::InfoUpdater(const std::vector& in_params, std::vector& out_params, + BehaviorInfo& behavior_info) + : in_params(in_params), out_params(out_params), behavior_info(behavior_info) { + ASSERT( + AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader))); + std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader)); + output_header.total_size = sizeof(AudioCommon::UpdateDataHeader); +} + +InfoUpdater::~InfoUpdater() = default; + +bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) { + if (input_header.size.behavior != sizeof(BehaviorInfo::InParams)) { + LOG_ERROR(Audio, "Behavior info is an invalid size, expecting 0x{:X} but got 0x{:X}", + sizeof(BehaviorInfo::InParams), input_header.size.behavior); + return false; + } + + if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, + sizeof(BehaviorInfo::InParams))) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + BehaviorInfo::InParams behavior_in{}; + std::memcpy(&behavior_in, in_params.data() + input_offset, sizeof(BehaviorInfo::InParams)); + input_offset += sizeof(BehaviorInfo::InParams); + + // Make sure it's an audio revision we can actually support + if (!AudioCommon::IsValidRevision(behavior_in.revision)) { + LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", behavior_in.revision); + return false; + } + + // Make sure that our behavior info revision matches the input + if (in_behavior_info.GetUserRevision() != behavior_in.revision) { + LOG_ERROR(Audio, + "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}", + in_behavior_info.GetUserRevision(), behavior_in.revision); + return false; + } + + // Update behavior info flags + in_behavior_info.ClearError(); + in_behavior_info.UpdateFlags(behavior_in.flags); + + return true; +} + +bool InfoUpdater::UpdateMemoryPools(std::vector& memory_pool_info) { + const auto force_mapping = behavior_info.IsMemoryPoolForceMappingEnabled(); + const auto memory_pool_count = memory_pool_info.size(); + const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count; + const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count; + + if (input_header.size.memory_pool != total_memory_pool_in) { + LOG_ERROR(Audio, "Memory pools are an invalid size, expecting 0x{:X} but got 0x{:X}", + total_memory_pool_in, input_header.size.memory_pool); + return false; + } + + if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_memory_pool_in)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + std::vector mempool_in(memory_pool_count); + std::vector mempool_out(memory_pool_count); + + std::memcpy(mempool_in.data(), in_params.data() + input_offset, total_memory_pool_in); + input_offset += total_memory_pool_in; + + // Update our memory pools + for (std::size_t i = 0; i < memory_pool_count; i++) { + if (!memory_pool_info[i].Update(mempool_in[i], mempool_out[i])) { + LOG_ERROR(Audio, "Failed to update memory pool {}!", i); + return false; + } + } + + if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, + sizeof(BehaviorInfo::InParams))) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + std::memcpy(out_params.data() + output_offset, mempool_out.data(), total_memory_pool_out); + output_offset += total_memory_pool_out; + output_header.size.memory_pool = static_cast(total_memory_pool_out); + return true; +} + +bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) { + const auto voice_count = voice_context.GetVoiceCount(); + const auto voice_size = voice_count * sizeof(VoiceChannelResource::InParams); + std::vector resources_in(voice_count); + + if (input_header.size.voice_channel_resource != voice_size) { + LOG_ERROR(Audio, "VoiceChannelResource is an invalid size, expecting 0x{:X} but got 0x{:X}", + voice_size, input_header.size.voice_channel_resource); + return false; + } + + if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_size)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + std::memcpy(resources_in.data(), in_params.data() + input_offset, voice_size); + input_offset += voice_size; + + // Update our channel resources + for (std::size_t i = 0; i < voice_count; i++) { + // Grab our channel resource + auto& resource = voice_context.GetChannelResource(i); + resource.Update(resources_in[i]); + } + + return true; +} + +bool InfoUpdater::UpdateVoices(VoiceContext& voice_context, + std::vector& memory_pool_info, + VAddr audio_codec_dsp_addr) { + const auto voice_count = voice_context.GetVoiceCount(); + std::vector voice_in(voice_count); + std::vector voice_out(voice_count); + + const auto voice_in_size = voice_count * sizeof(VoiceInfo::InParams); + const auto voice_out_size = voice_count * sizeof(VoiceInfo::OutParams); + + if (input_header.size.voice != voice_in_size) { + LOG_ERROR(Audio, "Voices are an invalid size, expecting 0x{:X} but got 0x{:X}", + voice_in_size, input_header.size.voice); + return false; + } + + if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_in_size)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + std::memcpy(voice_in.data(), in_params.data() + input_offset, voice_in_size); + input_offset += voice_in_size; + + // Set all voices to not be in use + for (std::size_t i = 0; i < voice_count; i++) { + voice_context.GetInfo(i).GetInParams().in_use = false; + } + + // Update our voices + for (std::size_t i = 0; i < voice_count; i++) { + auto& in_params = voice_in[i]; + const auto channel_count = static_cast(in_params.channel_count); + // Skip if it's not currently in use + if (!in_params.is_in_use) { + continue; + } + // Voice states for each channel + std::array voice_states{}; + ASSERT(in_params.id < voice_count); + + // Grab our current voice info + auto& voice_info = voice_context.GetInfo(static_cast(in_params.id)); + + ASSERT(channel_count <= AudioCommon::MAX_CHANNEL_COUNT); + + // Get all our channel voice states + for (std::size_t channel = 0; channel < channel_count; channel++) { + voice_states[channel] = + &voice_context.GetState(in_params.voice_channel_resource_ids[channel]); + } + + if (in_params.is_new) { + // Default our values for our voice + voice_info.Initialize(); + if (channel_count == 0 || channel_count > AudioCommon::MAX_CHANNEL_COUNT) { + continue; + } + + // Zero out our voice states + for (std::size_t channel = 0; channel < channel_count; channel++) { + std::memset(voice_states[channel], 0, sizeof(VoiceState)); + } + } + + // Update our voice + voice_info.UpdateParameters(in_params, behavior_info); + // TODO(ogniK): Handle mapping errors with behavior info based on in params response + + // Update our wave buffers + voice_info.UpdateWaveBuffers(in_params, voice_states, behavior_info); + voice_info.WriteOutStatus(voice_out[i], in_params, voice_states); + } + + if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, voice_out_size)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + std::memcpy(out_params.data() + output_offset, voice_out.data(), voice_out_size); + output_offset += voice_out_size; + output_header.size.voice = static_cast(voice_out_size); + return true; +} + +bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) { + const auto effect_count = effect_context.GetCount(); + std::vector effect_in(effect_count); + std::vector effect_out(effect_count); + + const auto total_effect_in = effect_count * sizeof(EffectInfo::InParams); + const auto total_effect_out = effect_count * sizeof(EffectInfo::OutParams); + + if (input_header.size.effect != total_effect_in) { + LOG_ERROR(Audio, "Effects are an invalid size, expecting 0x{:X} but got 0x{:X}", + total_effect_in, input_header.size.effect); + return false; + } + + if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_effect_in)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + std::memcpy(effect_in.data(), in_params.data() + input_offset, total_effect_in); + input_offset += total_effect_in; + + // Update effects + for (std::size_t i = 0; i < effect_count; i++) { + auto* info = effect_context.GetInfo(i); + info->Update(effect_in[i]); + + // TODO(ogniK): Update individual effects + if ((!is_active && info->GetUsage() != UsageStatus::New) || + info->GetUsage() == UsageStatus::Removed) { + effect_out[i].status = UsageStatus::Removed; + } else if (info->GetUsage() == UsageStatus::New) { + effect_out[i].status = UsageStatus::New; + } else { + effect_out[i].status = UsageStatus::Used; + } + } + + if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_effect_out)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + std::memcpy(out_params.data() + output_offset, effect_out.data(), total_effect_out); + output_offset += total_effect_out; + output_header.size.effect = static_cast(total_effect_out); + + return true; +} + +bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) { + std::size_t start_offset = input_offset; + std::size_t bytes_read{}; + // Update splitter context + if (!splitter_context.Update(in_params, input_offset, bytes_read)) { + LOG_ERROR(Audio, "Failed to update splitter context!"); + return false; + } + + const auto consumed = input_offset - start_offset; + + if (input_header.size.splitter != consumed) { + LOG_ERROR(Audio, "Splitters is an invalid size, expecting 0x{:X} but got 0x{:X}", + bytes_read, input_header.size.splitter); + return false; + } + + return true; +} + +ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count, + SplitterContext& splitter_context) { + std::vector mix_in_params; + + if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) { + // If we're not dirty, get ALL mix in parameters + const auto context_mix_count = mix_context.GetCount(); + const auto total_mix_in = context_mix_count * sizeof(MixInfo::InParams); + if (input_header.size.mixer != total_mix_in) { + LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}", + total_mix_in, input_header.size.mixer); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_mix_in)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + mix_in_params.resize(context_mix_count); + std::memcpy(mix_in_params.data(), in_params.data() + input_offset, total_mix_in); + + input_offset += total_mix_in; + } else { + // Only update the "dirty" mixes + MixInfo::DirtyHeader dirty_header{}; + if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, + sizeof(MixInfo::DirtyHeader))) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + std::memcpy(&dirty_header, in_params.data() + input_offset, sizeof(MixInfo::DirtyHeader)); + input_offset += sizeof(MixInfo::DirtyHeader); + + const auto total_mix_in = + dirty_header.mixer_count * sizeof(MixInfo::InParams) + sizeof(MixInfo::DirtyHeader); + + if (input_header.size.mixer != total_mix_in) { + LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}", + total_mix_in, input_header.size.mixer); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (dirty_header.mixer_count != 0) { + mix_in_params.resize(dirty_header.mixer_count); + std::memcpy(mix_in_params.data(), in_params.data() + input_offset, + mix_in_params.size() * sizeof(MixInfo::InParams)); + input_offset += mix_in_params.size() * sizeof(MixInfo::InParams); + } + } + + // Get our total input count + const auto mix_count = mix_in_params.size(); + + if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) { + // Only verify our buffer count if we're not dirty + std::size_t total_buffer_count{}; + for (std::size_t i = 0; i < mix_count; i++) { + const auto& in = mix_in_params[i]; + total_buffer_count += in.buffer_count; + if (in.dest_mix_id > mix_count && in.dest_mix_id != AudioCommon::NO_MIX && + in.mix_id != AudioCommon::FINAL_MIX) { + LOG_ERROR( + Audio, + "Invalid mix destination, mix_id={:X}, dest_mix_id={:X}, mix_buffer_count={:X}", + in.mix_id, in.dest_mix_id, mix_buffer_count); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + } + + if (total_buffer_count > mix_buffer_count) { + LOG_ERROR(Audio, + "Too many mix buffers used! mix_buffer_count={:X}, requesting_buffers={:X}", + mix_buffer_count, total_buffer_count); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + } + + if (mix_buffer_count == 0) { + LOG_ERROR(Audio, "No mix buffers!"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + bool should_sort = false; + for (std::size_t i = 0; i < mix_count; i++) { + const auto& mix_in = mix_in_params[i]; + std::size_t target_mix{}; + if (behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) { + target_mix = mix_in.mix_id; + } else { + // Non dirty supported games just use i instead of the actual mix_id + target_mix = i; + } + auto& mix_info = mix_context.GetInfo(target_mix); + auto& mix_info_params = mix_info.GetInParams(); + if (mix_info_params.in_use != mix_in.in_use) { + mix_info_params.in_use = mix_in.in_use; + // TODO(ogniK): Update effect processing order + should_sort = true; + } + + if (mix_in.in_use) { + should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info, + splitter_context); + } + } + + if (should_sort && behavior_info.IsSplitterSupported()) { + // Sort our splitter data + if (!mix_context.TsortInfo(splitter_context)) { + return AudioCommon::Audren::ERR_SPLITTER_SORT_FAILED; + } + } + + // TODO(ogniK): Sort when splitter is suppoorted + + return RESULT_SUCCESS; +} + +bool InfoUpdater::UpdateSinks(SinkContext& sink_context) { + const auto sink_count = sink_context.GetCount(); + std::vector sink_in_params(sink_count); + const auto total_sink_in = sink_count * sizeof(SinkInfo::InParams); + + if (input_header.size.sink != total_sink_in) { + LOG_ERROR(Audio, "Sinks are an invalid size, expecting 0x{:X} but got 0x{:X}", + total_sink_in, input_header.size.effect); + return false; + } + + if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_sink_in)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + std::memcpy(sink_in_params.data(), in_params.data() + input_offset, total_sink_in); + input_offset += total_sink_in; + + // TODO(ogniK): Properly update sinks + if (!sink_in_params.empty()) { + sink_context.UpdateMainSink(sink_in_params[0]); + } + + output_header.size.sink = static_cast(0x20 * sink_count); + output_offset += 0x20 * sink_count; + return true; +} + +bool InfoUpdater::UpdatePerformanceBuffer() { + output_header.size.performance = 0x10; + output_offset += 0x10; + return true; +} + +bool InfoUpdater::UpdateErrorInfo(BehaviorInfo& in_behavior_info) { + const auto total_beahvior_info_out = sizeof(BehaviorInfo::OutParams); + + if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_beahvior_info_out)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + BehaviorInfo::OutParams behavior_info_out{}; + behavior_info.CopyErrorInfo(behavior_info_out); + + std::memcpy(out_params.data() + output_offset, &behavior_info_out, total_beahvior_info_out); + output_offset += total_beahvior_info_out; + output_header.size.behavior = total_beahvior_info_out; + + return true; +} + +struct RendererInfo { + u64_le elasped_frame_count{}; + INSERT_PADDING_WORDS(2); +}; +static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size"); + +bool InfoUpdater::UpdateRendererInfo(std::size_t elapsed_frame_count) { + const auto total_renderer_info_out = sizeof(RendererInfo); + if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_renderer_info_out)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + RendererInfo out{}; + out.elasped_frame_count = elapsed_frame_count; + std::memcpy(out_params.data() + output_offset, &out, total_renderer_info_out); + output_offset += total_renderer_info_out; + output_header.size.render_info = total_renderer_info_out; + + return true; +} + +bool InfoUpdater::CheckConsumedSize() const { + if (output_offset != out_params.size()) { + LOG_ERROR(Audio, "Output is not consumed! Consumed {}, but requires {}. {} bytes remaining", + output_offset, out_params.size(), out_params.size() - output_offset); + return false; + } + /*if (input_offset != in_params.size()) { + LOG_ERROR(Audio, "Input is not consumed!"); + return false; + }*/ + return true; +} + +bool InfoUpdater::WriteOutputHeader() { + if (!AudioCommon::CanConsumeBuffer(out_params.size(), 0, + sizeof(AudioCommon::UpdateDataHeader))) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + output_header.revision = AudioCommon::CURRENT_PROCESS_REVISION; + const auto& sz = output_header.size; + output_header.total_size += sz.behavior + sz.memory_pool + sz.voice + + sz.voice_channel_resource + sz.effect + sz.mixer + sz.sink + + sz.performance + sz.splitter + sz.render_info; + + std::memcpy(out_params.data(), &output_header, sizeof(AudioCommon::UpdateDataHeader)); + return true; +} + +} // namespace AudioCore diff --git a/src/audio_core/info_updater.h b/src/audio_core/info_updater.h new file mode 100644 index 0000000000..6969de67dd --- /dev/null +++ b/src/audio_core/info_updater.h @@ -0,0 +1,58 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "audio_core/common.h" +#include "common/common_types.h" + +namespace AudioCore { + +class BehaviorInfo; +class ServerMemoryPoolInfo; +class VoiceContext; +class EffectContext; +class MixContext; +class SinkContext; +class SplitterContext; + +class InfoUpdater { +public: + // TODO(ogniK): Pass process handle when we support it + InfoUpdater(const std::vector& in_params, std::vector& out_params, + BehaviorInfo& behavior_info); + ~InfoUpdater(); + + bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info); + bool UpdateMemoryPools(std::vector& memory_pool_info); + bool UpdateVoiceChannelResources(VoiceContext& voice_context); + bool UpdateVoices(VoiceContext& voice_context, + std::vector& memory_pool_info, + VAddr audio_codec_dsp_addr); + bool UpdateEffects(EffectContext& effect_context, bool is_active); + bool UpdateSplitterInfo(SplitterContext& splitter_context); + ResultCode UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count, + SplitterContext& splitter_context); + bool UpdateSinks(SinkContext& sink_context); + bool UpdatePerformanceBuffer(); + bool UpdateErrorInfo(BehaviorInfo& in_behavior_info); + bool UpdateRendererInfo(std::size_t elapsed_frame_count); + bool CheckConsumedSize() const; + + bool WriteOutputHeader(); + +private: + const std::vector& in_params; + std::vector& out_params; + BehaviorInfo& behavior_info; + + AudioCommon::UpdateDataHeader input_header{}; + AudioCommon::UpdateDataHeader output_header{}; + + std::size_t input_offset{sizeof(AudioCommon::UpdateDataHeader)}; + std::size_t output_offset{sizeof(AudioCommon::UpdateDataHeader)}; +}; + +} // namespace AudioCore diff --git a/src/audio_core/memory_pool.cpp b/src/audio_core/memory_pool.cpp new file mode 100644 index 0000000000..5a3453063e --- /dev/null +++ b/src/audio_core/memory_pool.cpp @@ -0,0 +1,62 @@ + +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/memory_pool.h" +#include "common/logging/log.h" + +namespace AudioCore { + +ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default; +ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default; +bool ServerMemoryPoolInfo::Update(const ServerMemoryPoolInfo::InParams& in_params, + ServerMemoryPoolInfo::OutParams& out_params) { + // Our state does not need to be changed + if (in_params.state != ServerMemoryPoolInfo::State::RequestAttach && + in_params.state != ServerMemoryPoolInfo::State::RequestDetach) { + return true; + } + + // Address or size is null + if (in_params.address == 0 || in_params.size == 0) { + LOG_ERROR(Audio, "Memory pool address or size is zero! address={:X}, size={:X}", + in_params.address, in_params.size); + return false; + } + + // Address or size is not aligned + if ((in_params.address % 0x1000) != 0 || (in_params.size % 0x1000) != 0) { + LOG_ERROR(Audio, "Memory pool address or size is not aligned! address={:X}, size={:X}", + in_params.address, in_params.size); + return false; + } + + if (in_params.state == ServerMemoryPoolInfo::State::RequestAttach) { + cpu_address = in_params.address; + size = in_params.size; + used = true; + out_params.state = ServerMemoryPoolInfo::State::Attached; + } else { + // Unexpected address + if (cpu_address != in_params.address) { + LOG_ERROR(Audio, "Memory pool address differs! Expecting {:X} but address is {:X}", + cpu_address, in_params.address); + return false; + } + + if (size != in_params.size) { + LOG_ERROR(Audio, "Memory pool size differs! Expecting {:X} but size is {:X}", size, + in_params.size); + return false; + } + + cpu_address = 0; + size = 0; + used = false; + out_params.state = ServerMemoryPoolInfo::State::Detached; + } + return true; +} + +} // namespace AudioCore diff --git a/src/audio_core/memory_pool.h b/src/audio_core/memory_pool.h new file mode 100644 index 0000000000..8ac503f1cd --- /dev/null +++ b/src/audio_core/memory_pool.h @@ -0,0 +1,53 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace AudioCore { + +class ServerMemoryPoolInfo { +public: + ServerMemoryPoolInfo(); + ~ServerMemoryPoolInfo(); + + enum class State : u32_le { + Invalid = 0x0, + Aquired = 0x1, + RequestDetach = 0x2, + Detached = 0x3, + RequestAttach = 0x4, + Attached = 0x5, + Released = 0x6, + }; + + struct InParams { + u64_le address{}; + u64_le size{}; + ServerMemoryPoolInfo::State state{}; + INSERT_PADDING_WORDS(3); + }; + static_assert(sizeof(ServerMemoryPoolInfo::InParams) == 0x20, "InParams are an invalid size"); + + struct OutParams { + ServerMemoryPoolInfo::State state{}; + INSERT_PADDING_WORDS(3); + }; + static_assert(sizeof(ServerMemoryPoolInfo::OutParams) == 0x10, "OutParams are an invalid size"); + + bool Update(const ServerMemoryPoolInfo::InParams& in_params, + ServerMemoryPoolInfo::OutParams& out_params); + +private: + // There's another entry here which is the DSP address, however since we're not talking to the + // DSP we can just use the same address provided by the guest without needing to remap + u64_le cpu_address{}; + u64_le size{}; + bool used{}; +}; + +} // namespace AudioCore diff --git a/src/audio_core/mix_context.cpp b/src/audio_core/mix_context.cpp new file mode 100644 index 0000000000..8e150db03b --- /dev/null +++ b/src/audio_core/mix_context.cpp @@ -0,0 +1,264 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/behavior_info.h" +#include "audio_core/common.h" +#include "audio_core/mix_context.h" +#include "audio_core/splitter_context.h" + +namespace AudioCore { +MixContext::MixContext() = default; +MixContext::~MixContext() = default; + +void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count) { + info_count = mix_count; + infos.resize(info_count); + auto& final_mix = GetInfo(AudioCommon::FINAL_MIX); + final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX; + for (auto& info : infos) { + sorted_info.push_back(&info); + } + + // Only initialize our edge matrix and node states if splitters are supported + if (behavior_info.IsSplitterSupported()) { + node_states.Initialize(mix_count); + edge_matrix.Initialize(mix_count); + } +} + +void MixContext::UpdateDistancesFromFinalMix() { + // Set all distances to be invalid + for (std::size_t i = 0; i < info_count; i++) { + GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX; + } + + for (std::size_t i = 0; i < info_count; i++) { + auto& info = GetInfo(i); + auto& in_params = info.GetInParams(); + // Populate our sorted info + sorted_info[i] = &info; + + if (!in_params.in_use) { + continue; + } + + auto mix_id = in_params.mix_id; + // Needs to be referenced out of scope + s32 distance_to_final_mix{AudioCommon::FINAL_MIX}; + for (; distance_to_final_mix < info_count; distance_to_final_mix++) { + if (mix_id == AudioCommon::FINAL_MIX) { + // If we're at the final mix, we're done + break; + } else if (mix_id == AudioCommon::NO_MIX) { + // If we have no more mix ids, we're done + distance_to_final_mix = AudioCommon::NO_FINAL_MIX; + break; + } else { + const auto& dest_mix = GetInfo(mix_id); + const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance; + + if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) { + // If our current mix isn't pointing to a final mix, follow through + mix_id = dest_mix.GetInParams().dest_mix_id; + } else { + // Our current mix + 1 = final distance + distance_to_final_mix = dest_mix_distance + 1; + break; + } + } + } + + // If we're out of range for our distance, mark it as no final mix + if (distance_to_final_mix >= info_count) { + distance_to_final_mix = AudioCommon::NO_FINAL_MIX; + } + + in_params.final_mix_distance = distance_to_final_mix; + } +} + +void MixContext::CalcMixBufferOffset() { + s32 offset{}; + for (std::size_t i = 0; i < info_count; i++) { + auto& info = GetSortedInfo(i); + auto& in_params = info.GetInParams(); + if (in_params.in_use) { + // Only update if in use + in_params.buffer_offset = offset; + offset += in_params.buffer_count; + } + } +} + +void MixContext::SortInfo() { + // Get the distance to the final mix + UpdateDistancesFromFinalMix(); + + // Sort based on the distance to the final mix + std::sort(sorted_info.begin(), sorted_info.end(), + [](const ServerMixInfo* lhs, const ServerMixInfo* rhs) { + return lhs->GetInParams().final_mix_distance > + rhs->GetInParams().final_mix_distance; + }); + + // Calculate the mix buffer offset + CalcMixBufferOffset(); +} + +bool MixContext::TsortInfo(SplitterContext& splitter_context) { + // If we're not using mixes, just calculate the mix buffer offset + if (!splitter_context.UsingSplitter()) { + CalcMixBufferOffset(); + return true; + } + // Sort our node states + if (!node_states.Tsort(edge_matrix)) { + return false; + } + + // Get our sorted list + const auto sorted_list = node_states.GetIndexList(); + std::size_t info_id{}; + for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) { + // Set our sorted info + sorted_info[info_id++] = &GetInfo(*itr); + } + + // Calculate the mix buffer offset + CalcMixBufferOffset(); + return true; +} + +std::size_t MixContext::GetCount() const { + return info_count; +} + +ServerMixInfo& MixContext::GetInfo(std::size_t i) { + ASSERT(i < info_count); + return infos.at(i); +} + +const ServerMixInfo& MixContext::GetInfo(std::size_t i) const { + ASSERT(i < info_count); + return infos.at(i); +} + +ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) { + ASSERT(i < info_count); + return *sorted_info.at(i); +} + +const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const { + ASSERT(i < info_count); + return *sorted_info.at(i); +} + +ServerMixInfo& MixContext::GetFinalMixInfo() { + return infos.at(AudioCommon::FINAL_MIX); +} + +const ServerMixInfo& MixContext::GetFinalMixInfo() const { + return infos.at(AudioCommon::FINAL_MIX); +} + +EdgeMatrix& MixContext::GetEdgeMatrix() { + return edge_matrix; +} + +const EdgeMatrix& MixContext::GetEdgeMatrix() const { + return edge_matrix; +} + +ServerMixInfo::ServerMixInfo() { + Cleanup(); +} +ServerMixInfo::~ServerMixInfo() = default; + +const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const { + return in_params; +} + +ServerMixInfo::InParams& ServerMixInfo::GetInParams() { + return in_params; +} + +bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, + BehaviorInfo& behavior_info, SplitterContext& splitter_context) { + in_params.volume = mix_in.volume; + in_params.sample_rate = mix_in.sample_rate; + in_params.buffer_count = mix_in.buffer_count; + in_params.in_use = mix_in.in_use; + in_params.mix_id = mix_in.mix_id; + in_params.node_id = mix_in.node_id; + for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) { + std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(), + in_params.mix_volume[i].begin()); + } + + bool require_sort = false; + + if (behavior_info.IsSplitterSupported()) { + require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context); + } else { + in_params.dest_mix_id = mix_in.dest_mix_id; + in_params.splitter_id = AudioCommon::NO_SPLITTER; + } + + // TODO(ogniK): Update effect processing order + return require_sort; +} + +bool ServerMixInfo::HasAnyConnection() const { + return in_params.splitter_id != AudioCommon::NO_SPLITTER || + in_params.mix_id != AudioCommon::NO_MIX; +} + +void ServerMixInfo::Cleanup() { + in_params.volume = 0.0f; + in_params.sample_rate = 0; + in_params.buffer_count = 0; + in_params.in_use = false; + in_params.mix_id = AudioCommon::NO_MIX; + in_params.node_id = 0; + in_params.buffer_offset = 0; + in_params.dest_mix_id = AudioCommon::NO_MIX; + in_params.splitter_id = AudioCommon::NO_SPLITTER; + std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size()); +} + +bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, + SplitterContext& splitter_context) { + // Mixes are identical + if (in_params.dest_mix_id == mix_in.dest_mix_id && + in_params.splitter_id == mix_in.splitter_id && + ((in_params.splitter_id == AudioCommon::NO_SPLITTER) || + !splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) { + return false; + } + // Remove current edges for mix id + edge_matrix.RemoveEdges(in_params.mix_id); + if (mix_in.dest_mix_id != AudioCommon::NO_MIX) { + // If we have a valid destination mix id, set our edge matrix + edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id); + } else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) { + // Recurse our splitter linked and set our edges + auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id); + const auto length = splitter_info.GetLength(); + for (s32 i = 0; i < length; i++) { + const auto* splitter_destination = + splitter_context.GetDestinationData(mix_in.splitter_id, i); + if (splitter_destination == nullptr) { + continue; + } + if (splitter_destination->ValidMixId()) { + edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId()); + } + } + } + in_params.dest_mix_id = mix_in.dest_mix_id; + in_params.splitter_id = mix_in.splitter_id; + return true; +} + +} // namespace AudioCore diff --git a/src/audio_core/mix_context.h b/src/audio_core/mix_context.h new file mode 100644 index 0000000000..3815666999 --- /dev/null +++ b/src/audio_core/mix_context.h @@ -0,0 +1,107 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "audio_core/common.h" +#include "audio_core/splitter_context.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace AudioCore { +class BehaviorInfo; + +class MixInfo { +public: + struct DirtyHeader { + u32_le magic{}; + u32_le mixer_count{}; + INSERT_PADDING_BYTES(0x18); + }; + static_assert(sizeof(DirtyHeader) == 0x20, "MixInfo::DirtyHeader is an invalid size"); + + struct InParams { + float_le volume{}; + s32_le sample_rate{}; + s32_le buffer_count{}; + bool in_use{}; + INSERT_PADDING_BYTES(3); + s32_le mix_id{}; + s32_le effect_count{}; + u32_le node_id{}; + INSERT_PADDING_WORDS(2); + std::array, AudioCommon::MAX_MIX_BUFFERS> + mix_volume{}; + s32_le dest_mix_id{}; + s32_le splitter_id{}; + INSERT_PADDING_WORDS(1); + }; + static_assert(sizeof(MixInfo::InParams) == 0x930, "MixInfo::InParams is an invalid size"); +}; + +class ServerMixInfo { +public: + struct InParams { + float volume{}; + s32 sample_rate{}; + s32 buffer_count{}; + bool in_use{}; + s32 mix_id{}; + u32 node_id{}; + std::array, AudioCommon::MAX_MIX_BUFFERS> + mix_volume{}; + s32 dest_mix_id{}; + s32 splitter_id{}; + s32 buffer_offset{}; + s32 final_mix_distance{}; + }; + ServerMixInfo(); + ~ServerMixInfo(); + + const ServerMixInfo::InParams& GetInParams() const; + ServerMixInfo::InParams& GetInParams(); + + bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, + BehaviorInfo& behavior_info, SplitterContext& splitter_context); + bool HasAnyConnection() const; + void Cleanup(); + +private: + InParams in_params{}; + bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, + SplitterContext& splitter_context); +}; + +class MixContext { +public: + MixContext(); + ~MixContext(); + + void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count); + void SortInfo(); + bool TsortInfo(SplitterContext& splitter_context); + + std::size_t GetCount() const; + ServerMixInfo& GetInfo(std::size_t i); + const ServerMixInfo& GetInfo(std::size_t i) const; + ServerMixInfo& GetSortedInfo(std::size_t i); + const ServerMixInfo& GetSortedInfo(std::size_t i) const; + ServerMixInfo& GetFinalMixInfo(); + const ServerMixInfo& GetFinalMixInfo() const; + EdgeMatrix& GetEdgeMatrix(); + const EdgeMatrix& GetEdgeMatrix() const; + +private: + void CalcMixBufferOffset(); + void UpdateDistancesFromFinalMix(); + + NodeStates node_states{}; + EdgeMatrix edge_matrix{}; + std::size_t info_count{}; + std::vector infos{}; + std::vector sorted_info{}; +}; +} // namespace AudioCore diff --git a/src/audio_core/sink_context.cpp b/src/audio_core/sink_context.cpp new file mode 100644 index 0000000000..0882b411af --- /dev/null +++ b/src/audio_core/sink_context.cpp @@ -0,0 +1,31 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/sink_context.h" + +namespace AudioCore { +SinkContext::SinkContext(std::size_t sink_count) : sink_count(sink_count) {} +SinkContext::~SinkContext() = default; + +std::size_t SinkContext::GetCount() const { + return sink_count; +} + +void SinkContext::UpdateMainSink(SinkInfo::InParams& in) { + in_use = in.in_use; + use_count = in.device.input_count; + std::memcpy(buffers.data(), in.device.input.data(), AudioCommon::MAX_CHANNEL_COUNT); +} + +bool SinkContext::InUse() const { + return in_use; +} + +std::vector SinkContext::OutputBuffers() const { + std::vector buffer_ret(use_count); + std::memcpy(buffer_ret.data(), buffers.data(), use_count); + return buffer_ret; +} + +} // namespace AudioCore diff --git a/src/audio_core/sink_context.h b/src/audio_core/sink_context.h new file mode 100644 index 0000000000..d7aa72ba71 --- /dev/null +++ b/src/audio_core/sink_context.h @@ -0,0 +1,89 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "audio_core/common.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace AudioCore { + +enum class SinkTypes : u8 { + Invalid = 0, + Device = 1, + Circular = 2, +}; + +enum class SinkSampleFormat : u32_le { + None = 0, + Pcm8 = 1, + Pcm16 = 2, + Pcm24 = 3, + Pcm32 = 4, + PcmFloat = 5, + Adpcm = 6, +}; + +class SinkInfo { +public: + struct CircularBufferIn { + u64_le address; + u32_le size; + u32_le input_count; + u32_le sample_count; + u32_le previous_position; + SinkSampleFormat sample_format; + std::array input; + bool in_use; + INSERT_UNION_PADDING_BYTES(5); + }; + static_assert(sizeof(SinkInfo::CircularBufferIn) == 0x28, + "SinkInfo::CircularBufferIn is in invalid size"); + + struct DeviceIn { + std::array device_name; + INSERT_UNION_PADDING_BYTES(1); + s32_le input_count; + std::array input; + INSERT_UNION_PADDING_BYTES(1); + bool down_matrix_enabled; + std::array down_matrix_coef; + }; + static_assert(sizeof(SinkInfo::DeviceIn) == 0x11c, "SinkInfo::DeviceIn is an invalid size"); + + struct InParams { + SinkTypes type{}; + bool in_use{}; + INSERT_PADDING_BYTES(2); + u32_le node_id{}; + INSERT_PADDING_WORDS(6); + union { + // std::array raw{}; + SinkInfo::DeviceIn device; + SinkInfo::CircularBufferIn circular_buffer; + }; + }; + static_assert(sizeof(SinkInfo::InParams) == 0x140, "SinkInfo::InParams are an invalid size!"); +}; + +class SinkContext { +public: + explicit SinkContext(std::size_t sink_count); + ~SinkContext(); + + std::size_t GetCount() const; + + void UpdateMainSink(SinkInfo::InParams& in); + bool InUse() const; + std::vector OutputBuffers() const; + +private: + bool in_use{false}; + s32 use_count{}; + std::array buffers{}; + std::size_t sink_count{}; +}; +} // namespace AudioCore diff --git a/src/audio_core/splitter_context.cpp b/src/audio_core/splitter_context.cpp new file mode 100644 index 0000000000..c0be26be12 --- /dev/null +++ b/src/audio_core/splitter_context.cpp @@ -0,0 +1,617 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/behavior_info.h" +#include "audio_core/splitter_context.h" +#include "common/alignment.h" +#include "common/assert.h" +#include "common/logging/log.h" + +namespace AudioCore { + +ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id) : id(id) {} +ServerSplitterDestinationData::~ServerSplitterDestinationData() = default; + +void ServerSplitterDestinationData::Update(SplitterInfo::InDestinationParams& header) { + // Log error as these are not actually failure states + if (header.magic != SplitterMagic::DataHeader) { + LOG_ERROR(Audio, "Splitter destination header is invalid!"); + return; + } + + // Incorrect splitter id + if (header.splitter_id != id) { + LOG_ERROR(Audio, "Splitter destination ids do not match!"); + return; + } + + mix_id = header.mix_id; + // Copy our mix volumes + std::copy(header.mix_volumes.begin(), header.mix_volumes.end(), current_mix_volumes.begin()); + if (!in_use && header.in_use) { + // Update mix volumes + std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin()); + needs_update = false; + } + in_use = header.in_use; +} + +ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() { + return next; +} + +const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const { + return next; +} + +void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) { + next = dest; +} + +bool ServerSplitterDestinationData::ValidMixId() const { + return GetMixId() != AudioCommon::NO_MIX; +} + +s32 ServerSplitterDestinationData::GetMixId() const { + return mix_id; +} + +bool ServerSplitterDestinationData::IsConfigured() const { + return in_use && ValidMixId(); +} + +float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const { + ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); + return current_mix_volumes.at(i); +} + +const std::array& +ServerSplitterDestinationData::CurrentMixVolumes() const { + return current_mix_volumes; +} + +const std::array& +ServerSplitterDestinationData::LastMixVolumes() const { + return last_mix_volumes; +} + +void ServerSplitterDestinationData::MarkDirty() { + needs_update = true; +} + +void ServerSplitterDestinationData::UpdateInternalState() { + if (in_use && needs_update) { + std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin()); + } + needs_update = false; +} + +ServerSplitterInfo::ServerSplitterInfo(s32 id) : id(id) {} +ServerSplitterInfo::~ServerSplitterInfo() = default; + +void ServerSplitterInfo::InitializeInfos() { + send_length = 0; + head = nullptr; + new_connection = true; +} + +void ServerSplitterInfo::ClearNewConnectionFlag() { + new_connection = false; +} + +std::size_t ServerSplitterInfo::Update(SplitterInfo::InInfoPrams& header) { + if (header.send_id != id) { + return 0; + } + + sample_rate = header.sample_rate; + new_connection = true; + // We need to update the size here due to the splitter bug being present and providing an + // incorrect size. We're suppose to also update the header here but we just ignore and continue + return (sizeof(s32_le) * (header.length - 1)) + (sizeof(s32_le) * 3); +} + +ServerSplitterDestinationData* ServerSplitterInfo::GetHead() { + return head; +} + +const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const { + return head; +} + +ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) { + auto current_head = head; + for (std::size_t i = 0; i < depth; i++) { + if (current_head == nullptr) { + return nullptr; + } + current_head = current_head->GetNextDestination(); + } + return current_head; +} + +const ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) const { + auto current_head = head; + for (std::size_t i = 0; i < depth; i++) { + if (current_head == nullptr) { + return nullptr; + } + current_head = current_head->GetNextDestination(); + } + return current_head; +} + +bool ServerSplitterInfo::HasNewConnection() const { + return new_connection; +} + +s32 ServerSplitterInfo::GetLength() const { + return send_length; +} + +void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) { + head = new_head; +} + +void ServerSplitterInfo::SetHeadDepth(s32 length) { + send_length = length; +} + +SplitterContext::SplitterContext() = default; +SplitterContext::~SplitterContext() = default; + +void SplitterContext::Initialize(BehaviorInfo& behavior_info, std::size_t _info_count, + std::size_t _data_count) { + if (!behavior_info.IsSplitterSupported() || _data_count == 0 || _info_count == 0) { + Setup(0, 0, false); + return; + } + // Only initialize if we're using splitters + Setup(_info_count, _data_count, behavior_info.IsSplitterBugFixed()); +} + +bool SplitterContext::Update(const std::vector& input, std::size_t& input_offset, + std::size_t& bytes_read) { + auto UpdateOffsets = [&](std::size_t read) { + input_offset += read; + bytes_read += read; + }; + + if (info_count == 0 || data_count == 0) { + bytes_read = 0; + return true; + } + + if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, + sizeof(SplitterInfo::InHeader))) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + SplitterInfo::InHeader header{}; + std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InHeader)); + UpdateOffsets(sizeof(SplitterInfo::InHeader)); + + if (header.magic != SplitterMagic::SplitterHeader) { + LOG_ERROR(Audio, "Invalid header magic! Expecting {:X} but got {:X}", + SplitterMagic::SplitterHeader, header.magic); + return false; + } + + // Clear all connections + for (auto& info : infos) { + info.ClearNewConnectionFlag(); + } + + UpdateInfo(input, input_offset, bytes_read, header.info_count); + UpdateData(input, input_offset, bytes_read, header.data_count); + const auto aligned_bytes_read = Common::AlignUp(bytes_read, 16); + input_offset += aligned_bytes_read - bytes_read; + bytes_read = aligned_bytes_read; + return true; +} + +bool SplitterContext::UsingSplitter() const { + return info_count > 0 && data_count > 0; +} + +ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) { + ASSERT(i < info_count); + return infos.at(i); +} + +const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const { + ASSERT(i < info_count); + return infos.at(i); +} + +ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) { + ASSERT(i < data_count); + return datas.at(i); +} + +const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const { + ASSERT(i < data_count); + return datas.at(i); +} + +ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info, + std::size_t data) { + ASSERT(info < info_count); + auto& cur_info = GetInfo(info); + return cur_info.GetData(data); +} + +const ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info, + std::size_t data) const { + ASSERT(info < info_count); + auto& cur_info = GetInfo(info); + return cur_info.GetData(data); +} + +void SplitterContext::UpdateInternalState() { + if (data_count == 0) { + return; + } + + for (auto& data : datas) { + data.UpdateInternalState(); + } +} + +std::size_t SplitterContext::GetInfoCount() const { + return info_count; +} + +std::size_t SplitterContext::GetDataCount() const { + return data_count; +} + +void SplitterContext::Setup(std::size_t _info_count, std::size_t _data_count, + bool is_splitter_bug_fixed) { + + info_count = _info_count; + data_count = _data_count; + + for (std::size_t i = 0; i < info_count; i++) { + auto& splitter = infos.emplace_back(static_cast(i)); + splitter.InitializeInfos(); + } + for (std::size_t i = 0; i < data_count; i++) { + datas.emplace_back(static_cast(i)); + } + + bug_fixed = is_splitter_bug_fixed; +} + +bool SplitterContext::UpdateInfo(const std::vector& input, std::size_t& input_offset, + std::size_t& bytes_read, s32 in_splitter_count) { + auto UpdateOffsets = [&](std::size_t read) { + input_offset += read; + bytes_read += read; + }; + + for (s32 i = 0; i < in_splitter_count; i++) { + if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, + sizeof(SplitterInfo::InInfoPrams))) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + SplitterInfo::InInfoPrams header{}; + std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InInfoPrams)); + + // Logged as warning as these don't actually cause a bailout for some reason + if (header.magic != SplitterMagic::InfoHeader) { + LOG_ERROR(Audio, "Bad splitter data header"); + break; + } + + if (header.send_id < 0 || header.send_id > info_count) { + LOG_ERROR(Audio, "Bad splitter data id"); + break; + } + + UpdateOffsets(sizeof(SplitterInfo::InInfoPrams)); + auto& info = GetInfo(header.send_id); + if (!RecomposeDestination(info, header, input, input_offset)) { + LOG_ERROR(Audio, "Failed to recompose destination for splitter!"); + return false; + } + const std::size_t read = info.Update(header); + bytes_read += read; + input_offset += read; + } + return true; +} + +bool SplitterContext::UpdateData(const std::vector& input, std::size_t& input_offset, + std::size_t& bytes_read, s32 in_data_count) { + auto UpdateOffsets = [&](std::size_t read) { + input_offset += read; + bytes_read += read; + }; + + for (s32 i = 0; i < in_data_count; i++) { + if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, + sizeof(SplitterInfo::InDestinationParams))) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + SplitterInfo::InDestinationParams header{}; + std::memcpy(&header, input.data() + input_offset, + sizeof(SplitterInfo::InDestinationParams)); + UpdateOffsets(sizeof(SplitterInfo::InDestinationParams)); + + // Logged as warning as these don't actually cause a bailout for some reason + if (header.magic != SplitterMagic::DataHeader) { + LOG_ERROR(Audio, "Bad splitter data header"); + break; + } + + if (header.splitter_id < 0 || header.splitter_id > data_count) { + LOG_ERROR(Audio, "Bad splitter data id"); + break; + } + GetData(header.splitter_id).Update(header); + } + return true; +} + +bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info, + SplitterInfo::InInfoPrams& header, + const std::vector& input, + const std::size_t& input_offset) { + // Clear our current destinations + auto* current_head = info.GetHead(); + while (current_head != nullptr) { + auto next_head = current_head->GetNextDestination(); + current_head->SetNextDestination(nullptr); + current_head = next_head; + } + info.SetHead(nullptr); + + s32 size = header.length; + // If the splitter bug is present, calculate fixed size + if (!bug_fixed) { + if (info_count > 0) { + const auto factor = data_count / info_count; + size = std::min(header.length, static_cast(factor)); + } else { + size = 0; + } + } + + if (size < 1) { + LOG_ERROR(Audio, "Invalid splitter info size! size={:X}", size); + return true; + } + + auto* start_head = &GetData(header.resource_id_base); + current_head = start_head; + std::vector resource_ids(size - 1); + if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, + resource_ids.size() * sizeof(s32_le))) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + std::memcpy(resource_ids.data(), input.data() + input_offset, + resource_ids.size() * sizeof(s32_le)); + + for (auto resource_id : resource_ids) { + auto* head = &GetData(resource_id); + current_head->SetNextDestination(head); + current_head = head; + } + + info.SetHead(start_head); + info.SetHeadDepth(size); + + return true; +} + +NodeStates::NodeStates() = default; +NodeStates::~NodeStates() = default; + +void NodeStates::Initialize(std::size_t _node_count) { + // Setup our work parameters + node_count = _node_count; + was_node_found.resize(node_count); + was_node_completed.resize(node_count); + index_list.resize(node_count); + index_stack.Reset(node_count * node_count); +} + +bool NodeStates::Tsort(EdgeMatrix& edge_matrix) { + return DepthFirstSearch(edge_matrix); +} + +std::size_t NodeStates::GetIndexPos() const { + return index_pos; +} + +const std::vector& NodeStates::GetIndexList() const { + return index_list; +} + +void NodeStates::PushTsortResult(s32 index) { + ASSERT(index < node_count); + index_list[index_pos++] = index; +} + +bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) { + ResetState(); + for (std::size_t i = 0; i < node_count; i++) { + const auto node_id = static_cast(i); + + // If we don't have a state, send to our index stack for work + if (GetState(i) == NodeStates::State::NoState) { + index_stack.push(node_id); + } + + // While we have work to do in our stack + while (index_stack.Count() > 0) { + // Get the current node + const auto current_stack_index = index_stack.top(); + // Check if we've seen the node yet + const auto index_state = GetState(current_stack_index); + if (index_state == NodeStates::State::NoState) { + // Mark the node as seen + UpdateState(NodeStates::State::InFound, current_stack_index); + } else if (index_state == NodeStates::State::InFound) { + // We've seen this node before, mark it as completed + UpdateState(NodeStates::State::InCompleted, current_stack_index); + // Update our index list + PushTsortResult(current_stack_index); + // Pop the stack + index_stack.pop(); + continue; + } else if (index_state == NodeStates::State::InCompleted) { + // If our node is already sorted, clear it + index_stack.pop(); + continue; + } + + const auto node_count = edge_matrix.GetNodeCount(); + for (s32 j = 0; j < static_cast(node_count); j++) { + // Check if our node is connected to our edge matrix + if (!edge_matrix.Connected(current_stack_index, j)) { + continue; + } + + // Check if our node exists + const auto node_state = GetState(j); + if (node_state == NodeStates::State::NoState) { + // Add more work + index_stack.push(j); + } else if (node_state == NodeStates::State::InFound) { + UNREACHABLE_MSG("Node start marked as found"); + ResetState(); + return false; + } + } + } + } + return true; +} + +void NodeStates::ResetState() { + // Reset to the start of our index stack + index_pos = 0; + for (std::size_t i = 0; i < node_count; i++) { + // Mark all nodes as not found + was_node_found[i] = false; + // Mark all nodes as uncompleted + was_node_completed[i] = false; + // Mark all indexes as invalid + index_list[i] = -1; + } +} + +void NodeStates::UpdateState(NodeStates::State state, std::size_t i) { + switch (state) { + case NodeStates::State::NoState: + was_node_found[i] = false; + was_node_completed[i] = false; + break; + case NodeStates::State::InFound: + was_node_found[i] = true; + was_node_completed[i] = false; + break; + case NodeStates::State::InCompleted: + was_node_found[i] = false; + was_node_completed[i] = true; + break; + } +} + +NodeStates::State NodeStates::GetState(std::size_t i) { + ASSERT(i < node_count); + if (was_node_found[i]) { + // If our node exists in our found list + return NodeStates::State::InFound; + } else if (was_node_completed[i]) { + // If node is in the completed list + return NodeStates::State::InCompleted; + } else { + // If in neither + return NodeStates::State::NoState; + } +} + +NodeStates::Stack::Stack() = default; +NodeStates::Stack::~Stack() = default; + +void NodeStates::Stack::Reset(std::size_t size) { + // Mark our stack as empty + stack.resize(size); + stack_size = size; + stack_pos = 0; + std::fill(stack.begin(), stack.end(), 0); +} + +void NodeStates::Stack::push(s32 val) { + ASSERT(stack_pos < stack_size); + stack[stack_pos++] = val; +} + +std::size_t NodeStates::Stack::Count() const { + return stack_pos; +} + +s32 NodeStates::Stack::top() const { + ASSERT(stack_pos > 0); + return stack[stack_pos - 1]; +} + +s32 NodeStates::Stack::pop() { + ASSERT(stack_pos > 0); + stack_pos--; + return stack[stack_pos]; +} + +EdgeMatrix::EdgeMatrix() = default; +EdgeMatrix::~EdgeMatrix() = default; + +void EdgeMatrix::Initialize(std::size_t _node_count) { + node_count = _node_count; + edge_matrix.resize(node_count * node_count); +} + +bool EdgeMatrix::Connected(s32 a, s32 b) { + return GetState(a, b); +} + +void EdgeMatrix::Connect(s32 a, s32 b) { + SetState(a, b, true); +} + +void EdgeMatrix::Disconnect(s32 a, s32 b) { + SetState(a, b, false); +} + +void EdgeMatrix::RemoveEdges(s32 edge) { + for (std::size_t i = 0; i < node_count; i++) { + SetState(edge, static_cast(i), false); + } +} + +std::size_t EdgeMatrix::GetNodeCount() const { + return node_count; +} + +void EdgeMatrix::SetState(s32 a, s32 b, bool state) { + ASSERT(InRange(a, b)); + edge_matrix.at(a * node_count + b) = state; +} + +bool EdgeMatrix::GetState(s32 a, s32 b) { + ASSERT(InRange(a, b)); + return edge_matrix.at(a * node_count + b); +} + +bool EdgeMatrix::InRange(s32 a, s32 b) const { + const std::size_t pos = a * node_count + b; + return pos < (node_count * node_count); +} + +} // namespace AudioCore diff --git a/src/audio_core/splitter_context.h b/src/audio_core/splitter_context.h new file mode 100644 index 0000000000..ea6239fdb1 --- /dev/null +++ b/src/audio_core/splitter_context.h @@ -0,0 +1,221 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "audio_core/common.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace AudioCore { +class BehaviorInfo; + +class EdgeMatrix { +public: + EdgeMatrix(); + ~EdgeMatrix(); + + void Initialize(std::size_t _node_count); + bool Connected(s32 a, s32 b); + void Connect(s32 a, s32 b); + void Disconnect(s32 a, s32 b); + void RemoveEdges(s32 edge); + std::size_t GetNodeCount() const; + +private: + void SetState(s32 a, s32 b, bool state); + bool GetState(s32 a, s32 b); + + bool InRange(s32 a, s32 b) const; + std::vector edge_matrix{}; + std::size_t node_count{}; +}; + +class NodeStates { +public: + enum class State { + NoState = 0, + InFound = 1, + InCompleted = 2, + }; + + // Looks to be a fixed size stack. Placed within the NodeStates class based on symbols + class Stack { + public: + Stack(); + ~Stack(); + + void Reset(std::size_t size); + void push(s32 val); + std::size_t Count() const; + s32 top() const; + s32 pop(); + + private: + std::vector stack{}; + std::size_t stack_size{}; + std::size_t stack_pos{}; + }; + NodeStates(); + ~NodeStates(); + + void Initialize(std::size_t _node_count); + bool Tsort(EdgeMatrix& edge_matrix); + std::size_t GetIndexPos() const; + const std::vector& GetIndexList() const; + +private: + void PushTsortResult(s32 index); + bool DepthFirstSearch(EdgeMatrix& edge_matrix); + void ResetState(); + void UpdateState(NodeStates::State state, std::size_t i); + NodeStates::State GetState(std::size_t i); + + std::size_t node_count{}; + std::vector was_node_found{}; + std::vector was_node_completed{}; + std::size_t index_pos{}; + std::vector index_list{}; + NodeStates::Stack index_stack{}; +}; + +enum class SplitterMagic : u32_le { + SplitterHeader = Common::MakeMagic('S', 'N', 'D', 'H'), + DataHeader = Common::MakeMagic('S', 'N', 'D', 'D'), + InfoHeader = Common::MakeMagic('S', 'N', 'D', 'I'), +}; + +class SplitterInfo { +public: + struct InHeader { + SplitterMagic magic{}; + s32_le info_count{}; + s32_le data_count{}; + INSERT_PADDING_WORDS(5); + }; + static_assert(sizeof(SplitterInfo::InHeader) == 0x20, + "SplitterInfo::InHeader is an invalid size"); + + struct InInfoPrams { + SplitterMagic magic{}; + s32_le send_id{}; + s32_le sample_rate{}; + s32_le length{}; + s32_le resource_id_base{}; + }; + static_assert(sizeof(SplitterInfo::InInfoPrams) == 0x14, + "SplitterInfo::InInfoPrams is an invalid size"); + + struct InDestinationParams { + SplitterMagic magic{}; + s32_le splitter_id{}; + std::array mix_volumes{}; + s32_le mix_id{}; + bool in_use{}; + INSERT_PADDING_BYTES(3); + }; + static_assert(sizeof(SplitterInfo::InDestinationParams) == 0x70, + "SplitterInfo::InDestinationParams is an invalid size"); +}; + +class ServerSplitterDestinationData { +public: + explicit ServerSplitterDestinationData(s32 id); + ~ServerSplitterDestinationData(); + + void Update(SplitterInfo::InDestinationParams& header); + + ServerSplitterDestinationData* GetNextDestination(); + const ServerSplitterDestinationData* GetNextDestination() const; + void SetNextDestination(ServerSplitterDestinationData* dest); + bool ValidMixId() const; + s32 GetMixId() const; + bool IsConfigured() const; + float GetMixVolume(std::size_t i) const; + const std::array& CurrentMixVolumes() const; + const std::array& LastMixVolumes() const; + void MarkDirty(); + void UpdateInternalState(); + +private: + bool needs_update{}; + bool in_use{}; + s32 id{}; + s32 mix_id{}; + std::array current_mix_volumes{}; + std::array last_mix_volumes{}; + ServerSplitterDestinationData* next = nullptr; +}; + +class ServerSplitterInfo { +public: + explicit ServerSplitterInfo(s32 id); + ~ServerSplitterInfo(); + + void InitializeInfos(); + void ClearNewConnectionFlag(); + std::size_t Update(SplitterInfo::InInfoPrams& header); + + ServerSplitterDestinationData* GetHead(); + const ServerSplitterDestinationData* GetHead() const; + ServerSplitterDestinationData* GetData(std::size_t depth); + const ServerSplitterDestinationData* GetData(std::size_t depth) const; + + bool HasNewConnection() const; + s32 GetLength() const; + + void SetHead(ServerSplitterDestinationData* new_head); + void SetHeadDepth(s32 length); + +private: + s32 sample_rate{}; + s32 id{}; + s32 send_length{}; + ServerSplitterDestinationData* head = nullptr; + bool new_connection{}; +}; + +class SplitterContext { +public: + SplitterContext(); + ~SplitterContext(); + + void Initialize(BehaviorInfo& behavior_info, std::size_t splitter_count, + std::size_t data_count); + + bool Update(const std::vector& input, std::size_t& input_offset, std::size_t& bytes_read); + bool UsingSplitter() const; + + ServerSplitterInfo& GetInfo(std::size_t i); + const ServerSplitterInfo& GetInfo(std::size_t i) const; + ServerSplitterDestinationData& GetData(std::size_t i); + const ServerSplitterDestinationData& GetData(std::size_t i) const; + ServerSplitterDestinationData* GetDestinationData(std::size_t info, std::size_t data); + const ServerSplitterDestinationData* GetDestinationData(std::size_t info, + std::size_t data) const; + void UpdateInternalState(); + + std::size_t GetInfoCount() const; + std::size_t GetDataCount() const; + +private: + void Setup(std::size_t info_count, std::size_t data_count, bool is_splitter_bug_fixed); + bool UpdateInfo(const std::vector& input, std::size_t& input_offset, + std::size_t& bytes_read, s32 in_splitter_count); + bool UpdateData(const std::vector& input, std::size_t& input_offset, + std::size_t& bytes_read, s32 in_data_count); + bool RecomposeDestination(ServerSplitterInfo& info, SplitterInfo::InInfoPrams& header, + const std::vector& input, const std::size_t& input_offset); + + std::vector infos{}; + std::vector datas{}; + + std::size_t info_count{}; + std::size_t data_count{}; + bool bug_fixed{}; +}; +} // namespace AudioCore diff --git a/src/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp new file mode 100644 index 0000000000..038595ae04 --- /dev/null +++ b/src/audio_core/voice_context.cpp @@ -0,0 +1,531 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/behavior_info.h" +#include "audio_core/voice_context.h" +#include "core/memory.h" + +namespace AudioCore { + +ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id) : id(id) {} +ServerVoiceChannelResource::~ServerVoiceChannelResource() = default; + +bool ServerVoiceChannelResource::InUse() const { + return in_use; +} + +float ServerVoiceChannelResource::GetCurrentMixVolumeAt(std::size_t i) const { + ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); + return mix_volume.at(i); +} + +float ServerVoiceChannelResource::GetLastMixVolumeAt(std::size_t i) const { + ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); + return last_mix_volume.at(i); +} + +void ServerVoiceChannelResource::Update(VoiceChannelResource::InParams& in_params) { + in_use = in_params.in_use; + // Update our mix volumes only if it's in use + if (in_params.in_use) { + std::copy(in_params.mix_volume.begin(), in_params.mix_volume.end(), mix_volume.begin()); + } +} + +void ServerVoiceChannelResource::UpdateLastMixVolumes() { + std::copy(mix_volume.begin(), mix_volume.end(), last_mix_volume.begin()); +} + +const std::array& +ServerVoiceChannelResource::GetCurrentMixVolume() const { + return mix_volume; +} + +const std::array& +ServerVoiceChannelResource::GetLastMixVolume() const { + return last_mix_volume; +} + +ServerVoiceInfo::ServerVoiceInfo() { + Initialize(); +} +ServerVoiceInfo::~ServerVoiceInfo() = default; + +void ServerVoiceInfo::Initialize() { + in_params.in_use = false; + in_params.node_id = 0; + in_params.id = 0; + in_params.current_playstate = ServerPlayState::Stop; + in_params.priority = 255; + in_params.sample_rate = 0; + in_params.sample_format = SampleFormat::Invalid; + in_params.channel_count = 0; + in_params.pitch = 0.0f; + in_params.volume = 0.0f; + in_params.last_volume = 0.0f; + std::memset(in_params.biquad_filter.data(), 0, + sizeof(BiquadFilterParameter) * in_params.biquad_filter.size()); + in_params.wave_buffer_count = 0; + in_params.wave_bufffer_head = 0; + in_params.mix_id = AudioCommon::NO_MIX; + in_params.splitter_info_id = AudioCommon::NO_SPLITTER; + in_params.additional_params_address = 0; + in_params.additional_params_size = 0; + in_params.is_new = false; + out_params.played_sample_count = 0; + out_params.wave_buffer_consumed = 0; + in_params.voice_drop_flag = false; + in_params.buffer_mapped = false; + in_params.wave_buffer_flush_request_count = 0; + std::fill(in_params.was_biquad_filter_enabled.begin(), + in_params.was_biquad_filter_enabled.end(), false); + + for (auto& wave_buffer : in_params.wave_buffer) { + wave_buffer.start_sample_offset = 0; + wave_buffer.end_sample_offset = 0; + wave_buffer.is_looping = false; + wave_buffer.end_of_stream = false; + wave_buffer.buffer_address = 0; + wave_buffer.buffer_size = 0; + wave_buffer.context_address = 0; + wave_buffer.context_size = 0; + wave_buffer.sent_to_dsp = true; + } + + stored_samples.clear(); +} + +void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in, + BehaviorInfo& behavior_info) { + in_params.in_use = voice_in.is_in_use; + in_params.id = voice_in.id; + in_params.node_id = voice_in.node_id; + in_params.last_playstate = in_params.current_playstate; + switch (voice_in.play_state) { + case PlayState::Paused: + in_params.current_playstate = ServerPlayState::Paused; + break; + case PlayState::Stopped: + if (in_params.current_playstate != ServerPlayState::Stop) { + in_params.current_playstate = ServerPlayState::RequestStop; + } + break; + case PlayState::Started: + in_params.current_playstate = ServerPlayState::Play; + break; + default: + UNREACHABLE_MSG("Unknown playstate {}", voice_in.play_state); + break; + } + + in_params.priority = voice_in.priority; + in_params.sorting_order = voice_in.sorting_order; + in_params.sample_rate = voice_in.sample_rate; + in_params.sample_format = voice_in.sample_format; + in_params.channel_count = voice_in.channel_count; + in_params.pitch = voice_in.pitch; + in_params.volume = voice_in.volume; + std::memcpy(in_params.biquad_filter.data(), voice_in.biquad_filter.data(), + sizeof(BiquadFilterParameter) * voice_in.biquad_filter.size()); + in_params.wave_buffer_count = voice_in.wave_buffer_count; + in_params.wave_bufffer_head = voice_in.wave_buffer_head; + if (behavior_info.IsFlushVoiceWaveBuffersSupported()) { + in_params.wave_buffer_flush_request_count += voice_in.wave_buffer_flush_request_count; + } + in_params.mix_id = voice_in.mix_id; + if (behavior_info.IsSplitterSupported()) { + in_params.splitter_info_id = voice_in.splitter_info_id; + } else { + in_params.splitter_info_id = AudioCommon::NO_SPLITTER; + } + + std::memcpy(in_params.voice_channel_resource_id.data(), + voice_in.voice_channel_resource_ids.data(), + sizeof(s32) * in_params.voice_channel_resource_id.size()); + + if (behavior_info.IsVoicePlayedSampleCountResetAtLoopPointSupported()) { + in_params.behavior_flags.is_played_samples_reset_at_loop_point = + voice_in.behavior_flags.is_played_samples_reset_at_loop_point; + } else { + in_params.behavior_flags.is_played_samples_reset_at_loop_point.Assign(0); + } + if (behavior_info.IsVoicePitchAndSrcSkippedSupported()) { + in_params.behavior_flags.is_pitch_and_src_skipped = + voice_in.behavior_flags.is_pitch_and_src_skipped; + } else { + in_params.behavior_flags.is_pitch_and_src_skipped.Assign(0); + } + + if (voice_in.is_voice_drop_flag_clear_requested) { + in_params.voice_drop_flag = false; + } + + if (in_params.additional_params_address != voice_in.additional_params_address || + in_params.additional_params_size != voice_in.additional_params_size) { + in_params.additional_params_address = voice_in.additional_params_address; + in_params.additional_params_size = voice_in.additional_params_size; + // TODO(ogniK): Reattach buffer, do we actually need to? Maybe just signal to the DSP that + // our context is new + } +} + +void ServerVoiceInfo::UpdateWaveBuffers( + const VoiceInfo::InParams& voice_in, + std::array& voice_states, + BehaviorInfo& behavior_info) { + if (voice_in.is_new) { + // Initialize our wave buffers + for (auto& wave_buffer : in_params.wave_buffer) { + wave_buffer.start_sample_offset = 0; + wave_buffer.end_sample_offset = 0; + wave_buffer.is_looping = false; + wave_buffer.end_of_stream = false; + wave_buffer.buffer_address = 0; + wave_buffer.buffer_size = 0; + wave_buffer.context_address = 0; + wave_buffer.context_size = 0; + wave_buffer.sent_to_dsp = true; + } + + // Mark all our wave buffers as invalid + for (std::size_t channel = 0; channel < static_cast(in_params.channel_count); + channel++) { + for (auto& is_valid : voice_states[channel]->is_wave_buffer_valid) { + is_valid = false; + } + } + } + + // Update our wave buffers + for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) { + // Assume that we have at least 1 channel voice state + const auto have_valid_wave_buffer = voice_states[0]->is_wave_buffer_valid[i]; + + UpdateWaveBuffer(in_params.wave_buffer[i], voice_in.wave_buffer[i], in_params.sample_format, + have_valid_wave_buffer, behavior_info); + } +} + +void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, + const WaveBuffer& in_wave_buffer, SampleFormat sample_format, + bool is_buffer_valid, BehaviorInfo& behavior_info) { + if (!is_buffer_valid && out_wavebuffer.sent_to_dsp) { + out_wavebuffer.buffer_address = 0; + out_wavebuffer.buffer_size = 0; + } + + if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) { + // Validate sample offset sizings + if (sample_format == SampleFormat::Pcm16) { + const auto buffer_size = in_wave_buffer.buffer_size; + if (in_wave_buffer.start_sample_offset < 0 || in_wave_buffer.end_sample_offset < 0 || + (buffer_size < (sizeof(s16) * in_wave_buffer.start_sample_offset)) || + (buffer_size < (sizeof(s16) * in_wave_buffer.end_sample_offset))) { + // TODO(ogniK): Write error info + return; + } + } + // TODO(ogniK): ADPCM Size error + + out_wavebuffer.sent_to_dsp = false; + out_wavebuffer.start_sample_offset = in_wave_buffer.start_sample_offset; + out_wavebuffer.end_sample_offset = in_wave_buffer.end_sample_offset; + out_wavebuffer.is_looping = in_wave_buffer.is_looping; + out_wavebuffer.end_of_stream = in_wave_buffer.end_of_stream; + + out_wavebuffer.buffer_address = in_wave_buffer.buffer_address; + out_wavebuffer.buffer_size = in_wave_buffer.buffer_size; + out_wavebuffer.context_address = in_wave_buffer.context_address; + out_wavebuffer.context_size = in_wave_buffer.context_size; + in_params.buffer_mapped = + in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0; + // TODO(ogniK): Pool mapper attachment + // TODO(ogniK): IsAdpcmLoopContextBugFixed + } +} + +void ServerVoiceInfo::WriteOutStatus( + VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in, + std::array& voice_states) { + if (voice_in.is_new) { + in_params.is_new = true; + voice_out.wave_buffer_consumed = 0; + voice_out.played_sample_count = 0; + voice_out.voice_dropped = false; + } else if (!in_params.is_new) { + voice_out.wave_buffer_consumed = voice_states[0]->wave_buffer_consumed; + voice_out.played_sample_count = voice_states[0]->played_sample_count; + voice_out.voice_dropped = in_params.voice_drop_flag; + } else { + voice_out.wave_buffer_consumed = 0; + voice_out.played_sample_count = 0; + voice_out.voice_dropped = false; + } +} + +const ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() const { + return in_params; +} + +ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() { + return in_params; +} + +const ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() const { + return out_params; +} + +ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() { + return out_params; +} + +bool ServerVoiceInfo::ShouldSkip() const { + // TODO(ogniK): Handle unmapped wave buffers or parameters + return !in_params.in_use || (in_params.wave_buffer_count == 0) || in_params.voice_drop_flag; +} + +bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) { + std::array dsp_voice_states{}; + if (in_params.is_new) { + ResetResources(voice_context); + in_params.last_volume = in_params.volume; + in_params.is_new = false; + } + + const s32 channel_count = in_params.channel_count; + for (s32 i = 0; i < channel_count; i++) { + const auto channel_resource = in_params.voice_channel_resource_id[i]; + dsp_voice_states[i] = + &voice_context.GetDspSharedState(static_cast(channel_resource)); + } + return UpdateParametersForCommandGeneration(dsp_voice_states); +} + +void ServerVoiceInfo::ResetResources(VoiceContext& voice_context) { + const s32 channel_count = in_params.channel_count; + for (s32 i = 0; i < channel_count; i++) { + const auto channel_resource = in_params.voice_channel_resource_id[i]; + auto& dsp_state = + voice_context.GetDspSharedState(static_cast(channel_resource)); + std::memset(&dsp_state, 0, sizeof(VoiceState)); + voice_context.GetChannelResource(static_cast(channel_resource)) + .UpdateLastMixVolumes(); + } +} + +bool ServerVoiceInfo::UpdateParametersForCommandGeneration( + std::array& dsp_voice_states) { + const s32 channel_count = in_params.channel_count; + if (in_params.wave_buffer_flush_request_count > 0) { + FlushWaveBuffers(in_params.wave_buffer_flush_request_count, dsp_voice_states, + channel_count); + in_params.wave_buffer_flush_request_count = 0; + } + + switch (in_params.current_playstate) { + case ServerPlayState::Play: { + for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) { + if (!in_params.wave_buffer[i].sent_to_dsp) { + for (s32 channel = 0; channel < channel_count; channel++) { + dsp_voice_states[channel]->is_wave_buffer_valid[i] = true; + } + in_params.wave_buffer[i].sent_to_dsp = true; + } + } + in_params.should_depop = false; + return HasValidWaveBuffer(dsp_voice_states[0]); + } + case ServerPlayState::Paused: + case ServerPlayState::Stop: { + in_params.should_depop = in_params.last_playstate == ServerPlayState::Play; + return in_params.should_depop; + } + case ServerPlayState::RequestStop: { + for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) { + in_params.wave_buffer[i].sent_to_dsp = true; + for (s32 channel = 0; channel < channel_count; channel++) { + auto* dsp_state = dsp_voice_states[channel]; + + if (dsp_state->is_wave_buffer_valid[i]) { + dsp_state->wave_buffer_index = + (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; + dsp_state->wave_buffer_consumed++; + } + + dsp_state->is_wave_buffer_valid[i] = false; + } + } + + for (s32 channel = 0; channel < channel_count; channel++) { + auto* dsp_state = dsp_voice_states[channel]; + dsp_state->offset = 0; + dsp_state->played_sample_count = 0; + dsp_state->fraction = 0; + std::memset(dsp_state->sample_history.data(), 0, + sizeof(s32) * dsp_state->sample_history.size()); + std::memset(&dsp_state->context, 0, sizeof(dsp_state->context)); + } + + in_params.current_playstate = ServerPlayState::Stop; + in_params.should_depop = in_params.last_playstate == ServerPlayState::Play; + return in_params.should_depop; + } + default: + UNREACHABLE_MSG("Invalid playstate {}", in_params.current_playstate); + } + + return false; +} + +void ServerVoiceInfo::FlushWaveBuffers( + u8 flush_count, std::array& dsp_voice_states, + s32 channel_count) { + auto wave_head = in_params.wave_bufffer_head; + + for (u8 i = 0; i < flush_count; i++) { + in_params.wave_buffer[wave_head].sent_to_dsp = true; + for (s32 channel = 0; channel < channel_count; channel++) { + auto* dsp_state = dsp_voice_states[channel]; + dsp_state->wave_buffer_consumed++; + dsp_state->is_wave_buffer_valid[wave_head] = false; + dsp_state->wave_buffer_index = + (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; + } + wave_head = (wave_head + 1) % AudioCommon::MAX_WAVE_BUFFERS; + } +} + +bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const { + const auto& valid_wb = state->is_wave_buffer_valid; + return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end(); +} + +VoiceContext::VoiceContext(std::size_t voice_count) : voice_count(voice_count) { + for (std::size_t i = 0; i < voice_count; i++) { + voice_channel_resources.emplace_back(static_cast(i)); + sorted_voice_info.push_back(&voice_info.emplace_back()); + voice_states.emplace_back(); + dsp_voice_states.emplace_back(); + } +} + +VoiceContext::~VoiceContext() { + sorted_voice_info.clear(); +} + +std::size_t VoiceContext::GetVoiceCount() const { + return voice_count; +} + +ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) { + ASSERT(i < voice_count); + return voice_channel_resources.at(i); +} + +const ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) const { + ASSERT(i < voice_count); + return voice_channel_resources.at(i); +} + +VoiceState& VoiceContext::GetState(std::size_t i) { + ASSERT(i < voice_count); + return voice_states.at(i); +} + +const VoiceState& VoiceContext::GetState(std::size_t i) const { + ASSERT(i < voice_count); + return voice_states.at(i); +} + +VoiceState& VoiceContext::GetDspSharedState(std::size_t i) { + ASSERT(i < voice_count); + return dsp_voice_states.at(i); +} + +const VoiceState& VoiceContext::GetDspSharedState(std::size_t i) const { + ASSERT(i < voice_count); + return dsp_voice_states.at(i); +} + +ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) { + ASSERT(i < voice_count); + return voice_info.at(i); +} + +const ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) const { + ASSERT(i < voice_count); + return voice_info.at(i); +} + +ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) { + ASSERT(i < voice_count); + return *sorted_voice_info.at(i); +} + +const ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) const { + ASSERT(i < voice_count); + return *sorted_voice_info.at(i); +} + +s32 VoiceContext::DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel, + s32 channel_count, s32 buffer_offset, s32 sample_count, + Core::Memory::Memory& memory) { + if (wave_buffer->buffer_address == 0) { + return 0; + } + if (wave_buffer->buffer_size == 0) { + return 0; + } + if (wave_buffer->end_sample_offset < wave_buffer->start_sample_offset) { + return 0; + } + + const auto samples_remaining = + (wave_buffer->end_sample_offset - wave_buffer->start_sample_offset) - buffer_offset; + const auto start_offset = (wave_buffer->start_sample_offset + buffer_offset) * channel_count; + const auto buffer_pos = wave_buffer->buffer_address + start_offset; + + s16* buffer_data = reinterpret_cast(memory.GetPointer(buffer_pos)); + + const auto samples_processed = std::min(sample_count, samples_remaining); + + // Fast path + if (channel_count == 1) { + for (std::size_t i = 0; i < samples_processed; i++) { + output_buffer[i] = buffer_data[i]; + } + } else { + for (std::size_t i = 0; i < samples_processed; i++) { + output_buffer[i] = buffer_data[i * channel_count + channel]; + } + } + + return samples_processed; +} + +void VoiceContext::SortInfo() { + for (std::size_t i = 0; i < voice_count; i++) { + sorted_voice_info[i] = &voice_info[i]; + } + + std::sort(sorted_voice_info.begin(), sorted_voice_info.end(), + [](const ServerVoiceInfo* lhs, const ServerVoiceInfo* rhs) { + const auto& lhs_in = lhs->GetInParams(); + const auto& rhs_in = rhs->GetInParams(); + // Sort by priority + if (lhs_in.priority != rhs_in.priority) { + return lhs_in.priority > rhs_in.priority; + } else { + // If the priorities match, sort by sorting order + return lhs_in.sorting_order > rhs_in.sorting_order; + } + }); +} + +void VoiceContext::UpdateStateByDspShared() { + std::memcpy(voice_states.data(), dsp_voice_states.data(), + sizeof(VoiceState) * dsp_voice_states.size()); +} + +} // namespace AudioCore diff --git a/src/audio_core/voice_context.h b/src/audio_core/voice_context.h new file mode 100644 index 0000000000..b1d5547664 --- /dev/null +++ b/src/audio_core/voice_context.h @@ -0,0 +1,291 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "audio_core/algorithm/interpolate.h" +#include "audio_core/codec.h" +#include "audio_core/common.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Core::Memory { +class Memory; +} + +namespace AudioCore { + +class BehaviorInfo; +class VoiceContext; + +enum class SampleFormat : u8 { + Invalid = 0, + Pcm8 = 1, + Pcm16 = 2, + Pcm24 = 3, + Pcm32 = 4, + PcmFloat = 5, + Adpcm = 6, +}; + +enum class PlayState : u8 { + Started = 0, + Stopped = 1, + Paused = 2, +}; + +enum class ServerPlayState { + Play = 0, + Stop = 1, + RequestStop = 2, + Paused = 3, +}; + +struct BiquadFilterParameter { + bool enabled{}; + INSERT_PADDING_BYTES(1); + std::array numerator{}; + std::array denominator{}; +}; +static_assert(sizeof(BiquadFilterParameter) == 0xc, "BiquadFilterParameter is an invalid size"); + +struct WaveBuffer { + u64_le buffer_address{}; + u64_le buffer_size{}; + s32_le start_sample_offset{}; + s32_le end_sample_offset{}; + u8 is_looping{}; + u8 end_of_stream{}; + u8 sent_to_server{}; + INSERT_PADDING_BYTES(5); + u64 context_address{}; + u64 context_size{}; + INSERT_PADDING_BYTES(8); +}; +static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size"); + +struct ServerWaveBuffer { + VAddr buffer_address{}; + std::size_t buffer_size{}; + s32 start_sample_offset{}; + s32 end_sample_offset{}; + bool is_looping{}; + bool end_of_stream{}; + VAddr context_address{}; + std::size_t context_size{}; + bool sent_to_dsp{true}; +}; + +struct BehaviorFlags { + BitField<0, 1, u16> is_played_samples_reset_at_loop_point; + BitField<1, 1, u16> is_pitch_and_src_skipped; +}; +static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size"); + +struct VoiceState { + s64 played_sample_count{}; + s32 offset{}; + s32 wave_buffer_index{}; + std::array is_wave_buffer_valid{}; + s32 wave_buffer_consumed{}; + std::array sample_history{}; + s32 fraction{}; + VAddr context_address{}; + Codec::ADPCM_Coeff coeff{}; + Codec::ADPCMState context{}; + std::array biquad_filter_state{}; + std::array previous_samples{}; + u32 external_context_size{}; + bool is_external_context_used{}; + bool voice_dropped{}; + // TODO(ogniK): Hack until ADPCM streaming is implemented + std::vector adpcm_samples{}; +}; + +class VoiceChannelResource { +public: + struct InParams { + s32_le id{}; + std::array mix_volume{}; + bool in_use{}; + INSERT_PADDING_BYTES(11); + }; + static_assert(sizeof(VoiceChannelResource::InParams) == 0x70, "InParams is an invalid size"); +}; + +class ServerVoiceChannelResource { +public: + explicit ServerVoiceChannelResource(s32 id); + ~ServerVoiceChannelResource(); + + bool InUse() const; + float GetCurrentMixVolumeAt(std::size_t i) const; + float GetLastMixVolumeAt(std::size_t i) const; + void Update(VoiceChannelResource::InParams& in_params); + void UpdateLastMixVolumes(); + + const std::array& GetCurrentMixVolume() const; + const std::array& GetLastMixVolume() const; + +private: + s32 id{}; + std::array mix_volume{}; + std::array last_mix_volume{}; + bool in_use{}; +}; + +class VoiceInfo { +public: + struct InParams { + s32_le id{}; + u32_le node_id{}; + u8 is_new{}; + u8 is_in_use{}; + PlayState play_state{}; + SampleFormat sample_format{}; + s32_le sample_rate{}; + s32_le priority{}; + s32_le sorting_order{}; + s32_le channel_count{}; + float_le pitch{}; + float_le volume{}; + std::array biquad_filter{}; + s32_le wave_buffer_count{}; + s16_le wave_buffer_head{}; + INSERT_PADDING_BYTES(6); + u64_le additional_params_address{}; + u64_le additional_params_size{}; + s32_le mix_id{}; + s32_le splitter_info_id{}; + std::array wave_buffer{}; + std::array voice_channel_resource_ids{}; + // TODO(ogniK): Remaining flags + u8 is_voice_drop_flag_clear_requested{}; + u8 wave_buffer_flush_request_count{}; + INSERT_PADDING_BYTES(2); + BehaviorFlags behavior_flags{}; + INSERT_PADDING_BYTES(16); + }; + static_assert(sizeof(VoiceInfo::InParams) == 0x170, "InParams is an invalid size"); + + struct OutParams { + u64_le played_sample_count{}; + u32_le wave_buffer_consumed{}; + u8 voice_dropped{}; + INSERT_PADDING_BYTES(3); + }; + static_assert(sizeof(VoiceInfo::OutParams) == 0x10, "OutParams is an invalid size"); +}; + +class ServerVoiceInfo { +public: + struct InParams { + bool in_use{}; + bool is_new{}; + bool should_depop{}; + SampleFormat sample_format{}; + s32 sample_rate{}; + s32 channel_count{}; + s32 id{}; + s32 node_id{}; + s32 mix_id{}; + ServerPlayState current_playstate{}; + ServerPlayState last_playstate{}; + s32 priority{}; + s32 sorting_order{}; + float pitch{}; + float volume{}; + float last_volume{}; + std::array biquad_filter{}; + s32 wave_buffer_count{}; + s16 wave_bufffer_head{}; + INSERT_PADDING_BYTES(2); + BehaviorFlags behavior_flags{}; + VAddr additional_params_address{}; + std::size_t additional_params_size{}; + std::array wave_buffer{}; + std::array voice_channel_resource_id{}; + s32 splitter_info_id{}; + u8 wave_buffer_flush_request_count{}; + bool voice_drop_flag{}; + bool buffer_mapped{}; + std::array was_biquad_filter_enabled{}; + }; + + struct OutParams { + s64 played_sample_count{}; + s32 wave_buffer_consumed{}; + }; + + ServerVoiceInfo(); + ~ServerVoiceInfo(); + void Initialize(); + void UpdateParameters(const VoiceInfo::InParams& voice_in, BehaviorInfo& behavior_info); + void UpdateWaveBuffers(const VoiceInfo::InParams& voice_in, + std::array& voice_states, + BehaviorInfo& behavior_info); + void UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, const WaveBuffer& in_wave_buffer, + SampleFormat sample_format, bool is_buffer_valid, + BehaviorInfo& behavior_info); + void WriteOutStatus(VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in, + std::array& voice_states); + + const InParams& GetInParams() const; + InParams& GetInParams(); + + const OutParams& GetOutParams() const; + OutParams& GetOutParams(); + + bool ShouldSkip() const; + bool UpdateForCommandGeneration(VoiceContext& voice_context); + void ResetResources(VoiceContext& voice_context); + bool UpdateParametersForCommandGeneration( + std::array& dsp_voice_states); + void FlushWaveBuffers(u8 flush_count, + std::array& dsp_voice_states, + s32 channel_count); + +private: + std::vector stored_samples; + InParams in_params{}; + OutParams out_params{}; + + bool HasValidWaveBuffer(const VoiceState* state) const; +}; + +class VoiceContext { +public: + VoiceContext(std::size_t voice_count); + ~VoiceContext(); + + std::size_t GetVoiceCount() const; + ServerVoiceChannelResource& GetChannelResource(std::size_t i); + const ServerVoiceChannelResource& GetChannelResource(std::size_t i) const; + VoiceState& GetState(std::size_t i); + const VoiceState& GetState(std::size_t i) const; + VoiceState& GetDspSharedState(std::size_t i); + const VoiceState& GetDspSharedState(std::size_t i) const; + ServerVoiceInfo& GetInfo(std::size_t i); + const ServerVoiceInfo& GetInfo(std::size_t i) const; + ServerVoiceInfo& GetSortedInfo(std::size_t i); + const ServerVoiceInfo& GetSortedInfo(std::size_t i) const; + + s32 DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel, + s32 channel_count, s32 buffer_offset, s32 sample_count, + Core::Memory::Memory& memory); + void SortInfo(); + void UpdateStateByDspShared(); + +private: + std::size_t voice_count{}; + std::vector voice_channel_resources{}; + std::vector voice_states{}; + std::vector dsp_voice_states{}; + std::vector voice_info{}; + std::vector sorted_voice_info{}; +}; + +} // namespace AudioCore diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index d8359abaa1..a2d3ded7ba 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -26,7 +26,7 @@ namespace Service::Audio { class IAudioRenderer final : public ServiceFramework { public: - explicit IAudioRenderer(Core::System& system, AudioCore::AudioRendererParameter audren_params, + explicit IAudioRenderer(Core::System& system, AudioCommon::AudioRendererParameter audren_params, const std::size_t instance_number) : ServiceFramework("IAudioRenderer") { // clang-format off @@ -94,14 +94,15 @@ private: void RequestUpdateImpl(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "(STUBBED) called"); - auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer()); + std::vector output_params(ctx.GetWriteBufferSize()); + auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params); - if (result.Succeeded()) { - ctx.WriteBuffer(result.Unwrap()); + if (result.IsSuccess()) { + ctx.WriteBuffer(output_params); } IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); + rb.Push(result); } void Start(Kernel::HLERequestContext& ctx) { @@ -346,7 +347,7 @@ void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { OpenAudioRendererImpl(ctx); } -static u64 CalculateNumPerformanceEntries(const AudioCore::AudioRendererParameter& params) { +static u64 CalculateNumPerformanceEntries(const AudioCommon::AudioRendererParameter& params) { // +1 represents the final mix. return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count + 1; @@ -375,7 +376,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { constexpr u64 upsampler_manager_size = 0x48; // Calculates the part of the size that relates to mix buffers. - const auto calculate_mix_buffer_sizes = [](const AudioCore::AudioRendererParameter& params) { + const auto calculate_mix_buffer_sizes = [](const AudioCommon::AudioRendererParameter& params) { // As of 8.0.0 this is the maximum on voice channels. constexpr u64 max_voice_channels = 6; @@ -397,7 +398,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { }; // Calculates the portion of the size related to the mix data (and the sorting thereof). - const auto calculate_mix_info_size = [](const AudioCore::AudioRendererParameter& params) { + const auto calculate_mix_info_size = [](const AudioCommon::AudioRendererParameter& params) { // The size of the mixing info data structure. constexpr u64 mix_info_size = 0x940; @@ -447,7 +448,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { }; // Calculates the part of the size related to voice channel info. - const auto calculate_voice_info_size = [](const AudioCore::AudioRendererParameter& params) { + const auto calculate_voice_info_size = [](const AudioCommon::AudioRendererParameter& params) { constexpr u64 voice_info_size = 0x220; constexpr u64 voice_resource_size = 0xD0; @@ -461,7 +462,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { }; // Calculates the part of the size related to memory pools. - const auto calculate_memory_pools_size = [](const AudioCore::AudioRendererParameter& params) { + const auto calculate_memory_pools_size = [](const AudioCommon::AudioRendererParameter& params) { const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count); const u64 memory_pool_info_size = 0x20; return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size); @@ -469,7 +470,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { // Calculates the part of the size related to the splitter context. const auto calculate_splitter_context_size = - [](const AudioCore::AudioRendererParameter& params) -> u64 { + [](const AudioCommon::AudioRendererParameter& params) -> u64 { if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) { return 0; } @@ -488,27 +489,29 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { }; // Calculates the part of the size related to the upsampler info. - const auto calculate_upsampler_info_size = [](const AudioCore::AudioRendererParameter& params) { - constexpr u64 upsampler_info_size = 0x280; - // Yes, using the buffer size over info alignment size is intentional here. - return Common::AlignUp(upsampler_info_size * (u64{params.submix_count} + params.sink_count), - buffer_alignment_size); - }; + const auto calculate_upsampler_info_size = + [](const AudioCommon::AudioRendererParameter& params) { + constexpr u64 upsampler_info_size = 0x280; + // Yes, using the buffer size over info alignment size is intentional here. + return Common::AlignUp(upsampler_info_size * + (u64{params.submix_count} + params.sink_count), + buffer_alignment_size); + }; // Calculates the part of the size related to effect info. - const auto calculate_effect_info_size = [](const AudioCore::AudioRendererParameter& params) { + const auto calculate_effect_info_size = [](const AudioCommon::AudioRendererParameter& params) { constexpr u64 effect_info_size = 0x2B0; return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size); }; // Calculates the part of the size related to audio sink info. - const auto calculate_sink_info_size = [](const AudioCore::AudioRendererParameter& params) { + const auto calculate_sink_info_size = [](const AudioCommon::AudioRendererParameter& params) { const u64 sink_info_size = 0x170; return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size); }; // Calculates the part of the size related to voice state info. - const auto calculate_voice_state_size = [](const AudioCore::AudioRendererParameter& params) { + const auto calculate_voice_state_size = [](const AudioCommon::AudioRendererParameter& params) { const u64 voice_state_size = 0x100; const u64 additional_size = buffer_alignment_size - 1; return Common::AlignUp(voice_state_size * params.voice_count + additional_size, @@ -516,7 +519,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { }; // Calculates the part of the size related to performance statistics. - const auto calculate_perf_size = [](const AudioCore::AudioRendererParameter& params) { + const auto calculate_perf_size = [](const AudioCommon::AudioRendererParameter& params) { // Extra size value appended to the end of the calculation. constexpr u64 appended = 128; @@ -543,79 +546,81 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { }; // Calculates the part of the size that relates to the audio command buffer. - const auto calculate_command_buffer_size = [](const AudioCore::AudioRendererParameter& params) { - constexpr u64 alignment = (buffer_alignment_size - 1) * 2; + const auto calculate_command_buffer_size = + [](const AudioCommon::AudioRendererParameter& params) { + constexpr u64 alignment = (buffer_alignment_size - 1) * 2; - if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) { - constexpr u64 command_buffer_size = 0x18000; + if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) { + constexpr u64 command_buffer_size = 0x18000; - return command_buffer_size + alignment; - } + return command_buffer_size + alignment; + } - // When the variadic command buffer is supported, this means - // the command generator for the audio renderer can issue commands - // that are (as one would expect), variable in size. So what we need to do - // is determine the maximum possible size for a few command data structures - // then multiply them by the amount of present commands indicated by the given - // respective audio parameters. + // When the variadic command buffer is supported, this means + // the command generator for the audio renderer can issue commands + // that are (as one would expect), variable in size. So what we need to do + // is determine the maximum possible size for a few command data structures + // then multiply them by the amount of present commands indicated by the given + // respective audio parameters. - constexpr u64 max_biquad_filters = 2; - constexpr u64 max_mix_buffers = 24; + constexpr u64 max_biquad_filters = 2; + constexpr u64 max_mix_buffers = 24; - constexpr u64 biquad_filter_command_size = 0x2C; + constexpr u64 biquad_filter_command_size = 0x2C; - constexpr u64 depop_mix_command_size = 0x24; - constexpr u64 depop_setup_command_size = 0x50; + constexpr u64 depop_mix_command_size = 0x24; + constexpr u64 depop_setup_command_size = 0x50; - constexpr u64 effect_command_max_size = 0x540; + constexpr u64 effect_command_max_size = 0x540; - constexpr u64 mix_command_size = 0x1C; - constexpr u64 mix_ramp_command_size = 0x24; - constexpr u64 mix_ramp_grouped_command_size = 0x13C; + constexpr u64 mix_command_size = 0x1C; + constexpr u64 mix_ramp_command_size = 0x24; + constexpr u64 mix_ramp_grouped_command_size = 0x13C; - constexpr u64 perf_command_size = 0x28; + constexpr u64 perf_command_size = 0x28; - constexpr u64 sink_command_size = 0x130; + constexpr u64 sink_command_size = 0x130; - constexpr u64 submix_command_max_size = - depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers; + constexpr u64 submix_command_max_size = + depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers; - constexpr u64 volume_command_size = 0x1C; - constexpr u64 volume_ramp_command_size = 0x20; + constexpr u64 volume_command_size = 0x1C; + constexpr u64 volume_ramp_command_size = 0x20; - constexpr u64 voice_biquad_filter_command_size = - biquad_filter_command_size * max_biquad_filters; - constexpr u64 voice_data_command_size = 0x9C; - const u64 voice_command_max_size = - (params.splitter_count * depop_setup_command_size) + - (voice_data_command_size + voice_biquad_filter_command_size + volume_ramp_command_size + - mix_ramp_grouped_command_size); + constexpr u64 voice_biquad_filter_command_size = + biquad_filter_command_size * max_biquad_filters; + constexpr u64 voice_data_command_size = 0x9C; + const u64 voice_command_max_size = + (params.splitter_count * depop_setup_command_size) + + (voice_data_command_size + voice_biquad_filter_command_size + + volume_ramp_command_size + mix_ramp_grouped_command_size); - // Now calculate the individual elements that comprise the size and add them together. - const u64 effect_commands_size = params.effect_count * effect_command_max_size; + // Now calculate the individual elements that comprise the size and add them together. + const u64 effect_commands_size = params.effect_count * effect_command_max_size; - const u64 final_mix_commands_size = - depop_mix_command_size + volume_command_size * max_mix_buffers; + const u64 final_mix_commands_size = + depop_mix_command_size + volume_command_size * max_mix_buffers; - const u64 perf_commands_size = - perf_command_size * (CalculateNumPerformanceEntries(params) + max_perf_detail_entries); + const u64 perf_commands_size = + perf_command_size * + (CalculateNumPerformanceEntries(params) + max_perf_detail_entries); - const u64 sink_commands_size = params.sink_count * sink_command_size; + const u64 sink_commands_size = params.sink_count * sink_command_size; - const u64 splitter_commands_size = - params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size; + const u64 splitter_commands_size = + params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size; - const u64 submix_commands_size = params.submix_count * submix_command_max_size; + const u64 submix_commands_size = params.submix_count * submix_command_max_size; - const u64 voice_commands_size = params.voice_count * voice_command_max_size; + const u64 voice_commands_size = params.voice_count * voice_command_max_size; - return effect_commands_size + final_mix_commands_size + perf_commands_size + - sink_commands_size + splitter_commands_size + submix_commands_size + - voice_commands_size + alignment; - }; + return effect_commands_size + final_mix_commands_size + perf_commands_size + + sink_commands_size + splitter_commands_size + submix_commands_size + + voice_commands_size + alignment; + }; IPC::RequestParser rp{ctx}; - const auto params = rp.PopRaw(); + const auto params = rp.PopRaw(); u64 size = 0; size += calculate_mix_buffer_sizes(params); @@ -681,7 +686,7 @@ void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& c void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto params = rp.PopRaw(); + const auto params = rp.PopRaw(); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS);