diff options
Diffstat (limited to 'src/audio_core/renderer/command')
65 files changed, 12755 insertions, 0 deletions
diff --git a/src/audio_core/renderer/command/command_buffer.cpp b/src/audio_core/renderer/command/command_buffer.cpp new file mode 100644 index 000000000..40074cf14 --- /dev/null +++ b/src/audio_core/renderer/command/command_buffer.cpp @@ -0,0 +1,714 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/command/command_buffer.h" +#include "audio_core/renderer/command/command_list_header.h" +#include "audio_core/renderer/command/command_processing_time_estimator.h" +#include "audio_core/renderer/effect/biquad_filter.h" +#include "audio_core/renderer/effect/delay.h" +#include "audio_core/renderer/effect/reverb.h" +#include "audio_core/renderer/memory/memory_pool_info.h" +#include "audio_core/renderer/mix/mix_info.h" +#include "audio_core/renderer/sink/circular_buffer_sink_info.h" +#include "audio_core/renderer/sink/device_sink_info.h" +#include "audio_core/renderer/sink/sink_info_base.h" +#include "audio_core/renderer/voice/voice_info.h" +#include "audio_core/renderer/voice/voice_state.h" + +namespace AudioCore::AudioRenderer { + +template <typename T, CommandId Id> +T& CommandBuffer::GenerateStart(const s32 node_id) { + if (size + sizeof(T) >= command_list.size_bytes()) { + LOG_ERROR( + Service_Audio, + "Attempting to write commands beyond the end of allocated command buffer memory!"); + UNREACHABLE(); + } + + auto& cmd{*std::construct_at<T>(reinterpret_cast<T*>(&command_list[size]))}; + + cmd.magic = CommandMagic; + cmd.enabled = true; + cmd.type = Id; + cmd.size = sizeof(T); + cmd.node_id = node_id; + + return cmd; +} + +template <typename T> +void CommandBuffer::GenerateEnd(T& cmd) { + cmd.estimated_process_time = time_estimator->Estimate(cmd); + estimated_process_time += cmd.estimated_process_time; + size += sizeof(T); + count++; +} + +void CommandBuffer::GeneratePcmInt16Version1Command(const s32 node_id, + const MemoryPoolInfo& memory_pool_, + VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel) { + auto& cmd{ + GenerateStart<PcmInt16DataSourceVersion1Command, CommandId::DataSourcePcmInt16Version1>( + node_id)}; + + cmd.src_quality = voice_info.src_quality; + cmd.output_index = buffer_count + channel; + cmd.flags = voice_info.flags & 3; + cmd.sample_rate = voice_info.sample_rate; + cmd.pitch = voice_info.pitch; + cmd.channel_index = channel; + cmd.channel_count = voice_info.channel_count; + + for (u32 i = 0; i < MaxWaveBuffers; i++) { + voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); + } + + cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); + + GenerateEnd<PcmInt16DataSourceVersion1Command>(cmd); +} + +void CommandBuffer::GeneratePcmInt16Version2Command(const s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel) { + auto& cmd{ + GenerateStart<PcmInt16DataSourceVersion2Command, CommandId::DataSourcePcmInt16Version2>( + node_id)}; + + cmd.src_quality = voice_info.src_quality; + cmd.output_index = buffer_count + channel; + cmd.flags = voice_info.flags & 3; + cmd.sample_rate = voice_info.sample_rate; + cmd.pitch = voice_info.pitch; + cmd.channel_index = channel; + cmd.channel_count = voice_info.channel_count; + + for (u32 i = 0; i < MaxWaveBuffers; i++) { + voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); + } + + cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); + + GenerateEnd<PcmInt16DataSourceVersion2Command>(cmd); +} + +void CommandBuffer::GeneratePcmFloatVersion1Command(const s32 node_id, + const MemoryPoolInfo& memory_pool_, + VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel) { + auto& cmd{ + GenerateStart<PcmFloatDataSourceVersion1Command, CommandId::DataSourcePcmFloatVersion1>( + node_id)}; + + cmd.src_quality = voice_info.src_quality; + cmd.output_index = buffer_count + channel; + cmd.flags = voice_info.flags & 3; + cmd.sample_rate = voice_info.sample_rate; + cmd.pitch = voice_info.pitch; + cmd.channel_index = channel; + cmd.channel_count = voice_info.channel_count; + + for (u32 i = 0; i < MaxWaveBuffers; i++) { + voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); + } + + cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); + + GenerateEnd<PcmFloatDataSourceVersion1Command>(cmd); +} + +void CommandBuffer::GeneratePcmFloatVersion2Command(const s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel) { + auto& cmd{ + GenerateStart<PcmFloatDataSourceVersion2Command, CommandId::DataSourcePcmFloatVersion2>( + node_id)}; + + cmd.src_quality = voice_info.src_quality; + cmd.output_index = buffer_count + channel; + cmd.flags = voice_info.flags & 3; + cmd.sample_rate = voice_info.sample_rate; + cmd.pitch = voice_info.pitch; + cmd.channel_index = channel; + cmd.channel_count = voice_info.channel_count; + + for (u32 i = 0; i < MaxWaveBuffers; i++) { + voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); + } + + cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); + + GenerateEnd<PcmFloatDataSourceVersion2Command>(cmd); +} + +void CommandBuffer::GenerateAdpcmVersion1Command(const s32 node_id, + const MemoryPoolInfo& memory_pool_, + VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel) { + auto& cmd{ + GenerateStart<AdpcmDataSourceVersion1Command, CommandId::DataSourceAdpcmVersion1>(node_id)}; + + cmd.src_quality = voice_info.src_quality; + cmd.output_index = buffer_count + channel; + cmd.flags = voice_info.flags & 3; + cmd.sample_rate = voice_info.sample_rate; + cmd.pitch = voice_info.pitch; + + for (u32 i = 0; i < MaxWaveBuffers; i++) { + voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); + } + + cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); + cmd.data_address = voice_info.data_address.GetReference(true); + cmd.data_size = voice_info.data_address.GetSize(); + + GenerateEnd<AdpcmDataSourceVersion1Command>(cmd); +} + +void CommandBuffer::GenerateAdpcmVersion2Command(const s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel) { + auto& cmd{ + GenerateStart<AdpcmDataSourceVersion2Command, CommandId::DataSourceAdpcmVersion2>(node_id)}; + + cmd.src_quality = voice_info.src_quality; + cmd.output_index = buffer_count + channel; + cmd.flags = voice_info.flags & 3; + cmd.sample_rate = voice_info.sample_rate; + cmd.pitch = voice_info.pitch; + cmd.channel_index = channel; + cmd.channel_count = voice_info.channel_count; + + for (u32 i = 0; i < MaxWaveBuffers; i++) { + voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); + } + + cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); + cmd.data_address = voice_info.data_address.GetReference(true); + cmd.data_size = voice_info.data_address.GetSize(); + + GenerateEnd<AdpcmDataSourceVersion2Command>(cmd); +} + +void CommandBuffer::GenerateVolumeCommand(const s32 node_id, const s16 buffer_offset, + const s16 input_index, const f32 volume, + const u8 precision) { + auto& cmd{GenerateStart<VolumeCommand, CommandId::Volume>(node_id)}; + + cmd.precision = precision; + cmd.input_index = buffer_offset + input_index; + cmd.output_index = buffer_offset + input_index; + cmd.volume = volume; + + GenerateEnd<VolumeCommand>(cmd); +} + +void CommandBuffer::GenerateVolumeRampCommand(const s32 node_id, VoiceInfo& voice_info, + const s16 buffer_count, const u8 precision) { + auto& cmd{GenerateStart<VolumeRampCommand, CommandId::VolumeRamp>(node_id)}; + + cmd.input_index = buffer_count; + cmd.output_index = buffer_count; + cmd.prev_volume = voice_info.prev_volume; + cmd.volume = voice_info.volume; + cmd.precision = precision; + + GenerateEnd<VolumeRampCommand>(cmd); +} + +void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel, + const u32 biquad_index, + const bool use_float_processing) { + auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)}; + + cmd.input = buffer_count + channel; + cmd.output = buffer_count + channel; + + cmd.biquad = voice_info.biquads[biquad_index]; + + cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()), + MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); + + cmd.needs_init = !voice_info.biquad_initialized[biquad_index]; + cmd.use_float_processing = use_float_processing; + + GenerateEnd<BiquadFilterCommand>(cmd); +} + +void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBase& effect_info, + const s16 buffer_offset, const s8 channel, + const bool needs_init, + const bool use_float_processing) { + auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)}; + + const auto& parameter{ + *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())}; + const auto state{ + reinterpret_cast<VoiceState::BiquadFilterState*>(effect_info.GetStateBuffer())}; + + cmd.input = buffer_offset + parameter.inputs[channel]; + cmd.output = buffer_offset + parameter.outputs[channel]; + + cmd.biquad.b = parameter.b; + cmd.biquad.a = parameter.a; + + cmd.state = memory_pool->Translate(CpuAddr(state), + MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); + + cmd.needs_init = needs_init; + cmd.use_float_processing = use_float_processing; + + GenerateEnd<BiquadFilterCommand>(cmd); +} + +void CommandBuffer::GenerateMixCommand(const s32 node_id, const s16 input_index, + const s16 output_index, const s16 buffer_offset, + const f32 volume, const u8 precision) { + auto& cmd{GenerateStart<MixCommand, CommandId::Mix>(node_id)}; + + cmd.input_index = input_index; + cmd.output_index = output_index; + cmd.volume = volume; + cmd.precision = precision; + + GenerateEnd<MixCommand>(cmd); +} + +void CommandBuffer::GenerateMixRampCommand(const s32 node_id, + [[maybe_unused]] const s16 buffer_count, + const s16 input_index, const s16 output_index, + const f32 volume, const f32 prev_volume, + const CpuAddr prev_samples, const u8 precision) { + if (volume == 0.0f && prev_volume == 0.0f) { + return; + } + + auto& cmd{GenerateStart<MixRampCommand, CommandId::MixRamp>(node_id)}; + + cmd.input_index = input_index; + cmd.output_index = output_index; + cmd.prev_volume = prev_volume; + cmd.volume = volume; + cmd.previous_sample = prev_samples; + cmd.precision = precision; + + GenerateEnd<MixRampCommand>(cmd); +} + +void CommandBuffer::GenerateMixRampGroupedCommand(const s32 node_id, const s16 buffer_count, + const s16 input_index, s16 output_index, + std::span<const f32> volumes, + std::span<const f32> prev_volumes, + const CpuAddr prev_samples, const u8 precision) { + auto& cmd{GenerateStart<MixRampGroupedCommand, CommandId::MixRampGrouped>(node_id)}; + + cmd.buffer_count = buffer_count; + + for (s32 i = 0; i < buffer_count; i++) { + cmd.inputs[i] = input_index; + cmd.outputs[i] = output_index++; + cmd.prev_volumes[i] = prev_volumes[i]; + cmd.volumes[i] = volumes[i]; + } + + cmd.previous_samples = prev_samples; + cmd.precision = precision; + + GenerateEnd<MixRampGroupedCommand>(cmd); +} + +void CommandBuffer::GenerateDepopPrepareCommand(const s32 node_id, const VoiceState& voice_state, + std::span<const s32> buffer, const s16 buffer_count, + s16 buffer_offset, const bool was_playing) { + auto& cmd{GenerateStart<DepopPrepareCommand, CommandId::DepopPrepare>(node_id)}; + + cmd.enabled = was_playing; + + for (u32 i = 0; i < MaxMixBuffers; i++) { + cmd.inputs[i] = buffer_offset++; + } + + cmd.previous_samples = memory_pool->Translate(CpuAddr(voice_state.previous_samples.data()), + MaxMixBuffers * sizeof(s32)); + cmd.buffer_count = buffer_count; + cmd.depop_buffer = memory_pool->Translate(CpuAddr(buffer.data()), buffer_count * sizeof(s32)); + + GenerateEnd<DepopPrepareCommand>(cmd); +} + +void CommandBuffer::GenerateDepopForMixBuffersCommand(const s32 node_id, const MixInfo& mix_info, + std::span<const s32> depop_buffer) { + auto& cmd{GenerateStart<DepopForMixBuffersCommand, CommandId::DepopForMixBuffers>(node_id)}; + + cmd.input = mix_info.buffer_offset; + cmd.count = mix_info.buffer_count; + cmd.decay = mix_info.sample_rate == TargetSampleRate ? 0.96218872f : 0.94369507f; + cmd.depop_buffer = + memory_pool->Translate(CpuAddr(depop_buffer.data()), mix_info.buffer_count * sizeof(s32)); + + GenerateEnd<DepopForMixBuffersCommand>(cmd); +} + +void CommandBuffer::GenerateDelayCommand(const s32 node_id, EffectInfoBase& effect_info, + const s16 buffer_offset) { + auto& cmd{GenerateStart<DelayCommand, CommandId::Delay>(node_id)}; + + const auto& parameter{ + *reinterpret_cast<DelayInfo::ParameterVersion1*>(effect_info.GetParameter())}; + const auto state{effect_info.GetStateBuffer()}; + + if (IsChannelCountValid(parameter.channel_count)) { + const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(DelayInfo::State))}; + if (state_buffer) { + for (s16 channel = 0; channel < parameter.channel_count; channel++) { + cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; + cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; + } + + if (!behavior->IsDelayChannelMappingChanged() && parameter.channel_count == 6) { + UseOldChannelMapping(cmd.inputs, cmd.outputs); + } + + cmd.parameter = parameter; + cmd.effect_enabled = effect_info.IsEnabled(); + cmd.state = state_buffer; + cmd.workbuffer = effect_info.GetWorkbuffer(-1); + } + } + + GenerateEnd<DelayCommand>(cmd); +} + +void CommandBuffer::GenerateUpsampleCommand(const s32 node_id, const s16 buffer_offset, + UpsamplerInfo& upsampler_info, const u32 input_count, + std::span<const s8> inputs, const s16 buffer_count, + const u32 sample_count_, const u32 sample_rate_) { + auto& cmd{GenerateStart<UpsampleCommand, CommandId::Upsample>(node_id)}; + + cmd.samples_buffer = memory_pool->Translate(upsampler_info.samples_pos, + upsampler_info.sample_count * sizeof(s32)); + cmd.inputs = memory_pool->Translate(CpuAddr(upsampler_info.inputs.data()), MaxChannels); + cmd.buffer_count = buffer_count; + cmd.unk_20 = 0; + cmd.source_sample_count = sample_count_; + cmd.source_sample_rate = sample_rate_; + + upsampler_info.input_count = input_count; + for (u32 i = 0; i < input_count; i++) { + upsampler_info.inputs[i] = buffer_offset + inputs[i]; + } + + cmd.upsampler_info = memory_pool->Translate(CpuAddr(&upsampler_info), sizeof(UpsamplerInfo)); + + GenerateEnd<UpsampleCommand>(cmd); +} + +void CommandBuffer::GenerateDownMix6chTo2chCommand(const s32 node_id, std::span<const s8> inputs, + const s16 buffer_offset, + std::span<const f32> downmix_coeff) { + auto& cmd{GenerateStart<DownMix6chTo2chCommand, CommandId::DownMix6chTo2ch>(node_id)}; + + for (u32 i = 0; i < MaxChannels; i++) { + cmd.inputs[i] = buffer_offset + inputs[i]; + cmd.outputs[i] = buffer_offset + inputs[i]; + } + + for (u32 i = 0; i < 4; i++) { + cmd.down_mix_coeff[i] = downmix_coeff[i]; + } + + GenerateEnd<DownMix6chTo2chCommand>(cmd); +} + +void CommandBuffer::GenerateAuxCommand(const s32 node_id, EffectInfoBase& effect_info, + const s16 input_index, const s16 output_index, + const s16 buffer_offset, const u32 update_count, + const u32 count_max, const u32 write_offset) { + auto& cmd{GenerateStart<AuxCommand, CommandId::Aux>(node_id)}; + + if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) { + cmd.input = buffer_offset + input_index; + cmd.output = buffer_offset + output_index; + cmd.send_buffer_info = effect_info.GetSendBufferInfo(); + cmd.send_buffer = effect_info.GetSendBuffer(); + cmd.return_buffer_info = effect_info.GetReturnBufferInfo(); + cmd.return_buffer = effect_info.GetReturnBuffer(); + cmd.count_max = count_max; + cmd.write_offset = write_offset; + cmd.update_count = update_count; + cmd.effect_enabled = effect_info.IsEnabled(); + } + + GenerateEnd<AuxCommand>(cmd); +} + +void CommandBuffer::GenerateDeviceSinkCommand(const s32 node_id, const s16 buffer_offset, + SinkInfoBase& sink_info, const u32 session_id, + std::span<s32> samples_buffer) { + auto& cmd{GenerateStart<DeviceSinkCommand, CommandId::DeviceSink>(node_id)}; + const auto& parameter{ + *reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())}; + auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())}; + + cmd.session_id = session_id; + + if (state.upsampler_info != nullptr) { + const auto size_{state.upsampler_info->sample_count * parameter.input_count}; + const auto size_bytes{size_ * sizeof(s32)}; + const auto addr{memory_pool->Translate(state.upsampler_info->samples_pos, size_bytes)}; + cmd.sample_buffer = {reinterpret_cast<s32*>(addr), + parameter.input_count * state.upsampler_info->sample_count}; + } else { + cmd.sample_buffer = samples_buffer; + } + + cmd.input_count = parameter.input_count; + for (u32 i = 0; i < parameter.input_count; i++) { + cmd.inputs[i] = buffer_offset + parameter.inputs[i]; + } + + GenerateEnd<DeviceSinkCommand>(cmd); +} + +void CommandBuffer::GenerateCircularBufferSinkCommand(const s32 node_id, SinkInfoBase& sink_info, + const s16 buffer_offset) { + auto& cmd{GenerateStart<CircularBufferSinkCommand, CommandId::CircularBufferSink>(node_id)}; + const auto& parameter{*reinterpret_cast<CircularBufferSinkInfo::CircularBufferInParameter*>( + sink_info.GetParameter())}; + auto state{ + *reinterpret_cast<CircularBufferSinkInfo::CircularBufferState*>(sink_info.GetState())}; + + cmd.input_count = parameter.input_count; + for (u32 i = 0; i < parameter.input_count; i++) { + cmd.inputs[i] = buffer_offset + parameter.inputs[i]; + } + + cmd.address = state.address_info.GetReference(true); + cmd.size = parameter.size; + cmd.pos = state.current_pos; + + GenerateEnd<CircularBufferSinkCommand>(cmd); +} + +void CommandBuffer::GenerateReverbCommand(const s32 node_id, EffectInfoBase& effect_info, + const s16 buffer_offset, + const bool long_size_pre_delay_supported) { + auto& cmd{GenerateStart<ReverbCommand, CommandId::Reverb>(node_id)}; + + const auto& parameter{ + *reinterpret_cast<ReverbInfo::ParameterVersion2*>(effect_info.GetParameter())}; + const auto state{effect_info.GetStateBuffer()}; + + if (IsChannelCountValid(parameter.channel_count)) { + const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(ReverbInfo::State))}; + if (state_buffer) { + for (s16 channel = 0; channel < parameter.channel_count; channel++) { + cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; + cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; + } + + if (!behavior->IsReverbChannelMappingChanged() && parameter.channel_count == 6) { + UseOldChannelMapping(cmd.inputs, cmd.outputs); + } + + cmd.parameter = parameter; + cmd.effect_enabled = effect_info.IsEnabled(); + cmd.state = state_buffer; + cmd.workbuffer = effect_info.GetWorkbuffer(-1); + cmd.long_size_pre_delay_supported = long_size_pre_delay_supported; + } + } + + GenerateEnd<ReverbCommand>(cmd); +} + +void CommandBuffer::GenerateI3dl2ReverbCommand(const s32 node_id, EffectInfoBase& effect_info, + const s16 buffer_offset) { + auto& cmd{GenerateStart<I3dl2ReverbCommand, CommandId::I3dl2Reverb>(node_id)}; + + const auto& parameter{ + *reinterpret_cast<I3dl2ReverbInfo::ParameterVersion1*>(effect_info.GetParameter())}; + const auto state{effect_info.GetStateBuffer()}; + + if (IsChannelCountValid(parameter.channel_count)) { + const auto state_buffer{ + memory_pool->Translate(CpuAddr(state), sizeof(I3dl2ReverbInfo::State))}; + if (state_buffer) { + for (s16 channel = 0; channel < parameter.channel_count; channel++) { + cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; + cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; + } + + if (!behavior->IsI3dl2ReverbChannelMappingChanged() && parameter.channel_count == 6) { + UseOldChannelMapping(cmd.inputs, cmd.outputs); + } + + cmd.parameter = parameter; + cmd.effect_enabled = effect_info.IsEnabled(); + cmd.state = state_buffer; + cmd.workbuffer = effect_info.GetWorkbuffer(-1); + } + } + + GenerateEnd<I3dl2ReverbCommand>(cmd); +} + +void CommandBuffer::GeneratePerformanceCommand(const s32 node_id, const PerformanceState state, + const PerformanceEntryAddresses& entry_addresses) { + auto& cmd{GenerateStart<PerformanceCommand, CommandId::Performance>(node_id)}; + + cmd.state = state; + cmd.entry_address = entry_addresses; + + GenerateEnd<PerformanceCommand>(cmd); +} + +void CommandBuffer::GenerateClearMixCommand(const s32 node_id) { + auto& cmd{GenerateStart<ClearMixBufferCommand, CommandId::ClearMixBuffer>(node_id)}; + GenerateEnd<ClearMixBufferCommand>(cmd); +} + +void CommandBuffer::GenerateCopyMixBufferCommand(const s32 node_id, EffectInfoBase& effect_info, + const s16 buffer_offset, const s8 channel) { + auto& cmd{GenerateStart<CopyMixBufferCommand, CommandId::CopyMixBuffer>(node_id)}; + + const auto& parameter{ + *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())}; + cmd.input_index = buffer_offset + parameter.inputs[channel]; + cmd.output_index = buffer_offset + parameter.outputs[channel]; + + GenerateEnd<CopyMixBufferCommand>(cmd); +} + +void CommandBuffer::GenerateLightLimiterCommand( + const s32 node_id, const s16 buffer_offset, + const LightLimiterInfo::ParameterVersion1& parameter, const LightLimiterInfo::State& state, + const bool enabled, const CpuAddr workbuffer) { + auto& cmd{GenerateStart<LightLimiterVersion1Command, CommandId::LightLimiterVersion1>(node_id)}; + + if (IsChannelCountValid(parameter.channel_count)) { + const auto state_buffer{ + memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))}; + if (state_buffer) { + for (s8 channel = 0; channel < parameter.channel_count; channel++) { + cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; + cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; + } + + std::memcpy(&cmd.parameter, ¶meter, sizeof(LightLimiterInfo::ParameterVersion1)); + cmd.effect_enabled = enabled; + cmd.state = state_buffer; + cmd.workbuffer = workbuffer; + } + } + + GenerateEnd<LightLimiterVersion1Command>(cmd); +} + +void CommandBuffer::GenerateLightLimiterCommand( + const s32 node_id, const s16 buffer_offset, + const LightLimiterInfo::ParameterVersion2& parameter, + const LightLimiterInfo::StatisticsInternal& statistics, const LightLimiterInfo::State& state, + const bool enabled, const CpuAddr workbuffer) { + auto& cmd{GenerateStart<LightLimiterVersion2Command, CommandId::LightLimiterVersion2>(node_id)}; + if (IsChannelCountValid(parameter.channel_count)) { + const auto state_buffer{ + memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))}; + if (state_buffer) { + for (s8 channel = 0; channel < parameter.channel_count; channel++) { + cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; + cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; + } + + cmd.parameter = parameter; + cmd.effect_enabled = enabled; + cmd.state = state_buffer; + if (cmd.parameter.statistics_enabled) { + cmd.result_state = memory_pool->Translate( + CpuAddr(&statistics), sizeof(LightLimiterInfo::StatisticsInternal)); + } else { + cmd.result_state = 0; + } + cmd.workbuffer = workbuffer; + } + } + + GenerateEnd<LightLimiterVersion2Command>(cmd); +} + +void CommandBuffer::GenerateMultitapBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel) { + auto& cmd{GenerateStart<MultiTapBiquadFilterCommand, CommandId::MultiTapBiquadFilter>(node_id)}; + + cmd.input = buffer_count + channel; + cmd.output = buffer_count + channel; + cmd.biquads = voice_info.biquads; + + cmd.states[0] = + memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()), + MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); + cmd.states[1] = + memory_pool->Translate(CpuAddr(voice_state.biquad_states[1].data()), + MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); + + cmd.needs_init[0] = !voice_info.biquad_initialized[0]; + cmd.needs_init[1] = !voice_info.biquad_initialized[1]; + cmd.filter_tap_count = MaxBiquadFilters; + + GenerateEnd<MultiTapBiquadFilterCommand>(cmd); +} + +void CommandBuffer::GenerateCaptureCommand(const s32 node_id, EffectInfoBase& effect_info, + const s16 input_index, const s16 output_index, + const s16 buffer_offset, const u32 update_count, + const u32 count_max, const u32 write_offset) { + auto& cmd{GenerateStart<CaptureCommand, CommandId::Capture>(node_id)}; + + if (effect_info.GetSendBuffer()) { + cmd.input = buffer_offset + input_index; + cmd.output = buffer_offset + output_index; + cmd.send_buffer_info = effect_info.GetSendBufferInfo(); + cmd.send_buffer = effect_info.GetSendBuffer(); + cmd.count_max = count_max; + cmd.write_offset = write_offset; + cmd.update_count = update_count; + cmd.effect_enabled = effect_info.IsEnabled(); + } + + GenerateEnd<CaptureCommand>(cmd); +} + +void CommandBuffer::GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, + s32 node_id) { + auto& cmd{GenerateStart<CompressorCommand, CommandId::Compressor>(node_id)}; + + auto& parameter{ + *reinterpret_cast<CompressorInfo::ParameterVersion2*>(effect_info.GetParameter())}; + auto state{reinterpret_cast<CompressorInfo::State*>(effect_info.GetStateBuffer())}; + + if (IsChannelCountValid(parameter.channel_count)) { + auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(CompressorInfo::State))}; + if (state_buffer) { + for (u16 channel = 0; channel < parameter.channel_count; channel++) { + cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; + cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; + } + cmd.parameter = parameter; + cmd.workbuffer = state_buffer; + cmd.enabled = effect_info.IsEnabled(); + } + } + + GenerateEnd<CompressorCommand>(cmd); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/command_buffer.h b/src/audio_core/renderer/command/command_buffer.h new file mode 100644 index 000000000..496b0e50a --- /dev/null +++ b/src/audio_core/renderer/command/command_buffer.h @@ -0,0 +1,466 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "audio_core/renderer/command/commands.h" +#include "audio_core/renderer/effect/light_limiter.h" +#include "audio_core/renderer/performance/performance_manager.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +struct UpsamplerInfo; +struct VoiceState; +class EffectInfoBase; +class ICommandProcessingTimeEstimator; +class MixInfo; +class MemoryPoolInfo; +class SinkInfoBase; +class VoiceInfo; + +/** + * Utility functions to generate and add commands into the current command list. + */ +class CommandBuffer { +public: + /** + * Generate a PCM s16 version 1 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param memory_pool - Memory pool for translating buffer addresses to the DSP. + * @param voice_info - The voice info this command is generated from. + * @param voice_state - The voice state the DSP will use for this command. + * @param buffer_count - Number of mix buffers in use, + * data will be read into this index + channel. + * @param channel - Channel index for this command. + */ + void GeneratePcmInt16Version1Command(s32 node_id, const MemoryPoolInfo& memory_pool, + VoiceInfo& voice_info, const VoiceState& voice_state, + s16 buffer_count, s8 channel); + + /** + * Generate a PCM s16 version 2 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param voice_info - The voice info this command is generated from. + * @param voice_state - The voice state the DSP will use for this command. + * @param buffer_count - Number of mix buffers in use, + * data will be read into this index + channel. + * @param channel - Channel index for this command. + */ + void GeneratePcmInt16Version2Command(s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, s16 buffer_count, + s8 channel); + + /** + * Generate a PCM f32 version 1 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param memory_pool - Memory pool for translating buffer addresses to the DSP. + * @param voice_info - The voice info this command is generated from. + * @param voice_state - The voice state the DSP will use for this command. + * @param buffer_count - Number of mix buffers in use, + * data will be read into this index + channel. + * @param channel - Channel index for this command. + */ + void GeneratePcmFloatVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool, + VoiceInfo& voice_info, const VoiceState& voice_state, + s16 buffer_count, s8 channel); + + /** + * Generate a PCM f32 version 2 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param voice_info - The voice info this command is generated from. + * @param voice_state - The voice state the DSP will use for this command. + * @param buffer_count - Number of mix buffers in use, + * data will be read into this index + channel. + * @param channel - Channel index for this command. + */ + void GeneratePcmFloatVersion2Command(s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, s16 buffer_count, + s8 channel); + + /** + * Generate an ADPCM version 1 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param memory_pool - Memory pool for translating buffer addresses to the DSP. + * @param voice_info - The voice info this command is generated from. + * @param voice_state - The voice state the DSP will use for this command. + * @param buffer_count - Number of mix buffers in use, + * data will be read into this index + channel. + * @param channel - Channel index for this command. + */ + void GenerateAdpcmVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool, + VoiceInfo& voice_info, const VoiceState& voice_state, + s16 buffer_count, s8 channel); + + /** + * Generate an ADPCM version 2 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param voice_info - The voice info this command is generated from. + * @param voice_state - The voice state the DSP will use for this command. + * @param buffer_count - Number of mix buffers in use, + * data will be read into this index + channel. + * @param channel - Channel index for this command. + */ + void GenerateAdpcmVersion2Command(s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, s16 buffer_count, s8 channel); + + /** + * Generate a volume command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param buffer_offset - Base mix buffer index to generate this command at. + * @param input_index - Channel index and mix buffer offset for this command. + * @param volume - Mix volume added to the input samples. + * @param precision - Number of decimal bits for fixed point operations. + */ + void GenerateVolumeCommand(s32 node_id, s16 buffer_offset, s16 input_index, f32 volume, + u8 precision); + + /** + * Generate a volume ramp command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param voice_info - The voice info this command takes its volumes from. + * @param buffer_count - Number of active mix buffers, command will generate at this index. + * @param precision - Number of decimal bits for fixed point operations. + */ + void GenerateVolumeRampCommand(s32 node_id, VoiceInfo& voice_info, s16 buffer_count, + u8 precision); + + /** + * Generate a biquad filter command from a voice, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param voice_info - The voice info this command takes biquad parameters from. + * @param voice_state - Used by the AudioRenderer to track previous samples. + * @param buffer_count - Number of active mix buffers, + * command will generate at this index + channel. + * @param channel - Channel index for this filter to work on. + * @param biquad_index - Which biquad filter to use for this command (0-1). + * @param use_float_processing - Should int or float processing be used? + */ + void GenerateBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, s16 buffer_count, s8 channel, + u32 biquad_index, bool use_float_processing); + + /** + * Generate a biquad filter effect command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param effect_info - The effect info this command takes biquad parameters from. + * @param buffer_offset - Mix buffer offset this command will use, + * command will generate at this index + channel. + * @param channel - Channel index for this filter to work on. + * @param needs_init - True if the biquad state needs initialisation. + * @param use_float_processing - Should int or float processing be used? + */ + void GenerateBiquadFilterCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset, + s8 channel, bool needs_init, bool use_float_processing); + + /** + * Generate a mix command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param input_index - Input mix buffer index for this command. + * Added to the buffer offset. + * @param output_index - Output mix buffer index for this command. + * Added to the buffer offset. + * @param buffer_offset - Mix buffer offset this command will use. + * @param volume - Volume to be applied to the input. + * @param precision - Number of decimal bits for fixed point operations. + */ + void GenerateMixCommand(s32 node_id, s16 input_index, s16 output_index, s16 buffer_offset, + f32 volume, u8 precision); + + /** + * Generate a mix ramp command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param buffer_count - Number of active mix buffers. + * @param input_index - Input mix buffer index for this command. + * Added to buffer_count. + * @param output_index - Output mix buffer index for this command. + * Added to buffer_count. + * @param volume - Current mix volume used for calculating the ramp. + * @param prev_volume - Previous mix volume, used for calculating the ramp, + * also applied to the input. + * @param precision - Number of decimal bits for fixed point operations. + */ + void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index, + f32 volume, f32 prev_volume, CpuAddr prev_samples, u8 precision); + + /** + * Generate a mix ramp grouped command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param buffer_count - Number of active mix buffers. + * @param input_index - Input mix buffer index for this command. + * Added to buffer_count. + * @param output_index - Output mix buffer index for this command. + * Added to buffer_count. + * @param volumes - Current mix volumes used for calculating the ramp. + * @param prev_volumes - Previous mix volumes, used for calculating the ramp, + * also applied to the input. + * @param precision - Number of decimal bits for fixed point operations. + */ + void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index, + s16 output_index, std::span<const f32> volumes, + std::span<const f32> prev_volumes, CpuAddr prev_samples, + u8 precision); + + /** + * Generate a depop prepare command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param voice_state - State to track the previous depop samples for each mix buffer. + * @param buffer - State to track the current depop samples for each mix buffer. + * @param buffer_count - Number of active mix buffers. + * @param buffer_offset - Base mix buffer index to generate the channel depops at. + * @param was_playing - Command only needs to work if the voice was previously playing. + */ + void GenerateDepopPrepareCommand(s32 node_id, const VoiceState& voice_state, + std::span<const s32> buffer, s16 buffer_count, + s16 buffer_offset, bool was_playing); + + /** + * Generate a depop command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param mix_info - Mix info to get the buffer count and base offsets from. + * @param depop_buffer - Buffer of current depop sample values to be added to the input + * channels. + */ + void GenerateDepopForMixBuffersCommand(s32 node_id, const MixInfo& mix_info, + std::span<const s32> depop_buffer); + + /** + * Generate a delay command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param effect_info - Delay effect info to generate this command from. + * @param buffer_offset - Base mix buffer offset to apply the apply the delay. + */ + void GenerateDelayCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset); + + /** + * Generate an upsample command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param buffer_offset - Base mix buffer offset to upsample. + * @param upsampler_info - Upsampler info to control the upsampling. + * @param input_count - Number of input channels to upsample. + * @param inputs - Input mix buffer indexes. + * @param buffer_count - Number of active mix buffers. + * @param sample_count - Source sample count of the input. + * @param sample_rate - Source sample rate of the input. + */ + void GenerateUpsampleCommand(s32 node_id, s16 buffer_offset, UpsamplerInfo& upsampler_info, + u32 input_count, std::span<const s8> inputs, s16 buffer_count, + u32 sample_count, u32 sample_rate); + + /** + * Generate a downmix 6 -> 2 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param inputs - Input mix buffer indexes. + * @param buffer_offset - Base mix buffer offset of the channels to downmix. + * @param downmix_coeff - Downmixing coefficients. + */ + void GenerateDownMix6chTo2chCommand(s32 node_id, std::span<const s8> inputs, s16 buffer_offset, + std::span<const f32> downmix_coeff); + + /** + * Generate an aux buffer command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param effect_info - Aux effect info to generate this command from. + * @param input_index - Input mix buffer index for this command. + * Added to buffer_offset. + * @param output_index - Output mix buffer index for this command. + * Added to buffer_offset. + * @param buffer_offset - Base mix buffer offset to use. + * @param update_count - Number of samples to write back to the game as updated, can be 0. + * @param count_max - Maximum number of samples to read or write. + * @param write_offset - Current read or write offset within the buffer. + */ + void GenerateAuxCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index, + s16 output_index, s16 buffer_offset, u32 update_count, u32 count_max, + u32 write_offset); + + /** + * Generate a device sink command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param sink_info - The sink_info to generate this command from. + * @session_id - System session id this command is generated from. + * @samples_buffer - The buffer to be sent to the sink if upsampling is not used. + */ + void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info, + u32 session_id, std::span<s32> samples_buffer); + + /** + * Generate a circular buffer sink command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param sink_info - The sink_info to generate this command from. + * @param buffer_offset - Base mix buffer offset to use. + */ + void GenerateCircularBufferSinkCommand(s32 node_id, SinkInfoBase& sink_info, s16 buffer_offset); + + /** + * Generate a reverb command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param effect_info - Reverb effect info to generate this command from. + * @param buffer_offset - Base mix buffer offset to use. + * @param long_size_pre_delay_supported - Should a longer pre-delay time be used before reverb + * begins? + */ + void GenerateReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset, + bool long_size_pre_delay_supported); + + /** + * Generate an I3DL2 reverb command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param effect_info - I3DL2Reverb effect info to generate this command from. + * @param buffer_offset - Base mix buffer offset to use. + */ + void GenerateI3dl2ReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset); + + /** + * Generate a performance command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param state - State of the performance. + * @param entry_addresses - The addresses to be filled in by the AudioRenderer. + */ + void GeneratePerformanceCommand(s32 node_id, PerformanceState state, + const PerformanceEntryAddresses& entry_addresses); + + /** + * Generate a clear mix command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + */ + void GenerateClearMixCommand(s32 node_id); + + /** + * Generate a copy mix command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param effect_info - BiquadFilter effect info to generate this command from. + * @param buffer_offset - Base mix buffer offset to use. + * @param channel - Index to the effect's parameters input indexes for this command. + */ + void GenerateCopyMixBufferCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset, + s8 channel); + + /** + * Generate a light limiter version 1 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param parameter - Effect parameter to generate from. + * @param state - State used by the AudioRenderer between commands. + * @param enabled - Is this command enabled? + * @param workbuffer - Game-supplied memory for the state. + */ + void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset, + const LightLimiterInfo::ParameterVersion1& parameter, + const LightLimiterInfo::State& state, bool enabled, + CpuAddr workbuffer); + + /** + * Generate a light limiter version 2 command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param parameter - Effect parameter to generate from. + * @param statistics - Statistics reported by the AudioRenderer on the limiter's state. + * @param state - State used by the AudioRenderer between commands. + * @param enabled - Is this command enabled? + * @param workbuffer - Game-supplied memory for the state. + */ + void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset, + const LightLimiterInfo::ParameterVersion2& parameter, + const LightLimiterInfo::StatisticsInternal& statistics, + const LightLimiterInfo::State& state, bool enabled, + CpuAddr workbuffer); + + /** + * Generate a multitap biquad filter command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param voice_info - The voice info this command takes biquad parameters from. + * @param voice_state - Used by the AudioRenderer to track previous samples. + * @param buffer_count - Number of active mix buffers, + * command will generate at this index + channel. + * @param channel - Channel index for this filter to work on. + */ + void GenerateMultitapBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info, + const VoiceState& voice_state, s16 buffer_count, + s8 channel); + + /** + * Generate a capture command, adding it to the command list. + * + * @param node_id - Node id of the voice this command is generated for. + * @param effect_info - Capture effect info to generate this command from. + * @param input_index - Input mix buffer index for this command. + * Added to buffer_offset. + * @param output_index - Output mix buffer index for this command (unused). + * Added to buffer_offset. + * @param buffer_offset - Base mix buffer offset to use. + * @param update_count - Number of samples to write back to the game as updated, can be 0. + * @param count_max - Maximum number of samples to read or write. + * @param write_offset - Current read or write offset within the buffer. + */ + void GenerateCaptureCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index, + s16 output_index, s16 buffer_offset, u32 update_count, + u32 count_max, u32 write_offset); + + /** + * Generate a compressor command, adding it to the command list. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info - Capture effect info to generate this command from. + * @param node_id - Node id of the voice this command is generated for. + */ + void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); + + /// Command list buffer generated commands will be added to + std::span<u8> command_list{}; + /// Input sample count, unused + u32 sample_count{}; + /// Input sample rate, unused + u32 sample_rate{}; + /// Current size of the command buffer + u64 size{}; + /// Current number of commands added + u32 count{}; + /// Current estimated processing time for all commands + u32 estimated_process_time{}; + /// Used for mapping buffers for the AudioRenderer + MemoryPoolInfo* memory_pool{}; + /// Used for estimating command process times + ICommandProcessingTimeEstimator* time_estimator{}; + /// Used to check which rendering features are currently enabled + BehaviorInfo* behavior{}; + +private: + template <typename T, CommandId Id> + T& GenerateStart(const s32 node_id); + template <typename T> + void GenerateEnd(T& cmd); +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/command_generator.cpp b/src/audio_core/renderer/command/command_generator.cpp new file mode 100644 index 000000000..2ea50d128 --- /dev/null +++ b/src/audio_core/renderer/command/command_generator.cpp @@ -0,0 +1,796 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/common/audio_renderer_parameter.h" +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/command/command_buffer.h" +#include "audio_core/renderer/command/command_generator.h" +#include "audio_core/renderer/command/command_list_header.h" +#include "audio_core/renderer/effect/aux_.h" +#include "audio_core/renderer/effect/biquad_filter.h" +#include "audio_core/renderer/effect/buffer_mixer.h" +#include "audio_core/renderer/effect/capture.h" +#include "audio_core/renderer/effect/effect_context.h" +#include "audio_core/renderer/effect/light_limiter.h" +#include "audio_core/renderer/mix/mix_context.h" +#include "audio_core/renderer/performance/detail_aspect.h" +#include "audio_core/renderer/performance/entry_aspect.h" +#include "audio_core/renderer/sink/device_sink_info.h" +#include "audio_core/renderer/sink/sink_context.h" +#include "audio_core/renderer/splitter/splitter_context.h" +#include "audio_core/renderer/voice/voice_context.h" +#include "common/alignment.h" + +namespace AudioCore::AudioRenderer { + +CommandGenerator::CommandGenerator(CommandBuffer& command_buffer_, + const CommandListHeader& command_list_header_, + const AudioRendererSystemContext& render_context_, + VoiceContext& voice_context_, MixContext& mix_context_, + EffectContext& effect_context_, SinkContext& sink_context_, + SplitterContext& splitter_context_, + PerformanceManager* performance_manager_) + : command_buffer{command_buffer_}, command_header{command_list_header_}, + render_context{render_context_}, voice_context{voice_context_}, mix_context{mix_context_}, + effect_context{effect_context_}, sink_context{sink_context_}, + splitter_context{splitter_context_}, performance_manager{performance_manager_} { + command_buffer.GenerateClearMixCommand(InvalidNodeId); +} + +void CommandGenerator::GenerateDataSourceCommand(VoiceInfo& voice_info, + const VoiceState& voice_state, const s8 channel) { + if (voice_info.mix_id == UnusedMixId) { + if (voice_info.splitter_id != UnusedSplitterId) { + auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, 0)}; + u32 dest_id{0}; + while (destination != nullptr) { + if (destination->IsConfigured()) { + auto mix_id{destination->GetMixId()}; + if (mix_id < mix_context.GetCount()) { + auto mix_info{mix_context.GetInfo(mix_id)}; + command_buffer.GenerateDepopPrepareCommand( + voice_info.node_id, voice_state, render_context.depop_buffer, + mix_info->buffer_count, mix_info->buffer_offset, + voice_info.was_playing); + } + } + dest_id++; + destination = splitter_context.GetDesintationData(voice_info.splitter_id, dest_id); + } + } + } else { + auto mix_info{mix_context.GetInfo(voice_info.mix_id)}; + command_buffer.GenerateDepopPrepareCommand( + voice_info.node_id, voice_state, render_context.depop_buffer, mix_info->buffer_count, + mix_info->buffer_offset, voice_info.was_playing); + } + + if (voice_info.was_playing) { + return; + } + + if (render_context.behavior->IsWaveBufferVer2Supported()) { + switch (voice_info.sample_format) { + case SampleFormat::PcmInt16: + command_buffer.GeneratePcmInt16Version2Command( + voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count, + channel); + break; + case SampleFormat::PcmFloat: + command_buffer.GeneratePcmFloatVersion2Command( + voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count, + channel); + break; + case SampleFormat::Adpcm: + command_buffer.GenerateAdpcmVersion2Command(voice_info.node_id, voice_info, voice_state, + render_context.mix_buffer_count, channel); + break; + default: + LOG_ERROR(Service_Audio, "Invalid SampleFormat {}", + static_cast<u32>(voice_info.sample_format)); + break; + } + } else { + switch (voice_info.sample_format) { + case SampleFormat::PcmInt16: + command_buffer.GeneratePcmInt16Version1Command( + voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state, + render_context.mix_buffer_count, channel); + break; + case SampleFormat::PcmFloat: + command_buffer.GeneratePcmFloatVersion1Command( + voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state, + render_context.mix_buffer_count, channel); + break; + case SampleFormat::Adpcm: + command_buffer.GenerateAdpcmVersion1Command( + voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state, + render_context.mix_buffer_count, channel); + break; + default: + LOG_ERROR(Service_Audio, "Invalid SampleFormat {}", + static_cast<u32>(voice_info.sample_format)); + break; + } + } +} + +void CommandGenerator::GenerateVoiceMixCommand(std::span<const f32> mix_volumes, + std::span<const f32> prev_mix_volumes, + const VoiceState& voice_state, s16 output_index, + const s16 buffer_count, const s16 input_index, + const s32 node_id) { + u8 precision{15}; + if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { + precision = 23; + } + + if (buffer_count > 8) { + const auto prev_samples{render_context.memory_pool_info->Translate( + CpuAddr(voice_state.previous_samples.data()), buffer_count * sizeof(s32))}; + command_buffer.GenerateMixRampGroupedCommand(node_id, buffer_count, input_index, + output_index, mix_volumes, prev_mix_volumes, + prev_samples, precision); + } else { + for (s16 i = 0; i < buffer_count; i++, output_index++) { + const auto prev_samples{render_context.memory_pool_info->Translate( + CpuAddr(&voice_state.previous_samples[i]), sizeof(s32))}; + + command_buffer.GenerateMixRampCommand(node_id, buffer_count, input_index, output_index, + mix_volumes[i], prev_mix_volumes[i], prev_samples, + precision); + } + } +} + +void CommandGenerator::GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, + const VoiceState& voice_state, + const s16 buffer_count, const s8 channel, + const s32 node_id) { + const bool both_biquads_enabled{voice_info.biquads[0].enabled && voice_info.biquads[1].enabled}; + const auto use_float_processing{render_context.behavior->UseBiquadFilterFloatProcessing()}; + + if (both_biquads_enabled && render_context.behavior->UseMultiTapBiquadFilterProcessing() && + use_float_processing) { + command_buffer.GenerateMultitapBiquadFilterCommand(node_id, voice_info, voice_state, + buffer_count, channel); + } else { + for (u32 i = 0; i < MaxBiquadFilters; i++) { + if (voice_info.biquads[i].enabled) { + command_buffer.GenerateBiquadFilterCommand(node_id, voice_info, voice_state, + buffer_count, channel, i, + use_float_processing); + } + } + } +} + +void CommandGenerator::GenerateVoiceCommand(VoiceInfo& voice_info) { + u8 precision{15}; + if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { + precision = 23; + } + + for (s8 channel = 0; channel < voice_info.channel_count; channel++) { + const auto resource_id{voice_info.channel_resource_ids[channel]}; + auto& voice_state{voice_context.GetDspSharedState(resource_id)}; + auto& channel_resource{voice_context.GetChannelResource(resource_id)}; + + PerformanceDetailType detail_type{PerformanceDetailType::Invalid}; + switch (voice_info.sample_format) { + case SampleFormat::PcmInt16: + detail_type = PerformanceDetailType::Unk1; + break; + case SampleFormat::PcmFloat: + detail_type = PerformanceDetailType::Unk10; + break; + default: + detail_type = PerformanceDetailType::Unk2; + break; + } + + DetailAspect data_source_detail(*this, PerformanceEntryType::Voice, voice_info.node_id, + detail_type); + GenerateDataSourceCommand(voice_info, voice_state, channel); + + if (data_source_detail.initialized) { + command_buffer.GeneratePerformanceCommand(data_source_detail.node_id, + PerformanceState::Stop, + data_source_detail.performance_entry_address); + } + + if (voice_info.was_playing) { + voice_info.prev_volume = 0.0f; + continue; + } + + if (!voice_info.HasAnyConnection()) { + continue; + } + + DetailAspect biquad_detail_aspect(*this, PerformanceEntryType::Voice, voice_info.node_id, + PerformanceDetailType::Unk4); + GenerateBiquadFilterCommandForVoice( + voice_info, voice_state, render_context.mix_buffer_count, channel, voice_info.node_id); + + if (biquad_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + biquad_detail_aspect.node_id, PerformanceState::Stop, + biquad_detail_aspect.performance_entry_address); + } + + DetailAspect volume_ramp_detail_aspect(*this, PerformanceEntryType::Voice, + voice_info.node_id, PerformanceDetailType::Unk3); + command_buffer.GenerateVolumeRampCommand( + voice_info.node_id, voice_info, render_context.mix_buffer_count + channel, precision); + if (volume_ramp_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + volume_ramp_detail_aspect.node_id, PerformanceState::Stop, + volume_ramp_detail_aspect.performance_entry_address); + } + + voice_info.prev_volume = voice_info.volume; + + if (voice_info.mix_id == UnusedMixId) { + if (voice_info.splitter_id != UnusedSplitterId) { + auto i{channel}; + auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, i)}; + while (destination != nullptr) { + if (destination->IsConfigured()) { + const auto mix_id{destination->GetMixId()}; + if (mix_id < mix_context.GetCount() && + static_cast<s32>(mix_id) != UnusedSplitterId) { + auto mix_info{mix_context.GetInfo(mix_id)}; + GenerateVoiceMixCommand( + destination->GetMixVolume(), destination->GetMixVolumePrev(), + voice_state, mix_info->buffer_offset, mix_info->buffer_count, + render_context.mix_buffer_count + channel, voice_info.node_id); + destination->MarkAsNeedToUpdateInternalState(); + } + } + i += voice_info.channel_count; + destination = splitter_context.GetDesintationData(voice_info.splitter_id, i); + } + } + } else { + DetailAspect volume_mix_detail_aspect(*this, PerformanceEntryType::Voice, + voice_info.node_id, PerformanceDetailType::Unk3); + auto mix_info{mix_context.GetInfo(voice_info.mix_id)}; + GenerateVoiceMixCommand(channel_resource.mix_volumes, channel_resource.prev_mix_volumes, + voice_state, mix_info->buffer_offset, mix_info->buffer_count, + render_context.mix_buffer_count + channel, voice_info.node_id); + if (volume_mix_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + volume_mix_detail_aspect.node_id, PerformanceState::Stop, + volume_mix_detail_aspect.performance_entry_address); + } + + channel_resource.prev_mix_volumes = channel_resource.mix_volumes; + } + voice_info.biquad_initialized[0] = voice_info.biquads[0].enabled; + voice_info.biquad_initialized[1] = voice_info.biquads[1].enabled; + } +} + +void CommandGenerator::GenerateVoiceCommands() { + const auto voice_count{voice_context.GetCount()}; + + for (u32 i = 0; i < voice_count; i++) { + auto sorted_info{voice_context.GetSortedInfo(i)}; + + if (sorted_info->ShouldSkip() || !sorted_info->UpdateForCommandGeneration(voice_context)) { + continue; + } + + EntryAspect voice_entry_aspect(*this, PerformanceEntryType::Voice, sorted_info->node_id); + + GenerateVoiceCommand(*sorted_info); + + if (voice_entry_aspect.initialized) { + command_buffer.GeneratePerformanceCommand(voice_entry_aspect.node_id, + PerformanceState::Stop, + voice_entry_aspect.performance_entry_address); + } + } + + splitter_context.UpdateInternalState(); +} + +void CommandGenerator::GenerateBufferMixerCommand(const s16 buffer_offset, + EffectInfoBase& effect_info, const s32 node_id) { + u8 precision{15}; + if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { + precision = 23; + } + + if (effect_info.IsEnabled()) { + const auto& parameter{ + *reinterpret_cast<BufferMixerInfo::ParameterVersion1*>(effect_info.GetParameter())}; + for (u32 i = 0; i < parameter.mix_count; i++) { + if (parameter.volumes[i] != 0.0f) { + command_buffer.GenerateMixCommand(node_id, buffer_offset + parameter.inputs[i], + buffer_offset + parameter.outputs[i], + buffer_offset, parameter.volumes[i], precision); + } + } + } +} + +void CommandGenerator::GenerateDelayCommand(const s16 buffer_offset, EffectInfoBase& effect_info, + const s32 node_id) { + command_buffer.GenerateDelayCommand(node_id, effect_info, buffer_offset); +} + +void CommandGenerator::GenerateReverbCommand(const s16 buffer_offset, EffectInfoBase& effect_info, + const s32 node_id, + const bool long_size_pre_delay_supported) { + command_buffer.GenerateReverbCommand(node_id, effect_info, buffer_offset, + long_size_pre_delay_supported); +} + +void CommandGenerator::GenerateI3dl2ReverbEffectCommand(const s16 buffer_offset, + EffectInfoBase& effect_info, + const s32 node_id) { + command_buffer.GenerateI3dl2ReverbCommand(node_id, effect_info, buffer_offset); +} + +void CommandGenerator::GenerateAuxCommand(const s16 buffer_offset, EffectInfoBase& effect_info, + const s32 node_id) { + + if (effect_info.IsEnabled()) { + effect_info.GetWorkbuffer(0); + effect_info.GetWorkbuffer(1); + } + + if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) { + const auto& parameter{ + *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())}; + auto channel_index{parameter.mix_buffer_count - 1}; + u32 write_offset{0}; + for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) { + auto new_update_count{command_header.sample_count + write_offset}; + const auto update_count{channel_index > 0 ? 0 : new_update_count}; + command_buffer.GenerateAuxCommand(node_id, effect_info, parameter.inputs[i], + parameter.outputs[i], buffer_offset, update_count, + parameter.count_max, write_offset); + write_offset = new_update_count; + } + } +} + +void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset, + EffectInfoBase& effect_info, + const s32 node_id) { + const auto& parameter{ + *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())}; + if (effect_info.IsEnabled()) { + bool needs_init{false}; + + switch (parameter.state) { + case EffectInfoBase::ParameterState::Initialized: + needs_init = true; + break; + case EffectInfoBase::ParameterState::Updating: + case EffectInfoBase::ParameterState::Updated: + if (render_context.behavior->IsBiquadFilterEffectStateClearBugFixed()) { + needs_init = false; + } else { + needs_init = parameter.state == EffectInfoBase::ParameterState::Updating; + } + break; + default: + LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}", + static_cast<u32>(parameter.state)); + break; + } + + for (s8 channel = 0; channel < parameter.channel_count; channel++) { + command_buffer.GenerateBiquadFilterCommand( + node_id, effect_info, buffer_offset, channel, needs_init, + render_context.behavior->UseBiquadFilterFloatProcessing()); + } + } else { + for (s8 channel = 0; channel < parameter.channel_count; channel++) { + command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset, + channel); + } + } +} + +void CommandGenerator::GenerateLightLimiterEffectCommand(const s16 buffer_offset, + EffectInfoBase& effect_info, + const s32 node_id, + const u32 effect_index) { + + const auto& state{*reinterpret_cast<LightLimiterInfo::State*>(effect_info.GetStateBuffer())}; + + if (render_context.behavior->IsEffectInfoVersion2Supported()) { + const auto& parameter{ + *reinterpret_cast<LightLimiterInfo::ParameterVersion2*>(effect_info.GetParameter())}; + const auto& result_state{*reinterpret_cast<LightLimiterInfo::StatisticsInternal*>( + &effect_context.GetDspSharedResultState(effect_index))}; + command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, result_state, + state, effect_info.IsEnabled(), + effect_info.GetWorkbuffer(-1)); + } else { + const auto& parameter{ + *reinterpret_cast<LightLimiterInfo::ParameterVersion1*>(effect_info.GetParameter())}; + command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, state, + effect_info.IsEnabled(), + effect_info.GetWorkbuffer(-1)); + } +} + +void CommandGenerator::GenerateCaptureCommand(const s16 buffer_offset, EffectInfoBase& effect_info, + const s32 node_id) { + if (effect_info.IsEnabled()) { + effect_info.GetWorkbuffer(0); + } + + if (effect_info.GetSendBuffer()) { + const auto& parameter{ + *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())}; + auto channel_index{parameter.mix_buffer_count - 1}; + u32 write_offset{0}; + for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) { + auto new_update_count{command_header.sample_count + write_offset}; + const auto update_count{channel_index > 0 ? 0 : new_update_count}; + command_buffer.GenerateCaptureCommand(node_id, effect_info, parameter.inputs[i], + parameter.outputs[i], buffer_offset, update_count, + parameter.count_max, write_offset); + write_offset = new_update_count; + } + } +} + +void CommandGenerator::GenerateCompressorCommand(const s16 buffer_offset, + EffectInfoBase& effect_info, const s32 node_id) { + command_buffer.GenerateCompressorCommand(buffer_offset, effect_info, node_id); +} + +void CommandGenerator::GenerateEffectCommand(MixInfo& mix_info) { + const auto effect_count{effect_context.GetCount()}; + for (u32 i = 0; i < effect_count; i++) { + const auto effect_index{mix_info.effect_order_buffer[i]}; + if (effect_index == -1) { + break; + } + + auto& effect_info = effect_context.GetInfo(effect_index); + if (effect_info.ShouldSkip()) { + continue; + } + + const auto entry_type{mix_info.mix_id == FinalMixId ? PerformanceEntryType::FinalMix + : PerformanceEntryType::SubMix}; + + switch (effect_info.GetType()) { + case EffectInfoBase::Type::Mix: { + DetailAspect mix_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk5); + GenerateBufferMixerCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); + if (mix_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + mix_detail_aspect.node_id, PerformanceState::Stop, + mix_detail_aspect.performance_entry_address); + } + } break; + + case EffectInfoBase::Type::Aux: { + DetailAspect aux_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk7); + GenerateAuxCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); + if (aux_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + aux_detail_aspect.node_id, PerformanceState::Stop, + aux_detail_aspect.performance_entry_address); + } + } break; + + case EffectInfoBase::Type::Delay: { + DetailAspect delay_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk6); + GenerateDelayCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); + if (delay_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + delay_detail_aspect.node_id, PerformanceState::Stop, + delay_detail_aspect.performance_entry_address); + } + } break; + + case EffectInfoBase::Type::Reverb: { + DetailAspect reverb_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk8); + GenerateReverbCommand(mix_info.buffer_offset, effect_info, mix_info.node_id, + render_context.behavior->IsLongSizePreDelaySupported()); + if (reverb_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + reverb_detail_aspect.node_id, PerformanceState::Stop, + reverb_detail_aspect.performance_entry_address); + } + } break; + + case EffectInfoBase::Type::I3dl2Reverb: { + DetailAspect i3dl2_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk9); + GenerateI3dl2ReverbEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); + if (i3dl2_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + i3dl2_detail_aspect.node_id, PerformanceState::Stop, + i3dl2_detail_aspect.performance_entry_address); + } + } break; + + case EffectInfoBase::Type::BiquadFilter: { + DetailAspect biquad_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk4); + GenerateBiquadFilterEffectCommand(mix_info.buffer_offset, effect_info, + mix_info.node_id); + if (biquad_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + biquad_detail_aspect.node_id, PerformanceState::Stop, + biquad_detail_aspect.performance_entry_address); + } + } break; + + case EffectInfoBase::Type::LightLimiter: { + DetailAspect light_limiter_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk11); + GenerateLightLimiterEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id, + effect_index); + if (light_limiter_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + light_limiter_detail_aspect.node_id, PerformanceState::Stop, + light_limiter_detail_aspect.performance_entry_address); + } + } break; + + case EffectInfoBase::Type::Capture: { + DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk12); + GenerateCaptureCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); + if (capture_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + capture_detail_aspect.node_id, PerformanceState::Stop, + capture_detail_aspect.performance_entry_address); + } + } break; + + case EffectInfoBase::Type::Compressor: { + DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id, + PerformanceDetailType::Unk13); + GenerateCompressorCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); + if (capture_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + capture_detail_aspect.node_id, PerformanceState::Stop, + capture_detail_aspect.performance_entry_address); + } + } break; + + default: + LOG_ERROR(Service_Audio, "Invalid effect type {}", + static_cast<u32>(effect_info.GetType())); + break; + } + + effect_info.UpdateForCommandGeneration(); + } +} + +void CommandGenerator::GenerateMixCommands(MixInfo& mix_info) { + u8 precision{15}; + if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { + precision = 23; + } + + if (!mix_info.HasAnyConnection()) { + return; + } + + if (mix_info.dst_mix_id == UnusedMixId) { + if (mix_info.dst_splitter_id != UnusedSplitterId) { + s16 dest_id{0}; + auto destination{ + splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id)}; + while (destination != nullptr) { + if (destination->IsConfigured()) { + auto splitter_mix_id{destination->GetMixId()}; + if (splitter_mix_id < mix_context.GetCount()) { + auto splitter_mix_info{mix_context.GetInfo(splitter_mix_id)}; + const s16 input_index{static_cast<s16>(mix_info.buffer_offset + + (dest_id % mix_info.buffer_count))}; + for (s16 i = 0; i < splitter_mix_info->buffer_count; i++) { + auto volume{mix_info.volume * destination->GetMixVolume(i)}; + if (volume != 0.0f) { + command_buffer.GenerateMixCommand( + mix_info.node_id, input_index, + splitter_mix_info->buffer_offset + i, mix_info.buffer_offset, + volume, precision); + } + } + } + } + dest_id++; + destination = + splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id); + } + } + } else { + auto dest_mix_info{mix_context.GetInfo(mix_info.dst_mix_id)}; + for (s16 i = 0; i < mix_info.buffer_count; i++) { + for (s16 j = 0; j < dest_mix_info->buffer_count; j++) { + auto volume{mix_info.volume * mix_info.mix_volumes[i][j]}; + if (volume != 0.0f) { + command_buffer.GenerateMixCommand(mix_info.node_id, mix_info.buffer_offset + i, + dest_mix_info->buffer_offset + j, + mix_info.buffer_offset, volume, precision); + } + } + } + } +} + +void CommandGenerator::GenerateSubMixCommand(MixInfo& mix_info) { + command_buffer.GenerateDepopForMixBuffersCommand(mix_info.node_id, mix_info, + render_context.depop_buffer); + GenerateEffectCommand(mix_info); + + DetailAspect mix_detail_aspect(*this, PerformanceEntryType::SubMix, mix_info.node_id, + PerformanceDetailType::Unk5); + + GenerateMixCommands(mix_info); + + if (mix_detail_aspect.initialized) { + command_buffer.GeneratePerformanceCommand(mix_detail_aspect.node_id, PerformanceState::Stop, + mix_detail_aspect.performance_entry_address); + } +} + +void CommandGenerator::GenerateSubMixCommands() { + const auto submix_count{mix_context.GetCount()}; + for (s32 i = 0; i < submix_count; i++) { + auto sorted_info{mix_context.GetSortedInfo(i)}; + if (!sorted_info->in_use || sorted_info->mix_id == FinalMixId) { + continue; + } + + EntryAspect submix_entry_aspect(*this, PerformanceEntryType::SubMix, sorted_info->node_id); + + GenerateSubMixCommand(*sorted_info); + + if (submix_entry_aspect.initialized) { + command_buffer.GeneratePerformanceCommand( + submix_entry_aspect.node_id, PerformanceState::Stop, + submix_entry_aspect.performance_entry_address); + } + } +} + +void CommandGenerator::GenerateFinalMixCommand() { + auto& final_mix_info{*mix_context.GetFinalMixInfo()}; + + command_buffer.GenerateDepopForMixBuffersCommand(final_mix_info.node_id, final_mix_info, + render_context.depop_buffer); + GenerateEffectCommand(final_mix_info); + + u8 precision{15}; + if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { + precision = 23; + } + + for (s16 i = 0; i < final_mix_info.buffer_count; i++) { + DetailAspect volume_aspect(*this, PerformanceEntryType::FinalMix, final_mix_info.node_id, + PerformanceDetailType::Unk3); + command_buffer.GenerateVolumeCommand(final_mix_info.node_id, final_mix_info.buffer_offset, + i, final_mix_info.volume, precision); + if (volume_aspect.initialized) { + command_buffer.GeneratePerformanceCommand(volume_aspect.node_id, PerformanceState::Stop, + volume_aspect.performance_entry_address); + } + } +} + +void CommandGenerator::GenerateFinalMixCommands() { + auto final_mix_info{mix_context.GetFinalMixInfo()}; + EntryAspect final_mix_entry(*this, PerformanceEntryType::FinalMix, final_mix_info->node_id); + GenerateFinalMixCommand(); + if (final_mix_entry.initialized) { + command_buffer.GeneratePerformanceCommand(final_mix_entry.node_id, PerformanceState::Stop, + final_mix_entry.performance_entry_address); + } +} + +void CommandGenerator::GenerateSinkCommands() { + const auto sink_count{sink_context.GetCount()}; + + for (u32 i = 0; i < sink_count; i++) { + auto sink_info{sink_context.GetInfo(i)}; + if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::DeviceSink) { + auto state{reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info->GetState())}; + if (command_header.sample_rate != TargetSampleRate && + state->upsampler_info == nullptr) { + auto device_state{sink_info->GetDeviceState()}; + device_state->upsampler_info = render_context.upsampler_manager->Allocate(); + } + + EntryAspect device_sink_entry(*this, PerformanceEntryType::Sink, + sink_info->GetNodeId()); + auto final_mix{mix_context.GetFinalMixInfo()}; + GenerateSinkCommand(final_mix->buffer_offset, *sink_info); + + if (device_sink_entry.initialized) { + command_buffer.GeneratePerformanceCommand( + device_sink_entry.node_id, PerformanceState::Stop, + device_sink_entry.performance_entry_address); + } + } + } + + for (u32 i = 0; i < sink_count; i++) { + auto sink_info{sink_context.GetInfo(i)}; + if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::CircularBufferSink) { + EntryAspect circular_buffer_entry(*this, PerformanceEntryType::Sink, + sink_info->GetNodeId()); + auto final_mix{mix_context.GetFinalMixInfo()}; + GenerateSinkCommand(final_mix->buffer_offset, *sink_info); + + if (circular_buffer_entry.initialized) { + command_buffer.GeneratePerformanceCommand( + circular_buffer_entry.node_id, PerformanceState::Stop, + circular_buffer_entry.performance_entry_address); + } + } + } +} + +void CommandGenerator::GenerateSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) { + if (sink_info.ShouldSkip()) { + return; + } + + switch (sink_info.GetType()) { + case SinkInfoBase::Type::DeviceSink: + GenerateDeviceSinkCommand(buffer_offset, sink_info); + break; + + case SinkInfoBase::Type::CircularBufferSink: + command_buffer.GenerateCircularBufferSinkCommand(sink_info.GetNodeId(), sink_info, + buffer_offset); + break; + + default: + LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(sink_info.GetType())); + break; + } + + sink_info.UpdateForCommandGeneration(); +} + +void CommandGenerator::GenerateDeviceSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) { + auto& parameter{ + *reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())}; + auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())}; + + if (render_context.channels == 2 && parameter.downmix_enabled) { + command_buffer.GenerateDownMix6chTo2chCommand(InvalidNodeId, parameter.inputs, + buffer_offset, parameter.downmix_coeff); + } + + if (state.upsampler_info != nullptr) { + command_buffer.GenerateUpsampleCommand( + InvalidNodeId, buffer_offset, *state.upsampler_info, parameter.input_count, + parameter.inputs, command_header.buffer_count, command_header.sample_count, + command_header.sample_rate); + } + + command_buffer.GenerateDeviceSinkCommand(InvalidNodeId, buffer_offset, sink_info, + render_context.session_id, + command_header.samples_buffer); +} + +void CommandGenerator::GeneratePerformanceCommand( + s32 node_id, PerformanceState state, const PerformanceEntryAddresses& entry_addresses) { + command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/command_generator.h b/src/audio_core/renderer/command/command_generator.h new file mode 100644 index 000000000..d80d9b0d8 --- /dev/null +++ b/src/audio_core/renderer/command/command_generator.h @@ -0,0 +1,349 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "audio_core/renderer/command/commands.h" +#include "audio_core/renderer/performance/performance_manager.h" +#include "common/common_types.h" + +namespace AudioCore { +struct AudioRendererSystemContext; + +namespace AudioRenderer { +class CommandBuffer; +struct CommandListHeader; +class VoiceContext; +class MixContext; +class EffectContext; +class SplitterContext; +class SinkContext; +class BehaviorInfo; +class VoiceInfo; +struct VoiceState; +class MixInfo; +class SinkInfoBase; + +/** + * Generates all commands to build up a command list, which are sent to the AudioRender for + * processing. + */ +class CommandGenerator { +public: + explicit CommandGenerator(CommandBuffer& command_buffer, + const CommandListHeader& command_list_header, + const AudioRendererSystemContext& render_context, + VoiceContext& voice_context, MixContext& mix_context, + EffectContext& effect_context, SinkContext& sink_context, + SplitterContext& splitter_context, + PerformanceManager* performance_manager); + + /** + * Calculate the buffer size needed for commands. + * + * @param behavior - Used to check what features are enabled. + * @param params - Input rendering parameters for numbers of voices/mixes/sinks etc. + */ + static u64 CalculateCommandBufferSize(const BehaviorInfo& behavior, + const AudioRendererParameterInternal& params) { + u64 size{0}; + + // Effects + size += params.effects * sizeof(EffectInfoBase); + + // Voices + u64 voice_size{0}; + if (behavior.IsWaveBufferVer2Supported()) { + voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion2Command), + sizeof(PcmInt16DataSourceVersion2Command)), + sizeof(PcmFloatDataSourceVersion2Command)); + } else { + voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion1Command), + sizeof(PcmInt16DataSourceVersion1Command)), + sizeof(PcmFloatDataSourceVersion1Command)); + } + voice_size += sizeof(BiquadFilterCommand) * MaxBiquadFilters; + voice_size += sizeof(VolumeRampCommand); + voice_size += sizeof(MixRampGroupedCommand); + + size += params.voices * (params.splitter_infos * sizeof(DepopPrepareCommand) + voice_size); + + // Sub mixes + size += sizeof(DepopForMixBuffersCommand) + + (sizeof(MixCommand) * MaxMixBuffers) * MaxMixBuffers; + + // Final mix + size += sizeof(DepopForMixBuffersCommand) + sizeof(VolumeCommand) * MaxMixBuffers; + + // Splitters + size += params.splitter_destinations * sizeof(MixRampCommand) * MaxMixBuffers; + + // Sinks + size += + params.sinks * std::max(sizeof(DeviceSinkCommand), sizeof(CircularBufferSinkCommand)); + + // Performance + size += (params.effects + params.voices + params.sinks + params.sub_mixes + 1 + + PerformanceManager::MaxDetailEntries) * + sizeof(PerformanceCommand); + return size; + } + + /** + * Get the current command buffer used to generate commands. + * + * @return The command buffer. + */ + CommandBuffer& GetCommandBuffer() { + return command_buffer; + } + + /** + * Get the current performance manager, + * + * @return The performance manager. May be nullptr. + */ + PerformanceManager* GetPerformanceManager() { + return performance_manager; + } + + /** + * Generate a data source command. + * These are the basis for all audio output. + * + * @param voice_info - Generate the command from this voice. + * @param voice_state - State used by the AudioRenderer across calls. + * @param channel - Channel index to generate the command into. + */ + void GenerateDataSourceCommand(VoiceInfo& voice_info, const VoiceState& voice_state, + s8 channel); + + /** + * Generate voice mixing commands. + * These are used to mix buffers together, to mix one input to many outputs, + * and also used as copy commands to move data around and prevent it being accidentally + * overwritten, e.g by another data source command into the same channel. + * + * @param mix_volumes - Current volumes of the mix. + * @param prev_mix_volumes - Previous volumes of the mix. + * @param voice_state - State used by the AudioRenderer across calls. + * @param output_index - Output mix buffer index. + * @param buffer_count - Number of active mix buffers. + * @param input_index - Input mix buffer index. + * @param node_id - Node id of the voice this command is generated for. + */ + void GenerateVoiceMixCommand(std::span<const f32> mix_volumes, + std::span<const f32> prev_mix_volumes, + const VoiceState& voice_state, s16 output_index, s16 buffer_count, + s16 input_index, s32 node_id); + + /** + * Generate a biquad filter command for a voice. + * + * @param voice_info - Voice info this command is generated from. + * @param voice_state - State used by the AudioRenderer across calls. + * @param buffer_count - Number of active mix buffers. + * @param channel - Channel index of this command. + * @param node_id - Node id of the voice this command is generated for. + */ + void GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, const VoiceState& voice_state, + s16 buffer_count, s8 channel, s32 node_id); + + /** + * Generate commands for a voice. + * Includes a data source, biquad filter, volume and mixing. + * + * @param voice_info - Voice info these commands are generated from. + */ + void GenerateVoiceCommand(VoiceInfo& voice_info); + + /** + * Generate commands for all voices. + */ + void GenerateVoiceCommands(); + + /** + * Generate a mixing command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - BufferMixer effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateBufferMixerCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, + s32 node_id); + + /** + * Generate a delay effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Delay effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateDelayCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id); + + /** + * Generate a reverb effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Reverb effect info. + * @param node_id - Node id of the mix this command is generated for. + * @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb starts. + */ + void GenerateReverbCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id, + bool long_size_pre_delay_supported); + + /** + * Generate an I3DL2 reverb effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - I3DL2Reverb effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, + s32 node_id); + + /** + * Generate an aux effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Aux effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); + + /** + * Generate a biquad filter effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Aux effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, + s32 node_id); + + /** + * Generate a light limiter effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Limiter effect info. + * @param node_id - Node id of the mix this command is generated for. + * @param effect_index - Index for the statistics state. + */ + void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, + s32 node_id, u32 effect_index); + + /** + * Generate a capture effect command. + * Writes a mix buffer back to game memory. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Capture effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); + + /** + * Generate a compressor effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Compressor effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info, + const s32 node_id); + + /** + * Generate all effect commands for a mix. + * + * @param mix_info - Mix to generate effects from. + */ + void GenerateEffectCommand(MixInfo& mix_info); + + /** + * Generate all mix commands. + * + * @param mix_info - Mix to generate effects from. + */ + void GenerateMixCommands(MixInfo& mix_info); + + /** + * Generate a submix command. + * Generates all effects and all mixing commands. + * + * @param mix_info - Mix to generate effects from. + */ + void GenerateSubMixCommand(MixInfo& mix_info); + + /** + * Generate all submix command. + */ + void GenerateSubMixCommands(); + + /** + * Generate the final mix. + */ + void GenerateFinalMixCommand(); + + /** + * Generate the final mix commands. + */ + void GenerateFinalMixCommands(); + + /** + * Generate all sink commands. + */ + void GenerateSinkCommands(); + + /** + * Generate a sink command. + * Sends samples out to the backend, or a game-supplied circular buffer. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param sink_info - Sink info to generate the commands from. + */ + void GenerateSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info); + + /** + * Generate a device sink command. + * Sends samples out to the backend. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param sink_info - Sink info to generate the commands from. + */ + void GenerateDeviceSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info); + + /** + * Generate a performance command. + * Used to report performance metrics of the AudioRenderer back to the game. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param sink_info - Sink info to generate the commands from. + */ + void GeneratePerformanceCommand(s32 node_id, PerformanceState state, + const PerformanceEntryAddresses& entry_addresses); + +private: + /// Commands will be written by this buffer + CommandBuffer& command_buffer; + /// Header information for the commands generated + const CommandListHeader& command_header; + /// Various things to control generation + const AudioRendererSystemContext& render_context; + /// Used for generating voices + VoiceContext& voice_context; + /// Used for generating mixes + MixContext& mix_context; + /// Used for generating effects + EffectContext& effect_context; + /// Used for generating sinks + SinkContext& sink_context; + /// Used for generating submixes + SplitterContext& splitter_context; + /// Used for generating performance + PerformanceManager* performance_manager; +}; + +} // namespace AudioRenderer +} // namespace AudioCore diff --git a/src/audio_core/renderer/command/command_list_header.h b/src/audio_core/renderer/command/command_list_header.h new file mode 100644 index 000000000..988530b1f --- /dev/null +++ b/src/audio_core/renderer/command/command_list_header.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +struct CommandListHeader { + u64 buffer_size; + u32 command_count; + std::span<s32> samples_buffer; + s16 buffer_count; + u32 sample_count; + u32 sample_rate; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.cpp b/src/audio_core/renderer/command/command_processing_time_estimator.cpp new file mode 100644 index 000000000..3091f587a --- /dev/null +++ b/src/audio_core/renderer/command/command_processing_time_estimator.cpp @@ -0,0 +1,3620 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/command/command_processing_time_estimator.h" + +namespace AudioCore::AudioRenderer { + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + const PcmInt16DataSourceVersion1Command& command) const { + return static_cast<u32>(command.pitch * 0.25f * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + const PcmInt16DataSourceVersion2Command& command) const { + return static_cast<u32>(command.pitch * 0.25f * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const PcmFloatDataSourceVersion1Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const PcmFloatDataSourceVersion2Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + const AdpcmDataSourceVersion1Command& command) const { + return static_cast<u32>(command.pitch * 0.25f * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + const AdpcmDataSourceVersion2Command& command) const { + return static_cast<u32>(command.pitch * 0.25f * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const VolumeCommand& command) const { + return static_cast<u32>((static_cast<f32>(sample_count) * 8.8f) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const VolumeRampCommand& command) const { + return static_cast<u32>((static_cast<f32>(sample_count) * 9.8f) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const BiquadFilterCommand& command) const { + return static_cast<u32>((static_cast<f32>(sample_count) * 58.0f) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const MixCommand& command) const { + return static_cast<u32>((static_cast<f32>(sample_count) * 10.0f) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const MixRampCommand& command) const { + return static_cast<u32>((static_cast<f32>(sample_count) * 14.4f) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate(const MixRampGroupedCommand& command) const { + u32 count{0}; + for (u32 i = 0; i < command.buffer_count; i++) { + if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { + count++; + } + } + + return static_cast<u32>(((static_cast<f32>(sample_count) * 14.4f) * 1.2f) * + static_cast<f32>(count)); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const DepopPrepareCommand& command) const { + return 1080; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + const DepopForMixBuffersCommand& command) const { + return static_cast<u32>((static_cast<f32>(sample_count) * 8.9f) * + static_cast<f32>(command.count)); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate(const DelayCommand& command) const { + return static_cast<u32>((static_cast<f32>(sample_count) * command.parameter.channel_count) * + 202.5f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const UpsampleCommand& command) const { + return 357915; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const DownMix6chTo2chCommand& command) const { + return 16108; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate(const AuxCommand& command) const { + if (command.enabled) { + return 15956; + } + return 3765; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const DeviceSinkCommand& command) const { + return 10042; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const CircularBufferSinkCommand& command) const { + return 55; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate(const ReverbCommand& command) const { + if (command.enabled) { + return static_cast<u32>( + (command.parameter.channel_count * static_cast<f32>(sample_count) * 750) * 1.2f); + } + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate(const I3dl2ReverbCommand& command) const { + if (command.enabled) { + return static_cast<u32>( + (command.parameter.channel_count * static_cast<f32>(sample_count) * 530) * 1.2f); + } + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const PerformanceCommand& command) const { + return 1454; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const ClearMixBufferCommand& command) const { + return static_cast<u32>( + ((static_cast<f32>(sample_count) * 0.83f) * static_cast<f32>(buffer_count)) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const CopyMixBufferCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const LightLimiterVersion1Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const LightLimiterVersion2Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const CaptureCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const CompressorCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const PcmInt16DataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 749.269f + + 6138.94f); + case 240: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 1195.456f + + 7797.047f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const PcmInt16DataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 749.269f + + 6138.94f); + case 240: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 1195.456f + + 7797.047f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const PcmFloatDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 749.269f + + 6138.94f); + case 240: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 1195.456f + + 7797.047f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const PcmFloatDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 749.269f + + 6138.94f); + case 240: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 1195.456f + + 7797.047f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const AdpcmDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 2125.588f + + 9039.47f); + case 240: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 3564.088 + + 6225.471); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const AdpcmDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 2125.588f + + 9039.47f); + case 240: + return static_cast<u32>( + (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 2.0f) * 3564.088 + + 6225.471); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const VolumeCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1280.3f); + case 240: + return static_cast<u32>(1737.8f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const VolumeRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1403.9f); + case 240: + return static_cast<u32>(1884.3f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const BiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(4813.2f); + case 240: + return static_cast<u32>(6915.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const MixCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1342.2f); + case 240: + return static_cast<u32>(1833.2f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const MixRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1859.0f); + case 240: + return static_cast<u32>(2286.1f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const MixRampGroupedCommand& command) const { + u32 count{0}; + for (u32 i = 0; i < command.buffer_count; i++) { + if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { + count++; + } + } + + switch (sample_count) { + case 160: + return static_cast<u32>((static_cast<f32>(sample_count) * 7.245f) * + static_cast<f32>(count)); + case 240: + return static_cast<u32>((static_cast<f32>(sample_count) * 7.245f) * + static_cast<f32>(count)); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const DepopPrepareCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(306.62f); + case 240: + return static_cast<u32>(293.22f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const DepopForMixBuffersCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(762.96f); + case 240: + return static_cast<u32>(726.96f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const DelayCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(41635.555f); + case 2: + return static_cast<u32>(97861.211f); + case 4: + return static_cast<u32>(192515.516f); + case 6: + return static_cast<u32>(301755.969f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(578.529f); + case 2: + return static_cast<u32>(663.064f); + case 4: + return static_cast<u32>(703.983f); + case 6: + return static_cast<u32>(760.032f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(8770.345f); + case 2: + return static_cast<u32>(25741.18f); + case 4: + return static_cast<u32>(47551.168f); + case 6: + return static_cast<u32>(81629.219f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(521.283f); + case 2: + return static_cast<u32>(585.396f); + case 4: + return static_cast<u32>(629.884f); + case 6: + return static_cast<u32>(713.57f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const UpsampleCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(292000.0f); + case 240: + return static_cast<u32>(0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const DownMix6chTo2chCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(10009.0f); + case 240: + return static_cast<u32>(14577.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const AuxCommand& command) const { + // Is this function bugged, returning the wrong time? + // Surely the larger time should be returned when enabled... + // CMP W8, #0 + // MOV W8, #0x60; // 489.163f + // MOV W10, #0x64; // 7177.936f + // CSEL X8, X10, X8, EQ + + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast<u32>(489.163f); + } + return static_cast<u32>(7177.936f); + case 240: + if (command.enabled) { + return static_cast<u32>(485.562f); + } + return static_cast<u32>(9499.822f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const DeviceSinkCommand& command) const { + switch (command.input_count) { + case 2: + switch (sample_count) { + case 160: + return static_cast<u32>(9261.545f); + case 240: + return static_cast<u32>(9336.054f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast<u32>(9336.054f); + case 240: + return static_cast<u32>(9566.728f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const CircularBufferSinkCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(static_cast<f32>(command.input_count) * 853.629f + 1284.517f); + case 240: + return static_cast<u32>(static_cast<f32>(command.input_count) * 1726.021f + 1369.683f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(97192.227f); + case 2: + return static_cast<u32>(103278.555f); + case 4: + return static_cast<u32>(109579.039f); + case 6: + return static_cast<u32>(115065.438f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(492.009f); + case 2: + return static_cast<u32>(554.463f); + case 4: + return static_cast<u32>(595.864f); + case 6: + return static_cast<u32>(656.617f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(136463.641f); + case 2: + return static_cast<u32>(145749.047f); + case 4: + return static_cast<u32>(154796.938f); + case 6: + return static_cast<u32>(161968.406f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(495.789f); + case 2: + return static_cast<u32>(527.163f); + case 4: + return static_cast<u32>(598.752f); + case 6: + return static_cast<u32>(666.025f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const I3dl2ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(138836.484f); + case 2: + return static_cast<u32>(135428.172f); + case 4: + return static_cast<u32>(199181.844f); + case 6: + return static_cast<u32>(247345.906f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(718.704f); + case 2: + return static_cast<u32>(751.296f); + case 4: + return static_cast<u32>(797.464f); + case 6: + return static_cast<u32>(867.426f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(199952.734f); + case 2: + return static_cast<u32>(195199.5f); + case 4: + return static_cast<u32>(290575.875f); + case 6: + return static_cast<u32>(363494.531f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(534.24f); + case 2: + return static_cast<u32>(570.874f); + case 4: + return static_cast<u32>(660.933f); + case 6: + return static_cast<u32>(694.596f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const PerformanceCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(489.35f); + case 240: + return static_cast<u32>(491.18f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const ClearMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(static_cast<f32>(buffer_count) * 260.4f + 139.65f); + case 240: + return static_cast<u32>(static_cast<f32>(buffer_count) * 668.85f + 193.2f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const CopyMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(836.32f); + case 240: + return static_cast<u32>(1000.9f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const LightLimiterVersion1Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const LightLimiterVersion2Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const CaptureCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const CompressorCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const PcmInt16DataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 427.52f + + 6329.442f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 710.143f + + 7853.286f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const PcmInt16DataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 427.52f + + 6329.442f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 371.876f + + 8049.415f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 423.43f + + 5062.659f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 710.143f + + 7853.286f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 610.487f + + 10138.842f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 676.722f + + 5810.962f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const PcmFloatDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 1672.026f + + 7681.211f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 2550.414f + + 9663.969f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const PcmFloatDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.026f + + 7681.211f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.982f + + 9038.011f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1673.216f + + 6027.577f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2550.414f + + 9663.969f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2522.303f + + 11758.571f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2537.061f + + 7369.309f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const AdpcmDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 1827.665f + + 7913.808f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 2756.372f + + 9736.702f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const AdpcmDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1827.665f + + 7913.808f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1829.285f + + 9607.814f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1824.609f + + 6517.476f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2756.372f + + 9736.702f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2731.308f + + 12154.379f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2732.152f + + 7929.442f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const VolumeCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1311.1f); + case 240: + return static_cast<u32>(1713.6f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const VolumeRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1425.3f); + case 240: + return static_cast<u32>(1700.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const BiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(4173.2f); + case 240: + return static_cast<u32>(5585.1f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const MixCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1402.8f); + case 240: + return static_cast<u32>(1853.2f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const MixRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1968.7f); + case 240: + return static_cast<u32>(2459.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const MixRampGroupedCommand& command) const { + u32 count{0}; + for (u32 i = 0; i < command.buffer_count; i++) { + if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { + count++; + } + } + + switch (sample_count) { + case 160: + return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) * + static_cast<f32>(count)); + case 240: + return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) * + static_cast<f32>(count)); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const DepopPrepareCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const DepopForMixBuffersCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(739.64f); + case 240: + return static_cast<u32>(910.97f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const DelayCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(8929.042f); + case 2: + return static_cast<u32>(25500.75f); + case 4: + return static_cast<u32>(47759.617f); + case 6: + return static_cast<u32>(82203.07f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(1295.206f); + case 2: + return static_cast<u32>(1213.6f); + case 4: + return static_cast<u32>(942.028f); + case 6: + return static_cast<u32>(1001.553f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(11941.051f); + case 2: + return static_cast<u32>(37197.371f); + case 4: + return static_cast<u32>(69749.836f); + case 6: + return static_cast<u32>(120042.398f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(997.668f); + case 2: + return static_cast<u32>(977.634f); + case 4: + return static_cast<u32>(792.309f); + case 6: + return static_cast<u32>(875.427f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const UpsampleCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(312990.0f); + case 240: + return static_cast<u32>(0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const DownMix6chTo2chCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(9949.7f); + case 240: + return static_cast<u32>(14679.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const AuxCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast<u32>(7182.136f); + } + return static_cast<u32>(472.111f); + case 240: + if (command.enabled) { + return static_cast<u32>(9435.961f); + } + return static_cast<u32>(462.619f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const DeviceSinkCommand& command) const { + switch (command.input_count) { + case 2: + switch (sample_count) { + case 160: + return static_cast<u32>(8979.956f); + case 240: + return static_cast<u32>(9221.907f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast<u32>(9177.903f); + case 240: + return static_cast<u32>(9725.897f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const CircularBufferSinkCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f); + case 240: + return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(81475.055f); + case 2: + return static_cast<u32>(84975.0f); + case 4: + return static_cast<u32>(91625.148f); + case 6: + return static_cast<u32>(95332.266f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(536.298f); + case 2: + return static_cast<u32>(588.798f); + case 4: + return static_cast<u32>(643.702f); + case 6: + return static_cast<u32>(705.999f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(120174.469f); + case 2: + return static_cast<u32>(125262.219f); + case 4: + return static_cast<u32>(135751.234f); + case 6: + return static_cast<u32>(141129.234f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(617.641f); + case 2: + return static_cast<u32>(659.536f); + case 4: + return static_cast<u32>(711.438f); + case 6: + return static_cast<u32>(778.071f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const I3dl2ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(116754.984f); + case 2: + return static_cast<u32>(125912.055f); + case 4: + return static_cast<u32>(146336.031f); + case 6: + return static_cast<u32>(165812.656f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(735.0f); + case 2: + return static_cast<u32>(766.615f); + case 4: + return static_cast<u32>(834.067f); + case 6: + return static_cast<u32>(875.437f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(170292.344f); + case 2: + return static_cast<u32>(183875.625f); + case 4: + return static_cast<u32>(214696.188f); + case 6: + return static_cast<u32>(243846.766f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(508.473f); + case 2: + return static_cast<u32>(582.445f); + case 4: + return static_cast<u32>(626.419f); + case 6: + return static_cast<u32>(682.468f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const PerformanceCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(498.17f); + case 240: + return static_cast<u32>(489.42f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const ClearMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f); + case 240: + return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const CopyMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(842.59f); + case 240: + return static_cast<u32>(986.72f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const LightLimiterVersion1Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(21392.383f); + case 2: + return static_cast<u32>(26829.389f); + case 4: + return static_cast<u32>(32405.152f); + case 6: + return static_cast<u32>(52218.586f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(897.004f); + case 2: + return static_cast<u32>(931.549f); + case 4: + return static_cast<u32>(975.387f); + case 6: + return static_cast<u32>(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(30555.504f); + case 2: + return static_cast<u32>(39010.785f); + case 4: + return static_cast<u32>(48270.18f); + case 6: + return static_cast<u32>(76711.875f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(874.429f); + case 2: + return static_cast<u32>(921.553f); + case 4: + return static_cast<u32>(945.262f); + case 6: + return static_cast<u32>(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const LightLimiterVersion2Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(23308.928f); + case 2: + return static_cast<u32>(29954.062f); + case 4: + return static_cast<u32>(35807.477f); + case 6: + return static_cast<u32>(58339.773f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(21392.383f); + case 2: + return static_cast<u32>(26829.389f); + case 4: + return static_cast<u32>(32405.152f); + case 6: + return static_cast<u32>(52218.586f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(897.004f); + case 2: + return static_cast<u32>(931.549f); + case 4: + return static_cast<u32>(975.387f); + case 6: + return static_cast<u32>(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(33526.121f); + case 2: + return static_cast<u32>(43549.355f); + case 4: + return static_cast<u32>(52190.281f); + case 6: + return static_cast<u32>(85526.516f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(30555.504f); + case 2: + return static_cast<u32>(39010.785f); + case 4: + return static_cast<u32>(48270.18f); + case 6: + return static_cast<u32>(76711.875f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(874.429f); + case 2: + return static_cast<u32>(921.553f); + case 4: + return static_cast<u32>(945.262f); + case 6: + return static_cast<u32>(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const CaptureCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const CompressorCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const PcmInt16DataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 427.52f + + 6329.442f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 710.143f + + 7853.286f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const PcmInt16DataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 427.52f + + 6329.442f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 371.876f + + 8049.415f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 423.43f + + 5062.659f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 710.143f + + 7853.286f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 610.487f + + 10138.842f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 676.722f + + 5810.962f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const PcmFloatDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 1672.026f + + 7681.211f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 2550.414f + + 9663.969f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const PcmFloatDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.026f + + 7681.211f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.982f + + 9038.011f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1673.216f + + 6027.577f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2550.414f + + 9663.969f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2522.303f + + 11758.571f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2537.061f + + 7369.309f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const AdpcmDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 1827.665f + + 7913.808f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 2756.372f + + 9736.702f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const AdpcmDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1827.665f + + 7913.808f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1829.285f + + 9607.814f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1824.609f + + 6517.476f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2756.372f + + 9736.702f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2731.308f + + 12154.379f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2732.152f + + 7929.442f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const VolumeCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1311.1f); + case 240: + return static_cast<u32>(1713.6f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const VolumeRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1425.3f); + case 240: + return static_cast<u32>(1700.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const BiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(4173.2f); + case 240: + return static_cast<u32>(5585.1f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const MixCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1402.8f); + case 240: + return static_cast<u32>(1853.2f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const MixRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1968.7f); + case 240: + return static_cast<u32>(2459.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const MixRampGroupedCommand& command) const { + u32 count{0}; + for (u32 i = 0; i < command.buffer_count; i++) { + if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { + count++; + } + } + + switch (sample_count) { + case 160: + return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) * + static_cast<f32>(count)); + case 240: + return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) * + static_cast<f32>(count)); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const DepopPrepareCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const DepopForMixBuffersCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(739.64f); + case 240: + return static_cast<u32>(910.97f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const DelayCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(8929.042f); + case 2: + return static_cast<u32>(25500.75f); + case 4: + return static_cast<u32>(47759.617f); + case 6: + return static_cast<u32>(82203.07f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(1295.206f); + case 2: + return static_cast<u32>(1213.6f); + case 4: + return static_cast<u32>(942.028f); + case 6: + return static_cast<u32>(1001.553f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(11941.051f); + case 2: + return static_cast<u32>(37197.371f); + case 4: + return static_cast<u32>(69749.836f); + case 6: + return static_cast<u32>(120042.398f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(997.668f); + case 2: + return static_cast<u32>(977.634f); + case 4: + return static_cast<u32>(792.309f); + case 6: + return static_cast<u32>(875.427f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const UpsampleCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(312990.0f); + case 240: + return static_cast<u32>(0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const DownMix6chTo2chCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(9949.7f); + case 240: + return static_cast<u32>(14679.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const AuxCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast<u32>(7182.136f); + } + return static_cast<u32>(472.111f); + case 240: + if (command.enabled) { + return static_cast<u32>(9435.961f); + } + return static_cast<u32>(462.619f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const DeviceSinkCommand& command) const { + switch (command.input_count) { + case 2: + switch (sample_count) { + case 160: + return static_cast<u32>(8979.956f); + case 240: + return static_cast<u32>(9221.907f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast<u32>(9177.903f); + case 240: + return static_cast<u32>(9725.897f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const CircularBufferSinkCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f); + case 240: + return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(81475.055f); + case 2: + return static_cast<u32>(84975.0f); + case 4: + return static_cast<u32>(91625.148f); + case 6: + return static_cast<u32>(95332.266f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(536.298f); + case 2: + return static_cast<u32>(588.798f); + case 4: + return static_cast<u32>(643.702f); + case 6: + return static_cast<u32>(705.999f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(120174.469f); + case 2: + return static_cast<u32>(125262.219f); + case 4: + return static_cast<u32>(135751.234f); + case 6: + return static_cast<u32>(141129.234f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(617.641f); + case 2: + return static_cast<u32>(659.536f); + case 4: + return static_cast<u32>(711.438f); + case 6: + return static_cast<u32>(778.071f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const I3dl2ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(116754.984f); + case 2: + return static_cast<u32>(125912.055f); + case 4: + return static_cast<u32>(146336.031f); + case 6: + return static_cast<u32>(165812.656f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(735.0f); + case 2: + return static_cast<u32>(766.615f); + case 4: + return static_cast<u32>(834.067f); + case 6: + return static_cast<u32>(875.437f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(170292.344f); + case 2: + return static_cast<u32>(183875.625f); + case 4: + return static_cast<u32>(214696.188f); + case 6: + return static_cast<u32>(243846.766f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(508.473f); + case 2: + return static_cast<u32>(582.445f); + case 4: + return static_cast<u32>(626.419f); + case 6: + return static_cast<u32>(682.468f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const PerformanceCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(498.17f); + case 240: + return static_cast<u32>(489.42f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const ClearMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f); + case 240: + return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const CopyMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(842.59f); + case 240: + return static_cast<u32>(986.72f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const LightLimiterVersion1Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(21392.383f); + case 2: + return static_cast<u32>(26829.389f); + case 4: + return static_cast<u32>(32405.152f); + case 6: + return static_cast<u32>(52218.586f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(897.004f); + case 2: + return static_cast<u32>(931.549f); + case 4: + return static_cast<u32>(975.387f); + case 6: + return static_cast<u32>(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(30555.504f); + case 2: + return static_cast<u32>(39010.785f); + case 4: + return static_cast<u32>(48270.18f); + case 6: + return static_cast<u32>(76711.875f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(874.429f); + case 2: + return static_cast<u32>(921.553f); + case 4: + return static_cast<u32>(945.262f); + case 6: + return static_cast<u32>(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const LightLimiterVersion2Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(23308.928f); + case 2: + return static_cast<u32>(29954.062f); + case 4: + return static_cast<u32>(35807.477f); + case 6: + return static_cast<u32>(58339.773f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(21392.383f); + case 2: + return static_cast<u32>(26829.389f); + case 4: + return static_cast<u32>(32405.152f); + case 6: + return static_cast<u32>(52218.586f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(897.004f); + case 2: + return static_cast<u32>(931.549f); + case 4: + return static_cast<u32>(975.387f); + case 6: + return static_cast<u32>(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(33526.121f); + case 2: + return static_cast<u32>(43549.355f); + case 4: + return static_cast<u32>(52190.281f); + case 6: + return static_cast<u32>(85526.516f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(30555.504f); + case 2: + return static_cast<u32>(39010.785f); + case 4: + return static_cast<u32>(48270.18f); + case 6: + return static_cast<u32>(76711.875f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(874.429f); + case 2: + return static_cast<u32>(921.553f); + case 4: + return static_cast<u32>(945.262f); + case 6: + return static_cast<u32>(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(7424.5f); + case 240: + return static_cast<u32>(9730.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const CaptureCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast<u32>(426.982f); + } + return static_cast<u32>(4261.005f); + case 240: + if (command.enabled) { + return static_cast<u32>(435.204f); + } + return static_cast<u32>(5858.265f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const CompressorCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const PcmInt16DataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 427.52f + + 6329.442f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 710.143f + + 7853.286f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const PcmInt16DataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 427.52f + + 6329.442f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 371.876f + + 8049.415f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 423.43f + + 5062.659f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 710.143f + + 7853.286f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 610.487f + + 10138.842f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 676.722f + + 5810.962f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const PcmFloatDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 1672.026f + + 7681.211f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 2550.414f + + 9663.969f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const PcmFloatDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.026f + + 7681.211f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.982f + + 9038.011f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1673.216f + + 6027.577f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2550.414f + + 9663.969f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2522.303f + + 11758.571f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2537.061f + + 7369.309f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const AdpcmDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 1827.665f + + 7913.808f); + case 240: + return static_cast<u32>( + ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) * + 2756.372f + + 9736.702f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const AdpcmDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1827.665f + + 7913.808f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1829.285f + + 9607.814f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1824.609f + + 6517.476f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2756.372f + + 9736.702f); + case SrcQuality::High: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2731.308f + + 12154.379f); + case SrcQuality::Low: + return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / + static_cast<f32>(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2732.152f + + 7929.442f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast<u32>(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const VolumeCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1311.1f); + case 240: + return static_cast<u32>(1713.6f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const VolumeRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1425.3f); + case 240: + return static_cast<u32>(1700.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const BiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(4173.2f); + case 240: + return static_cast<u32>(5585.1f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const MixCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1402.8f); + case 240: + return static_cast<u32>(1853.2f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const MixRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(1968.7f); + case 240: + return static_cast<u32>(2459.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const MixRampGroupedCommand& command) const { + u32 count{0}; + for (u32 i = 0; i < command.buffer_count; i++) { + if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { + count++; + } + } + + switch (sample_count) { + case 160: + return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) * + static_cast<f32>(count)); + case 240: + return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) * + static_cast<f32>(count)); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const DepopPrepareCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const DepopForMixBuffersCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(739.64f); + case 240: + return static_cast<u32>(910.97f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const DelayCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(8929.042f); + case 2: + return static_cast<u32>(25500.75f); + case 4: + return static_cast<u32>(47759.617f); + case 6: + return static_cast<u32>(82203.07f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(1295.206f); + case 2: + return static_cast<u32>(1213.6f); + case 4: + return static_cast<u32>(942.028f); + case 6: + return static_cast<u32>(1001.553f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(11941.051f); + case 2: + return static_cast<u32>(37197.371f); + case 4: + return static_cast<u32>(69749.836f); + case 6: + return static_cast<u32>(120042.398f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(997.668f); + case 2: + return static_cast<u32>(977.634f); + case 4: + return static_cast<u32>(792.309f); + case 6: + return static_cast<u32>(875.427f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const UpsampleCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(312990.0f); + case 240: + return static_cast<u32>(0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const DownMix6chTo2chCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(9949.7f); + case 240: + return static_cast<u32>(14679.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const AuxCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast<u32>(7182.136f); + } + return static_cast<u32>(472.111f); + case 240: + if (command.enabled) { + return static_cast<u32>(9435.961f); + } + return static_cast<u32>(462.619f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const DeviceSinkCommand& command) const { + switch (command.input_count) { + case 2: + switch (sample_count) { + case 160: + return static_cast<u32>(8979.956f); + case 240: + return static_cast<u32>(9221.907f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast<u32>(9177.903f); + case 240: + return static_cast<u32>(9725.897f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const CircularBufferSinkCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f); + case 240: + return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(81475.055f); + case 2: + return static_cast<u32>(84975.0f); + case 4: + return static_cast<u32>(91625.148f); + case 6: + return static_cast<u32>(95332.266f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(536.298f); + case 2: + return static_cast<u32>(588.798f); + case 4: + return static_cast<u32>(643.702f); + case 6: + return static_cast<u32>(705.999f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(120174.469f); + case 2: + return static_cast<u32>(125262.219f); + case 4: + return static_cast<u32>(135751.234f); + case 6: + return static_cast<u32>(141129.234f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(617.641f); + case 2: + return static_cast<u32>(659.536f); + case 4: + return static_cast<u32>(711.438f); + case 6: + return static_cast<u32>(778.071f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const I3dl2ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(116754.984f); + case 2: + return static_cast<u32>(125912.055f); + case 4: + return static_cast<u32>(146336.031f); + case 6: + return static_cast<u32>(165812.656f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(735.0f); + case 2: + return static_cast<u32>(766.615f); + case 4: + return static_cast<u32>(834.067f); + case 6: + return static_cast<u32>(875.437f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(170292.344f); + case 2: + return static_cast<u32>(183875.625f); + case 4: + return static_cast<u32>(214696.188f); + case 6: + return static_cast<u32>(243846.766f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(508.473f); + case 2: + return static_cast<u32>(582.445f); + case 4: + return static_cast<u32>(626.419f); + case 6: + return static_cast<u32>(682.468f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const PerformanceCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(498.17f); + case 240: + return static_cast<u32>(489.42f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const ClearMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f); + case 240: + return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const CopyMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(842.59f); + case 240: + return static_cast<u32>(986.72f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const LightLimiterVersion1Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(21508.01f); + case 2: + return static_cast<u32>(23120.453f); + case 4: + return static_cast<u32>(26270.053f); + case 6: + return static_cast<u32>(40471.902f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(897.004f); + case 2: + return static_cast<u32>(931.549f); + case 4: + return static_cast<u32>(975.387f); + case 6: + return static_cast<u32>(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(30565.961f); + case 2: + return static_cast<u32>(32812.91f); + case 4: + return static_cast<u32>(37354.852f); + case 6: + return static_cast<u32>(58486.699f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(874.429f); + case 2: + return static_cast<u32>(921.553f); + case 4: + return static_cast<u32>(945.262f); + case 6: + return static_cast<u32>(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const LightLimiterVersion2Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + if (command.parameter.processing_mode == LightLimiterInfo::ProcessingMode::Mode0) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(23639.584f); + case 2: + return static_cast<u32>(24666.725f); + case 4: + return static_cast<u32>(28876.459f); + case 6: + return static_cast<u32>(47096.078f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } else { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(21508.01f); + case 2: + return static_cast<u32>(23120.453f); + case 4: + return static_cast<u32>(26270.053f); + case 6: + return static_cast<u32>(40471.902f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + } + } else if (command.parameter.processing_mode == + LightLimiterInfo::ProcessingMode::Mode1) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(23639.584f); + case 2: + return static_cast<u32>(29954.062f); + case 4: + return static_cast<u32>(35807.477f); + case 6: + return static_cast<u32>(58339.773f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } else { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(23639.584f); + case 2: + return static_cast<u32>(29954.062f); + case 4: + return static_cast<u32>(35807.477f); + case 6: + return static_cast<u32>(58339.773f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + } + } else { + LOG_ERROR(Service_Audio, "Invalid processing mode {}", + command.parameter.processing_mode); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(897.004f); + case 2: + return static_cast<u32>(931.549f); + case 4: + return static_cast<u32>(975.387f); + case 6: + return static_cast<u32>(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + if (command.parameter.processing_mode == LightLimiterInfo::ProcessingMode::Mode0) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(33875.023f); + case 2: + return static_cast<u32>(35199.938f); + case 4: + return static_cast<u32>(41371.230f); + case 6: + return static_cast<u32>(68370.914f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } else { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(30565.961f); + case 2: + return static_cast<u32>(32812.91f); + case 4: + return static_cast<u32>(37354.852f); + case 6: + return static_cast<u32>(58486.699f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + } else if (command.parameter.processing_mode == + LightLimiterInfo::ProcessingMode::Mode1) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(33942.980f); + case 2: + return static_cast<u32>(28698.893f); + case 4: + return static_cast<u32>(34774.277f); + case 6: + return static_cast<u32>(61897.773f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } else { + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(30610.248f); + case 2: + return static_cast<u32>(26322.408f); + case 4: + return static_cast<u32>(30369.000f); + case 6: + return static_cast<u32>(51892.090f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + } else { + LOG_ERROR(Service_Audio, "Invalid processing mode {}", + command.parameter.processing_mode); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast<u32>(874.429f); + case 2: + return static_cast<u32>(921.553f); + case 4: + return static_cast<u32>(945.262f); + case 6: + return static_cast<u32>(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast<u32>(7424.5f); + case 240: + return static_cast<u32>(9730.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CaptureCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast<u32>(426.982f); + } + return static_cast<u32>(4261.005f); + case 240: + if (command.enabled) { + return static_cast<u32>(435.204f); + } + return static_cast<u32>(5858.265f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CompressorCommand& command) const { + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + switch (sample_count) { + case 160: + return static_cast<u32>(34430.570f); + case 240: + return static_cast<u32>(51095.348f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 2: + switch (sample_count) { + case 160: + return static_cast<u32>(44253.320f); + case 240: + return static_cast<u32>(65693.094f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 4: + switch (sample_count) { + case 160: + return static_cast<u32>(63827.457f); + case 240: + return static_cast<u32>(95382.852f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast<u32>(83361.484f); + case 240: + return static_cast<u32>(124509.906f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + switch (sample_count) { + case 160: + return static_cast<u32>(630.115f); + case 240: + return static_cast<u32>(840.136f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 2: + switch (sample_count) { + case 160: + return static_cast<u32>(638.274f); + case 240: + return static_cast<u32>(826.098f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 4: + switch (sample_count) { + case 160: + return static_cast<u32>(705.862f); + case 240: + return static_cast<u32>(901.876f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast<u32>(782.019f); + case 240: + return static_cast<u32>(965.286f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.h b/src/audio_core/renderer/command/command_processing_time_estimator.h new file mode 100644 index 000000000..452217196 --- /dev/null +++ b/src/audio_core/renderer/command/command_processing_time_estimator.h @@ -0,0 +1,254 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/command/commands.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Estimate the processing time required for all commands. + */ +class ICommandProcessingTimeEstimator { +public: + virtual ~ICommandProcessingTimeEstimator() = default; + + virtual u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const = 0; + virtual u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const = 0; + virtual u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const = 0; + virtual u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const = 0; + virtual u32 Estimate(const AdpcmDataSourceVersion1Command& command) const = 0; + virtual u32 Estimate(const AdpcmDataSourceVersion2Command& command) const = 0; + virtual u32 Estimate(const VolumeCommand& command) const = 0; + virtual u32 Estimate(const VolumeRampCommand& command) const = 0; + virtual u32 Estimate(const BiquadFilterCommand& command) const = 0; + virtual u32 Estimate(const MixCommand& command) const = 0; + virtual u32 Estimate(const MixRampCommand& command) const = 0; + virtual u32 Estimate(const MixRampGroupedCommand& command) const = 0; + virtual u32 Estimate(const DepopPrepareCommand& command) const = 0; + virtual u32 Estimate(const DepopForMixBuffersCommand& command) const = 0; + virtual u32 Estimate(const DelayCommand& command) const = 0; + virtual u32 Estimate(const UpsampleCommand& command) const = 0; + virtual u32 Estimate(const DownMix6chTo2chCommand& command) const = 0; + virtual u32 Estimate(const AuxCommand& command) const = 0; + virtual u32 Estimate(const DeviceSinkCommand& command) const = 0; + virtual u32 Estimate(const CircularBufferSinkCommand& command) const = 0; + virtual u32 Estimate(const ReverbCommand& command) const = 0; + virtual u32 Estimate(const I3dl2ReverbCommand& command) const = 0; + virtual u32 Estimate(const PerformanceCommand& command) const = 0; + virtual u32 Estimate(const ClearMixBufferCommand& command) const = 0; + virtual u32 Estimate(const CopyMixBufferCommand& command) const = 0; + virtual u32 Estimate(const LightLimiterVersion1Command& command) const = 0; + virtual u32 Estimate(const LightLimiterVersion2Command& command) const = 0; + virtual u32 Estimate(const MultiTapBiquadFilterCommand& command) const = 0; + virtual u32 Estimate(const CaptureCommand& command) const = 0; + virtual u32 Estimate(const CompressorCommand& command) const = 0; +}; + +class CommandProcessingTimeEstimatorVersion1 final : public ICommandProcessingTimeEstimator { +public: + CommandProcessingTimeEstimatorVersion1(u32 sample_count_, u32 buffer_count_) + : sample_count{sample_count_}, buffer_count{buffer_count_} {} + + u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; + u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; + u32 Estimate(const VolumeCommand& command) const override; + u32 Estimate(const VolumeRampCommand& command) const override; + u32 Estimate(const BiquadFilterCommand& command) const override; + u32 Estimate(const MixCommand& command) const override; + u32 Estimate(const MixRampCommand& command) const override; + u32 Estimate(const MixRampGroupedCommand& command) const override; + u32 Estimate(const DepopPrepareCommand& command) const override; + u32 Estimate(const DepopForMixBuffersCommand& command) const override; + u32 Estimate(const DelayCommand& command) const override; + u32 Estimate(const UpsampleCommand& command) const override; + u32 Estimate(const DownMix6chTo2chCommand& command) const override; + u32 Estimate(const AuxCommand& command) const override; + u32 Estimate(const DeviceSinkCommand& command) const override; + u32 Estimate(const CircularBufferSinkCommand& command) const override; + u32 Estimate(const ReverbCommand& command) const override; + u32 Estimate(const I3dl2ReverbCommand& command) const override; + u32 Estimate(const PerformanceCommand& command) const override; + u32 Estimate(const ClearMixBufferCommand& command) const override; + u32 Estimate(const CopyMixBufferCommand& command) const override; + u32 Estimate(const LightLimiterVersion1Command& command) const override; + u32 Estimate(const LightLimiterVersion2Command& command) const override; + u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; + u32 Estimate(const CaptureCommand& command) const override; + u32 Estimate(const CompressorCommand& command) const override; + +private: + u32 sample_count{}; + u32 buffer_count{}; +}; + +class CommandProcessingTimeEstimatorVersion2 final : public ICommandProcessingTimeEstimator { +public: + CommandProcessingTimeEstimatorVersion2(u32 sample_count_, u32 buffer_count_) + : sample_count{sample_count_}, buffer_count{buffer_count_} {} + + u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; + u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; + u32 Estimate(const VolumeCommand& command) const override; + u32 Estimate(const VolumeRampCommand& command) const override; + u32 Estimate(const BiquadFilterCommand& command) const override; + u32 Estimate(const MixCommand& command) const override; + u32 Estimate(const MixRampCommand& command) const override; + u32 Estimate(const MixRampGroupedCommand& command) const override; + u32 Estimate(const DepopPrepareCommand& command) const override; + u32 Estimate(const DepopForMixBuffersCommand& command) const override; + u32 Estimate(const DelayCommand& command) const override; + u32 Estimate(const UpsampleCommand& command) const override; + u32 Estimate(const DownMix6chTo2chCommand& command) const override; + u32 Estimate(const AuxCommand& command) const override; + u32 Estimate(const DeviceSinkCommand& command) const override; + u32 Estimate(const CircularBufferSinkCommand& command) const override; + u32 Estimate(const ReverbCommand& command) const override; + u32 Estimate(const I3dl2ReverbCommand& command) const override; + u32 Estimate(const PerformanceCommand& command) const override; + u32 Estimate(const ClearMixBufferCommand& command) const override; + u32 Estimate(const CopyMixBufferCommand& command) const override; + u32 Estimate(const LightLimiterVersion1Command& command) const override; + u32 Estimate(const LightLimiterVersion2Command& command) const override; + u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; + u32 Estimate(const CaptureCommand& command) const override; + u32 Estimate(const CompressorCommand& command) const override; + +private: + u32 sample_count{}; + u32 buffer_count{}; +}; + +class CommandProcessingTimeEstimatorVersion3 final : public ICommandProcessingTimeEstimator { +public: + CommandProcessingTimeEstimatorVersion3(u32 sample_count_, u32 buffer_count_) + : sample_count{sample_count_}, buffer_count{buffer_count_} {} + + u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; + u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; + u32 Estimate(const VolumeCommand& command) const override; + u32 Estimate(const VolumeRampCommand& command) const override; + u32 Estimate(const BiquadFilterCommand& command) const override; + u32 Estimate(const MixCommand& command) const override; + u32 Estimate(const MixRampCommand& command) const override; + u32 Estimate(const MixRampGroupedCommand& command) const override; + u32 Estimate(const DepopPrepareCommand& command) const override; + u32 Estimate(const DepopForMixBuffersCommand& command) const override; + u32 Estimate(const DelayCommand& command) const override; + u32 Estimate(const UpsampleCommand& command) const override; + u32 Estimate(const DownMix6chTo2chCommand& command) const override; + u32 Estimate(const AuxCommand& command) const override; + u32 Estimate(const DeviceSinkCommand& command) const override; + u32 Estimate(const CircularBufferSinkCommand& command) const override; + u32 Estimate(const ReverbCommand& command) const override; + u32 Estimate(const I3dl2ReverbCommand& command) const override; + u32 Estimate(const PerformanceCommand& command) const override; + u32 Estimate(const ClearMixBufferCommand& command) const override; + u32 Estimate(const CopyMixBufferCommand& command) const override; + u32 Estimate(const LightLimiterVersion1Command& command) const override; + u32 Estimate(const LightLimiterVersion2Command& command) const override; + u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; + u32 Estimate(const CaptureCommand& command) const override; + u32 Estimate(const CompressorCommand& command) const override; + +private: + u32 sample_count{}; + u32 buffer_count{}; +}; + +class CommandProcessingTimeEstimatorVersion4 final : public ICommandProcessingTimeEstimator { +public: + CommandProcessingTimeEstimatorVersion4(u32 sample_count_, u32 buffer_count_) + : sample_count{sample_count_}, buffer_count{buffer_count_} {} + + u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; + u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; + u32 Estimate(const VolumeCommand& command) const override; + u32 Estimate(const VolumeRampCommand& command) const override; + u32 Estimate(const BiquadFilterCommand& command) const override; + u32 Estimate(const MixCommand& command) const override; + u32 Estimate(const MixRampCommand& command) const override; + u32 Estimate(const MixRampGroupedCommand& command) const override; + u32 Estimate(const DepopPrepareCommand& command) const override; + u32 Estimate(const DepopForMixBuffersCommand& command) const override; + u32 Estimate(const DelayCommand& command) const override; + u32 Estimate(const UpsampleCommand& command) const override; + u32 Estimate(const DownMix6chTo2chCommand& command) const override; + u32 Estimate(const AuxCommand& command) const override; + u32 Estimate(const DeviceSinkCommand& command) const override; + u32 Estimate(const CircularBufferSinkCommand& command) const override; + u32 Estimate(const ReverbCommand& command) const override; + u32 Estimate(const I3dl2ReverbCommand& command) const override; + u32 Estimate(const PerformanceCommand& command) const override; + u32 Estimate(const ClearMixBufferCommand& command) const override; + u32 Estimate(const CopyMixBufferCommand& command) const override; + u32 Estimate(const LightLimiterVersion1Command& command) const override; + u32 Estimate(const LightLimiterVersion2Command& command) const override; + u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; + u32 Estimate(const CaptureCommand& command) const override; + u32 Estimate(const CompressorCommand& command) const override; + +private: + u32 sample_count{}; + u32 buffer_count{}; +}; + +class CommandProcessingTimeEstimatorVersion5 final : public ICommandProcessingTimeEstimator { +public: + CommandProcessingTimeEstimatorVersion5(u32 sample_count_, u32 buffer_count_) + : sample_count{sample_count_}, buffer_count{buffer_count_} {} + + u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; + u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; + u32 Estimate(const VolumeCommand& command) const override; + u32 Estimate(const VolumeRampCommand& command) const override; + u32 Estimate(const BiquadFilterCommand& command) const override; + u32 Estimate(const MixCommand& command) const override; + u32 Estimate(const MixRampCommand& command) const override; + u32 Estimate(const MixRampGroupedCommand& command) const override; + u32 Estimate(const DepopPrepareCommand& command) const override; + u32 Estimate(const DepopForMixBuffersCommand& command) const override; + u32 Estimate(const DelayCommand& command) const override; + u32 Estimate(const UpsampleCommand& command) const override; + u32 Estimate(const DownMix6chTo2chCommand& command) const override; + u32 Estimate(const AuxCommand& command) const override; + u32 Estimate(const DeviceSinkCommand& command) const override; + u32 Estimate(const CircularBufferSinkCommand& command) const override; + u32 Estimate(const ReverbCommand& command) const override; + u32 Estimate(const I3dl2ReverbCommand& command) const override; + u32 Estimate(const PerformanceCommand& command) const override; + u32 Estimate(const ClearMixBufferCommand& command) const override; + u32 Estimate(const CopyMixBufferCommand& command) const override; + u32 Estimate(const LightLimiterVersion1Command& command) const override; + u32 Estimate(const LightLimiterVersion2Command& command) const override; + u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; + u32 Estimate(const CaptureCommand& command) const override; + u32 Estimate(const CompressorCommand& command) const override; + +private: + u32 sample_count{}; + u32 buffer_count{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/commands.h b/src/audio_core/renderer/command/commands.h new file mode 100644 index 000000000..6d8b8546d --- /dev/null +++ b/src/audio_core/renderer/command/commands.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/command/data_source/adpcm.h" +#include "audio_core/renderer/command/data_source/pcm_float.h" +#include "audio_core/renderer/command/data_source/pcm_int16.h" +#include "audio_core/renderer/command/effect/aux_.h" +#include "audio_core/renderer/command/effect/biquad_filter.h" +#include "audio_core/renderer/command/effect/capture.h" +#include "audio_core/renderer/command/effect/compressor.h" +#include "audio_core/renderer/command/effect/delay.h" +#include "audio_core/renderer/command/effect/i3dl2_reverb.h" +#include "audio_core/renderer/command/effect/light_limiter.h" +#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h" +#include "audio_core/renderer/command/effect/reverb.h" +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/command/mix/clear_mix.h" +#include "audio_core/renderer/command/mix/copy_mix.h" +#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h" +#include "audio_core/renderer/command/mix/depop_prepare.h" +#include "audio_core/renderer/command/mix/mix.h" +#include "audio_core/renderer/command/mix/mix_ramp.h" +#include "audio_core/renderer/command/mix/mix_ramp_grouped.h" +#include "audio_core/renderer/command/mix/volume.h" +#include "audio_core/renderer/command/mix/volume_ramp.h" +#include "audio_core/renderer/command/performance/performance.h" +#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h" +#include "audio_core/renderer/command/resample/upsample.h" +#include "audio_core/renderer/command/sink/circular_buffer.h" +#include "audio_core/renderer/command/sink/device.h" diff --git a/src/audio_core/renderer/command/data_source/adpcm.cpp b/src/audio_core/renderer/command/data_source/adpcm.cpp new file mode 100644 index 000000000..e66ed2990 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/adpcm.cpp @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <span> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/data_source/adpcm.h" +#include "audio_core/renderer/command/data_source/decode.h" + +namespace AudioCore::AudioRenderer { + +void AdpcmDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample " + "rate {} target sample rate {} src quality {}\n", + output_index, sample_rate, processor.target_sample_rate, src_quality); +} + +void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { + auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + + DecodeFromWaveBuffersArgs args{ + .sample_format{SampleFormat::Adpcm}, + .output{out_buffer}, + .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, + .wave_buffers{wave_buffers}, + .channel{0}, + .channel_count{1}, + .src_quality{src_quality}, + .pitch{pitch}, + .source_sample_rate{sample_rate}, + .target_sample_rate{processor.target_sample_rate}, + .sample_count{processor.sample_count}, + .data_address{data_address}, + .data_size{data_size}, + .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, + .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, + }; + + DecodeFromWaveBuffers(*processor.memory, args); +} + +bool AdpcmDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +void AdpcmDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample " + "rate {} target sample rate {} src quality {}\n", + output_index, sample_rate, processor.target_sample_rate, src_quality); +} + +void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { + auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + + DecodeFromWaveBuffersArgs args{ + .sample_format{SampleFormat::Adpcm}, + .output{out_buffer}, + .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, + .wave_buffers{wave_buffers}, + .channel{0}, + .channel_count{1}, + .src_quality{src_quality}, + .pitch{pitch}, + .source_sample_rate{sample_rate}, + .target_sample_rate{processor.target_sample_rate}, + .sample_count{processor.sample_count}, + .data_address{data_address}, + .data_size{data_size}, + .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, + .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, + }; + + DecodeFromWaveBuffers(*processor.memory, args); +} + +bool AdpcmDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/adpcm.h b/src/audio_core/renderer/command/data_source/adpcm.h new file mode 100644 index 000000000..a9cf9cee4 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/adpcm.h @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <string> + +#include "audio_core/common/common.h" +#include "audio_core/common/wave_buffer.h" +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers + * into the output_index mix buffer. + */ +struct AdpcmDataSourceVersion1Command : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Quality used for sample rate conversion + SrcQuality src_quality; + /// Mix buffer index for decoded samples + s16 output_index; + /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) + u16 flags; + /// Wavebuffer sample rate + u32 sample_rate; + /// Pitch used for sample rate conversion + f32 pitch; + /// Wavebuffers containing the wavebuffer address, context address, looping information etc + std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; + /// Voice state, updated each call and written back to game + CpuAddr voice_state; + /// Coefficients data address + CpuAddr data_address; + /// Coefficients data size + u64 data_size; +}; + +/** + * AudioRenderer command to decode ADPCM-encoded version 2 wavebuffers + * into the output_index mix buffer. + */ +struct AdpcmDataSourceVersion2Command : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Quality used for sample rate conversion + SrcQuality src_quality; + /// Mix buffer index for decoded samples + s16 output_index; + /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) + u16 flags; + /// Wavebuffer sample rate + u32 sample_rate; + /// Pitch used for sample rate conversion + f32 pitch; + /// Target channel to read within the wavebuffer + s8 channel_index; + /// Number of channels within the wavebuffer + s8 channel_count; + /// Wavebuffers containing the wavebuffer address, context address, looping information etc + std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; + /// Voice state, updated each call and written back to game + CpuAddr voice_state; + /// Coefficients data address + CpuAddr data_address; + /// Coefficients data size + u64 data_size; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/decode.cpp b/src/audio_core/renderer/command/data_source/decode.cpp new file mode 100644 index 000000000..ff5d31bd6 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/decode.cpp @@ -0,0 +1,428 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <array> +#include <vector> + +#include "audio_core/renderer/command/data_source/decode.h" +#include "audio_core/renderer/command/resample/resample.h" +#include "common/fixed_point.h" +#include "common/logging/log.h" +#include "core/memory.h" + +namespace AudioCore::AudioRenderer { + +constexpr u32 TempBufferSize = 0x3F00; +constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4}; + +/** + * Decode PCM data. Only s16 or f32 is supported. + * + * @tparam T - Type to decode. Only s16 and f32 are supported. + * @param memory - Core memory for reading samples. + * @param out_buffer - Output mix buffer to receive the samples. + * @param req - Information for how to decode. + * @return Number of samples decoded. + */ +template <typename T> +static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, + const DecodeArg& req) { + constexpr s32 min{std::numeric_limits<s16>::min()}; + constexpr s32 max{std::numeric_limits<s16>::max()}; + + if (req.buffer == 0 || req.buffer_size == 0) { + return 0; + } + + if (req.start_offset >= req.end_offset) { + return 0; + } + + auto samples_to_decode{ + std::min(req.samples_to_read, req.end_offset - req.start_offset - req.offset)}; + u32 channel_count{static_cast<u32>(req.channel_count)}; + + switch (req.channel_count) { + default: { + const VAddr source{req.buffer + + (((req.start_offset + req.offset) * channel_count) * sizeof(T))}; + const u64 size{channel_count * samples_to_decode}; + const u64 size_bytes{size * sizeof(T)}; + + std::vector<T> samples(size); + memory.ReadBlockUnsafe(source, samples.data(), size_bytes); + + if constexpr (std::is_floating_point_v<T>) { + for (u32 i = 0; i < samples_to_decode; i++) { + auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] * + std::numeric_limits<s16>::max())}; + out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max)); + } + } else { + for (u32 i = 0; i < samples_to_decode; i++) { + out_buffer[i] = samples[i * channel_count + req.target_channel]; + } + } + } break; + + case 1: + if (req.target_channel != 0) { + LOG_ERROR(Service_Audio, "Invalid target channel, expected 0, got {}", + req.target_channel); + return 0; + } + + const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))}; + std::vector<T> samples(samples_to_decode); + memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T)); + + if constexpr (std::is_floating_point_v<T>) { + for (u32 i = 0; i < samples_to_decode; i++) { + auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] * + std::numeric_limits<s16>::max())}; + out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max)); + } + } else { + std::memcpy(out_buffer.data(), samples.data(), samples_to_decode * sizeof(s16)); + } + break; + } + + return samples_to_decode; +} + +/** + * Decode ADPCM data. + * + * @param memory - Core memory for reading samples. + * @param out_buffer - Output mix buffer to receive the samples. + * @param req - Information for how to decode. + * @return Number of samples decoded. + */ +static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, + const DecodeArg& req) { + constexpr u32 SamplesPerFrame{14}; + constexpr u32 NibblesPerFrame{16}; + + if (req.buffer == 0 || req.buffer_size == 0) { + return 0; + } + + if (req.end_offset < req.start_offset) { + return 0; + } + + auto end{(req.end_offset % SamplesPerFrame) + + NibblesPerFrame * (req.end_offset / SamplesPerFrame)}; + if (req.end_offset % SamplesPerFrame) { + end += 3; + } else { + end += 1; + } + + if (req.buffer_size < end / 2) { + return 0; + } + + auto samples_to_process{ + std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)}; + + auto samples_to_read{samples_to_process}; + auto start_pos{req.start_offset + req.offset}; + auto samples_remaining_in_frame{start_pos % SamplesPerFrame}; + auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame + + samples_remaining_in_frame}; + + if (samples_remaining_in_frame) { + position_in_frame += 2; + } + + const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)}; + std::vector<u8> wavebuffer(size); + memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(), + wavebuffer.size()); + + auto context{req.adpcm_context}; + auto header{context->header}; + u8 coeff_index{static_cast<u8>((header >> 4U) & 0xFU)}; + u8 scale{static_cast<u8>(header & 0xFU)}; + s32 coeff0{req.coefficients[coeff_index * 2 + 0]}; + s32 coeff1{req.coefficients[coeff_index * 2 + 1]}; + + auto yn0{context->yn0}; + auto yn1{context->yn1}; + + static constexpr std::array<s32, 16> Steps{ + 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1, + }; + + const auto decode_sample = [&](const s32 code) -> s16 { + const auto xn = code * (1 << scale); + const auto prediction = coeff0 * yn0 + coeff1 * yn1; + const auto sample = ((xn << 11) + 0x400 + prediction) >> 11; + const auto saturated = std::clamp<s32>(sample, -0x8000, 0x7FFF); + yn1 = yn0; + yn0 = static_cast<s16>(saturated); + return yn0; + }; + + u32 read_index{0}; + u32 write_index{0}; + + while (samples_to_read > 0) { + // Are we at a new frame? + if ((position_in_frame % NibblesPerFrame) == 0) { + header = wavebuffer[read_index++]; + coeff_index = (header >> 4) & 0xF; + scale = header & 0xF; + coeff0 = req.coefficients[coeff_index * 2 + 0]; + coeff1 = req.coefficients[coeff_index * 2 + 1]; + position_in_frame += 2; + + // Can we consume all of this frame's samples? + if (samples_to_read >= SamplesPerFrame) { + // Can grab all samples until the next header + for (u32 i = 0; i < SamplesPerFrame / 2; i++) { + auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]}; + auto code1{Steps[wavebuffer[read_index] & 0xF]}; + read_index++; + + out_buffer[write_index++] = decode_sample(code0); + out_buffer[write_index++] = decode_sample(code1); + } + + position_in_frame += SamplesPerFrame; + samples_to_read -= SamplesPerFrame; + continue; + } + } + + // Decode a single sample + auto code{wavebuffer[read_index]}; + if (position_in_frame & 1) { + code &= 0xF; + read_index++; + } else { + code >>= 4; + } + + out_buffer[write_index++] = decode_sample(Steps[code]); + + position_in_frame++; + samples_to_read--; + } + + context->header = header; + context->yn0 = yn0; + context->yn1 = yn1; + + return samples_to_process; +} + +/** + * Decode implementation. + * Decode wavebuffers according to the given args. + * + * @param memory - Core memory to read data from. + * @param args - The wavebuffer data, and information for how to decode it. + */ +void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) { + auto& voice_state{*args.voice_state}; + auto remaining_sample_count{args.sample_count}; + auto fraction{voice_state.fraction}; + + const auto sample_rate_ratio{ + (Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) * + args.pitch}; + const auto size_required{fraction + remaining_sample_count * sample_rate_ratio}; + + if (size_required < 0) { + return; + } + + auto pitch{PitchBySrcQuality[static_cast<u32>(args.src_quality)]}; + if (static_cast<u32>(pitch + size_required.to_int_floor()) > TempBufferSize) { + return; + } + + auto max_remaining_sample_count{ + ((Common::FixedPoint<17, 15>(TempBufferSize) - fraction) / sample_rate_ratio) + .to_uint_floor()}; + max_remaining_sample_count = std::min(max_remaining_sample_count, remaining_sample_count); + + auto wavebuffers_consumed{voice_state.wave_buffers_consumed}; + auto wavebuffer_index{voice_state.wave_buffer_index}; + auto played_sample_count{voice_state.played_sample_count}; + + bool is_buffer_starved{false}; + u32 offset{voice_state.offset}; + + auto output_buffer{args.output}; + std::vector<s16> temp_buffer(TempBufferSize, 0); + + while (remaining_sample_count > 0) { + const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)}; + const auto samples_to_read{ + (fraction + samples_to_write * sample_rate_ratio).to_uint_floor()}; + + u32 temp_buffer_pos{0}; + + if (!args.IsVoicePitchAndSrcSkippedSupported) { + for (u32 i = 0; i < pitch; i++) { + temp_buffer[i] = voice_state.sample_history[i]; + } + temp_buffer_pos = pitch; + } + + u32 samples_read{0}; + while (samples_read < samples_to_read) { + if (wavebuffer_index >= MaxWaveBuffers) { + LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index); + wavebuffer_index = 0; + voice_state.wave_buffer_valid.fill(false); + wavebuffers_consumed = MaxWaveBuffers; + } + + if (!voice_state.wave_buffer_valid[wavebuffer_index]) { + is_buffer_starved = true; + break; + } + + auto& wavebuffer{args.wave_buffers[wavebuffer_index]}; + + if (offset == 0 && args.sample_format == SampleFormat::Adpcm && + wavebuffer.context != 0) { + memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context, + wavebuffer.context_size); + } + + auto start_offset{wavebuffer.start_offset}; + auto end_offset{wavebuffer.end_offset}; + + if (wavebuffer.loop && voice_state.loop_count > 0 && + wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 && + wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) { + start_offset = wavebuffer.loop_start_offset; + end_offset = wavebuffer.loop_end_offset; + } + + DecodeArg decode_arg{.buffer{wavebuffer.buffer}, + .buffer_size{wavebuffer.buffer_size}, + .start_offset{start_offset}, + .end_offset{end_offset}, + .channel_count{args.channel_count}, + .coefficients{}, + .adpcm_context{nullptr}, + .target_channel{args.channel}, + .offset{offset}, + .samples_to_read{samples_to_read - samples_read}}; + + s32 samples_decoded{0}; + + switch (args.sample_format) { + case SampleFormat::PcmInt16: + samples_decoded = DecodePcm<s16>( + memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, + decode_arg); + break; + + case SampleFormat::PcmFloat: + samples_decoded = DecodePcm<f32>( + memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, + decode_arg); + break; + + case SampleFormat::Adpcm: { + decode_arg.adpcm_context = &voice_state.adpcm_context; + memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size); + samples_decoded = DecodeAdpcm( + memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, + decode_arg); + } break; + + default: + LOG_ERROR(Service_Audio, "Invalid sample format to decode {}", + static_cast<u32>(args.sample_format)); + samples_decoded = 0; + break; + } + + played_sample_count += samples_decoded; + samples_read += samples_decoded; + temp_buffer_pos += samples_decoded; + offset += samples_decoded; + + if (samples_decoded == 0 || offset >= end_offset - start_offset) { + offset = 0; + if (!wavebuffer.loop) { + voice_state.wave_buffer_valid[wavebuffer_index] = false; + voice_state.loop_count = 0; + + if (wavebuffer.stream_ended) { + played_sample_count = 0; + } + + wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; + wavebuffers_consumed++; + } else { + voice_state.loop_count++; + if (wavebuffer.loop_count > 0 && + (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) { + voice_state.wave_buffer_valid[wavebuffer_index] = false; + voice_state.loop_count = 0; + + if (wavebuffer.stream_ended) { + played_sample_count = 0; + } + + wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; + wavebuffers_consumed++; + } + + if (samples_decoded == 0) { + is_buffer_starved = true; + break; + } + + if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) { + played_sample_count = 0; + } + } + } + } + + if (args.IsVoicePitchAndSrcSkippedSupported) { + if (samples_read > output_buffer.size()) { + LOG_ERROR(Service_Audio, "Attempting to write past the end of output buffer!"); + } + for (u32 i = 0; i < samples_read; i++) { + output_buffer[i] = temp_buffer[i]; + } + } else { + std::memset(&temp_buffer[temp_buffer_pos], 0, + (samples_to_read - samples_read) * sizeof(s16)); + + Resample(output_buffer, temp_buffer, sample_rate_ratio, fraction, samples_to_write, + args.src_quality); + + std::memcpy(voice_state.sample_history.data(), &temp_buffer[samples_to_read], + pitch * sizeof(s16)); + } + + remaining_sample_count -= samples_to_write; + if (remaining_sample_count != 0 && is_buffer_starved) { + LOG_ERROR(Service_Audio, "Samples remaining but buffer is starving??"); + break; + } + + output_buffer = output_buffer.subspan(samples_to_write); + } + + voice_state.wave_buffers_consumed = wavebuffers_consumed; + voice_state.played_sample_count = played_sample_count; + voice_state.wave_buffer_index = wavebuffer_index; + voice_state.offset = offset; + voice_state.fraction = fraction; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/decode.h b/src/audio_core/renderer/command/data_source/decode.h new file mode 100644 index 000000000..4d63d6fa8 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/decode.h @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <span> + +#include "audio_core/common/common.h" +#include "audio_core/common/wave_buffer.h" +#include "audio_core/renderer/voice/voice_state.h" +#include "common/common_types.h" + +namespace Core::Memory { +class Memory; +} + +namespace AudioCore::AudioRenderer { + +struct DecodeFromWaveBuffersArgs { + SampleFormat sample_format; + std::span<s32> output; + VoiceState* voice_state; + std::span<WaveBufferVersion2> wave_buffers; + s8 channel; + s8 channel_count; + SrcQuality src_quality; + f32 pitch; + u32 source_sample_rate; + u32 target_sample_rate; + u32 sample_count; + CpuAddr data_address; + u64 data_size; + bool IsVoicePlayedSampleCountResetAtLoopPointSupported; + bool IsVoicePitchAndSrcSkippedSupported; +}; + +struct DecodeArg { + CpuAddr buffer; + u64 buffer_size; + u32 start_offset; + u32 end_offset; + s8 channel_count; + std::array<s16, 16> coefficients; + VoiceState::AdpcmContext* adpcm_context; + s8 target_channel; + u32 offset; + u32 samples_to_read; +}; + +/** + * Decode wavebuffers according to the given args. + * + * @param memory - Core memory to read data from. + * @param args - The wavebuffer data, and information for how to decode it. + */ +void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/pcm_float.cpp b/src/audio_core/renderer/command/data_source/pcm_float.cpp new file mode 100644 index 000000000..be77fab69 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/pcm_float.cpp @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/data_source/decode.h" +#include "audio_core/renderer/command/data_source/pcm_float.h" + +namespace AudioCore::AudioRenderer { + +void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, + std::string& string) { + string += + fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} " + "channel count {} source sample rate {} target sample rate {} src quality {}\n", + output_index, channel_index, channel_count, sample_rate, + processor.target_sample_rate, src_quality); +} + +void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { + auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count); + + DecodeFromWaveBuffersArgs args{ + .sample_format{SampleFormat::PcmFloat}, + .output{out_buffer}, + .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, + .wave_buffers{wave_buffers}, + .channel{channel_index}, + .channel_count{channel_count}, + .src_quality{src_quality}, + .pitch{pitch}, + .source_sample_rate{sample_rate}, + .target_sample_rate{processor.target_sample_rate}, + .sample_count{processor.sample_count}, + .data_address{0}, + .data_size{0}, + .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, + .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, + }; + + DecodeFromWaveBuffers(*processor.memory, args); +} + +bool PcmFloatDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, + std::string& string) { + string += + fmt::format("PcmFloatDataSourceVersion2Command\n\toutput_index {:02X} channel {} " + "channel count {} source sample rate {} target sample rate {} src quality {}\n", + output_index, channel_index, channel_count, sample_rate, + processor.target_sample_rate, src_quality); +} + +void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { + auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count); + + DecodeFromWaveBuffersArgs args{ + .sample_format{SampleFormat::PcmFloat}, + .output{out_buffer}, + .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, + .wave_buffers{wave_buffers}, + .channel{channel_index}, + .channel_count{channel_count}, + .src_quality{src_quality}, + .pitch{pitch}, + .source_sample_rate{sample_rate}, + .target_sample_rate{processor.target_sample_rate}, + .sample_count{processor.sample_count}, + .data_address{0}, + .data_size{0}, + .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, + .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, + }; + + DecodeFromWaveBuffers(*processor.memory, args); +} + +bool PcmFloatDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/pcm_float.h b/src/audio_core/renderer/command/data_source/pcm_float.h new file mode 100644 index 000000000..e4af77c20 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/pcm_float.h @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/common/wave_buffer.h" +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command to decode PCM float-encoded version 1 wavebuffers + * into the output_index mix buffer. + */ +struct PcmFloatDataSourceVersion1Command : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Quality used for sample rate conversion + SrcQuality src_quality; + /// Mix buffer index for decoded samples + s16 output_index; + /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) + u16 flags; + /// Wavebuffer sample rate + u32 sample_rate; + /// Pitch used for sample rate conversion + f32 pitch; + /// Target channel to read within the wavebuffer + s8 channel_index; + /// Number of channels within the wavebuffer + s8 channel_count; + /// Wavebuffers containing the wavebuffer address, context address, looping information etc + std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; + /// Voice state, updated each call and written back to game + CpuAddr voice_state; +}; + +/** + * AudioRenderer command to decode PCM float-encoded version 2 wavebuffers + * into the output_index mix buffer. + */ +struct PcmFloatDataSourceVersion2Command : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Quality used for sample rate conversion + SrcQuality src_quality; + /// Mix buffer index for decoded samples + s16 output_index; + /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) + u16 flags; + /// Wavebuffer sample rate + u32 sample_rate; + /// Pitch used for sample rate conversion + f32 pitch; + /// Target channel to read within the wavebuffer + s8 channel_index; + /// Number of channels within the wavebuffer + s8 channel_count; + /// Wavebuffers containing the wavebuffer address, context address, looping information etc + std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; + /// Voice state, updated each call and written back to game + CpuAddr voice_state; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/pcm_int16.cpp b/src/audio_core/renderer/command/data_source/pcm_int16.cpp new file mode 100644 index 000000000..7a27463e4 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/pcm_int16.cpp @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <span> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/data_source/decode.h" +#include "audio_core/renderer/command/data_source/pcm_int16.h" + +namespace AudioCore::AudioRenderer { + +void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, + std::string& string) { + string += + fmt::format("PcmInt16DataSourceVersion1Command\n\toutput_index {:02X} channel {} " + "channel count {} source sample rate {} target sample rate {} src quality {}\n", + output_index, channel_index, channel_count, sample_rate, + processor.target_sample_rate, src_quality); +} + +void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { + auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count); + + DecodeFromWaveBuffersArgs args{ + .sample_format{SampleFormat::PcmInt16}, + .output{out_buffer}, + .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, + .wave_buffers{wave_buffers}, + .channel{channel_index}, + .channel_count{channel_count}, + .src_quality{src_quality}, + .pitch{pitch}, + .source_sample_rate{sample_rate}, + .target_sample_rate{processor.target_sample_rate}, + .sample_count{processor.sample_count}, + .data_address{0}, + .data_size{0}, + .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, + .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, + }; + + DecodeFromWaveBuffers(*processor.memory, args); +} + +bool PcmInt16DataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, + std::string& string) { + string += + fmt::format("PcmInt16DataSourceVersion2Command\n\toutput_index {:02X} channel {} " + "channel count {} source sample rate {} target sample rate {} src quality {}\n", + output_index, channel_index, channel_count, sample_rate, + processor.target_sample_rate, src_quality); +} + +void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { + auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count); + DecodeFromWaveBuffersArgs args{ + .sample_format{SampleFormat::PcmInt16}, + .output{out_buffer}, + .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, + .wave_buffers{wave_buffers}, + .channel{channel_index}, + .channel_count{channel_count}, + .src_quality{src_quality}, + .pitch{pitch}, + .source_sample_rate{sample_rate}, + .target_sample_rate{processor.target_sample_rate}, + .sample_count{processor.sample_count}, + .data_address{0}, + .data_size{0}, + .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, + .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, + }; + + DecodeFromWaveBuffers(*processor.memory, args); +} + +bool PcmInt16DataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/pcm_int16.h b/src/audio_core/renderer/command/data_source/pcm_int16.h new file mode 100644 index 000000000..5de1ad60d --- /dev/null +++ b/src/audio_core/renderer/command/data_source/pcm_int16.h @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/common/wave_buffer.h" +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers + * into the output_index mix buffer. + */ +struct PcmInt16DataSourceVersion1Command : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Quality used for sample rate conversion + SrcQuality src_quality; + /// Mix buffer index for decoded samples + s16 output_index; + /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) + u16 flags; + /// Wavebuffer sample rate + u32 sample_rate; + /// Pitch used for sample rate conversion + f32 pitch; + /// Target channel to read within the wavebuffer + s8 channel_index; + /// Number of channels within the wavebuffer + s8 channel_count; + /// Wavebuffers containing the wavebuffer address, context address, looping information etc + std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; + /// Voice state, updated each call and written back to game + CpuAddr voice_state; +}; + +/** + * AudioRenderer command to decode PCM s16-encoded version 2 wavebuffers + * into the output_index mix buffer. + */ +struct PcmInt16DataSourceVersion2Command : ICommand { + /** + * Print this command's information to a string. + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Quality used for sample rate conversion + SrcQuality src_quality; + /// Mix buffer index for decoded samples + s16 output_index; + /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) + u16 flags; + /// Wavebuffer sample rate + u32 sample_rate; + /// Pitch used for sample rate conversion + f32 pitch; + /// Target channel to read within the wavebuffer + s8 channel_index; + /// Number of channels within the wavebuffer + s8 channel_count; + /// Wavebuffers containing the wavebuffer address, context address, looping information etc + std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; + /// Voice state, updated each call and written back to game + CpuAddr voice_state; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/aux_.cpp b/src/audio_core/renderer/command/effect/aux_.cpp new file mode 100644 index 000000000..e76db893f --- /dev/null +++ b/src/audio_core/renderer/command/effect/aux_.cpp @@ -0,0 +1,207 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/aux_.h" +#include "audio_core/renderer/effect/aux_.h" +#include "core/memory.h" + +namespace AudioCore::AudioRenderer { +/** + * Reset an AuxBuffer. + * + * @param memory - Core memory for writing. + * @param aux_info - Memory address pointing to the AuxInfo to reset. + */ +static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) { + if (aux_info == 0) { + LOG_ERROR(Service_Audio, "Aux info is 0!"); + return; + } + + auto info{reinterpret_cast<AuxInfo::AuxInfoDsp*>(memory.GetPointer(aux_info))}; + info->read_offset = 0; + info->write_offset = 0; + info->total_sample_count = 0; +} + +/** + * Write the given input mix buffer to the memory at send_buffer, and update send_info_ if + * update_count is set, to notify the game that an update happened. + * + * @param memory - Core memory for writing. + * @param send_info_ - Meta information for where to write the mix buffer. + * @param sample_count - Unused. + * @param send_buffer - Memory address to write the mix buffer to. + * @param count_max - Maximum number of samples in the receiving buffer. + * @param input - Input mix buffer to write. + * @param write_count_ - Number of samples to write. + * @param write_offset - Current offset to begin writing the receiving buffer at. + * @param update_count - If non-zero, send_info_ will be updated. + * @return Number of samples written. + */ +static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_, + [[maybe_unused]] u32 sample_count, const CpuAddr send_buffer, + const u32 count_max, std::span<const s32> input, + const u32 write_count_, const u32 write_offset, + const u32 update_count) { + if (write_count_ > count_max) { + LOG_ERROR(Service_Audio, + "write_count must be smaller than count_max! write_count {}, count_max {}", + write_count_, count_max); + return 0; + } + + if (input.empty()) { + LOG_ERROR(Service_Audio, "input buffer is empty!"); + return 0; + } + + if (send_buffer == 0) { + LOG_ERROR(Service_Audio, "send_buffer is 0!"); + return 0; + } + + if (count_max == 0) { + return 0; + } + + AuxInfo::AuxInfoDsp send_info{}; + memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp)); + + u32 target_write_offset{send_info.write_offset + write_offset}; + if (target_write_offset > count_max || write_count_ == 0) { + return 0; + } + + u32 write_count{write_count_}; + u32 write_pos{0}; + while (write_count > 0) { + u32 to_write{std::min(count_max - target_write_offset, write_count)}; + + if (to_write > 0) { + memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32), + &input[write_pos], to_write * sizeof(s32)); + } + + target_write_offset = (target_write_offset + to_write) % count_max; + write_count -= to_write; + write_pos += to_write; + } + + if (update_count) { + send_info.write_offset = (send_info.write_offset + update_count) % count_max; + } + + memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp)); + + return write_count_; +} + +/** + * Read the given memory at return_buffer into the output mix buffer, and update return_info_ if + * update_count is set, to notify the game that an update happened. + * + * @param memory - Core memory for writing. + * @param return_info_ - Meta information for where to read the mix buffer. + * @param return_buffer - Memory address to read the samples from. + * @param count_max - Maximum number of samples in the receiving buffer. + * @param output - Output mix buffer which will receive the samples. + * @param count_ - Number of samples to read. + * @param read_offset - Current offset to begin reading the return_buffer at. + * @param update_count - If non-zero, send_info_ will be updated. + * @return Number of samples read. + */ +static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr return_info_, + const CpuAddr return_buffer, const u32 count_max, std::span<s32> output, + const u32 count_, const u32 read_offset, const u32 update_count) { + if (count_max == 0) { + return 0; + } + + if (count_ > count_max) { + LOG_ERROR(Service_Audio, "count must be smaller than count_max! count {}, count_max {}", + count_, count_max); + return 0; + } + + if (output.empty()) { + LOG_ERROR(Service_Audio, "output buffer is empty!"); + return 0; + } + + if (return_buffer == 0) { + LOG_ERROR(Service_Audio, "return_buffer is 0!"); + return 0; + } + + AuxInfo::AuxInfoDsp return_info{}; + memory.ReadBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp)); + + u32 target_read_offset{return_info.read_offset + read_offset}; + if (target_read_offset > count_max) { + return 0; + } + + u32 read_count{count_}; + u32 read_pos{0}; + while (read_count > 0) { + u32 to_read{std::min(count_max - target_read_offset, read_count)}; + + if (to_read > 0) { + memory.ReadBlockUnsafe(return_buffer + target_read_offset * sizeof(s32), + &output[read_pos], to_read * sizeof(s32)); + } + + target_read_offset = (target_read_offset + to_read) % count_max; + read_count -= to_read; + read_pos += to_read; + } + + if (update_count) { + return_info.read_offset = (return_info.read_offset + update_count) % count_max; + } + + memory.WriteBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp)); + + return count_; +} + +void AuxCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled, + input, output); +} + +void AuxCommand::Process(const ADSP::CommandListProcessor& processor) { + auto input_buffer{ + processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; + auto output_buffer{ + processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; + + if (effect_enabled) { + WriteAuxBufferDsp(*processor.memory, send_buffer_info, processor.sample_count, send_buffer, + count_max, input_buffer, processor.sample_count, write_offset, + update_count); + + auto read{ReadAuxBufferDsp(*processor.memory, return_buffer_info, return_buffer, count_max, + output_buffer, processor.sample_count, write_offset, + update_count)}; + + if (read != processor.sample_count) { + std::memset(&output_buffer[read], 0, processor.sample_count - read); + } + } else { + ResetAuxBufferDsp(*processor.memory, send_buffer_info); + ResetAuxBufferDsp(*processor.memory, return_buffer_info); + if (input != output) { + std::memcpy(output_buffer.data(), input_buffer.data(), output_buffer.size_bytes()); + } + } +} + +bool AuxCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/aux_.h b/src/audio_core/renderer/command/effect/aux_.h new file mode 100644 index 000000000..825c93732 --- /dev/null +++ b/src/audio_core/renderer/command/effect/aux_.h @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game + * memory, and reading into the output buffer from game memory. + */ +struct AuxCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer index + s16 input; + /// Output mix buffer index + s16 output; + /// Meta info for writing + CpuAddr send_buffer_info; + /// Meta info for reading + CpuAddr return_buffer_info; + /// Game memory write buffer + CpuAddr send_buffer; + /// Game memory read buffer + CpuAddr return_buffer; + /// Max samples to read/write + u32 count_max; + /// Current read/write offset + u32 write_offset; + /// Number of samples to update per call + u32 update_count; + /// is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/biquad_filter.cpp b/src/audio_core/renderer/command/effect/biquad_filter.cpp new file mode 100644 index 000000000..1baae74fd --- /dev/null +++ b/src/audio_core/renderer/command/effect/biquad_filter.cpp @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/biquad_filter.h" +#include "audio_core/renderer/voice/voice_state.h" + +namespace AudioCore::AudioRenderer { +/** + * Biquad filter float implementation. + * + * @param output - Output container for filtered samples. + * @param input - Input container for samples to be filtered. + * @param b - Feedforward coefficients. + * @param a - Feedback coefficients. + * @param state - State to track previous samples between calls. + * @param sample_count - Number of samples to process. + */ +void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input, + std::array<s16, 3>& b_, std::array<s16, 2>& a_, + VoiceState::BiquadFilterState& state, const u32 sample_count) { + constexpr s64 min{std::numeric_limits<s32>::min()}; + constexpr s64 max{std::numeric_limits<s32>::max()}; + std::array<f64, 3> b{Common::FixedPoint<50, 14>::from_base(b_[0]).to_double(), + Common::FixedPoint<50, 14>::from_base(b_[1]).to_double(), + Common::FixedPoint<50, 14>::from_base(b_[2]).to_double()}; + std::array<f64, 2> a{Common::FixedPoint<50, 14>::from_base(a_[0]).to_double(), + Common::FixedPoint<50, 14>::from_base(a_[1]).to_double()}; + std::array<f64, 4> s{state.s0.to_double(), state.s1.to_double(), state.s2.to_double(), + state.s3.to_double()}; + + for (u32 i = 0; i < sample_count; i++) { + f64 in_sample{static_cast<f64>(input[i])}; + auto sample{in_sample * b[0] + s[0] * b[1] + s[1] * b[2] + s[2] * a[0] + s[3] * a[1]}; + + output[i] = static_cast<s32>(std::clamp(static_cast<s64>(sample), min, max)); + + s[1] = s[0]; + s[0] = in_sample; + s[3] = s[2]; + s[2] = sample; + } + + state.s0 = s[0]; + state.s1 = s[1]; + state.s2 = s[2]; + state.s3 = s[3]; +} + +/** + * Biquad filter s32 implementation. + * + * @param output - Output container for filtered samples. + * @param input - Input container for samples to be filtered. + * @param b - Feedforward coefficients. + * @param a - Feedback coefficients. + * @param state - State to track previous samples between calls. + * @param sample_count - Number of samples to process. + */ +static void ApplyBiquadFilterInt(std::span<s32> output, std::span<const s32> input, + std::array<s16, 3>& b_, std::array<s16, 2>& a_, + VoiceState::BiquadFilterState& state, const u32 sample_count) { + constexpr s64 min{std::numeric_limits<s32>::min()}; + constexpr s64 max{std::numeric_limits<s32>::max()}; + std::array<Common::FixedPoint<50, 14>, 3> b{ + Common::FixedPoint<50, 14>::from_base(b_[0]), + Common::FixedPoint<50, 14>::from_base(b_[1]), + Common::FixedPoint<50, 14>::from_base(b_[2]), + }; + std::array<Common::FixedPoint<50, 14>, 3> a{ + Common::FixedPoint<50, 14>::from_base(a_[0]), + Common::FixedPoint<50, 14>::from_base(a_[1]), + }; + + for (u32 i = 0; i < sample_count; i++) { + s64 in_sample{input[i]}; + auto sample{in_sample * b[0] + state.s0}; + const auto out_sample{std::clamp(sample.to_long(), min, max)}; + + output[i] = static_cast<s32>(out_sample); + + state.s0 = state.s1 + b[1] * in_sample + a[0] * out_sample; + state.s1 = 0 + b[2] * in_sample + a[1] * out_sample; + } +} + +void BiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format( + "BiquadFilterCommand\n\tinput {:02X} output {:02X} needs_init {} use_float_processing {}\n", + input, output, needs_init, use_float_processing); +} + +void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) { + auto state_{reinterpret_cast<VoiceState::BiquadFilterState*>(state)}; + if (needs_init) { + std::memset(state_, 0, sizeof(VoiceState::BiquadFilterState)); + } + + auto input_buffer{ + processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; + auto output_buffer{ + processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; + + if (use_float_processing) { + ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_, + processor.sample_count); + } else { + ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_, + processor.sample_count); + } +} + +bool BiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/biquad_filter.h b/src/audio_core/renderer/command/effect/biquad_filter.h new file mode 100644 index 000000000..4c9c42d29 --- /dev/null +++ b/src/audio_core/renderer/command/effect/biquad_filter.h @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/voice/voice_info.h" +#include "audio_core/renderer/voice/voice_state.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for applying a biquad filter to the input mix buffer, saving the results to + * the output mix buffer. + */ +struct BiquadFilterCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer index + s16 input; + /// Output mix buffer index + s16 output; + /// Input parameters for biquad + VoiceInfo::BiquadFilterParameter biquad; + /// Biquad state, updated each call + CpuAddr state; + /// If true, reset the state + bool needs_init; + /// If true, use float processing rather than int + bool use_float_processing; +}; + +/** + * Biquad filter float implementation. + * + * @param output - Output container for filtered samples. + * @param input - Input container for samples to be filtered. + * @param b - Feedforward coefficients. + * @param a - Feedback coefficients. + * @param state - State to track previous samples. + * @param sample_count - Number of samples to process. + */ +void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input, + std::array<s16, 3>& b, std::array<s16, 2>& a, + VoiceState::BiquadFilterState& state, const u32 sample_count); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/capture.cpp b/src/audio_core/renderer/command/effect/capture.cpp new file mode 100644 index 000000000..042fd286e --- /dev/null +++ b/src/audio_core/renderer/command/effect/capture.cpp @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/capture.h" +#include "audio_core/renderer/effect/aux_.h" +#include "core/memory.h" + +namespace AudioCore::AudioRenderer { +/** + * Reset an AuxBuffer. + * + * @param memory - Core memory for writing. + * @param aux_info - Memory address pointing to the AuxInfo to reset. + */ +static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) { + if (aux_info == 0) { + LOG_ERROR(Service_Audio, "Aux info is 0!"); + return; + } + + memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, read_offset)), 0); + memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, write_offset)), 0); + memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, total_sample_count)), 0); +} + +/** + * Write the given input mix buffer to the memory at send_buffer, and update send_info_ if + * update_count is set, to notify the game that an update happened. + * + * @param memory - Core memory for writing. + * @param send_info_ - Header information for where to write the mix buffer. + * @param send_buffer - Memory address to write the mix buffer to. + * @param count_max - Maximum number of samples in the receiving buffer. + * @param input - Input mix buffer to write. + * @param write_count_ - Number of samples to write. + * @param write_offset - Current offset to begin writing the receiving buffer at. + * @param update_count - If non-zero, send_info_ will be updated. + * @return Number of samples written. + */ +static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_, + const CpuAddr send_buffer, u32 count_max, std::span<const s32> input, + const u32 write_count_, const u32 write_offset, + const u32 update_count) { + if (write_count_ > count_max) { + LOG_ERROR(Service_Audio, + "write_count must be smaller than count_max! write_count {}, count_max {}", + write_count_, count_max); + return 0; + } + + if (send_info_ == 0) { + LOG_ERROR(Service_Audio, "send_info is 0!"); + return 0; + } + + if (input.empty()) { + LOG_ERROR(Service_Audio, "input buffer is empty!"); + return 0; + } + + if (send_buffer == 0) { + LOG_ERROR(Service_Audio, "send_buffer is 0!"); + return 0; + } + + if (count_max == 0) { + return 0; + } + + AuxInfo::AuxBufferInfo send_info{}; + memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo)); + + u32 target_write_offset{send_info.dsp_info.write_offset + write_offset}; + if (target_write_offset > count_max || write_count_ == 0) { + return 0; + } + + u32 write_count{write_count_}; + u32 write_pos{0}; + while (write_count > 0) { + u32 to_write{std::min(count_max - target_write_offset, write_count)}; + + if (to_write > 0) { + memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32), + &input[write_pos], to_write * sizeof(s32)); + } + + target_write_offset = (target_write_offset + to_write) % count_max; + write_count -= to_write; + write_pos += to_write; + } + + if (update_count) { + const auto count_diff{send_info.dsp_info.total_sample_count - + send_info.cpu_info.total_sample_count}; + if (count_diff >= count_max) { + auto dsp_lost_count{send_info.dsp_info.lost_sample_count + update_count}; + if (dsp_lost_count - send_info.cpu_info.lost_sample_count < + send_info.dsp_info.lost_sample_count - send_info.cpu_info.lost_sample_count) { + dsp_lost_count = send_info.cpu_info.lost_sample_count - 1; + } + send_info.dsp_info.lost_sample_count = dsp_lost_count; + } + + send_info.dsp_info.write_offset = + (send_info.dsp_info.write_offset + update_count + count_max) % count_max; + + auto new_sample_count{send_info.dsp_info.total_sample_count + update_count}; + if (new_sample_count - send_info.cpu_info.total_sample_count < count_diff) { + new_sample_count = send_info.cpu_info.total_sample_count - 1; + } + send_info.dsp_info.total_sample_count = new_sample_count; + } + + memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo)); + + return write_count_; +} + +void CaptureCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled, + input, output); +} + +void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) { + if (effect_enabled) { + auto input_buffer{ + processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; + WriteAuxBufferDsp(*processor.memory, send_buffer_info, send_buffer, count_max, input_buffer, + processor.sample_count, write_offset, update_count); + } else { + ResetAuxBufferDsp(*processor.memory, send_buffer_info); + } +} + +bool CaptureCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/capture.h b/src/audio_core/renderer/command/effect/capture.h new file mode 100644 index 000000000..8670acb24 --- /dev/null +++ b/src/audio_core/renderer/command/effect/capture.h @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory + * address. + */ +struct CaptureCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer index + s16 input; + /// Output mix buffer index + s16 output; + /// Meta info for writing + CpuAddr send_buffer_info; + /// Game memory write buffer + CpuAddr send_buffer; + /// Max samples to read/write + u32 count_max; + /// Current read/write offset + u32 write_offset; + /// Number of samples to update per call + u32 update_count; + /// is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/compressor.cpp b/src/audio_core/renderer/command/effect/compressor.cpp new file mode 100644 index 000000000..2ebc140f1 --- /dev/null +++ b/src/audio_core/renderer/command/effect/compressor.cpp @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <cmath> +#include <span> +#include <vector> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/compressor.h" +#include "audio_core/renderer/effect/compressor.h" + +namespace AudioCore::AudioRenderer { + +static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params, + CompressorInfo::State& state) { + const auto ratio{1.0f / params.compressor_ratio}; + auto makeup_gain{0.0f}; + if (params.makeup_gain_enabled) { + makeup_gain = (params.threshold * 0.5f) * (ratio - 1.0f) - 3.0f; + } + state.makeup_gain = makeup_gain; + state.unk_18 = params.unk_28; + + const auto a{(params.out_gain + makeup_gain) / 20.0f * 3.3219f}; + const auto b{(a - std::trunc(a)) * 0.69315f}; + const auto c{std::pow(2.0f, b)}; + + state.unk_0C = (1.0f - ratio) / 6.0f; + state.unk_14 = params.threshold + 1.5f; + state.unk_10 = params.threshold - 1.5f; + state.unk_20 = c; +} + +static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params, + CompressorInfo::State& state) { + std::memset(&state, 0, sizeof(CompressorInfo::State)); + + state.unk_00 = 0; + state.unk_04 = 1.0f; + state.unk_08 = 1.0f; + + SetCompressorEffectParameter(params, state); +} + +static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params, + CompressorInfo::State& state, bool enabled, + std::vector<std::span<const s32>> input_buffers, + std::vector<std::span<s32>> output_buffers, u32 sample_count) { + if (enabled) { + auto state_00{state.unk_00}; + auto state_04{state.unk_04}; + auto state_08{state.unk_08}; + auto state_18{state.unk_18}; + + for (u32 i = 0; i < sample_count; i++) { + auto a{0.0f}; + for (s16 channel = 0; channel < params.channel_count; channel++) { + const auto input_sample{Common::FixedPoint<49, 15>(input_buffers[channel][i])}; + a += (input_sample * input_sample).to_float(); + } + + state_00 += params.unk_24 * ((a / params.channel_count) - state.unk_00); + + auto b{-100.0f}; + auto c{0.0f}; + if (state_00 >= 1.0e-10) { + b = std::log10(state_00) * 10.0f; + c = 1.0f; + } + + if (b >= state.unk_10) { + const auto d{b >= state.unk_14 + ? ((1.0f / params.compressor_ratio) - 1.0f) * + (b - params.threshold) + : (b - state.unk_10) * (b - state.unk_10) * -state.unk_0C}; + const auto e{d / 20.0f * 3.3219f}; + const auto f{(e - std::trunc(e)) * 0.69315f}; + c = std::pow(2.0f, f); + } + + state_18 = params.unk_28; + auto tmp{c}; + if ((state_04 - c) <= 0.08f) { + state_18 = params.unk_2C; + if (((state_04 - c) >= -0.08f) && (std::abs(state_08 - c) >= 0.001f)) { + tmp = state_04; + } + } + + state_04 = tmp; + state_08 += (c - state_08) * state_18; + + for (s16 channel = 0; channel < params.channel_count; channel++) { + output_buffers[channel][i] = static_cast<s32>( + static_cast<f32>(input_buffers[channel][i]) * state_08 * state.unk_20); + } + } + + state.unk_00 = state_00; + state.unk_04 = state_04; + state.unk_08 = state_08; + state.unk_18 = state_18; + } else { + for (s16 channel = 0; channel < params.channel_count; channel++) { + if (params.inputs[channel] != params.outputs[channel]) { + std::memcpy((char*)output_buffers[channel].data(), + (char*)input_buffers[channel].data(), + output_buffers[channel].size_bytes()); + } + } + } +} + +void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled); + for (s16 i = 0; i < parameter.channel_count; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (s16 i = 0; i < parameter.channel_count; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) { + std::vector<std::span<const s32>> input_buffers(parameter.channel_count); + std::vector<std::span<s32>> output_buffers(parameter.channel_count); + + for (s16 i = 0; i < parameter.channel_count; i++) { + input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count); + output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count); + } + + auto state_{reinterpret_cast<CompressorInfo::State*>(state)}; + + if (effect_enabled) { + if (parameter.state == CompressorInfo::ParameterState::Updating) { + SetCompressorEffectParameter(parameter, *state_); + } else if (parameter.state == CompressorInfo::ParameterState::Initialized) { + InitializeCompressorEffect(parameter, *state_); + } + } + + ApplyCompressorEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count); +} + +bool CompressorCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/compressor.h b/src/audio_core/renderer/command/effect/compressor.h new file mode 100644 index 000000000..f8e96cb43 --- /dev/null +++ b/src/audio_core/renderer/command/effect/compressor.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/effect/compressor.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for limiting volume between a high and low threshold. + * Version 1. + */ +struct CompressorCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array<s16, MaxChannels> inputs; + /// Output mix buffer offsets for each channel + std::array<s16, MaxChannels> outputs; + /// Input parameters + CompressorInfo::ParameterVersion2 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/delay.cpp b/src/audio_core/renderer/command/effect/delay.cpp new file mode 100644 index 000000000..a4e408d40 --- /dev/null +++ b/src/audio_core/renderer/command/effect/delay.cpp @@ -0,0 +1,238 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/delay.h" + +namespace AudioCore::AudioRenderer { +/** + * Update the DelayInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + */ +static void SetDelayEffectParameter(const DelayInfo::ParameterVersion1& params, + DelayInfo::State& state) { + auto channel_spread{params.channel_spread}; + state.feedback_gain = params.feedback_gain * 0.97998046875f; + state.delay_feedback_gain = state.feedback_gain * (1.0f - channel_spread); + if (params.channel_count == 4 || params.channel_count == 6) { + channel_spread >>= 1; + } + state.delay_feedback_cross_gain = channel_spread * state.feedback_gain; + state.lowpass_feedback_gain = params.lowpass_amount * 0.949951171875f; + state.lowpass_gain = 1.0f - state.lowpass_feedback_gain; +} + +/** + * Initialize a new DelayInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + * @param workbuffer - Game-supplied memory for the state. (Unused) + */ +static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params, + DelayInfo::State& state, + [[maybe_unused]] const CpuAddr workbuffer) { + state = {}; + + for (u32 channel = 0; channel < params.channel_count; channel++) { + Common::FixedPoint<32, 32> sample_count_max{0.064f}; + sample_count_max *= params.sample_rate.to_int_floor() * params.delay_time_max; + + Common::FixedPoint<18, 14> delay_time{params.delay_time}; + delay_time *= params.sample_rate / 1000; + Common::FixedPoint<32, 32> sample_count{delay_time}; + + if (sample_count > sample_count_max) { + sample_count = sample_count_max; + } + + state.delay_lines[channel].sample_count_max = sample_count_max.to_int_floor(); + state.delay_lines[channel].sample_count = sample_count.to_int_floor(); + state.delay_lines[channel].buffer.resize(state.delay_lines[channel].sample_count, 0); + if (state.delay_lines[channel].buffer.size() == 0) { + state.delay_lines[channel].buffer.push_back(0); + } + state.delay_lines[channel].buffer_pos = 0; + state.delay_lines[channel].decay_rate = 1.0f; + } + + SetDelayEffectParameter(params, state); +} + +/** + * Delay effect impl, according to the parameters and current state, on the input mix buffers, + * saving the results to the output mix buffers. + * + * @tparam NumChannels - Number of channels to process. 1-6. + * @param params - Input parameters to use. + * @param state - State to use, must be initialized (see InitializeDelayEffect). + * @param inputs - Input mix buffers to performan the delay on. + * @param outputs - Output mix buffers to receive the delayed samples. + * @param sample_count - Number of samples to process. + */ +template <size_t NumChannels> +static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state, + std::vector<std::span<const s32>>& inputs, + std::vector<std::span<s32>>& outputs, const u32 sample_count) { + for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { + std::array<Common::FixedPoint<50, 14>, NumChannels> input_samples{}; + for (u32 channel = 0; channel < NumChannels; channel++) { + input_samples[channel] = inputs[channel][sample_index] * 64; + } + + std::array<Common::FixedPoint<50, 14>, NumChannels> delay_samples{}; + for (u32 channel = 0; channel < NumChannels; channel++) { + delay_samples[channel] = state.delay_lines[channel].Read(); + } + + // clang-format off + std::array<std::array<Common::FixedPoint<18, 14>, NumChannels>, NumChannels> matrix{}; + if constexpr (NumChannels == 1) { + matrix = {{ + {state.feedback_gain}, + }}; + } else if constexpr (NumChannels == 2) { + matrix = {{ + {state.delay_feedback_gain, state.delay_feedback_cross_gain}, + {state.delay_feedback_cross_gain, state.delay_feedback_gain}, + }}; + } else if constexpr (NumChannels == 4) { + matrix = {{ + {state.delay_feedback_gain, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, 0.0f}, + {state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain}, + {state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain}, + {0.0f, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain}, + }}; + } else if constexpr (NumChannels == 6) { + matrix = {{ + {state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f}, + {0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain}, + {state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, params.feedback_gain, 0.0f, 0.0f}, + {state.delay_feedback_cross_gain, 0.0f, 0.0f, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain}, + {0.0f, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain, state.delay_feedback_gain}, + }}; + } + // clang-format on + + std::array<Common::FixedPoint<50, 14>, NumChannels> gained_samples{}; + for (u32 channel = 0; channel < NumChannels; channel++) { + Common::FixedPoint<50, 14> delay{}; + for (u32 j = 0; j < NumChannels; j++) { + delay += delay_samples[j] * matrix[j][channel]; + } + gained_samples[channel] = input_samples[channel] * params.in_gain + delay; + } + + for (u32 channel = 0; channel < NumChannels; channel++) { + state.lowpass_z[channel] = gained_samples[channel] * state.lowpass_gain + + state.lowpass_z[channel] * state.lowpass_feedback_gain; + state.delay_lines[channel].Write(state.lowpass_z[channel]); + } + + for (u32 channel = 0; channel < NumChannels; channel++) { + outputs[channel][sample_index] = (input_samples[channel] * params.dry_gain + + delay_samples[channel] * params.wet_gain) + .to_int_floor() / + 64; + } + } +} + +/** + * Apply a delay effect if enabled, according to the parameters and current state, on the input mix + * buffers, saving the results to the output mix buffers. + * + * @param params - Input parameters to use. + * @param state - State to use, must be initialized (see InitializeDelayEffect). + * @param enabled - If enabled, delay will be applied, otherwise input is copied to output. + * @param inputs - Input mix buffers to performan the delay on. + * @param outputs - Output mix buffers to receive the delayed samples. + * @param sample_count - Number of samples to process. + */ +static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state, + const bool enabled, std::vector<std::span<const s32>>& inputs, + std::vector<std::span<s32>>& outputs, const u32 sample_count) { + + if (!IsChannelCountValid(params.channel_count)) { + LOG_ERROR(Service_Audio, "Invalid delay channels {}", params.channel_count); + return; + } + + if (enabled) { + switch (params.channel_count) { + case 1: + ApplyDelay<1>(params, state, inputs, outputs, sample_count); + break; + case 2: + ApplyDelay<2>(params, state, inputs, outputs, sample_count); + break; + case 4: + ApplyDelay<4>(params, state, inputs, outputs, sample_count); + break; + case 6: + ApplyDelay<6>(params, state, inputs, outputs, sample_count); + break; + default: + for (u32 channel = 0; channel < params.channel_count; channel++) { + if (inputs[channel].data() != outputs[channel].data()) { + std::memcpy(outputs[channel].data(), inputs[channel].data(), + sample_count * sizeof(s32)); + } + } + break; + } + } else { + for (u32 channel = 0; channel < params.channel_count; channel++) { + if (inputs[channel].data() != outputs[channel].data()) { + std::memcpy(outputs[channel].data(), inputs[channel].data(), + sample_count * sizeof(s32)); + } + } + } +} + +void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("DelayCommand\n\tenabled {} \n\tinputs: ", effect_enabled); + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void DelayCommand::Process(const ADSP::CommandListProcessor& processor) { + std::vector<std::span<const s32>> input_buffers(parameter.channel_count); + std::vector<std::span<s32>> output_buffers(parameter.channel_count); + + for (s16 i = 0; i < parameter.channel_count; i++) { + input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count); + output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count); + } + + auto state_{reinterpret_cast<DelayInfo::State*>(state)}; + + if (effect_enabled) { + if (parameter.state == DelayInfo::ParameterState::Updating) { + SetDelayEffectParameter(parameter, *state_); + } else if (parameter.state == DelayInfo::ParameterState::Initialized) { + InitializeDelayEffect(parameter, *state_, workbuffer); + } + } + ApplyDelayEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count); +} + +bool DelayCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/delay.h b/src/audio_core/renderer/command/effect/delay.h new file mode 100644 index 000000000..b7a15ae6b --- /dev/null +++ b/src/audio_core/renderer/command/effect/delay.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/effect/delay.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for a delay effect. Delays inputs mix buffers according to the parameters + * and state, outputs receives the delayed samples. + */ +struct DelayCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array<s16, MaxChannels> inputs; + /// Output mix buffer offsets for each channel + std::array<s16, MaxChannels> outputs; + /// Input parameters + DelayInfo::ParameterVersion1 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp b/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp new file mode 100644 index 000000000..c4bf3943a --- /dev/null +++ b/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp @@ -0,0 +1,437 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <numbers> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/i3dl2_reverb.h" + +namespace AudioCore::AudioRenderer { + +constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MinDelayLineTimes{ + 5.0f, + 6.0f, + 13.0f, + 14.0f, +}; +constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MaxDelayLineTimes{ + 45.7042007446f, + 82.7817001343f, + 149.938293457f, + 271.575805664f, +}; +constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay0MaxDelayLineTimes{17.0f, 13.0f, + 9.0f, 7.0f}; +constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay1MaxDelayLineTimes{19.0f, 11.0f, + 10.0f, 6.0f}; +constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyTapTimes{ + 0.0171360000968f, + 0.0591540001333f, + 0.161733001471f, + 0.390186011791f, + 0.425262004137f, + 0.455410987139f, + 0.689737021923f, + 0.74590998888f, + 0.833844006062f, + 0.859502017498f, + 0.0f, + 0.0750240013003f, + 0.168788000941f, + 0.299901008606f, + 0.337442994118f, + 0.371903002262f, + 0.599011003971f, + 0.716741025448f, + 0.817858994007f, + 0.85166400671f, +}; + +constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyGains{ + 0.67096f, 0.61027f, 1.0f, 0.3568f, 0.68361f, 0.65978f, 0.51939f, + 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.3827f, + 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f}; + +/** + * Update the I3dl2ReverbInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + * @param reset - If enabled, the state buffers will be reset. Only set this on initialize. + */ +static void UpdateI3dl2ReverbEffectParameter(const I3dl2ReverbInfo::ParameterVersion1& params, + I3dl2ReverbInfo::State& state, const bool reset) { + const auto pow_10 = [](f32 val) -> f32 { + return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val); + }; + const auto sin = [](f32 degrees) -> f32 { + return std::sin(degrees * std::numbers::pi_v<f32> / 180.0f); + }; + const auto cos = [](f32 degrees) -> f32 { + return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f); + }; + + Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000.0f}; + + state.dry_gain = params.dry_gain; + Common::FixedPoint<50, 14> early_gain{ + std::min(params.room_gain + params.reflection_gain, 5000.0f) / 2000.0f}; + state.early_gain = pow_10(early_gain.to_float()); + Common::FixedPoint<50, 14> late_gain{std::min(params.room_gain + params.reverb_gain, 5000.0f) / + 2000.0f}; + state.late_gain = pow_10(late_gain.to_float()); + + Common::FixedPoint<50, 14> hf_gain{pow_10(params.room_HF_gain / 2000.0f)}; + if (hf_gain >= 1.0f) { + state.lowpass_1 = 0.0f; + state.lowpass_2 = 1.0f; + } else { + const auto reference_hf{(params.reference_HF * 256.0f) / + static_cast<f32>(params.sample_rate)}; + const Common::FixedPoint<50, 14> a{1.0f - hf_gain.to_float()}; + const Common::FixedPoint<50, 14> b{2.0f + (-cos(reference_hf) * (hf_gain * 2.0f))}; + const Common::FixedPoint<50, 14> c{ + std::sqrt(std::pow(b.to_float(), 2.0f) + (std::pow(a.to_float(), 2.0f) * -4.0f))}; + + state.lowpass_1 = std::min(((b - c) / (a * 2.0f)).to_float(), 0.99723f); + state.lowpass_2 = 1.0f - state.lowpass_1; + } + + state.early_to_late_taps = + (((params.reflection_delay + params.late_reverb_delay_time) * 1000.0f) * delay).to_int(); + state.last_reverb_echo = params.late_reverb_diffusion * 0.6f * 0.01f; + + for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) { + auto curr_delay{ + ((MinDelayLineTimes[i] + (params.late_reverb_density / 100.0f) * + (MaxDelayLineTimes[i] - MinDelayLineTimes[i])) * + delay) + .to_int()}; + state.fdn_delay_lines[i].SetDelay(curr_delay); + + const auto a{ + (static_cast<f32>(state.fdn_delay_lines[i].delay + state.decay_delay_lines0[i].delay + + state.decay_delay_lines1[i].delay) * + -60.0f) / + (params.late_reverb_decay_time * static_cast<f32>(params.sample_rate))}; + const auto b{a / params.late_reverb_HF_decay_ratio}; + const auto c{ + cos(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate)) / + sin(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate))}; + const auto d{pow_10((b - a) / 40.0f)}; + const auto e{pow_10((b + a) / 40.0f) * 0.7071f}; + + state.lowpass_coeff[i][0] = ((c * d + 1.0f) * e) / (c + d); + state.lowpass_coeff[i][1] = ((1.0f - (c * d)) * e) / (c + d); + state.lowpass_coeff[i][2] = (c - d) / (c + d); + + state.decay_delay_lines0[i].wet_gain = state.last_reverb_echo; + state.decay_delay_lines1[i].wet_gain = state.last_reverb_echo * -0.9f; + } + + if (reset) { + state.shelf_filter.fill(0.0f); + state.lowpass_0 = 0.0f; + for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) { + std::ranges::fill(state.fdn_delay_lines[i].buffer, 0); + std::ranges::fill(state.decay_delay_lines0[i].buffer, 0); + std::ranges::fill(state.decay_delay_lines1[i].buffer, 0); + } + std::ranges::fill(state.center_delay_line.buffer, 0); + std::ranges::fill(state.early_delay_line.buffer, 0); + } + + const auto reflection_time{(params.late_reverb_delay_time * 0.9998f + 0.02f) * 1000.0f}; + const auto reflection_delay{params.reflection_delay * 1000.0f}; + for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayTaps; i++) { + auto length{((reflection_delay + reflection_time * EarlyTapTimes[i]) * delay).to_int()}; + if (length >= state.early_delay_line.max_delay) { + length = state.early_delay_line.max_delay; + } + state.early_tap_steps[i] = length; + } +} + +/** + * Initialize a new I3dl2ReverbInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + * @param workbuffer - Game-supplied memory for the state. (Unused) + */ +static void InitializeI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params, + I3dl2ReverbInfo::State& state, const CpuAddr workbuffer) { + state = {}; + Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000}; + + for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) { + auto fdn_delay_time{(MaxDelayLineTimes[i] * delay).to_uint_floor()}; + state.fdn_delay_lines[i].Initialize(fdn_delay_time); + + auto decay0_delay_time{(Decay0MaxDelayLineTimes[i] * delay).to_uint_floor()}; + state.decay_delay_lines0[i].Initialize(decay0_delay_time); + + auto decay1_delay_time{(Decay1MaxDelayLineTimes[i] * delay).to_uint_floor()}; + state.decay_delay_lines1[i].Initialize(decay1_delay_time); + } + + const auto center_delay_time{(5 * delay).to_uint_floor()}; + state.center_delay_line.Initialize(center_delay_time); + + const auto early_delay_time{(400 * delay).to_uint_floor()}; + state.early_delay_line.Initialize(early_delay_time); + + UpdateI3dl2ReverbEffectParameter(params, state, true); +} + +/** + * Pass-through the effect, copying input to output directly, with no reverb applied. + * + * @param inputs - Array of input mix buffers to copy. + * @param outputs - Array of output mix buffers to receive copy. + * @param channel_count - Number of channels in inputs and outputs. + * @param sample_count - Number of samples within each channel (unused). + */ +static void ApplyI3dl2ReverbEffectBypass(std::span<std::span<const s32>> inputs, + std::span<std::span<s32>> outputs, const u32 channel_count, + [[maybe_unused]] const u32 sample_count) { + for (u32 i = 0; i < channel_count; i++) { + if (inputs[i].data() != outputs[i].data()) { + std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes()); + } + } +} + +/** + * Tick the delay lines, reading and returning their current output, and writing a new decaying + * sample (mix). + * + * @param decay0 - The first decay line. + * @param decay1 - The second decay line. + * @param fdn - Feedback delay network. + * @param mix - The new calculated sample to be written and decayed. + * @return The next delayed and decayed sample. + */ +static Common::FixedPoint<50, 14> Axfx2AllPassTick(I3dl2ReverbInfo::I3dl2DelayLine& decay0, + I3dl2ReverbInfo::I3dl2DelayLine& decay1, + I3dl2ReverbInfo::I3dl2DelayLine& fdn, + const Common::FixedPoint<50, 14> mix) { + auto val{decay0.Read()}; + auto mixed{mix - (val * decay0.wet_gain)}; + auto out{decay0.Tick(mixed) + (mixed * decay0.wet_gain)}; + + val = decay1.Read(); + mixed = out - (val * decay1.wet_gain); + out = decay1.Tick(mixed) + (mixed * decay1.wet_gain); + + fdn.Tick(out); + return out; +} + +/** + * Impl. Apply a I3DL2 reverb according to the current state, on the input mix buffers, + * saving the results to the output mix buffers. + * + * @tparam NumChannels - Number of channels to process. 1-6. + Inputs/outputs should have this many buffers. + * @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect). + * @param inputs - Input mix buffers to perform the reverb on. + * @param outputs - Output mix buffers to receive the reverbed samples. + * @param sample_count - Number of samples to process. + */ +template <size_t NumChannels> +static void ApplyI3dl2ReverbEffect(I3dl2ReverbInfo::State& state, + std::span<std::span<const s32>> inputs, + std::span<std::span<s32>> outputs, const u32 sample_count) { + constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{ + 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, + }; + constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{ + 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3, + }; + constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{ + 2, 0, 0, 1, 1, 1, 1, 4, 4, 4, 1, 1, 1, 0, 0, 0, 0, 5, 5, 5, + }; + + std::span<const u8> tap_indexes{}; + if constexpr (NumChannels == 1) { + tap_indexes = OutTapIndexes1Ch; + } else if constexpr (NumChannels == 2) { + tap_indexes = OutTapIndexes2Ch; + } else if constexpr (NumChannels == 4) { + tap_indexes = OutTapIndexes4Ch; + } else if constexpr (NumChannels == 6) { + tap_indexes = OutTapIndexes6Ch; + } + + for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { + Common::FixedPoint<50, 14> early_to_late_tap{ + state.early_delay_line.TapOut(state.early_to_late_taps)}; + std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{}; + + for (u32 early_tap = 0; early_tap < I3dl2ReverbInfo::MaxDelayTaps; early_tap++) { + output_samples[tap_indexes[early_tap]] += + state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) * + EarlyGains[early_tap]; + if constexpr (NumChannels == 6) { + output_samples[static_cast<u32>(Channels::LFE)] += + state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) * + EarlyGains[early_tap]; + } + } + + Common::FixedPoint<50, 14> current_sample{}; + for (u32 channel = 0; channel < NumChannels; channel++) { + current_sample += inputs[channel][sample_index]; + } + + state.lowpass_0 = + (current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1).to_float(); + state.early_delay_line.Tick(state.lowpass_0); + + for (u32 channel = 0; channel < NumChannels; channel++) { + output_samples[channel] *= state.early_gain; + } + + std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> filtered_samples{}; + for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) { + filtered_samples[delay_line] = + state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][0] + + state.shelf_filter[delay_line]; + state.shelf_filter[delay_line] = + (filtered_samples[delay_line] * state.lowpass_coeff[delay_line][2] + + state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][1]) + .to_float(); + } + + const std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> mix_matrix{ + filtered_samples[1] + filtered_samples[2] + early_to_late_tap * state.late_gain, + -filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain, + filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain, + filtered_samples[1] - filtered_samples[2] + early_to_late_tap * state.late_gain, + }; + + std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> allpass_samples{}; + for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) { + allpass_samples[delay_line] = Axfx2AllPassTick( + state.decay_delay_lines0[delay_line], state.decay_delay_lines1[delay_line], + state.fdn_delay_lines[delay_line], mix_matrix[delay_line]); + } + + if constexpr (NumChannels == 6) { + const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{ + allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3], + allpass_samples[3], allpass_samples[2], allpass_samples[3], + }; + + for (u32 channel = 0; channel < NumChannels; channel++) { + Common::FixedPoint<50, 14> allpass{}; + + if (channel == static_cast<u32>(Channels::Center)) { + allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f); + } else { + allpass = allpass_outputs[channel]; + } + + auto out_sample{output_samples[channel] + allpass + + state.dry_gain * static_cast<f32>(inputs[channel][sample_index])}; + + outputs[channel][sample_index] = + static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f)); + } + } else { + for (u32 channel = 0; channel < NumChannels; channel++) { + auto out_sample{output_samples[channel] + allpass_samples[channel] + + state.dry_gain * static_cast<f32>(inputs[channel][sample_index])}; + outputs[channel][sample_index] = + static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f)); + } + } + } +} + +/** + * Apply a I3DL2 reverb if enabled, according to the current state, on the input mix buffers, + * saving the results to the output mix buffers. + * + * @param params - Input parameters to use. + * @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect). + * @param enabled - If enabled, delay will be applied, otherwise input is copied to output. + * @param inputs - Input mix buffers to performan the delay on. + * @param outputs - Output mix buffers to receive the delayed samples. + * @param sample_count - Number of samples to process. + */ +static void ApplyI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params, + I3dl2ReverbInfo::State& state, const bool enabled, + std::span<std::span<const s32>> inputs, + std::span<std::span<s32>> outputs, const u32 sample_count) { + if (enabled) { + switch (params.channel_count) { + case 0: + return; + case 1: + ApplyI3dl2ReverbEffect<1>(state, inputs, outputs, sample_count); + break; + case 2: + ApplyI3dl2ReverbEffect<2>(state, inputs, outputs, sample_count); + break; + case 4: + ApplyI3dl2ReverbEffect<4>(state, inputs, outputs, sample_count); + break; + case 6: + ApplyI3dl2ReverbEffect<6>(state, inputs, outputs, sample_count); + break; + default: + ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count); + break; + } + } else { + ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count); + } +} + +void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("I3dl2ReverbCommand\n\tenabled {} \n\tinputs: ", effect_enabled); + for (u32 i = 0; i < parameter.channel_count; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (u32 i = 0; i < parameter.channel_count; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) { + std::vector<std::span<const s32>> input_buffers(parameter.channel_count); + std::vector<std::span<s32>> output_buffers(parameter.channel_count); + + for (u32 i = 0; i < parameter.channel_count; i++) { + input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count); + output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count); + } + + auto state_{reinterpret_cast<I3dl2ReverbInfo::State*>(state)}; + + if (effect_enabled) { + if (parameter.state == I3dl2ReverbInfo::ParameterState::Updating) { + UpdateI3dl2ReverbEffectParameter(parameter, *state_, false); + } else if (parameter.state == I3dl2ReverbInfo::ParameterState::Initialized) { + InitializeI3dl2ReverbEffect(parameter, *state_, workbuffer); + } + } + ApplyI3dl2ReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count); +} + +bool I3dl2ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/i3dl2_reverb.h b/src/audio_core/renderer/command/effect/i3dl2_reverb.h new file mode 100644 index 000000000..243877056 --- /dev/null +++ b/src/audio_core/renderer/command/effect/i3dl2_reverb.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/effect/i3dl2.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for a I3DL2Reverb effect. Apply a reverb to inputs mix buffer according to + * the I3DL2 spec, outputs receives the results. + */ +struct I3dl2ReverbCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array<s16, MaxChannels> inputs; + /// Output mix buffer offsets for each channel + std::array<s16, MaxChannels> outputs; + /// Input parameters + I3dl2ReverbInfo::ParameterVersion1 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/light_limiter.cpp b/src/audio_core/renderer/command/effect/light_limiter.cpp new file mode 100644 index 000000000..e8fb0e2fc --- /dev/null +++ b/src/audio_core/renderer/command/effect/light_limiter.cpp @@ -0,0 +1,222 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/light_limiter.h" + +namespace AudioCore::AudioRenderer { +/** + * Update the LightLimiterInfo state according to the given parameters. + * A no-op. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + */ +static void UpdateLightLimiterEffectParameter(const LightLimiterInfo::ParameterVersion2& params, + LightLimiterInfo::State& state) {} + +/** + * Initialize a new LightLimiterInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + * @param workbuffer - Game-supplied memory for the state. (Unused) + */ +static void InitializeLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params, + LightLimiterInfo::State& state, const CpuAddr workbuffer) { + state = {}; + state.samples_average.fill(0.0f); + state.compression_gain.fill(1.0f); + state.look_ahead_sample_offsets.fill(0); + for (u32 i = 0; i < params.channel_count; i++) { + state.look_ahead_sample_buffers[i].resize(params.look_ahead_samples_max, 0.0f); + } +} + +/** + * Apply a light limiter effect if enabled, according to the current state, on the input mix + * buffers, saving the results to the output mix buffers. + * + * @param params - Input parameters to use. + * @param state - State to use, must be initialized (see InitializeLightLimiterEffect). + * @param enabled - If enabled, limiter will be applied, otherwise input is copied to output. + * @param inputs - Input mix buffers to perform the limiter on. + * @param outputs - Output mix buffers to receive the limited samples. + * @param sample_count - Number of samples to process. + * @params statistics - Optional output statistics, only used with version 2. + */ +static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params, + LightLimiterInfo::State& state, const bool enabled, + std::vector<std::span<const s32>>& inputs, + std::vector<std::span<s32>>& outputs, const u32 sample_count, + LightLimiterInfo::StatisticsInternal* statistics) { + constexpr s64 min{std::numeric_limits<s32>::min()}; + constexpr s64 max{std::numeric_limits<s32>::max()}; + + const auto recip_estimate = [](f64 a) -> f64 { + s32 q, s; + f64 r; + q = (s32)(a * 512.0); /* a in units of 1/512 rounded down */ + r = 1.0 / (((f64)q + 0.5) / 512.0); /* reciprocal r */ + s = (s32)(256.0 * r + 0.5); /* r in units of 1/256 rounded to nearest */ + return ((f64)s / 256.0); + }; + + if (enabled) { + if (statistics && params.statistics_reset_required) { + for (u32 i = 0; i < params.channel_count; i++) { + statistics->channel_compression_gain_min[i] = 1.0f; + statistics->channel_max_sample[i] = 0; + } + } + + for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { + for (u32 channel = 0; channel < params.channel_count; channel++) { + auto sample{(Common::FixedPoint<49, 15>(inputs[channel][sample_index]) / + Common::FixedPoint<49, 15>::one) * + params.input_gain}; + auto abs_sample{sample}; + if (sample < 0.0f) { + abs_sample = -sample; + } + auto coeff{abs_sample > state.samples_average[channel] ? params.attack_coeff + : params.release_coeff}; + state.samples_average[channel] += + ((abs_sample - state.samples_average[channel]) * coeff).to_float(); + + // Reciprocal estimate + auto new_average_sample{Common::FixedPoint<49, 15>( + recip_estimate(state.samples_average[channel].to_double()))}; + if (params.processing_mode != LightLimiterInfo::ProcessingMode::Mode1) { + // Two Newton-Raphson steps + auto temp{2.0 - (state.samples_average[channel] * new_average_sample)}; + new_average_sample = 2.0 - (state.samples_average[channel] * temp); + } + + auto above_threshold{state.samples_average[channel] > params.threshold}; + auto attenuation{above_threshold ? params.threshold * new_average_sample : 1.0f}; + coeff = attenuation < state.compression_gain[channel] ? params.attack_coeff + : params.release_coeff; + state.compression_gain[channel] += + (attenuation - state.compression_gain[channel]) * coeff; + + auto lookahead_sample{ + state.look_ahead_sample_buffers[channel] + [state.look_ahead_sample_offsets[channel]]}; + + state.look_ahead_sample_buffers[channel][state.look_ahead_sample_offsets[channel]] = + sample; + state.look_ahead_sample_offsets[channel] = + (state.look_ahead_sample_offsets[channel] + 1) % params.look_ahead_samples_min; + + outputs[channel][sample_index] = static_cast<s32>( + std::clamp((lookahead_sample * state.compression_gain[channel] * + params.output_gain * Common::FixedPoint<49, 15>::one) + .to_long(), + min, max)); + + if (statistics) { + statistics->channel_max_sample[channel] = + std::max(statistics->channel_max_sample[channel], abs_sample.to_float()); + statistics->channel_compression_gain_min[channel] = + std::min(statistics->channel_compression_gain_min[channel], + state.compression_gain[channel].to_float()); + } + } + } + } else { + for (u32 i = 0; i < params.channel_count; i++) { + if (params.inputs[i] != params.outputs[i]) { + std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes()); + } + } + } +} + +void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("LightLimiterVersion1Command\n\tinputs: "); + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) { + std::vector<std::span<const s32>> input_buffers(parameter.channel_count); + std::vector<std::span<s32>> output_buffers(parameter.channel_count); + + for (u32 i = 0; i < parameter.channel_count; i++) { + input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count); + output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count); + } + + auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)}; + + if (effect_enabled) { + if (parameter.state == LightLimiterInfo::ParameterState::Updating) { + UpdateLightLimiterEffectParameter(parameter, *state_); + } else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) { + InitializeLightLimiterEffect(parameter, *state_, workbuffer); + } + } + + LightLimiterInfo::StatisticsInternal* statistics{nullptr}; + ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count, statistics); +} + +bool LightLimiterVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("LightLimiterVersion2Command\n\tinputs: \n"); + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) { + std::vector<std::span<const s32>> input_buffers(parameter.channel_count); + std::vector<std::span<s32>> output_buffers(parameter.channel_count); + + for (u32 i = 0; i < parameter.channel_count; i++) { + input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count); + output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count); + } + + auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)}; + + if (effect_enabled) { + if (parameter.state == LightLimiterInfo::ParameterState::Updating) { + UpdateLightLimiterEffectParameter(parameter, *state_); + } else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) { + InitializeLightLimiterEffect(parameter, *state_, workbuffer); + } + } + + auto statistics{reinterpret_cast<LightLimiterInfo::StatisticsInternal*>(result_state)}; + ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count, statistics); +} + +bool LightLimiterVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/light_limiter.h b/src/audio_core/renderer/command/effect/light_limiter.h new file mode 100644 index 000000000..5d98272c7 --- /dev/null +++ b/src/audio_core/renderer/command/effect/light_limiter.h @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/effect/light_limiter.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for limiting volume between a high and low threshold. + * Version 1. + */ +struct LightLimiterVersion1Command : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array<s16, MaxChannels> inputs; + /// Output mix buffer offsets for each channel + std::array<s16, MaxChannels> outputs; + /// Input parameters + LightLimiterInfo::ParameterVersion2 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Is this effect enabled? + bool effect_enabled; +}; + +/** + * AudioRenderer command for limiting volume between a high and low threshold. + * Version 2 with output statistics. + */ +struct LightLimiterVersion2Command : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array<s16, MaxChannels> inputs; + /// Output mix buffer offsets for each channel + std::array<s16, MaxChannels> outputs; + /// Input parameters + LightLimiterInfo::ParameterVersion2 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Optional statistics, sent back to the sysmodule + CpuAddr result_state; + /// Is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp new file mode 100644 index 000000000..b3c3ba4ba --- /dev/null +++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/biquad_filter.h" +#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h" + +namespace AudioCore::AudioRenderer { + +void MultiTapBiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format( + "MultiTapBiquadFilterCommand\n\tinput {:02X}\n\toutput {:02X}\n\tneeds_init ({}, {})\n", + input, output, needs_init[0], needs_init[1]); +} + +void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) { + if (filter_tap_count > MaxBiquadFilters) { + LOG_ERROR(Service_Audio, "Too many filter taps! {}", filter_tap_count); + filter_tap_count = MaxBiquadFilters; + } + + auto input_buffer{ + processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; + auto output_buffer{ + processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; + + // TODO: Fix this, currently just applies the filter to the input twice, + // and doesn't chain the biquads together at all. + for (u32 i = 0; i < filter_tap_count; i++) { + auto state{reinterpret_cast<VoiceState::BiquadFilterState*>(states[i])}; + if (needs_init[i]) { + std::memset(state, 0, sizeof(VoiceState::BiquadFilterState)); + } + + ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state, + processor.sample_count); + } +} + +bool MultiTapBiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h new file mode 100644 index 000000000..99c2c0830 --- /dev/null +++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/voice/voice_info.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for applying multiple biquads at once. + */ +struct MultiTapBiquadFilterCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer index + s16 input; + /// Output mix buffer index + s16 output; + /// Biquad parameters + std::array<VoiceInfo::BiquadFilterParameter, MaxBiquadFilters> biquads; + /// Biquad states, updated each call + std::array<CpuAddr, MaxBiquadFilters> states; + /// If each biquad needs initialisation + std::array<bool, MaxBiquadFilters> needs_init; + /// Number of active biquads + u8 filter_tap_count; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/reverb.cpp b/src/audio_core/renderer/command/effect/reverb.cpp new file mode 100644 index 000000000..fe2b1eb43 --- /dev/null +++ b/src/audio_core/renderer/command/effect/reverb.cpp @@ -0,0 +1,440 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <numbers> +#include <ranges> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/reverb.h" + +namespace AudioCore::AudioRenderer { + +constexpr std::array<f32, ReverbInfo::MaxDelayLines> FdnMaxDelayLineTimes = { + 53.9532470703125f, + 79.19256591796875f, + 116.23876953125f, + 170.61529541015625f, +}; + +constexpr std::array<f32, ReverbInfo::MaxDelayLines> DecayMaxDelayLineTimes = { + 7.0f, + 9.0f, + 13.0f, + 17.0f, +}; + +constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps + 1>, ReverbInfo::NumEarlyModes> + EarlyDelayTimes = { + {{0.000000f, 3.500000f, 2.799988f, 3.899963f, 2.699951f, 13.399963f, 7.899963f, 8.399963f, + 9.899963f, 12.000000f, 12.500000f}, + {0.000000f, 11.799988f, 5.500000f, 11.199951f, 10.399963f, 38.099976f, 22.199951f, + 29.599976f, 21.199951f, 24.799988f, 40.000000f}, + {0.000000f, 41.500000f, 20.500000f, 41.299988f, 0.000000f, 29.500000f, 33.799988f, + 45.199951f, 46.799988f, 0.000000f, 50.000000f}, + {33.099976f, 43.299988f, 22.799988f, 37.899963f, 14.899963f, 35.299988f, 17.899963f, + 34.199951f, 0.000000f, 43.299988f, 50.000000f}, + {0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, + 0.000000f, 0.000000f, 0.000000f}}, +}; + +constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps>, ReverbInfo::NumEarlyModes> + EarlyDelayGains = {{ + {0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, + 0.679993f, 0.679993f}, + {0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.679993f, 0.679993f, + 0.679993f, 0.679993f}, + {0.500000f, 0.699951f, 0.699951f, 0.679993f, 0.500000f, 0.679993f, 0.679993f, 0.699951f, + 0.679993f, 0.000000f}, + {0.929993f, 0.919983f, 0.869995f, 0.859985f, 0.939941f, 0.809998f, 0.799988f, 0.769958f, + 0.759949f, 0.649963f}, + {0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, + 0.000000f, 0.000000f}, + }}; + +constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes> + FdnDelayTimes = {{ + {53.953247f, 79.192566f, 116.238770f, 130.615295f}, + {53.953247f, 79.192566f, 116.238770f, 170.615295f}, + {5.000000f, 10.000000f, 5.000000f, 10.000000f}, + {47.029968f, 71.000000f, 103.000000f, 170.000000f}, + {53.953247f, 79.192566f, 116.238770f, 170.615295f}, + }}; + +constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes> + DecayDelayTimes = {{ + {7.000000f, 9.000000f, 13.000000f, 17.000000f}, + {7.000000f, 9.000000f, 13.000000f, 17.000000f}, + {1.000000f, 1.000000f, 1.000000f, 1.000000f}, + {7.000000f, 7.000000f, 13.000000f, 9.000000f}, + {7.000000f, 9.000000f, 13.000000f, 17.000000f}, + }}; + +/** + * Update the ReverbInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + */ +static void UpdateReverbEffectParameter(const ReverbInfo::ParameterVersion2& params, + ReverbInfo::State& state) { + const auto pow_10 = [](f32 val) -> f32 { + return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val); + }; + const auto cos = [](f32 degrees) -> f32 { + return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f); + }; + + static bool unk_initialized{false}; + static Common::FixedPoint<50, 14> unk_value{}; + + const auto sample_rate{Common::FixedPoint<50, 14>::from_base(params.sample_rate)}; + const auto pre_delay_time{Common::FixedPoint<50, 14>::from_base(params.pre_delay)}; + + for (u32 i = 0; i < ReverbInfo::MaxDelayTaps; i++) { + auto early_delay{ + ((pre_delay_time + EarlyDelayTimes[params.early_mode][i]) * sample_rate).to_int()}; + early_delay = std::min(early_delay, state.pre_delay_line.sample_count_max); + state.early_delay_times[i] = early_delay + 1; + state.early_gains[i] = Common::FixedPoint<50, 14>::from_base(params.early_gain) * + EarlyDelayGains[params.early_mode][i]; + } + + if (params.channel_count == 2) { + state.early_gains[4] * 0.5f; + state.early_gains[5] * 0.5f; + } + + auto pre_time{ + ((pre_delay_time + EarlyDelayTimes[params.early_mode][10]) * sample_rate).to_int()}; + state.pre_delay_time = std::min(pre_time, state.pre_delay_line.sample_count_max); + + if (!unk_initialized) { + unk_value = cos((1280.0f / sample_rate).to_float()); + unk_initialized = true; + } + + for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { + const auto fdn_delay{(FdnDelayTimes[params.late_mode][i] * sample_rate).to_int()}; + state.fdn_delay_lines[i].sample_count = + std::min(fdn_delay, state.fdn_delay_lines[i].sample_count_max); + state.fdn_delay_lines[i].buffer_end = + &state.fdn_delay_lines[i].buffer[state.fdn_delay_lines[i].sample_count - 1]; + + const auto decay_delay{(DecayDelayTimes[params.late_mode][i] * sample_rate).to_int()}; + state.decay_delay_lines[i].sample_count = + std::min(decay_delay, state.decay_delay_lines[i].sample_count_max); + state.decay_delay_lines[i].buffer_end = + &state.decay_delay_lines[i].buffer[state.decay_delay_lines[i].sample_count - 1]; + + state.decay_delay_lines[i].decay = + 0.5999755859375f * (1.0f - Common::FixedPoint<50, 14>::from_base(params.colouration)); + + auto a{(Common::FixedPoint<50, 14>(state.fdn_delay_lines[i].sample_count_max) + + state.decay_delay_lines[i].sample_count_max) * + -3}; + auto b{a / (Common::FixedPoint<50, 14>::from_base(params.decay_time) * sample_rate)}; + Common::FixedPoint<50, 14> c{0.0f}; + Common::FixedPoint<50, 14> d{0.0f}; + auto hf_decay_ratio{Common::FixedPoint<50, 14>::from_base(params.high_freq_decay_ratio)}; + + if (hf_decay_ratio > 0.99493408203125f) { + c = 0.0f; + d = 1.0f; + } else { + const auto e{ + pow_10(((((1.0f / hf_decay_ratio) - 1.0f) * 2) / 100 * (b / 10)).to_float())}; + const auto f{1.0f - e}; + const auto g{2.0f - (unk_value * e * 2)}; + const auto h{std::sqrt(std::pow(g.to_float(), 2.0f) - (std::pow(f, 2.0f) * 4))}; + + c = (g - h) / (f * 2.0f); + d = 1.0f - c; + } + + state.hf_decay_prev_gain[i] = c; + state.hf_decay_gain[i] = pow_10((b / 1000).to_float()) * d * 0.70709228515625f; + state.prev_feedback_output[i] = 0; + } +} + +/** + * Initialize a new ReverbInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + * @param workbuffer - Game-supplied memory for the state. (Unused) + * @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb begins. + */ +static void InitializeReverbEffect(const ReverbInfo::ParameterVersion2& params, + ReverbInfo::State& state, const CpuAddr workbuffer, + const bool long_size_pre_delay_supported) { + state = {}; + + auto delay{Common::FixedPoint<50, 14>::from_base(params.sample_rate)}; + + for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { + auto fdn_delay_time{(FdnMaxDelayLineTimes[i] * delay).to_uint_floor()}; + state.fdn_delay_lines[i].Initialize(fdn_delay_time, 1.0f); + + auto decay_delay_time{(DecayMaxDelayLineTimes[i] * delay).to_uint_floor()}; + state.decay_delay_lines[i].Initialize(decay_delay_time, 0.0f); + } + + const auto pre_delay{long_size_pre_delay_supported ? 350.0f : 150.0f}; + const auto pre_delay_line{(pre_delay * delay).to_uint_floor()}; + state.pre_delay_line.Initialize(pre_delay_line, 1.0f); + + const auto center_delay_time{(5 * delay).to_uint_floor()}; + state.center_delay_line.Initialize(center_delay_time, 1.0f); + + UpdateReverbEffectParameter(params, state); + + for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { + std::ranges::fill(state.fdn_delay_lines[i].buffer, 0); + std::ranges::fill(state.decay_delay_lines[i].buffer, 0); + } + std::ranges::fill(state.center_delay_line.buffer, 0); + std::ranges::fill(state.pre_delay_line.buffer, 0); +} + +/** + * Pass-through the effect, copying input to output directly, with no reverb applied. + * + * @param inputs - Array of input mix buffers to copy. + * @param outputs - Array of output mix buffers to receive copy. + * @param channel_count - Number of channels in inputs and outputs. + * @param sample_count - Number of samples within each channel. + */ +static void ApplyReverbEffectBypass(std::span<std::span<const s32>> inputs, + std::span<std::span<s32>> outputs, const u32 channel_count, + const u32 sample_count) { + for (u32 i = 0; i < channel_count; i++) { + if (inputs[i].data() != outputs[i].data()) { + std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes()); + } + } +} + +/** + * Tick the delay lines, reading and returning their current output, and writing a new decaying + * sample (mix). + * + * @param decay - The decay line. + * @param fdn - Feedback delay network. + * @param mix - The new calculated sample to be written and decayed. + * @return The next delayed and decayed sample. + */ +static Common::FixedPoint<50, 14> Axfx2AllPassTick(ReverbInfo::ReverbDelayLine& decay, + ReverbInfo::ReverbDelayLine& fdn, + const Common::FixedPoint<50, 14> mix) { + const auto val{decay.Read()}; + const auto mixed{mix - (val * decay.decay)}; + const auto out{decay.Tick(mixed) + (mixed * decay.decay)}; + + fdn.Tick(out); + return out; +} + +/** + * Impl. Apply a Reverb according to the current state, on the input mix buffers, + * saving the results to the output mix buffers. + * + * @tparam NumChannels - Number of channels to process. 1-6. + Inputs/outputs should have this many buffers. + * @param params - Input parameters to update the state. + * @param state - State to use, must be initialized (see InitializeReverbEffect). + * @param inputs - Input mix buffers to perform the reverb on. + * @param outputs - Output mix buffers to receive the reverbed samples. + * @param sample_count - Number of samples to process. + */ +template <size_t NumChannels> +static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state, + std::vector<std::span<const s32>>& inputs, + std::vector<std::span<s32>>& outputs, const u32 sample_count) { + constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{ + 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, + }; + constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{ + 0, 0, 1, 1, 0, 1, 2, 2, 3, 3, + }; + constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{ + 0, 0, 1, 1, 2, 2, 4, 4, 5, 5, + }; + + std::span<const u8> tap_indexes{}; + if constexpr (NumChannels == 1) { + tap_indexes = OutTapIndexes1Ch; + } else if constexpr (NumChannels == 2) { + tap_indexes = OutTapIndexes2Ch; + } else if constexpr (NumChannels == 4) { + tap_indexes = OutTapIndexes4Ch; + } else if constexpr (NumChannels == 6) { + tap_indexes = OutTapIndexes6Ch; + } + + for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { + std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{}; + + for (u32 early_tap = 0; early_tap < ReverbInfo::MaxDelayTaps; early_tap++) { + const auto sample{state.pre_delay_line.TapOut(state.early_delay_times[early_tap]) * + state.early_gains[early_tap]}; + output_samples[tap_indexes[early_tap]] += sample; + if constexpr (NumChannels == 6) { + output_samples[static_cast<u32>(Channels::LFE)] += sample; + } + } + + if constexpr (NumChannels == 6) { + output_samples[static_cast<u32>(Channels::LFE)] *= 0.2f; + } + + Common::FixedPoint<50, 14> input_sample{}; + for (u32 channel = 0; channel < NumChannels; channel++) { + input_sample += inputs[channel][sample_index]; + } + + input_sample *= 64; + input_sample *= Common::FixedPoint<50, 14>::from_base(params.base_gain); + state.pre_delay_line.Write(input_sample); + + for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { + state.prev_feedback_output[i] = + state.prev_feedback_output[i] * state.hf_decay_prev_gain[i] + + state.fdn_delay_lines[i].Read() * state.hf_decay_gain[i]; + } + + Common::FixedPoint<50, 14> pre_delay_sample{ + state.pre_delay_line.Read() * Common::FixedPoint<50, 14>::from_base(params.late_gain)}; + + std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> mix_matrix{ + state.prev_feedback_output[2] + state.prev_feedback_output[1] + pre_delay_sample, + -state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample, + state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample, + state.prev_feedback_output[1] - state.prev_feedback_output[2] + pre_delay_sample, + }; + + std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> allpass_samples{}; + for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { + allpass_samples[i] = Axfx2AllPassTick(state.decay_delay_lines[i], + state.fdn_delay_lines[i], mix_matrix[i]); + } + + const auto dry_gain{Common::FixedPoint<50, 14>::from_base(params.dry_gain)}; + const auto wet_gain{Common::FixedPoint<50, 14>::from_base(params.wet_gain)}; + + if constexpr (NumChannels == 6) { + const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{ + allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3], + allpass_samples[3], allpass_samples[2], allpass_samples[3], + }; + + for (u32 channel = 0; channel < NumChannels; channel++) { + auto in_sample{inputs[channel][sample_index] * dry_gain}; + + Common::FixedPoint<50, 14> allpass{}; + if (channel == static_cast<u32>(Channels::Center)) { + allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f); + } else { + allpass = allpass_outputs[channel]; + } + + auto out_sample{((output_samples[channel] + allpass) * wet_gain) / 64}; + outputs[channel][sample_index] = (in_sample + out_sample).to_int(); + } + } else { + for (u32 channel = 0; channel < NumChannels; channel++) { + auto in_sample{inputs[channel][sample_index] * dry_gain}; + auto out_sample{((output_samples[channel] + allpass_samples[channel]) * wet_gain) / + 64}; + outputs[channel][sample_index] = (in_sample + out_sample).to_int(); + } + } + } +} + +/** + * Apply a Reverb if enabled, according to the current state, on the input mix buffers, + * saving the results to the output mix buffers. + * + * @param params - Input parameters to use. + * @param state - State to use, must be initialized (see InitializeReverbEffect). + * @param enabled - If enabled, delay will be applied, otherwise input is copied to output. + * @param inputs - Input mix buffers to performan the reverb on. + * @param outputs - Output mix buffers to receive the reverbed samples. + * @param sample_count - Number of samples to process. + */ +static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state, + const bool enabled, std::vector<std::span<const s32>>& inputs, + std::vector<std::span<s32>>& outputs, const u32 sample_count) { + if (enabled) { + switch (params.channel_count) { + case 0: + return; + case 1: + ApplyReverbEffect<1>(params, state, inputs, outputs, sample_count); + break; + case 2: + ApplyReverbEffect<2>(params, state, inputs, outputs, sample_count); + break; + case 4: + ApplyReverbEffect<4>(params, state, inputs, outputs, sample_count); + break; + case 6: + ApplyReverbEffect<6>(params, state, inputs, outputs, sample_count); + break; + default: + ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count); + break; + } + } else { + ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count); + } +} + +void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format( + "ReverbCommand\n\tenabled {} long_size_pre_delay_supported {}\n\tinputs: ", effect_enabled, + long_size_pre_delay_supported); + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) { + std::vector<std::span<const s32>> input_buffers(parameter.channel_count); + std::vector<std::span<s32>> output_buffers(parameter.channel_count); + + for (u32 i = 0; i < parameter.channel_count; i++) { + input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count); + output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count); + } + + auto state_{reinterpret_cast<ReverbInfo::State*>(state)}; + + if (effect_enabled) { + if (parameter.state == ReverbInfo::ParameterState::Updating) { + UpdateReverbEffectParameter(parameter, *state_); + } else if (parameter.state == ReverbInfo::ParameterState::Initialized) { + InitializeReverbEffect(parameter, *state_, workbuffer, long_size_pre_delay_supported); + } + } + ApplyReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count); +} + +bool ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/reverb.h b/src/audio_core/renderer/command/effect/reverb.h new file mode 100644 index 000000000..328756150 --- /dev/null +++ b/src/audio_core/renderer/command/effect/reverb.h @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/effect/reverb.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for a Reverb effect. Apply a reverb to inputs mix buffer, outputs receives + * the results. + */ +struct ReverbCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array<s16, MaxChannels> inputs; + /// Output mix buffer offsets for each channel + std::array<s16, MaxChannels> outputs; + /// Input parameters + ReverbInfo::ParameterVersion2 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Is this effect enabled? + bool effect_enabled; + /// Is a longer pre-delay time supported? + bool long_size_pre_delay_supported; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/icommand.h b/src/audio_core/renderer/command/icommand.h new file mode 100644 index 000000000..f2dd41254 --- /dev/null +++ b/src/audio_core/renderer/command/icommand.h @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +enum class CommandId : u8 { + /* 0x00 */ Invalid, + /* 0x01 */ DataSourcePcmInt16Version1, + /* 0x02 */ DataSourcePcmInt16Version2, + /* 0x03 */ DataSourcePcmFloatVersion1, + /* 0x04 */ DataSourcePcmFloatVersion2, + /* 0x05 */ DataSourceAdpcmVersion1, + /* 0x06 */ DataSourceAdpcmVersion2, + /* 0x07 */ Volume, + /* 0x08 */ VolumeRamp, + /* 0x09 */ BiquadFilter, + /* 0x0A */ Mix, + /* 0x0B */ MixRamp, + /* 0x0C */ MixRampGrouped, + /* 0x0D */ DepopPrepare, + /* 0x0E */ DepopForMixBuffers, + /* 0x0F */ Delay, + /* 0x10 */ Upsample, + /* 0x11 */ DownMix6chTo2ch, + /* 0x12 */ Aux, + /* 0x13 */ DeviceSink, + /* 0x14 */ CircularBufferSink, + /* 0x15 */ Reverb, + /* 0x16 */ I3dl2Reverb, + /* 0x17 */ Performance, + /* 0x18 */ ClearMixBuffer, + /* 0x19 */ CopyMixBuffer, + /* 0x1A */ LightLimiterVersion1, + /* 0x1B */ LightLimiterVersion2, + /* 0x1C */ MultiTapBiquadFilter, + /* 0x1D */ Capture, + /* 0x1E */ Compressor, +}; + +constexpr u32 CommandMagic{0xCAFEBABE}; + +/** + * A command, generated by the host, and processed by the ADSP's AudioRenderer. + */ +struct ICommand { + virtual ~ICommand() = default; + + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + virtual void Dump(const ADSP::CommandListProcessor& processor, std::string& string) = 0; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + virtual void Process(const ADSP::CommandListProcessor& processor) = 0; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + virtual bool Verify(const ADSP::CommandListProcessor& processor) = 0; + + /// Command magic 0xCAFEBABE + u32 magic{}; + /// Command enabled + bool enabled{}; + /// Type of this command (see CommandId) + CommandId type{}; + /// Size of this command + s16 size{}; + /// Estimated processing time for this command + u32 estimated_process_time{}; + /// Node id of the voice or mix this command was generated from + u32 node_id{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/clear_mix.cpp b/src/audio_core/renderer/command/mix/clear_mix.cpp new file mode 100644 index 000000000..4f649d6a8 --- /dev/null +++ b/src/audio_core/renderer/command/mix/clear_mix.cpp @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <string> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/clear_mix.h" + +namespace AudioCore::AudioRenderer { + +void ClearMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("ClearMixBufferCommand\n"); +} + +void ClearMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) { + memset(processor.mix_buffers.data(), 0, processor.mix_buffers.size_bytes()); +} + +bool ClearMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/clear_mix.h b/src/audio_core/renderer/command/mix/clear_mix.h new file mode 100644 index 000000000..956ec0b65 --- /dev/null +++ b/src/audio_core/renderer/command/mix/clear_mix.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for a clearing the mix buffers. + * Used at the start of each command list. + */ +struct ClearMixBufferCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/copy_mix.cpp b/src/audio_core/renderer/command/mix/copy_mix.cpp new file mode 100644 index 000000000..1d49f1644 --- /dev/null +++ b/src/audio_core/renderer/command/mix/copy_mix.cpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/copy_mix.h" + +namespace AudioCore::AudioRenderer { + +void CopyMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("CopyMixBufferCommand\n\tinput {:02X} output {:02X}\n", input_index, + output_index); +} + +void CopyMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) { + auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, + processor.sample_count)}; + std::memcpy(output.data(), input.data(), processor.sample_count * sizeof(s32)); +} + +bool CopyMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/copy_mix.h b/src/audio_core/renderer/command/mix/copy_mix.h new file mode 100644 index 000000000..a59007fb6 --- /dev/null +++ b/src/audio_core/renderer/command/mix/copy_mix.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for a copying a mix buffer from input to output. + */ +struct CopyMixBufferCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer index + s16 input_index; + /// Output mix buffer index + s16 output_index; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp new file mode 100644 index 000000000..c2bc10061 --- /dev/null +++ b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/common/common.h" +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h" + +namespace AudioCore::AudioRenderer { +/** + * Apply depopping. Add the depopped sample to each incoming new sample, decaying it each time + * according to decay. + * + * @param output - Output buffer to be depopped. + * @param depop_sample - Depopped sample to apply to output samples. + * @param decay_ - Amount to decay the depopped sample for every output sample. + * @param sample_count - Samples to process. + * @return Final decayed depop sample. + */ +static s32 ApplyDepopMix(std::span<s32> output, const s32 depop_sample, + Common::FixedPoint<49, 15>& decay_, const u32 sample_count) { + auto sample{std::abs(depop_sample)}; + auto decay{decay_.to_raw()}; + + if (depop_sample <= 0) { + for (u32 i = 0; i < sample_count; i++) { + sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15); + output[i] -= sample; + } + return -sample; + } else { + for (u32 i = 0; i < sample_count; i++) { + sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15); + output[i] += sample; + } + return sample; + } +} + +void DepopForMixBuffersCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("DepopForMixBuffersCommand\n\tinput {:02X} count {} decay {}\n", input, + count, decay.to_float()); +} + +void DepopForMixBuffersCommand::Process(const ADSP::CommandListProcessor& processor) { + auto end_index{std::min(processor.buffer_count, input + count)}; + std::span<s32> depop_buff{reinterpret_cast<s32*>(depop_buffer), end_index}; + + for (u32 index = input; index < end_index; index++) { + const auto depop_sample{depop_buff[index]}; + if (depop_sample != 0) { + auto input_buffer{processor.mix_buffers.subspan(index * processor.sample_count, + processor.sample_count)}; + depop_buff[index] = + ApplyDepopMix(input_buffer, depop_sample, decay, processor.sample_count); + } + } +} + +bool DepopForMixBuffersCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h new file mode 100644 index 000000000..e7268ff27 --- /dev/null +++ b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for depopping a mix buffer. + * Adds a cumulation of previous samples to the current mix buffer with a decay. + */ +struct DepopForMixBuffersCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Starting input mix buffer index + u32 input; + /// Number of mix buffers to depop + u32 count; + /// Amount to decay the depop sample for each new sample + Common::FixedPoint<49, 15> decay; + /// Address of the depop buffer, holding the last sample for every mix buffer + CpuAddr depop_buffer; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/depop_prepare.cpp b/src/audio_core/renderer/command/mix/depop_prepare.cpp new file mode 100644 index 000000000..2ee076ef6 --- /dev/null +++ b/src/audio_core/renderer/command/mix/depop_prepare.cpp @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/depop_prepare.h" +#include "audio_core/renderer/voice/voice_state.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { + +void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("DepopPrepareCommand\n\tinputs: "); + for (u32 i = 0; i < buffer_count; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n"; +} + +void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) { + auto samples{reinterpret_cast<s32*>(previous_samples)}; + auto buffer{std::span(reinterpret_cast<s32*>(depop_buffer), buffer_count)}; + + for (u32 i = 0; i < buffer_count; i++) { + if (samples[i]) { + buffer[inputs[i]] += samples[i]; + samples[i] = 0; + } + } +} + +bool DepopPrepareCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/depop_prepare.h b/src/audio_core/renderer/command/mix/depop_prepare.h new file mode 100644 index 000000000..a5465da9a --- /dev/null +++ b/src/audio_core/renderer/command/mix/depop_prepare.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for preparing depop. + * Adds the previusly output last samples to the depop buffer. + */ +struct DepopPrepareCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Depop buffer offset for each mix buffer + std::array<s16, MaxMixBuffers> inputs; + /// Pointer to the previous mix buffer samples + CpuAddr previous_samples; + /// Number of mix buffers to use + u32 buffer_count; + /// Pointer to the current depop values + CpuAddr depop_buffer; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix.cpp b/src/audio_core/renderer/command/mix/mix.cpp new file mode 100644 index 000000000..8ecf9b05a --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix.cpp @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <algorithm> +#include <limits> +#include <span> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/mix.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +/** + * Mix input mix buffer into output mix buffer, with volume applied to the input. + * + * @tparam Q - Number of bits for fixed point operations. + * @param output - Output mix buffer. + * @param input - Input mix buffer. + * @param volume - Volume applied to the input. + * @param sample_count - Number of samples to process. + */ +template <size_t Q> +static void ApplyMix(std::span<s32> output, std::span<const s32> input, const f32 volume_, + const u32 sample_count) { + const Common::FixedPoint<64 - Q, Q> volume{volume_}; + for (u32 i = 0; i < sample_count; i++) { + output[i] = (output[i] + input[i] * volume).to_int(); + } +} + +void MixCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("MixCommand"); + string += fmt::format("\n\tinput {:02X}", input_index); + string += fmt::format("\n\toutput {:02X}", output_index); + string += fmt::format("\n\tvolume {:.8f}", volume); + string += "\n"; +} + +void MixCommand::Process(const ADSP::CommandListProcessor& processor) { + auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, + processor.sample_count)}; + + // If volume is 0, nothing will be added to the output, so just skip. + if (volume == 0.0f) { + return; + } + + switch (precision) { + case 15: + ApplyMix<15>(output, input, volume, processor.sample_count); + break; + + case 23: + ApplyMix<23>(output, input, volume, processor.sample_count); + break; + + default: + LOG_ERROR(Service_Audio, "Invalid precision {}", precision); + break; + } +} + +bool MixCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix.h b/src/audio_core/renderer/command/mix/mix.h new file mode 100644 index 000000000..0201cf171 --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume + * applied to the input. + */ +struct MixCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Fixed point precision + u8 precision; + /// Input mix buffer index + s16 input_index; + /// Output mix buffer index + s16 output_index; + /// Mix volume applied to the input + f32 volume; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix_ramp.cpp b/src/audio_core/renderer/command/mix/mix_ramp.cpp new file mode 100644 index 000000000..ffdafa1c8 --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix_ramp.cpp @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/mix_ramp.h" +#include "common/fixed_point.h" +#include "common/logging/log.h" + +namespace AudioCore::AudioRenderer { +/** + * Mix input mix buffer into output mix buffer, with volume applied to the input. + * + * @tparam Q - Number of bits for fixed point operations. + * @param output - Output mix buffer. + * @param input - Input mix buffer. + * @param volume - Volume applied to the input. + * @param ramp - Ramp applied to volume every sample. + * @param sample_count - Number of samples to process. + * @return The final gained input sample, used for depopping. + */ +template <size_t Q> +s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, + const f32 ramp_, const u32 sample_count) { + Common::FixedPoint<64 - Q, Q> volume{volume_}; + Common::FixedPoint<64 - Q, Q> sample{0}; + + if (ramp_ == 0.0f) { + for (u32 i = 0; i < sample_count; i++) { + sample = input[i] * volume; + output[i] = (output[i] + sample).to_int(); + } + } else { + Common::FixedPoint<64 - Q, Q> ramp{ramp_}; + for (u32 i = 0; i < sample_count; i++) { + sample = input[i] * volume; + output[i] = (output[i] + sample).to_int(); + volume += ramp; + } + } + return sample.to_int(); +} + +template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, const f32, const f32, + const u32); +template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, const f32, const f32, + const u32); + +void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { + const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; + string += fmt::format("MixRampCommand"); + string += fmt::format("\n\tinput {:02X}", input_index); + string += fmt::format("\n\toutput {:02X}", output_index); + string += fmt::format("\n\tvolume {:.8f}", volume); + string += fmt::format("\n\tprev_volume {:.8f}", prev_volume); + string += fmt::format("\n\tramp {:.8f}", ramp); + string += "\n"; +} + +void MixRampCommand::Process(const ADSP::CommandListProcessor& processor) { + auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, + processor.sample_count)}; + const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; + auto prev_sample_ptr{reinterpret_cast<s32*>(previous_sample)}; + + // If previous volume and ramp are both 0, nothing will be added to the output, so just skip. + if (prev_volume == 0.0f && ramp == 0.0f) { + *prev_sample_ptr = 0; + return; + } + + switch (precision) { + case 15: + *prev_sample_ptr = + ApplyMixRamp<15>(output, input, prev_volume, ramp, processor.sample_count); + break; + + case 23: + *prev_sample_ptr = + ApplyMixRamp<23>(output, input, prev_volume, ramp, processor.sample_count); + break; + + default: + LOG_ERROR(Service_Audio, "Invalid precision {}", precision); + break; + } +} + +bool MixRampCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix_ramp.h b/src/audio_core/renderer/command/mix/mix_ramp.h new file mode 100644 index 000000000..770f57e80 --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix_ramp.h @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume + * applied to the input, and volume ramping to smooth out the transition. + */ +struct MixRampCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Fixed point precision + u8 precision; + /// Input mix buffer index + s16 input_index; + /// Output mix buffer index + s16 output_index; + /// Previous mix volume + f32 prev_volume; + /// Current mix volume + f32 volume; + /// Pointer to the previous sample buffer, used for depopping + CpuAddr previous_sample; +}; + +/** + * Mix input mix buffer into output mix buffer, with volume applied to the input. + * @tparam Q - Number of bits for fixed point operations. + * @param output - Output mix buffer. + * @param input - Input mix buffer. + * @param volume - Volume applied to the input. + * @param ramp - Ramp applied to volume every sample. + * @param sample_count - Number of samples to process. + * @return The final gained input sample, used for depopping. + */ +template <size_t Q> +s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, + const f32 ramp_, const u32 sample_count); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp b/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp new file mode 100644 index 000000000..43dbef9fc --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/mix_ramp.h" +#include "audio_core/renderer/command/mix/mix_ramp_grouped.h" + +namespace AudioCore::AudioRenderer { + +void MixRampGroupedCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { + string += "MixRampGroupedCommand"; + for (u32 i = 0; i < buffer_count; i++) { + string += fmt::format("\n\t{}", i); + const auto ramp{(volumes[i] - prev_volumes[i]) / static_cast<f32>(processor.sample_count)}; + string += fmt::format("\n\t\tinput {:02X}", inputs[i]); + string += fmt::format("\n\t\toutput {:02X}", outputs[i]); + string += fmt::format("\n\t\tvolume {:.8f}", volumes[i]); + string += fmt::format("\n\t\tprev_volume {:.8f}", prev_volumes[i]); + string += fmt::format("\n\t\tramp {:.8f}", ramp); + string += "\n"; + } +} + +void MixRampGroupedCommand::Process(const ADSP::CommandListProcessor& processor) { + std::span<s32> prev_samples = {reinterpret_cast<s32*>(previous_samples), MaxMixBuffers}; + + for (u32 i = 0; i < buffer_count; i++) { + auto last_sample{0}; + if (prev_volumes[i] != 0.0f || volumes[i] != 0.0f) { + const auto output{processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count)}; + const auto input{processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count)}; + const auto ramp{(volumes[i] - prev_volumes[i]) / + static_cast<f32>(processor.sample_count)}; + + if (prev_volumes[i] == 0.0f && ramp == 0.0f) { + prev_samples[i] = 0; + continue; + } + + switch (precision) { + case 15: + last_sample = + ApplyMixRamp<15>(output, input, prev_volumes[i], ramp, processor.sample_count); + break; + case 23: + last_sample = + ApplyMixRamp<23>(output, input, prev_volumes[i], ramp, processor.sample_count); + break; + default: + LOG_ERROR(Service_Audio, "Invalid precision {}", precision); + break; + } + } + + prev_samples[i] = last_sample; + } +} + +bool MixRampGroupedCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h new file mode 100644 index 000000000..027276e5a --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for mixing multiple input mix buffers to multiple output mix buffers, with + * a volume applied to the input, and volume ramping to smooth out the transition. + */ +struct MixRampGroupedCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Fixed point precision + u8 precision; + /// Number of mix buffers to mix + u32 buffer_count; + /// Input mix buffer indexes for each mix buffer + std::array<s16, MaxMixBuffers> inputs; + /// Output mix buffer indexes for each mix buffer + std::array<s16, MaxMixBuffers> outputs; + /// Previous mix vloumes for each mix buffer + std::array<f32, MaxMixBuffers> prev_volumes; + /// Current mix vloumes for each mix buffer + std::array<f32, MaxMixBuffers> volumes; + /// Pointer to the previous sample buffer, used for depop + CpuAddr previous_samples; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/volume.cpp b/src/audio_core/renderer/command/mix/volume.cpp new file mode 100644 index 000000000..b045fb062 --- /dev/null +++ b/src/audio_core/renderer/command/mix/volume.cpp @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/volume.h" +#include "common/fixed_point.h" +#include "common/logging/log.h" + +namespace AudioCore::AudioRenderer { +/** + * Apply volume to the input mix buffer, saving to the output buffer. + * + * @tparam Q - Number of bits for fixed point operations. + * @param output - Output mix buffer. + * @param input - Input mix buffer. + * @param volume - Volume applied to the input. + * @param sample_count - Number of samples to process. + */ +template <size_t Q> +static void ApplyUniformGain(std::span<s32> output, std::span<const s32> input, const f32 volume, + const u32 sample_count) { + if (volume == 1.0f) { + std::memcpy(output.data(), input.data(), input.size_bytes()); + } else { + const Common::FixedPoint<64 - Q, Q> gain{volume}; + for (u32 i = 0; i < sample_count; i++) { + output[i] = (input[i] * gain).to_int(); + } + } +} + +void VolumeCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("VolumeCommand"); + string += fmt::format("\n\tinput {:02X}", input_index); + string += fmt::format("\n\toutput {:02X}", output_index); + string += fmt::format("\n\tvolume {:.8f}", volume); + string += "\n"; +} + +void VolumeCommand::Process(const ADSP::CommandListProcessor& processor) { + // If input and output buffers are the same, and the volume is 1.0f, this won't do + // anything, so just skip. + if (input_index == output_index && volume == 1.0f) { + return; + } + + auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, + processor.sample_count)}; + + switch (precision) { + case 15: + ApplyUniformGain<15>(output, input, volume, processor.sample_count); + break; + + case 23: + ApplyUniformGain<23>(output, input, volume, processor.sample_count); + break; + + default: + LOG_ERROR(Service_Audio, "Invalid precision {}", precision); + break; + } +} + +bool VolumeCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/volume.h b/src/audio_core/renderer/command/mix/volume.h new file mode 100644 index 000000000..6ae9fb794 --- /dev/null +++ b/src/audio_core/renderer/command/mix/volume.h @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for applying volume to a mix buffer. + */ +struct VolumeCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Fixed point precision + u8 precision; + /// Input mix buffer index + s16 input_index; + /// Output mix buffer index + s16 output_index; + /// Mix volume applied to the input + f32 volume; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/volume_ramp.cpp b/src/audio_core/renderer/command/mix/volume_ramp.cpp new file mode 100644 index 000000000..424307148 --- /dev/null +++ b/src/audio_core/renderer/command/mix/volume_ramp.cpp @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/volume_ramp.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +/** + * Apply volume with ramping to the input mix buffer, saving to the output buffer. + * + * @tparam Q - Number of bits for fixed point operations. + * @param output - Output mix buffers. + * @param input - Input mix buffers. + * @param volume - Volume applied to the input. + * @param ramp - Ramp applied to volume every sample. + * @param sample_count - Number of samples to process. + */ +template <size_t Q> +static void ApplyLinearEnvelopeGain(std::span<s32> output, std::span<const s32> input, + const f32 volume, const f32 ramp_, const u32 sample_count) { + if (volume == 0.0f && ramp_ == 0.0f) { + std::memset(output.data(), 0, output.size_bytes()); + } else if (volume == 1.0f && ramp_ == 0.0f) { + std::memcpy(output.data(), input.data(), output.size_bytes()); + } else if (ramp_ == 0.0f) { + const Common::FixedPoint<64 - Q, Q> gain{volume}; + for (u32 i = 0; i < sample_count; i++) { + output[i] = (input[i] * gain).to_int(); + } + } else { + Common::FixedPoint<64 - Q, Q> gain{volume}; + const Common::FixedPoint<64 - Q, Q> ramp{ramp_}; + for (u32 i = 0; i < sample_count; i++) { + output[i] = (input[i] * gain).to_int(); + gain += ramp; + } + } +} + +void VolumeRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { + const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; + string += fmt::format("VolumeRampCommand"); + string += fmt::format("\n\tinput {:02X}", input_index); + string += fmt::format("\n\toutput {:02X}", output_index); + string += fmt::format("\n\tvolume {:.8f}", volume); + string += fmt::format("\n\tprev_volume {:.8f}", prev_volume); + string += fmt::format("\n\tramp {:.8f}", ramp); + string += "\n"; +} + +void VolumeRampCommand::Process(const ADSP::CommandListProcessor& processor) { + auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, + processor.sample_count)}; + const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; + + // If input and output buffers are the same, and the volume is 1.0f, and there's no ramping, + // this won't do anything, so just skip. + if (input_index == output_index && prev_volume == 1.0f && ramp == 0.0f) { + return; + } + + switch (precision) { + case 15: + ApplyLinearEnvelopeGain<15>(output, input, prev_volume, ramp, processor.sample_count); + break; + + case 23: + ApplyLinearEnvelopeGain<23>(output, input, prev_volume, ramp, processor.sample_count); + break; + + default: + LOG_ERROR(Service_Audio, "Invalid precision {}", precision); + break; + } +} + +bool VolumeRampCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/volume_ramp.h b/src/audio_core/renderer/command/mix/volume_ramp.h new file mode 100644 index 000000000..77b61547e --- /dev/null +++ b/src/audio_core/renderer/command/mix/volume_ramp.h @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for applying volume to a mix buffer, with ramping for the volume to smooth + * out the transition. + */ +struct VolumeRampCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Fixed point precision + u8 precision; + /// Input mix buffer index + s16 input_index; + /// Output mix buffer index + s16 output_index; + /// Previous mix volume applied to the input + f32 prev_volume; + /// Current mix volume applied to the input + f32 volume; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/performance/performance.cpp b/src/audio_core/renderer/command/performance/performance.cpp new file mode 100644 index 000000000..985958b03 --- /dev/null +++ b/src/audio_core/renderer/command/performance/performance.cpp @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/performance/performance.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" + +namespace AudioCore::AudioRenderer { + +void PerformanceCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("PerformanceCommand\n\tstate {}\n", static_cast<u32>(state)); +} + +void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) { + auto base{entry_address.translated_address}; + if (state == PerformanceState::Start) { + auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)}; + *start_time_ptr = static_cast<u32>( + Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() - + processor.start_time - processor.current_processing_time) + .count()); + } else if (state == PerformanceState::Stop) { + auto processed_time_ptr{ + reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)}; + auto entry_count_ptr{ + reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)}; + + *processed_time_ptr = static_cast<u32>( + Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() - + processor.start_time - processor.current_processing_time) + .count()); + (*entry_count_ptr)++; + } +} + +bool PerformanceCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/performance/performance.h b/src/audio_core/renderer/command/performance/performance.h new file mode 100644 index 000000000..11a7d6c08 --- /dev/null +++ b/src/audio_core/renderer/command/performance/performance.h @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/performance/performance_entry_addresses.h" +#include "audio_core/renderer/performance/performance_manager.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for writing AudioRenderer performance metrics back to the sysmodule. + */ +struct PerformanceCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// State of the performance + PerformanceState state; + /// Pointers to be written + PerformanceEntryAddresses entry_address; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp new file mode 100644 index 000000000..1fd90308a --- /dev/null +++ b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h" + +namespace AudioCore::AudioRenderer { + +void DownMix6chTo2chCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("DownMix6chTo2chCommand\n\tinputs: "); + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void DownMix6chTo2chCommand::Process(const ADSP::CommandListProcessor& processor) { + auto in_front_left{ + processor.mix_buffers.subspan(inputs[0] * processor.sample_count, processor.sample_count)}; + auto in_front_right{ + processor.mix_buffers.subspan(inputs[1] * processor.sample_count, processor.sample_count)}; + auto in_center{ + processor.mix_buffers.subspan(inputs[2] * processor.sample_count, processor.sample_count)}; + auto in_lfe{ + processor.mix_buffers.subspan(inputs[3] * processor.sample_count, processor.sample_count)}; + auto in_back_left{ + processor.mix_buffers.subspan(inputs[4] * processor.sample_count, processor.sample_count)}; + auto in_back_right{ + processor.mix_buffers.subspan(inputs[5] * processor.sample_count, processor.sample_count)}; + + auto out_front_left{ + processor.mix_buffers.subspan(outputs[0] * processor.sample_count, processor.sample_count)}; + auto out_front_right{ + processor.mix_buffers.subspan(outputs[1] * processor.sample_count, processor.sample_count)}; + auto out_center{ + processor.mix_buffers.subspan(outputs[2] * processor.sample_count, processor.sample_count)}; + auto out_lfe{ + processor.mix_buffers.subspan(outputs[3] * processor.sample_count, processor.sample_count)}; + auto out_back_left{ + processor.mix_buffers.subspan(outputs[4] * processor.sample_count, processor.sample_count)}; + auto out_back_right{ + processor.mix_buffers.subspan(outputs[5] * processor.sample_count, processor.sample_count)}; + + for (u32 i = 0; i < processor.sample_count; i++) { + const auto left_sample{(in_front_left[i] * down_mix_coeff[0] + + in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] + + in_back_left[i] * down_mix_coeff[3]) + .to_int()}; + + const auto right_sample{(in_front_right[i] * down_mix_coeff[0] + + in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] + + in_back_right[i] * down_mix_coeff[3]) + .to_int()}; + + out_front_left[i] = left_sample; + out_front_right[i] = right_sample; + } + + std::memset(out_center.data(), 0, out_center.size_bytes()); + std::memset(out_lfe.data(), 0, out_lfe.size_bytes()); + std::memset(out_back_left.data(), 0, out_back_left.size_bytes()); + std::memset(out_back_right.data(), 0, out_back_right.size_bytes()); +} + +bool DownMix6chTo2chCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h new file mode 100644 index 000000000..dc133a73b --- /dev/null +++ b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for downmixing 6 channels to 2. + * Channel layout (SMPTE): + * 0 - front left + * 1 - front right + * 2 - center + * 3 - lfe + * 4 - back left + * 5 - back right + */ +struct DownMix6chTo2chCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array<s16, MaxChannels> inputs; + /// Output mix buffer offsets for each channel + std::array<s16, MaxChannels> outputs; + /// Coefficients used for downmixing + std::array<Common::FixedPoint<48, 16>, 4> down_mix_coeff; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/resample.cpp b/src/audio_core/renderer/command/resample/resample.cpp new file mode 100644 index 000000000..070c9d2b8 --- /dev/null +++ b/src/audio_core/renderer/command/resample/resample.cpp @@ -0,0 +1,883 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/command/resample/resample.h" + +namespace AudioCore::AudioRenderer { + +static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input, + const Common::FixedPoint<49, 15>& sample_rate_ratio, + Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) { + if (sample_rate_ratio == 1.0f) { + for (u32 i = 0; i < samples_to_write; i++) { + output[i] = input[i]; + } + } else { + u32 read_index{0}; + for (u32 i = 0; i < samples_to_write; i++) { + output[i] = input[read_index + (fraction >= 0.5f)]; + fraction += sample_rate_ratio; + read_index += static_cast<u32>(fraction.to_int_floor()); + fraction.clear_int(); + } + } +} + +static void ResampleNormalQuality(std::span<s32> output, std::span<const s16> input, + const Common::FixedPoint<49, 15>& sample_rate_ratio, + Common::FixedPoint<49, 15>& fraction, + const u32 samples_to_write) { + static constexpr std::array<f32, 512> lut0 = { + 0.20141602f, 0.59283447f, 0.20513916f, 0.00009155f, 0.19772339f, 0.59277344f, 0.20889282f, + 0.00027466f, 0.19406128f, 0.59262085f, 0.21264648f, 0.00045776f, 0.19039917f, 0.59240723f, + 0.21646118f, 0.00067139f, 0.18679810f, 0.59213257f, 0.22030640f, 0.00085449f, 0.18322754f, + 0.59176636f, 0.22415161f, 0.00103760f, 0.17968750f, 0.59133911f, 0.22802734f, 0.00125122f, + 0.17617798f, 0.59085083f, 0.23193359f, 0.00146484f, 0.17269897f, 0.59027100f, 0.23583984f, + 0.00167847f, 0.16925049f, 0.58963013f, 0.23977661f, 0.00189209f, 0.16583252f, 0.58892822f, + 0.24374390f, 0.00210571f, 0.16244507f, 0.58816528f, 0.24774170f, 0.00234985f, 0.15908813f, + 0.58731079f, 0.25173950f, 0.00256348f, 0.15576172f, 0.58639526f, 0.25576782f, 0.00280762f, + 0.15249634f, 0.58541870f, 0.25979614f, 0.00308228f, 0.14923096f, 0.58435059f, 0.26385498f, + 0.00332642f, 0.14602661f, 0.58325195f, 0.26794434f, 0.00360107f, 0.14285278f, 0.58206177f, + 0.27203369f, 0.00387573f, 0.13973999f, 0.58078003f, 0.27612305f, 0.00418091f, 0.13662720f, + 0.57946777f, 0.28024292f, 0.00448608f, 0.13357544f, 0.57806396f, 0.28436279f, 0.00479126f, + 0.13052368f, 0.57662964f, 0.28851318f, 0.00512695f, 0.12753296f, 0.57510376f, 0.29266357f, + 0.00546265f, 0.12460327f, 0.57351685f, 0.29681396f, 0.00579834f, 0.12167358f, 0.57183838f, + 0.30099487f, 0.00616455f, 0.11880493f, 0.57012939f, 0.30517578f, 0.00656128f, 0.11596680f, + 0.56835938f, 0.30935669f, 0.00695801f, 0.11318970f, 0.56649780f, 0.31353760f, 0.00735474f, + 0.11041260f, 0.56457520f, 0.31771851f, 0.00778198f, 0.10769653f, 0.56262207f, 0.32192993f, + 0.00823975f, 0.10501099f, 0.56057739f, 0.32614136f, 0.00869751f, 0.10238647f, 0.55847168f, + 0.33032227f, 0.00915527f, 0.09976196f, 0.55633545f, 0.33453369f, 0.00967407f, 0.09722900f, + 0.55410767f, 0.33874512f, 0.01019287f, 0.09469604f, 0.55181885f, 0.34295654f, 0.01071167f, + 0.09222412f, 0.54949951f, 0.34713745f, 0.01126099f, 0.08978271f, 0.54708862f, 0.35134888f, + 0.01184082f, 0.08737183f, 0.54464722f, 0.35552979f, 0.01245117f, 0.08499146f, 0.54214478f, + 0.35974121f, 0.01306152f, 0.08267212f, 0.53958130f, 0.36392212f, 0.01370239f, 0.08041382f, + 0.53695679f, 0.36810303f, 0.01437378f, 0.07815552f, 0.53427124f, 0.37225342f, 0.01507568f, + 0.07595825f, 0.53155518f, 0.37640381f, 0.01577759f, 0.07379150f, 0.52877808f, 0.38055420f, + 0.01651001f, 0.07165527f, 0.52593994f, 0.38470459f, 0.01727295f, 0.06958008f, 0.52307129f, + 0.38882446f, 0.01806641f, 0.06753540f, 0.52014160f, 0.39294434f, 0.01889038f, 0.06552124f, + 0.51715088f, 0.39703369f, 0.01974487f, 0.06356812f, 0.51409912f, 0.40112305f, 0.02059937f, + 0.06164551f, 0.51101685f, 0.40518188f, 0.02148438f, 0.05975342f, 0.50790405f, 0.40921021f, + 0.02243042f, 0.05789185f, 0.50473022f, 0.41323853f, 0.02337646f, 0.05609131f, 0.50152588f, + 0.41726685f, 0.02435303f, 0.05432129f, 0.49826050f, 0.42123413f, 0.02539062f, 0.05258179f, + 0.49493408f, 0.42520142f, 0.02642822f, 0.05087280f, 0.49160767f, 0.42913818f, 0.02749634f, + 0.04922485f, 0.48822021f, 0.43307495f, 0.02859497f, 0.04760742f, 0.48477173f, 0.43695068f, + 0.02975464f, 0.04602051f, 0.48132324f, 0.44082642f, 0.03091431f, 0.04446411f, 0.47781372f, + 0.44467163f, 0.03210449f, 0.04293823f, 0.47424316f, 0.44845581f, 0.03335571f, 0.04147339f, + 0.47067261f, 0.45223999f, 0.03460693f, 0.04003906f, 0.46704102f, 0.45599365f, 0.03591919f, + 0.03863525f, 0.46340942f, 0.45971680f, 0.03726196f, 0.03726196f, 0.45971680f, 0.46340942f, + 0.03863525f, 0.03591919f, 0.45599365f, 0.46704102f, 0.04003906f, 0.03460693f, 0.45223999f, + 0.47067261f, 0.04147339f, 0.03335571f, 0.44845581f, 0.47424316f, 0.04293823f, 0.03210449f, + 0.44467163f, 0.47781372f, 0.04446411f, 0.03091431f, 0.44082642f, 0.48132324f, 0.04602051f, + 0.02975464f, 0.43695068f, 0.48477173f, 0.04760742f, 0.02859497f, 0.43307495f, 0.48822021f, + 0.04922485f, 0.02749634f, 0.42913818f, 0.49160767f, 0.05087280f, 0.02642822f, 0.42520142f, + 0.49493408f, 0.05258179f, 0.02539062f, 0.42123413f, 0.49826050f, 0.05432129f, 0.02435303f, + 0.41726685f, 0.50152588f, 0.05609131f, 0.02337646f, 0.41323853f, 0.50473022f, 0.05789185f, + 0.02243042f, 0.40921021f, 0.50790405f, 0.05975342f, 0.02148438f, 0.40518188f, 0.51101685f, + 0.06164551f, 0.02059937f, 0.40112305f, 0.51409912f, 0.06356812f, 0.01974487f, 0.39703369f, + 0.51715088f, 0.06552124f, 0.01889038f, 0.39294434f, 0.52014160f, 0.06753540f, 0.01806641f, + 0.38882446f, 0.52307129f, 0.06958008f, 0.01727295f, 0.38470459f, 0.52593994f, 0.07165527f, + 0.01651001f, 0.38055420f, 0.52877808f, 0.07379150f, 0.01577759f, 0.37640381f, 0.53155518f, + 0.07595825f, 0.01507568f, 0.37225342f, 0.53427124f, 0.07815552f, 0.01437378f, 0.36810303f, + 0.53695679f, 0.08041382f, 0.01370239f, 0.36392212f, 0.53958130f, 0.08267212f, 0.01306152f, + 0.35974121f, 0.54214478f, 0.08499146f, 0.01245117f, 0.35552979f, 0.54464722f, 0.08737183f, + 0.01184082f, 0.35134888f, 0.54708862f, 0.08978271f, 0.01126099f, 0.34713745f, 0.54949951f, + 0.09222412f, 0.01071167f, 0.34295654f, 0.55181885f, 0.09469604f, 0.01019287f, 0.33874512f, + 0.55410767f, 0.09722900f, 0.00967407f, 0.33453369f, 0.55633545f, 0.09976196f, 0.00915527f, + 0.33032227f, 0.55847168f, 0.10238647f, 0.00869751f, 0.32614136f, 0.56057739f, 0.10501099f, + 0.00823975f, 0.32192993f, 0.56262207f, 0.10769653f, 0.00778198f, 0.31771851f, 0.56457520f, + 0.11041260f, 0.00735474f, 0.31353760f, 0.56649780f, 0.11318970f, 0.00695801f, 0.30935669f, + 0.56835938f, 0.11596680f, 0.00656128f, 0.30517578f, 0.57012939f, 0.11880493f, 0.00616455f, + 0.30099487f, 0.57183838f, 0.12167358f, 0.00579834f, 0.29681396f, 0.57351685f, 0.12460327f, + 0.00546265f, 0.29266357f, 0.57510376f, 0.12753296f, 0.00512695f, 0.28851318f, 0.57662964f, + 0.13052368f, 0.00479126f, 0.28436279f, 0.57806396f, 0.13357544f, 0.00448608f, 0.28024292f, + 0.57946777f, 0.13662720f, 0.00418091f, 0.27612305f, 0.58078003f, 0.13973999f, 0.00387573f, + 0.27203369f, 0.58206177f, 0.14285278f, 0.00360107f, 0.26794434f, 0.58325195f, 0.14602661f, + 0.00332642f, 0.26385498f, 0.58435059f, 0.14923096f, 0.00308228f, 0.25979614f, 0.58541870f, + 0.15249634f, 0.00280762f, 0.25576782f, 0.58639526f, 0.15576172f, 0.00256348f, 0.25173950f, + 0.58731079f, 0.15908813f, 0.00234985f, 0.24774170f, 0.58816528f, 0.16244507f, 0.00210571f, + 0.24374390f, 0.58892822f, 0.16583252f, 0.00189209f, 0.23977661f, 0.58963013f, 0.16925049f, + 0.00167847f, 0.23583984f, 0.59027100f, 0.17269897f, 0.00146484f, 0.23193359f, 0.59085083f, + 0.17617798f, 0.00125122f, 0.22802734f, 0.59133911f, 0.17968750f, 0.00103760f, 0.22415161f, + 0.59176636f, 0.18322754f, 0.00085449f, 0.22030640f, 0.59213257f, 0.18679810f, 0.00067139f, + 0.21646118f, 0.59240723f, 0.19039917f, 0.00045776f, 0.21264648f, 0.59262085f, 0.19406128f, + 0.00027466f, 0.20889282f, 0.59277344f, 0.19772339f, 0.00009155f, 0.20513916f, 0.59283447f, + 0.20141602f, + }; + + static constexpr std::array<f32, 512> lut1 = { + 0.00207520f, 0.99606323f, 0.00210571f, -0.00015259f, -0.00610352f, 0.99578857f, + 0.00646973f, -0.00045776f, -0.01000977f, 0.99526978f, 0.01095581f, -0.00079346f, + -0.01373291f, 0.99444580f, 0.01562500f, -0.00109863f, -0.01733398f, 0.99337769f, + 0.02041626f, -0.00143433f, -0.02075195f, 0.99203491f, 0.02539062f, -0.00177002f, + -0.02404785f, 0.99041748f, 0.03051758f, -0.00210571f, -0.02719116f, 0.98855591f, + 0.03582764f, -0.00244141f, -0.03021240f, 0.98641968f, 0.04125977f, -0.00280762f, + -0.03308105f, 0.98400879f, 0.04687500f, -0.00314331f, -0.03579712f, 0.98135376f, + 0.05261230f, -0.00350952f, -0.03839111f, 0.97842407f, 0.05856323f, -0.00390625f, + -0.04083252f, 0.97521973f, 0.06463623f, -0.00427246f, -0.04315186f, 0.97180176f, + 0.07086182f, -0.00466919f, -0.04534912f, 0.96810913f, 0.07727051f, -0.00509644f, + -0.04742432f, 0.96414185f, 0.08383179f, -0.00549316f, -0.04934692f, 0.95996094f, + 0.09054565f, -0.00592041f, -0.05114746f, 0.95550537f, 0.09741211f, -0.00637817f, + -0.05285645f, 0.95083618f, 0.10443115f, -0.00683594f, -0.05441284f, 0.94589233f, + 0.11160278f, -0.00732422f, -0.05584717f, 0.94073486f, 0.11892700f, -0.00781250f, + -0.05718994f, 0.93533325f, 0.12643433f, -0.00830078f, -0.05841064f, 0.92968750f, + 0.13406372f, -0.00881958f, -0.05953979f, 0.92382812f, 0.14184570f, -0.00936890f, + -0.06054688f, 0.91772461f, 0.14978027f, -0.00991821f, -0.06146240f, 0.91143799f, + 0.15783691f, -0.01046753f, -0.06225586f, 0.90490723f, 0.16607666f, -0.01104736f, + -0.06295776f, 0.89816284f, 0.17443848f, -0.01165771f, -0.06356812f, 0.89120483f, + 0.18292236f, -0.01229858f, -0.06408691f, 0.88403320f, 0.19155884f, -0.01293945f, + -0.06451416f, 0.87667847f, 0.20034790f, -0.01358032f, -0.06484985f, 0.86914062f, + 0.20925903f, -0.01428223f, -0.06509399f, 0.86138916f, 0.21829224f, -0.01495361f, + -0.06527710f, 0.85345459f, 0.22744751f, -0.01568604f, -0.06536865f, 0.84533691f, + 0.23675537f, -0.01641846f, -0.06536865f, 0.83703613f, 0.24615479f, -0.01718140f, + -0.06533813f, 0.82858276f, 0.25567627f, -0.01794434f, -0.06518555f, 0.81991577f, + 0.26531982f, -0.01873779f, -0.06500244f, 0.81112671f, 0.27505493f, -0.01956177f, + -0.06472778f, 0.80215454f, 0.28491211f, -0.02038574f, -0.06442261f, 0.79306030f, + 0.29489136f, -0.02124023f, -0.06402588f, 0.78378296f, 0.30496216f, -0.02209473f, + -0.06359863f, 0.77438354f, 0.31512451f, -0.02297974f, -0.06307983f, 0.76486206f, + 0.32537842f, -0.02389526f, -0.06253052f, 0.75518799f, 0.33569336f, -0.02481079f, + -0.06195068f, 0.74539185f, 0.34613037f, -0.02575684f, -0.06130981f, 0.73547363f, + 0.35662842f, -0.02670288f, -0.06060791f, 0.72543335f, 0.36721802f, -0.02767944f, + -0.05987549f, 0.71527100f, 0.37786865f, -0.02865601f, -0.05911255f, 0.70504761f, + 0.38858032f, -0.02966309f, -0.05831909f, 0.69470215f, 0.39935303f, -0.03067017f, + -0.05746460f, 0.68426514f, 0.41018677f, -0.03170776f, -0.05661011f, 0.67373657f, + 0.42108154f, -0.03271484f, -0.05569458f, 0.66311646f, 0.43200684f, -0.03378296f, + -0.05477905f, 0.65246582f, 0.44299316f, -0.03482056f, -0.05383301f, 0.64169312f, + 0.45401001f, -0.03588867f, -0.05285645f, 0.63088989f, 0.46505737f, -0.03695679f, + -0.05187988f, 0.62002563f, 0.47613525f, -0.03802490f, -0.05087280f, 0.60910034f, + 0.48721313f, -0.03912354f, -0.04983521f, 0.59814453f, 0.49832153f, -0.04019165f, + -0.04879761f, 0.58712769f, 0.50946045f, -0.04129028f, -0.04772949f, 0.57611084f, + 0.52056885f, -0.04235840f, -0.04669189f, 0.56503296f, 0.53170776f, -0.04345703f, + -0.04562378f, 0.55392456f, 0.54281616f, -0.04452515f, -0.04452515f, 0.54281616f, + 0.55392456f, -0.04562378f, -0.04345703f, 0.53170776f, 0.56503296f, -0.04669189f, + -0.04235840f, 0.52056885f, 0.57611084f, -0.04772949f, -0.04129028f, 0.50946045f, + 0.58712769f, -0.04879761f, -0.04019165f, 0.49832153f, 0.59814453f, -0.04983521f, + -0.03912354f, 0.48721313f, 0.60910034f, -0.05087280f, -0.03802490f, 0.47613525f, + 0.62002563f, -0.05187988f, -0.03695679f, 0.46505737f, 0.63088989f, -0.05285645f, + -0.03588867f, 0.45401001f, 0.64169312f, -0.05383301f, -0.03482056f, 0.44299316f, + 0.65246582f, -0.05477905f, -0.03378296f, 0.43200684f, 0.66311646f, -0.05569458f, + -0.03271484f, 0.42108154f, 0.67373657f, -0.05661011f, -0.03170776f, 0.41018677f, + 0.68426514f, -0.05746460f, -0.03067017f, 0.39935303f, 0.69470215f, -0.05831909f, + -0.02966309f, 0.38858032f, 0.70504761f, -0.05911255f, -0.02865601f, 0.37786865f, + 0.71527100f, -0.05987549f, -0.02767944f, 0.36721802f, 0.72543335f, -0.06060791f, + -0.02670288f, 0.35662842f, 0.73547363f, -0.06130981f, -0.02575684f, 0.34613037f, + 0.74539185f, -0.06195068f, -0.02481079f, 0.33569336f, 0.75518799f, -0.06253052f, + -0.02389526f, 0.32537842f, 0.76486206f, -0.06307983f, -0.02297974f, 0.31512451f, + 0.77438354f, -0.06359863f, -0.02209473f, 0.30496216f, 0.78378296f, -0.06402588f, + -0.02124023f, 0.29489136f, 0.79306030f, -0.06442261f, -0.02038574f, 0.28491211f, + 0.80215454f, -0.06472778f, -0.01956177f, 0.27505493f, 0.81112671f, -0.06500244f, + -0.01873779f, 0.26531982f, 0.81991577f, -0.06518555f, -0.01794434f, 0.25567627f, + 0.82858276f, -0.06533813f, -0.01718140f, 0.24615479f, 0.83703613f, -0.06536865f, + -0.01641846f, 0.23675537f, 0.84533691f, -0.06536865f, -0.01568604f, 0.22744751f, + 0.85345459f, -0.06527710f, -0.01495361f, 0.21829224f, 0.86138916f, -0.06509399f, + -0.01428223f, 0.20925903f, 0.86914062f, -0.06484985f, -0.01358032f, 0.20034790f, + 0.87667847f, -0.06451416f, -0.01293945f, 0.19155884f, 0.88403320f, -0.06408691f, + -0.01229858f, 0.18292236f, 0.89120483f, -0.06356812f, -0.01165771f, 0.17443848f, + 0.89816284f, -0.06295776f, -0.01104736f, 0.16607666f, 0.90490723f, -0.06225586f, + -0.01046753f, 0.15783691f, 0.91143799f, -0.06146240f, -0.00991821f, 0.14978027f, + 0.91772461f, -0.06054688f, -0.00936890f, 0.14184570f, 0.92382812f, -0.05953979f, + -0.00881958f, 0.13406372f, 0.92968750f, -0.05841064f, -0.00830078f, 0.12643433f, + 0.93533325f, -0.05718994f, -0.00781250f, 0.11892700f, 0.94073486f, -0.05584717f, + -0.00732422f, 0.11160278f, 0.94589233f, -0.05441284f, -0.00683594f, 0.10443115f, + 0.95083618f, -0.05285645f, -0.00637817f, 0.09741211f, 0.95550537f, -0.05114746f, + -0.00592041f, 0.09054565f, 0.95996094f, -0.04934692f, -0.00549316f, 0.08383179f, + 0.96414185f, -0.04742432f, -0.00509644f, 0.07727051f, 0.96810913f, -0.04534912f, + -0.00466919f, 0.07086182f, 0.97180176f, -0.04315186f, -0.00427246f, 0.06463623f, + 0.97521973f, -0.04083252f, -0.00390625f, 0.05856323f, 0.97842407f, -0.03839111f, + -0.00350952f, 0.05261230f, 0.98135376f, -0.03579712f, -0.00314331f, 0.04687500f, + 0.98400879f, -0.03308105f, -0.00280762f, 0.04125977f, 0.98641968f, -0.03021240f, + -0.00244141f, 0.03582764f, 0.98855591f, -0.02719116f, -0.00210571f, 0.03051758f, + 0.99041748f, -0.02404785f, -0.00177002f, 0.02539062f, 0.99203491f, -0.02075195f, + -0.00143433f, 0.02041626f, 0.99337769f, -0.01733398f, -0.00109863f, 0.01562500f, + 0.99444580f, -0.01373291f, -0.00079346f, 0.01095581f, 0.99526978f, -0.01000977f, + -0.00045776f, 0.00646973f, 0.99578857f, -0.00610352f, -0.00015259f, 0.00210571f, + 0.99606323f, -0.00207520f, + }; + + static constexpr std::array<f32, 512> lut2 = { + 0.09750366f, 0.80221558f, 0.10159302f, -0.00097656f, 0.09350586f, 0.80203247f, + 0.10580444f, -0.00103760f, 0.08959961f, 0.80169678f, 0.11010742f, -0.00115967f, + 0.08578491f, 0.80117798f, 0.11447144f, -0.00128174f, 0.08203125f, 0.80047607f, + 0.11892700f, -0.00140381f, 0.07836914f, 0.79962158f, 0.12347412f, -0.00152588f, + 0.07479858f, 0.79861450f, 0.12814331f, -0.00164795f, 0.07135010f, 0.79742432f, + 0.13287354f, -0.00177002f, 0.06796265f, 0.79605103f, 0.13769531f, -0.00192261f, + 0.06469727f, 0.79452515f, 0.14260864f, -0.00204468f, 0.06149292f, 0.79284668f, + 0.14761353f, -0.00219727f, 0.05834961f, 0.79098511f, 0.15270996f, -0.00231934f, + 0.05532837f, 0.78894043f, 0.15789795f, -0.00247192f, 0.05236816f, 0.78674316f, + 0.16317749f, -0.00265503f, 0.04949951f, 0.78442383f, 0.16851807f, -0.00280762f, + 0.04672241f, 0.78189087f, 0.17398071f, -0.00299072f, 0.04400635f, 0.77920532f, + 0.17950439f, -0.00314331f, 0.04141235f, 0.77636719f, 0.18511963f, -0.00332642f, + 0.03887939f, 0.77337646f, 0.19082642f, -0.00350952f, 0.03640747f, 0.77023315f, + 0.19659424f, -0.00369263f, 0.03402710f, 0.76693726f, 0.20248413f, -0.00387573f, + 0.03173828f, 0.76348877f, 0.20843506f, -0.00405884f, 0.02951050f, 0.75985718f, + 0.21444702f, -0.00427246f, 0.02737427f, 0.75610352f, 0.22055054f, -0.00445557f, + 0.02529907f, 0.75219727f, 0.22674561f, -0.00466919f, 0.02331543f, 0.74816895f, + 0.23300171f, -0.00485229f, 0.02139282f, 0.74398804f, 0.23931885f, -0.00506592f, + 0.01956177f, 0.73965454f, 0.24572754f, -0.00531006f, 0.01779175f, 0.73519897f, + 0.25219727f, -0.00552368f, 0.01605225f, 0.73059082f, 0.25872803f, -0.00570679f, + 0.01440430f, 0.72586060f, 0.26535034f, -0.00592041f, 0.01281738f, 0.72100830f, + 0.27203369f, -0.00616455f, 0.01132202f, 0.71600342f, 0.27877808f, -0.00637817f, + 0.00988770f, 0.71090698f, 0.28558350f, -0.00656128f, 0.00851440f, 0.70565796f, + 0.29244995f, -0.00677490f, 0.00720215f, 0.70031738f, 0.29934692f, -0.00701904f, + 0.00592041f, 0.69485474f, 0.30633545f, -0.00723267f, 0.00469971f, 0.68927002f, + 0.31338501f, -0.00741577f, 0.00357056f, 0.68356323f, 0.32046509f, -0.00762939f, + 0.00247192f, 0.67773438f, 0.32760620f, -0.00787354f, 0.00143433f, 0.67184448f, + 0.33477783f, -0.00808716f, 0.00045776f, 0.66583252f, 0.34197998f, -0.00827026f, + -0.00048828f, 0.65972900f, 0.34924316f, -0.00845337f, -0.00134277f, 0.65353394f, + 0.35656738f, -0.00863647f, -0.00216675f, 0.64721680f, 0.36389160f, -0.00885010f, + -0.00296021f, 0.64083862f, 0.37127686f, -0.00903320f, -0.00369263f, 0.63433838f, + 0.37869263f, -0.00921631f, -0.00436401f, 0.62777710f, 0.38613892f, -0.00933838f, + -0.00497437f, 0.62115479f, 0.39361572f, -0.00949097f, -0.00558472f, 0.61444092f, + 0.40109253f, -0.00964355f, -0.00613403f, 0.60763550f, 0.40859985f, -0.00979614f, + -0.00665283f, 0.60076904f, 0.41610718f, -0.00991821f, -0.00714111f, 0.59384155f, + 0.42364502f, -0.01000977f, -0.00756836f, 0.58685303f, 0.43121338f, -0.01013184f, + -0.00796509f, 0.57977295f, 0.43875122f, -0.01022339f, -0.00833130f, 0.57266235f, + 0.44631958f, -0.01028442f, -0.00866699f, 0.56552124f, 0.45388794f, -0.01034546f, + -0.00897217f, 0.55831909f, 0.46145630f, -0.01040649f, -0.00921631f, 0.55105591f, + 0.46902466f, -0.01040649f, -0.00946045f, 0.54373169f, 0.47659302f, -0.01040649f, + -0.00967407f, 0.53640747f, 0.48413086f, -0.01037598f, -0.00985718f, 0.52902222f, + 0.49166870f, -0.01037598f, -0.01000977f, 0.52160645f, 0.49917603f, -0.01031494f, + -0.01013184f, 0.51416016f, 0.50668335f, -0.01025391f, -0.01025391f, 0.50668335f, + 0.51416016f, -0.01013184f, -0.01031494f, 0.49917603f, 0.52160645f, -0.01000977f, + -0.01037598f, 0.49166870f, 0.52902222f, -0.00985718f, -0.01037598f, 0.48413086f, + 0.53640747f, -0.00967407f, -0.01040649f, 0.47659302f, 0.54373169f, -0.00946045f, + -0.01040649f, 0.46902466f, 0.55105591f, -0.00921631f, -0.01040649f, 0.46145630f, + 0.55831909f, -0.00897217f, -0.01034546f, 0.45388794f, 0.56552124f, -0.00866699f, + -0.01028442f, 0.44631958f, 0.57266235f, -0.00833130f, -0.01022339f, 0.43875122f, + 0.57977295f, -0.00796509f, -0.01013184f, 0.43121338f, 0.58685303f, -0.00756836f, + -0.01000977f, 0.42364502f, 0.59384155f, -0.00714111f, -0.00991821f, 0.41610718f, + 0.60076904f, -0.00665283f, -0.00979614f, 0.40859985f, 0.60763550f, -0.00613403f, + -0.00964355f, 0.40109253f, 0.61444092f, -0.00558472f, -0.00949097f, 0.39361572f, + 0.62115479f, -0.00497437f, -0.00933838f, 0.38613892f, 0.62777710f, -0.00436401f, + -0.00921631f, 0.37869263f, 0.63433838f, -0.00369263f, -0.00903320f, 0.37127686f, + 0.64083862f, -0.00296021f, -0.00885010f, 0.36389160f, 0.64721680f, -0.00216675f, + -0.00863647f, 0.35656738f, 0.65353394f, -0.00134277f, -0.00845337f, 0.34924316f, + 0.65972900f, -0.00048828f, -0.00827026f, 0.34197998f, 0.66583252f, 0.00045776f, + -0.00808716f, 0.33477783f, 0.67184448f, 0.00143433f, -0.00787354f, 0.32760620f, + 0.67773438f, 0.00247192f, -0.00762939f, 0.32046509f, 0.68356323f, 0.00357056f, + -0.00741577f, 0.31338501f, 0.68927002f, 0.00469971f, -0.00723267f, 0.30633545f, + 0.69485474f, 0.00592041f, -0.00701904f, 0.29934692f, 0.70031738f, 0.00720215f, + -0.00677490f, 0.29244995f, 0.70565796f, 0.00851440f, -0.00656128f, 0.28558350f, + 0.71090698f, 0.00988770f, -0.00637817f, 0.27877808f, 0.71600342f, 0.01132202f, + -0.00616455f, 0.27203369f, 0.72100830f, 0.01281738f, -0.00592041f, 0.26535034f, + 0.72586060f, 0.01440430f, -0.00570679f, 0.25872803f, 0.73059082f, 0.01605225f, + -0.00552368f, 0.25219727f, 0.73519897f, 0.01779175f, -0.00531006f, 0.24572754f, + 0.73965454f, 0.01956177f, -0.00506592f, 0.23931885f, 0.74398804f, 0.02139282f, + -0.00485229f, 0.23300171f, 0.74816895f, 0.02331543f, -0.00466919f, 0.22674561f, + 0.75219727f, 0.02529907f, -0.00445557f, 0.22055054f, 0.75610352f, 0.02737427f, + -0.00427246f, 0.21444702f, 0.75985718f, 0.02951050f, -0.00405884f, 0.20843506f, + 0.76348877f, 0.03173828f, -0.00387573f, 0.20248413f, 0.76693726f, 0.03402710f, + -0.00369263f, 0.19659424f, 0.77023315f, 0.03640747f, -0.00350952f, 0.19082642f, + 0.77337646f, 0.03887939f, -0.00332642f, 0.18511963f, 0.77636719f, 0.04141235f, + -0.00314331f, 0.17950439f, 0.77920532f, 0.04400635f, -0.00299072f, 0.17398071f, + 0.78189087f, 0.04672241f, -0.00280762f, 0.16851807f, 0.78442383f, 0.04949951f, + -0.00265503f, 0.16317749f, 0.78674316f, 0.05236816f, -0.00247192f, 0.15789795f, + 0.78894043f, 0.05532837f, -0.00231934f, 0.15270996f, 0.79098511f, 0.05834961f, + -0.00219727f, 0.14761353f, 0.79284668f, 0.06149292f, -0.00204468f, 0.14260864f, + 0.79452515f, 0.06469727f, -0.00192261f, 0.13769531f, 0.79605103f, 0.06796265f, + -0.00177002f, 0.13287354f, 0.79742432f, 0.07135010f, -0.00164795f, 0.12814331f, + 0.79861450f, 0.07479858f, -0.00152588f, 0.12347412f, 0.79962158f, 0.07836914f, + -0.00140381f, 0.11892700f, 0.80047607f, 0.08203125f, -0.00128174f, 0.11447144f, + 0.80117798f, 0.08578491f, -0.00115967f, 0.11010742f, 0.80169678f, 0.08959961f, + -0.00103760f, 0.10580444f, 0.80203247f, 0.09350586f, -0.00097656f, 0.10159302f, + 0.80221558f, 0.09750366f, + }; + + const auto get_lut = [&]() -> std::span<const f32> { + if (sample_rate_ratio <= 1.0f) { + return std::span<const f32>(lut2.data(), lut2.size()); + } else if (sample_rate_ratio < 1.3f) { + return std::span<const f32>(lut1.data(), lut1.size()); + } else { + return std::span<const f32>(lut0.data(), lut0.size()); + } + }; + + auto lut{get_lut()}; + u32 read_index{0}; + for (u32 i = 0; i < samples_to_write; i++) { + const auto lut_index{(fraction.get_frac() >> 8) * 4}; + const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]}; + const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]}; + const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]}; + const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]}; + output[i] = (sample0 + sample1 + sample2 + sample3).to_int_floor(); + fraction += sample_rate_ratio; + read_index += static_cast<u32>(fraction.to_int_floor()); + fraction.clear_int(); + } +} + +static void ResampleHighQuality(std::span<s32> output, std::span<const s16> input, + const Common::FixedPoint<49, 15>& sample_rate_ratio, + Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) { + static constexpr std::array<f32, 1024> lut0 = { + -0.01776123f, -0.00070190f, 0.26672363f, 0.50006104f, 0.26956177f, 0.00024414f, + -0.01800537f, 0.00000000f, -0.01748657f, -0.00164795f, 0.26388550f, 0.50003052f, + 0.27236938f, 0.00122070f, -0.01824951f, -0.00003052f, -0.01724243f, -0.00256348f, + 0.26107788f, 0.49996948f, 0.27520752f, 0.00219727f, -0.01849365f, -0.00003052f, + -0.01699829f, -0.00344849f, 0.25823975f, 0.49984741f, 0.27801514f, 0.00320435f, + -0.01873779f, -0.00006104f, -0.01675415f, -0.00433350f, 0.25543213f, 0.49972534f, + 0.28085327f, 0.00424194f, -0.01898193f, -0.00006104f, -0.01651001f, -0.00518799f, + 0.25259399f, 0.49954224f, 0.28366089f, 0.00527954f, -0.01922607f, -0.00009155f, + -0.01626587f, -0.00604248f, 0.24978638f, 0.49932861f, 0.28646851f, 0.00634766f, + -0.01947021f, -0.00012207f, -0.01602173f, -0.00686646f, 0.24697876f, 0.49908447f, + 0.28930664f, 0.00744629f, -0.01971436f, -0.00015259f, -0.01574707f, -0.00765991f, + 0.24414062f, 0.49877930f, 0.29211426f, 0.00854492f, -0.01995850f, -0.00015259f, + -0.01550293f, -0.00845337f, 0.24133301f, 0.49847412f, 0.29492188f, 0.00967407f, + -0.02020264f, -0.00018311f, -0.01525879f, -0.00921631f, 0.23852539f, 0.49810791f, + 0.29772949f, 0.01083374f, -0.02044678f, -0.00021362f, -0.01501465f, -0.00997925f, + 0.23571777f, 0.49774170f, 0.30050659f, 0.01199341f, -0.02069092f, -0.00024414f, + -0.01477051f, -0.01071167f, 0.23291016f, 0.49731445f, 0.30331421f, 0.01318359f, + -0.02093506f, -0.00027466f, -0.01452637f, -0.01141357f, 0.23010254f, 0.49685669f, + 0.30609131f, 0.01437378f, -0.02117920f, -0.00030518f, -0.01428223f, -0.01211548f, + 0.22732544f, 0.49636841f, 0.30886841f, 0.01559448f, -0.02142334f, -0.00033569f, + -0.01403809f, -0.01278687f, 0.22451782f, 0.49581909f, 0.31164551f, 0.01684570f, + -0.02163696f, -0.00039673f, -0.01379395f, -0.01345825f, 0.22174072f, 0.49526978f, + 0.31442261f, 0.01809692f, -0.02188110f, -0.00042725f, -0.01358032f, -0.01409912f, + 0.21896362f, 0.49465942f, 0.31719971f, 0.01937866f, -0.02209473f, -0.00045776f, + -0.01333618f, -0.01473999f, 0.21618652f, 0.49404907f, 0.31994629f, 0.02069092f, + -0.02233887f, -0.00048828f, -0.01309204f, -0.01535034f, 0.21343994f, 0.49337769f, + 0.32269287f, 0.02203369f, -0.02255249f, -0.00054932f, -0.01284790f, -0.01596069f, + 0.21066284f, 0.49267578f, 0.32543945f, 0.02337646f, -0.02279663f, -0.00057983f, + -0.01263428f, -0.01654053f, 0.20791626f, 0.49194336f, 0.32818604f, 0.02471924f, + -0.02301025f, -0.00064087f, -0.01239014f, -0.01708984f, 0.20516968f, 0.49118042f, + 0.33090210f, 0.02612305f, -0.02322388f, -0.00067139f, -0.01214600f, -0.01763916f, + 0.20242310f, 0.49035645f, 0.33361816f, 0.02752686f, -0.02343750f, -0.00073242f, + -0.01193237f, -0.01818848f, 0.19970703f, 0.48953247f, 0.33633423f, 0.02896118f, + -0.02365112f, -0.00079346f, -0.01168823f, -0.01867676f, 0.19696045f, 0.48864746f, + 0.33901978f, 0.03039551f, -0.02386475f, -0.00082397f, -0.01147461f, -0.01919556f, + 0.19427490f, 0.48776245f, 0.34170532f, 0.03186035f, -0.02407837f, -0.00088501f, + -0.01123047f, -0.01968384f, 0.19155884f, 0.48681641f, 0.34439087f, 0.03335571f, + -0.02429199f, -0.00094604f, -0.01101685f, -0.02014160f, 0.18887329f, 0.48583984f, + 0.34704590f, 0.03485107f, -0.02447510f, -0.00100708f, -0.01080322f, -0.02059937f, + 0.18615723f, 0.48483276f, 0.34970093f, 0.03637695f, -0.02468872f, -0.00106812f, + -0.01058960f, -0.02102661f, 0.18350220f, 0.48379517f, 0.35235596f, 0.03793335f, + -0.02487183f, -0.00112915f, -0.01034546f, -0.02145386f, 0.18081665f, 0.48272705f, + 0.35498047f, 0.03948975f, -0.02505493f, -0.00119019f, -0.01013184f, -0.02188110f, + 0.17816162f, 0.48162842f, 0.35760498f, 0.04107666f, -0.02523804f, -0.00125122f, + -0.00991821f, -0.02227783f, 0.17550659f, 0.48049927f, 0.36019897f, 0.04269409f, + -0.02542114f, -0.00131226f, -0.00970459f, -0.02264404f, 0.17288208f, 0.47933960f, + 0.36279297f, 0.04431152f, -0.02560425f, -0.00140381f, -0.00952148f, -0.02301025f, + 0.17025757f, 0.47814941f, 0.36538696f, 0.04595947f, -0.02578735f, -0.00146484f, + -0.00930786f, -0.02337646f, 0.16763306f, 0.47689819f, 0.36795044f, 0.04763794f, + -0.02593994f, -0.00152588f, -0.00909424f, -0.02371216f, 0.16503906f, 0.47564697f, + 0.37048340f, 0.04931641f, -0.02609253f, -0.00161743f, -0.00888062f, -0.02401733f, + 0.16244507f, 0.47436523f, 0.37304688f, 0.05102539f, -0.02627563f, -0.00170898f, + -0.00869751f, -0.02435303f, 0.15988159f, 0.47302246f, 0.37554932f, 0.05276489f, + -0.02642822f, -0.00177002f, -0.00848389f, -0.02462769f, 0.15731812f, 0.47167969f, + 0.37805176f, 0.05450439f, -0.02658081f, -0.00186157f, -0.00830078f, -0.02493286f, + 0.15475464f, 0.47027588f, 0.38055420f, 0.05627441f, -0.02670288f, -0.00195312f, + -0.00808716f, -0.02520752f, 0.15222168f, 0.46887207f, 0.38302612f, 0.05804443f, + -0.02685547f, -0.00204468f, -0.00790405f, -0.02545166f, 0.14968872f, 0.46743774f, + 0.38546753f, 0.05987549f, -0.02697754f, -0.00213623f, -0.00772095f, -0.02569580f, + 0.14718628f, 0.46594238f, 0.38790894f, 0.06170654f, -0.02709961f, -0.00222778f, + -0.00753784f, -0.02593994f, 0.14468384f, 0.46444702f, 0.39031982f, 0.06353760f, + -0.02722168f, -0.00231934f, -0.00735474f, -0.02615356f, 0.14218140f, 0.46289062f, + 0.39273071f, 0.06539917f, -0.02734375f, -0.00241089f, -0.00717163f, -0.02636719f, + 0.13970947f, 0.46133423f, 0.39511108f, 0.06729126f, -0.02743530f, -0.00250244f, + -0.00698853f, -0.02655029f, 0.13726807f, 0.45974731f, 0.39749146f, 0.06918335f, + -0.02755737f, -0.00259399f, -0.00680542f, -0.02673340f, 0.13479614f, 0.45812988f, + 0.39984131f, 0.07113647f, -0.02764893f, -0.00271606f, -0.00662231f, -0.02691650f, + 0.13238525f, 0.45648193f, 0.40216064f, 0.07305908f, -0.02774048f, -0.00280762f, + -0.00643921f, -0.02706909f, 0.12997437f, 0.45480347f, 0.40447998f, 0.07504272f, + -0.02780151f, -0.00292969f, -0.00628662f, -0.02722168f, 0.12756348f, 0.45309448f, + 0.40676880f, 0.07699585f, -0.02789307f, -0.00305176f, -0.00610352f, -0.02734375f, + 0.12518311f, 0.45135498f, 0.40902710f, 0.07901001f, -0.02795410f, -0.00314331f, + -0.00595093f, -0.02746582f, 0.12280273f, 0.44958496f, 0.41128540f, 0.08102417f, + -0.02801514f, -0.00326538f, -0.00579834f, -0.02758789f, 0.12045288f, 0.44778442f, + 0.41351318f, 0.08306885f, -0.02804565f, -0.00338745f, -0.00561523f, -0.02770996f, + 0.11813354f, 0.44598389f, 0.41571045f, 0.08511353f, -0.02810669f, -0.00350952f, + -0.00546265f, -0.02780151f, 0.11581421f, 0.44412231f, 0.41787720f, 0.08718872f, + -0.02813721f, -0.00363159f, -0.00531006f, -0.02786255f, 0.11349487f, 0.44226074f, + 0.42004395f, 0.08929443f, -0.02816772f, -0.00375366f, -0.00515747f, -0.02795410f, + 0.11120605f, 0.44036865f, 0.42218018f, 0.09140015f, -0.02816772f, -0.00387573f, + -0.00500488f, -0.02801514f, 0.10894775f, 0.43844604f, 0.42431641f, 0.09353638f, + -0.02819824f, -0.00402832f, -0.00485229f, -0.02807617f, 0.10668945f, 0.43649292f, + 0.42639160f, 0.09570312f, -0.02819824f, -0.00415039f, -0.00469971f, -0.02810669f, + 0.10446167f, 0.43453979f, 0.42846680f, 0.09786987f, -0.02819824f, -0.00427246f, + -0.00457764f, -0.02813721f, 0.10223389f, 0.43252563f, 0.43051147f, 0.10003662f, + -0.02816772f, -0.00442505f, -0.00442505f, -0.02816772f, 0.10003662f, 0.43051147f, + 0.43252563f, 0.10223389f, -0.02813721f, -0.00457764f, -0.00427246f, -0.02819824f, + 0.09786987f, 0.42846680f, 0.43453979f, 0.10446167f, -0.02810669f, -0.00469971f, + -0.00415039f, -0.02819824f, 0.09570312f, 0.42639160f, 0.43649292f, 0.10668945f, + -0.02807617f, -0.00485229f, -0.00402832f, -0.02819824f, 0.09353638f, 0.42431641f, + 0.43844604f, 0.10894775f, -0.02801514f, -0.00500488f, -0.00387573f, -0.02816772f, + 0.09140015f, 0.42218018f, 0.44036865f, 0.11120605f, -0.02795410f, -0.00515747f, + -0.00375366f, -0.02816772f, 0.08929443f, 0.42004395f, 0.44226074f, 0.11349487f, + -0.02786255f, -0.00531006f, -0.00363159f, -0.02813721f, 0.08718872f, 0.41787720f, + 0.44412231f, 0.11581421f, -0.02780151f, -0.00546265f, -0.00350952f, -0.02810669f, + 0.08511353f, 0.41571045f, 0.44598389f, 0.11813354f, -0.02770996f, -0.00561523f, + -0.00338745f, -0.02804565f, 0.08306885f, 0.41351318f, 0.44778442f, 0.12045288f, + -0.02758789f, -0.00579834f, -0.00326538f, -0.02801514f, 0.08102417f, 0.41128540f, + 0.44958496f, 0.12280273f, -0.02746582f, -0.00595093f, -0.00314331f, -0.02795410f, + 0.07901001f, 0.40902710f, 0.45135498f, 0.12518311f, -0.02734375f, -0.00610352f, + -0.00305176f, -0.02789307f, 0.07699585f, 0.40676880f, 0.45309448f, 0.12756348f, + -0.02722168f, -0.00628662f, -0.00292969f, -0.02780151f, 0.07504272f, 0.40447998f, + 0.45480347f, 0.12997437f, -0.02706909f, -0.00643921f, -0.00280762f, -0.02774048f, + 0.07305908f, 0.40216064f, 0.45648193f, 0.13238525f, -0.02691650f, -0.00662231f, + -0.00271606f, -0.02764893f, 0.07113647f, 0.39984131f, 0.45812988f, 0.13479614f, + -0.02673340f, -0.00680542f, -0.00259399f, -0.02755737f, 0.06918335f, 0.39749146f, + 0.45974731f, 0.13726807f, -0.02655029f, -0.00698853f, -0.00250244f, -0.02743530f, + 0.06729126f, 0.39511108f, 0.46133423f, 0.13970947f, -0.02636719f, -0.00717163f, + -0.00241089f, -0.02734375f, 0.06539917f, 0.39273071f, 0.46289062f, 0.14218140f, + -0.02615356f, -0.00735474f, -0.00231934f, -0.02722168f, 0.06353760f, 0.39031982f, + 0.46444702f, 0.14468384f, -0.02593994f, -0.00753784f, -0.00222778f, -0.02709961f, + 0.06170654f, 0.38790894f, 0.46594238f, 0.14718628f, -0.02569580f, -0.00772095f, + -0.00213623f, -0.02697754f, 0.05987549f, 0.38546753f, 0.46743774f, 0.14968872f, + -0.02545166f, -0.00790405f, -0.00204468f, -0.02685547f, 0.05804443f, 0.38302612f, + 0.46887207f, 0.15222168f, -0.02520752f, -0.00808716f, -0.00195312f, -0.02670288f, + 0.05627441f, 0.38055420f, 0.47027588f, 0.15475464f, -0.02493286f, -0.00830078f, + -0.00186157f, -0.02658081f, 0.05450439f, 0.37805176f, 0.47167969f, 0.15731812f, + -0.02462769f, -0.00848389f, -0.00177002f, -0.02642822f, 0.05276489f, 0.37554932f, + 0.47302246f, 0.15988159f, -0.02435303f, -0.00869751f, -0.00170898f, -0.02627563f, + 0.05102539f, 0.37304688f, 0.47436523f, 0.16244507f, -0.02401733f, -0.00888062f, + -0.00161743f, -0.02609253f, 0.04931641f, 0.37048340f, 0.47564697f, 0.16503906f, + -0.02371216f, -0.00909424f, -0.00152588f, -0.02593994f, 0.04763794f, 0.36795044f, + 0.47689819f, 0.16763306f, -0.02337646f, -0.00930786f, -0.00146484f, -0.02578735f, + 0.04595947f, 0.36538696f, 0.47814941f, 0.17025757f, -0.02301025f, -0.00952148f, + -0.00140381f, -0.02560425f, 0.04431152f, 0.36279297f, 0.47933960f, 0.17288208f, + -0.02264404f, -0.00970459f, -0.00131226f, -0.02542114f, 0.04269409f, 0.36019897f, + 0.48049927f, 0.17550659f, -0.02227783f, -0.00991821f, -0.00125122f, -0.02523804f, + 0.04107666f, 0.35760498f, 0.48162842f, 0.17816162f, -0.02188110f, -0.01013184f, + -0.00119019f, -0.02505493f, 0.03948975f, 0.35498047f, 0.48272705f, 0.18081665f, + -0.02145386f, -0.01034546f, -0.00112915f, -0.02487183f, 0.03793335f, 0.35235596f, + 0.48379517f, 0.18350220f, -0.02102661f, -0.01058960f, -0.00106812f, -0.02468872f, + 0.03637695f, 0.34970093f, 0.48483276f, 0.18615723f, -0.02059937f, -0.01080322f, + -0.00100708f, -0.02447510f, 0.03485107f, 0.34704590f, 0.48583984f, 0.18887329f, + -0.02014160f, -0.01101685f, -0.00094604f, -0.02429199f, 0.03335571f, 0.34439087f, + 0.48681641f, 0.19155884f, -0.01968384f, -0.01123047f, -0.00088501f, -0.02407837f, + 0.03186035f, 0.34170532f, 0.48776245f, 0.19427490f, -0.01919556f, -0.01147461f, + -0.00082397f, -0.02386475f, 0.03039551f, 0.33901978f, 0.48864746f, 0.19696045f, + -0.01867676f, -0.01168823f, -0.00079346f, -0.02365112f, 0.02896118f, 0.33633423f, + 0.48953247f, 0.19970703f, -0.01818848f, -0.01193237f, -0.00073242f, -0.02343750f, + 0.02752686f, 0.33361816f, 0.49035645f, 0.20242310f, -0.01763916f, -0.01214600f, + -0.00067139f, -0.02322388f, 0.02612305f, 0.33090210f, 0.49118042f, 0.20516968f, + -0.01708984f, -0.01239014f, -0.00064087f, -0.02301025f, 0.02471924f, 0.32818604f, + 0.49194336f, 0.20791626f, -0.01654053f, -0.01263428f, -0.00057983f, -0.02279663f, + 0.02337646f, 0.32543945f, 0.49267578f, 0.21066284f, -0.01596069f, -0.01284790f, + -0.00054932f, -0.02255249f, 0.02203369f, 0.32269287f, 0.49337769f, 0.21343994f, + -0.01535034f, -0.01309204f, -0.00048828f, -0.02233887f, 0.02069092f, 0.31994629f, + 0.49404907f, 0.21618652f, -0.01473999f, -0.01333618f, -0.00045776f, -0.02209473f, + 0.01937866f, 0.31719971f, 0.49465942f, 0.21896362f, -0.01409912f, -0.01358032f, + -0.00042725f, -0.02188110f, 0.01809692f, 0.31442261f, 0.49526978f, 0.22174072f, + -0.01345825f, -0.01379395f, -0.00039673f, -0.02163696f, 0.01684570f, 0.31164551f, + 0.49581909f, 0.22451782f, -0.01278687f, -0.01403809f, -0.00033569f, -0.02142334f, + 0.01559448f, 0.30886841f, 0.49636841f, 0.22732544f, -0.01211548f, -0.01428223f, + -0.00030518f, -0.02117920f, 0.01437378f, 0.30609131f, 0.49685669f, 0.23010254f, + -0.01141357f, -0.01452637f, -0.00027466f, -0.02093506f, 0.01318359f, 0.30331421f, + 0.49731445f, 0.23291016f, -0.01071167f, -0.01477051f, -0.00024414f, -0.02069092f, + 0.01199341f, 0.30050659f, 0.49774170f, 0.23571777f, -0.00997925f, -0.01501465f, + -0.00021362f, -0.02044678f, 0.01083374f, 0.29772949f, 0.49810791f, 0.23852539f, + -0.00921631f, -0.01525879f, -0.00018311f, -0.02020264f, 0.00967407f, 0.29492188f, + 0.49847412f, 0.24133301f, -0.00845337f, -0.01550293f, -0.00015259f, -0.01995850f, + 0.00854492f, 0.29211426f, 0.49877930f, 0.24414062f, -0.00765991f, -0.01574707f, + -0.00015259f, -0.01971436f, 0.00744629f, 0.28930664f, 0.49908447f, 0.24697876f, + -0.00686646f, -0.01602173f, -0.00012207f, -0.01947021f, 0.00634766f, 0.28646851f, + 0.49932861f, 0.24978638f, -0.00604248f, -0.01626587f, -0.00009155f, -0.01922607f, + 0.00527954f, 0.28366089f, 0.49954224f, 0.25259399f, -0.00518799f, -0.01651001f, + -0.00006104f, -0.01898193f, 0.00424194f, 0.28085327f, 0.49972534f, 0.25543213f, + -0.00433350f, -0.01675415f, -0.00006104f, -0.01873779f, 0.00320435f, 0.27801514f, + 0.49984741f, 0.25823975f, -0.00344849f, -0.01699829f, -0.00003052f, -0.01849365f, + 0.00219727f, 0.27520752f, 0.49996948f, 0.26107788f, -0.00256348f, -0.01724243f, + -0.00003052f, -0.01824951f, 0.00122070f, 0.27236938f, 0.50003052f, 0.26388550f, + -0.00164795f, -0.01748657f, 0.00000000f, -0.01800537f, 0.00024414f, 0.26956177f, + 0.50006104f, 0.26672363f, -0.00070190f, -0.01776123f, + }; + + static constexpr std::array<f32, 1024> lut1 = { + 0.01275635f, -0.07745361f, 0.18670654f, 0.75119019f, 0.19219971f, -0.07821655f, + 0.01272583f, 0.00000000f, 0.01281738f, -0.07666016f, 0.18124390f, 0.75106812f, + 0.19772339f, -0.07897949f, 0.01266479f, 0.00003052f, 0.01284790f, -0.07583618f, + 0.17581177f, 0.75088501f, 0.20330811f, -0.07971191f, 0.01257324f, 0.00006104f, + 0.01287842f, -0.07501221f, 0.17044067f, 0.75057983f, 0.20892334f, -0.08041382f, + 0.01248169f, 0.00009155f, 0.01290894f, -0.07415771f, 0.16510010f, 0.75018311f, + 0.21453857f, -0.08111572f, 0.01239014f, 0.00012207f, 0.01290894f, -0.07330322f, + 0.15979004f, 0.74966431f, 0.22021484f, -0.08178711f, 0.01229858f, 0.00015259f, + 0.01290894f, -0.07241821f, 0.15454102f, 0.74908447f, 0.22592163f, -0.08242798f, + 0.01217651f, 0.00018311f, 0.01290894f, -0.07150269f, 0.14932251f, 0.74838257f, + 0.23165894f, -0.08303833f, 0.01205444f, 0.00021362f, 0.01290894f, -0.07058716f, + 0.14416504f, 0.74755859f, 0.23742676f, -0.08364868f, 0.01193237f, 0.00024414f, + 0.01287842f, -0.06967163f, 0.13903809f, 0.74667358f, 0.24322510f, -0.08419800f, + 0.01177979f, 0.00027466f, 0.01284790f, -0.06872559f, 0.13397217f, 0.74566650f, + 0.24905396f, -0.08474731f, 0.01162720f, 0.00033569f, 0.01281738f, -0.06777954f, + 0.12893677f, 0.74456787f, 0.25491333f, -0.08526611f, 0.01147461f, 0.00036621f, + 0.01278687f, -0.06683350f, 0.12396240f, 0.74337769f, 0.26077271f, -0.08575439f, + 0.01129150f, 0.00042725f, 0.01275635f, -0.06585693f, 0.11901855f, 0.74206543f, + 0.26669312f, -0.08621216f, 0.01110840f, 0.00045776f, 0.01269531f, -0.06488037f, + 0.11413574f, 0.74069214f, 0.27261353f, -0.08663940f, 0.01092529f, 0.00051880f, + 0.01263428f, -0.06387329f, 0.10931396f, 0.73919678f, 0.27853394f, -0.08700562f, + 0.01071167f, 0.00057983f, 0.01257324f, -0.06286621f, 0.10452271f, 0.73760986f, + 0.28451538f, -0.08737183f, 0.01049805f, 0.00064087f, 0.01251221f, -0.06185913f, + 0.09979248f, 0.73593140f, 0.29049683f, -0.08770752f, 0.01025391f, 0.00067139f, + 0.01242065f, -0.06082153f, 0.09512329f, 0.73413086f, 0.29647827f, -0.08801270f, + 0.01000977f, 0.00073242f, 0.01232910f, -0.05981445f, 0.09051514f, 0.73226929f, + 0.30249023f, -0.08828735f, 0.00973511f, 0.00079346f, 0.01226807f, -0.05877686f, + 0.08593750f, 0.73028564f, 0.30853271f, -0.08850098f, 0.00949097f, 0.00088501f, + 0.01214600f, -0.05773926f, 0.08142090f, 0.72824097f, 0.31457520f, -0.08871460f, + 0.00918579f, 0.00094604f, 0.01205444f, -0.05670166f, 0.07696533f, 0.72607422f, + 0.32061768f, -0.08886719f, 0.00891113f, 0.00100708f, 0.01196289f, -0.05563354f, + 0.07257080f, 0.72381592f, 0.32669067f, -0.08898926f, 0.00860596f, 0.00106812f, + 0.01187134f, -0.05459595f, 0.06820679f, 0.72146606f, 0.33276367f, -0.08908081f, + 0.00827026f, 0.00115967f, 0.01174927f, -0.05352783f, 0.06393433f, 0.71902466f, + 0.33883667f, -0.08911133f, 0.00796509f, 0.00122070f, 0.01162720f, -0.05245972f, + 0.05969238f, 0.71649170f, 0.34494019f, -0.08914185f, 0.00759888f, 0.00131226f, + 0.01150513f, -0.05139160f, 0.05551147f, 0.71389771f, 0.35101318f, -0.08911133f, + 0.00726318f, 0.00137329f, 0.01138306f, -0.05032349f, 0.05139160f, 0.71118164f, + 0.35711670f, -0.08901978f, 0.00686646f, 0.00146484f, 0.01126099f, -0.04928589f, + 0.04733276f, 0.70837402f, 0.36322021f, -0.08892822f, 0.00650024f, 0.00155640f, + 0.01113892f, -0.04821777f, 0.04333496f, 0.70550537f, 0.36932373f, -0.08877563f, + 0.00610352f, 0.00164795f, 0.01101685f, -0.04714966f, 0.03939819f, 0.70251465f, + 0.37542725f, -0.08856201f, 0.00567627f, 0.00173950f, 0.01086426f, -0.04608154f, + 0.03549194f, 0.69946289f, 0.38153076f, -0.08834839f, 0.00527954f, 0.00183105f, + 0.01074219f, -0.04501343f, 0.03167725f, 0.69631958f, 0.38763428f, -0.08804321f, + 0.00482178f, 0.00192261f, 0.01058960f, -0.04394531f, 0.02792358f, 0.69308472f, + 0.39370728f, -0.08773804f, 0.00436401f, 0.00201416f, 0.01043701f, -0.04287720f, + 0.02420044f, 0.68975830f, 0.39981079f, -0.08737183f, 0.00390625f, 0.00210571f, + 0.01031494f, -0.04180908f, 0.02056885f, 0.68637085f, 0.40588379f, -0.08694458f, + 0.00344849f, 0.00222778f, 0.01016235f, -0.04074097f, 0.01699829f, 0.68289185f, + 0.41195679f, -0.08648682f, 0.00296021f, 0.00231934f, 0.01000977f, -0.03970337f, + 0.01345825f, 0.67932129f, 0.41802979f, -0.08596802f, 0.00244141f, 0.00244141f, + 0.00985718f, -0.03863525f, 0.01000977f, 0.67568970f, 0.42407227f, -0.08541870f, + 0.00192261f, 0.00253296f, 0.00970459f, -0.03759766f, 0.00662231f, 0.67196655f, + 0.43011475f, -0.08480835f, 0.00140381f, 0.00265503f, 0.00955200f, -0.03652954f, + 0.00326538f, 0.66815186f, 0.43612671f, -0.08416748f, 0.00085449f, 0.00277710f, + 0.00936890f, -0.03549194f, 0.00000000f, 0.66427612f, 0.44213867f, -0.08346558f, + 0.00027466f, 0.00289917f, 0.00921631f, -0.03445435f, -0.00320435f, 0.66030884f, + 0.44812012f, -0.08270264f, -0.00027466f, 0.00299072f, 0.00906372f, -0.03344727f, + -0.00634766f, 0.65631104f, 0.45407104f, -0.08190918f, -0.00088501f, 0.00311279f, + 0.00891113f, -0.03240967f, -0.00946045f, 0.65219116f, 0.46002197f, -0.08105469f, + -0.00146484f, 0.00323486f, 0.00872803f, -0.03140259f, -0.01248169f, 0.64801025f, + 0.46594238f, -0.08013916f, -0.00210571f, 0.00338745f, 0.00857544f, -0.03039551f, + -0.01544189f, 0.64376831f, 0.47183228f, -0.07919312f, -0.00271606f, 0.00350952f, + 0.00842285f, -0.02938843f, -0.01834106f, 0.63946533f, 0.47772217f, -0.07818604f, + -0.00335693f, 0.00363159f, 0.00823975f, -0.02838135f, -0.02117920f, 0.63507080f, + 0.48358154f, -0.07711792f, -0.00402832f, 0.00375366f, 0.00808716f, -0.02740479f, + -0.02395630f, 0.63061523f, 0.48937988f, -0.07598877f, -0.00469971f, 0.00390625f, + 0.00793457f, -0.02642822f, -0.02667236f, 0.62609863f, 0.49517822f, -0.07482910f, + -0.00537109f, 0.00402832f, 0.00775146f, -0.02545166f, -0.02932739f, 0.62152100f, + 0.50094604f, -0.07357788f, -0.00607300f, 0.00418091f, 0.00759888f, -0.02450562f, + -0.03192139f, 0.61685181f, 0.50665283f, -0.07229614f, -0.00677490f, 0.00430298f, + 0.00741577f, -0.02352905f, -0.03445435f, 0.61215210f, 0.51235962f, -0.07098389f, + -0.00750732f, 0.00445557f, 0.00726318f, -0.02258301f, -0.03689575f, 0.60736084f, + 0.51800537f, -0.06958008f, -0.00823975f, 0.00460815f, 0.00711060f, -0.02166748f, + -0.03930664f, 0.60253906f, 0.52362061f, -0.06811523f, -0.00897217f, 0.00476074f, + 0.00692749f, -0.02075195f, -0.04165649f, 0.59762573f, 0.52920532f, -0.06661987f, + -0.00973511f, 0.00488281f, 0.00677490f, -0.01983643f, -0.04394531f, 0.59268188f, + 0.53475952f, -0.06506348f, -0.01052856f, 0.00503540f, 0.00662231f, -0.01892090f, + -0.04617310f, 0.58767700f, 0.54025269f, -0.06344604f, -0.01129150f, 0.00518799f, + 0.00643921f, -0.01803589f, -0.04830933f, 0.58261108f, 0.54571533f, -0.06173706f, + -0.01208496f, 0.00534058f, 0.00628662f, -0.01715088f, -0.05041504f, 0.57748413f, + 0.55111694f, -0.05999756f, -0.01290894f, 0.00549316f, 0.00613403f, -0.01626587f, + -0.05245972f, 0.57232666f, 0.55648804f, -0.05819702f, -0.01373291f, 0.00564575f, + 0.00598145f, -0.01541138f, -0.05444336f, 0.56707764f, 0.56182861f, -0.05636597f, + -0.01455688f, 0.00582886f, 0.00582886f, -0.01455688f, -0.05636597f, 0.56182861f, + 0.56707764f, -0.05444336f, -0.01541138f, 0.00598145f, 0.00564575f, -0.01373291f, + -0.05819702f, 0.55648804f, 0.57232666f, -0.05245972f, -0.01626587f, 0.00613403f, + 0.00549316f, -0.01290894f, -0.05999756f, 0.55111694f, 0.57748413f, -0.05041504f, + -0.01715088f, 0.00628662f, 0.00534058f, -0.01208496f, -0.06173706f, 0.54571533f, + 0.58261108f, -0.04830933f, -0.01803589f, 0.00643921f, 0.00518799f, -0.01129150f, + -0.06344604f, 0.54025269f, 0.58767700f, -0.04617310f, -0.01892090f, 0.00662231f, + 0.00503540f, -0.01052856f, -0.06506348f, 0.53475952f, 0.59268188f, -0.04394531f, + -0.01983643f, 0.00677490f, 0.00488281f, -0.00973511f, -0.06661987f, 0.52920532f, + 0.59762573f, -0.04165649f, -0.02075195f, 0.00692749f, 0.00476074f, -0.00897217f, + -0.06811523f, 0.52362061f, 0.60253906f, -0.03930664f, -0.02166748f, 0.00711060f, + 0.00460815f, -0.00823975f, -0.06958008f, 0.51800537f, 0.60736084f, -0.03689575f, + -0.02258301f, 0.00726318f, 0.00445557f, -0.00750732f, -0.07098389f, 0.51235962f, + 0.61215210f, -0.03445435f, -0.02352905f, 0.00741577f, 0.00430298f, -0.00677490f, + -0.07229614f, 0.50665283f, 0.61685181f, -0.03192139f, -0.02450562f, 0.00759888f, + 0.00418091f, -0.00607300f, -0.07357788f, 0.50094604f, 0.62152100f, -0.02932739f, + -0.02545166f, 0.00775146f, 0.00402832f, -0.00537109f, -0.07482910f, 0.49517822f, + 0.62609863f, -0.02667236f, -0.02642822f, 0.00793457f, 0.00390625f, -0.00469971f, + -0.07598877f, 0.48937988f, 0.63061523f, -0.02395630f, -0.02740479f, 0.00808716f, + 0.00375366f, -0.00402832f, -0.07711792f, 0.48358154f, 0.63507080f, -0.02117920f, + -0.02838135f, 0.00823975f, 0.00363159f, -0.00335693f, -0.07818604f, 0.47772217f, + 0.63946533f, -0.01834106f, -0.02938843f, 0.00842285f, 0.00350952f, -0.00271606f, + -0.07919312f, 0.47183228f, 0.64376831f, -0.01544189f, -0.03039551f, 0.00857544f, + 0.00338745f, -0.00210571f, -0.08013916f, 0.46594238f, 0.64801025f, -0.01248169f, + -0.03140259f, 0.00872803f, 0.00323486f, -0.00146484f, -0.08105469f, 0.46002197f, + 0.65219116f, -0.00946045f, -0.03240967f, 0.00891113f, 0.00311279f, -0.00088501f, + -0.08190918f, 0.45407104f, 0.65631104f, -0.00634766f, -0.03344727f, 0.00906372f, + 0.00299072f, -0.00027466f, -0.08270264f, 0.44812012f, 0.66030884f, -0.00320435f, + -0.03445435f, 0.00921631f, 0.00289917f, 0.00027466f, -0.08346558f, 0.44213867f, + 0.66427612f, 0.00000000f, -0.03549194f, 0.00936890f, 0.00277710f, 0.00085449f, + -0.08416748f, 0.43612671f, 0.66815186f, 0.00326538f, -0.03652954f, 0.00955200f, + 0.00265503f, 0.00140381f, -0.08480835f, 0.43011475f, 0.67196655f, 0.00662231f, + -0.03759766f, 0.00970459f, 0.00253296f, 0.00192261f, -0.08541870f, 0.42407227f, + 0.67568970f, 0.01000977f, -0.03863525f, 0.00985718f, 0.00244141f, 0.00244141f, + -0.08596802f, 0.41802979f, 0.67932129f, 0.01345825f, -0.03970337f, 0.01000977f, + 0.00231934f, 0.00296021f, -0.08648682f, 0.41195679f, 0.68289185f, 0.01699829f, + -0.04074097f, 0.01016235f, 0.00222778f, 0.00344849f, -0.08694458f, 0.40588379f, + 0.68637085f, 0.02056885f, -0.04180908f, 0.01031494f, 0.00210571f, 0.00390625f, + -0.08737183f, 0.39981079f, 0.68975830f, 0.02420044f, -0.04287720f, 0.01043701f, + 0.00201416f, 0.00436401f, -0.08773804f, 0.39370728f, 0.69308472f, 0.02792358f, + -0.04394531f, 0.01058960f, 0.00192261f, 0.00482178f, -0.08804321f, 0.38763428f, + 0.69631958f, 0.03167725f, -0.04501343f, 0.01074219f, 0.00183105f, 0.00527954f, + -0.08834839f, 0.38153076f, 0.69946289f, 0.03549194f, -0.04608154f, 0.01086426f, + 0.00173950f, 0.00567627f, -0.08856201f, 0.37542725f, 0.70251465f, 0.03939819f, + -0.04714966f, 0.01101685f, 0.00164795f, 0.00610352f, -0.08877563f, 0.36932373f, + 0.70550537f, 0.04333496f, -0.04821777f, 0.01113892f, 0.00155640f, 0.00650024f, + -0.08892822f, 0.36322021f, 0.70837402f, 0.04733276f, -0.04928589f, 0.01126099f, + 0.00146484f, 0.00686646f, -0.08901978f, 0.35711670f, 0.71118164f, 0.05139160f, + -0.05032349f, 0.01138306f, 0.00137329f, 0.00726318f, -0.08911133f, 0.35101318f, + 0.71389771f, 0.05551147f, -0.05139160f, 0.01150513f, 0.00131226f, 0.00759888f, + -0.08914185f, 0.34494019f, 0.71649170f, 0.05969238f, -0.05245972f, 0.01162720f, + 0.00122070f, 0.00796509f, -0.08911133f, 0.33883667f, 0.71902466f, 0.06393433f, + -0.05352783f, 0.01174927f, 0.00115967f, 0.00827026f, -0.08908081f, 0.33276367f, + 0.72146606f, 0.06820679f, -0.05459595f, 0.01187134f, 0.00106812f, 0.00860596f, + -0.08898926f, 0.32669067f, 0.72381592f, 0.07257080f, -0.05563354f, 0.01196289f, + 0.00100708f, 0.00891113f, -0.08886719f, 0.32061768f, 0.72607422f, 0.07696533f, + -0.05670166f, 0.01205444f, 0.00094604f, 0.00918579f, -0.08871460f, 0.31457520f, + 0.72824097f, 0.08142090f, -0.05773926f, 0.01214600f, 0.00088501f, 0.00949097f, + -0.08850098f, 0.30853271f, 0.73028564f, 0.08593750f, -0.05877686f, 0.01226807f, + 0.00079346f, 0.00973511f, -0.08828735f, 0.30249023f, 0.73226929f, 0.09051514f, + -0.05981445f, 0.01232910f, 0.00073242f, 0.01000977f, -0.08801270f, 0.29647827f, + 0.73413086f, 0.09512329f, -0.06082153f, 0.01242065f, 0.00067139f, 0.01025391f, + -0.08770752f, 0.29049683f, 0.73593140f, 0.09979248f, -0.06185913f, 0.01251221f, + 0.00064087f, 0.01049805f, -0.08737183f, 0.28451538f, 0.73760986f, 0.10452271f, + -0.06286621f, 0.01257324f, 0.00057983f, 0.01071167f, -0.08700562f, 0.27853394f, + 0.73919678f, 0.10931396f, -0.06387329f, 0.01263428f, 0.00051880f, 0.01092529f, + -0.08663940f, 0.27261353f, 0.74069214f, 0.11413574f, -0.06488037f, 0.01269531f, + 0.00045776f, 0.01110840f, -0.08621216f, 0.26669312f, 0.74206543f, 0.11901855f, + -0.06585693f, 0.01275635f, 0.00042725f, 0.01129150f, -0.08575439f, 0.26077271f, + 0.74337769f, 0.12396240f, -0.06683350f, 0.01278687f, 0.00036621f, 0.01147461f, + -0.08526611f, 0.25491333f, 0.74456787f, 0.12893677f, -0.06777954f, 0.01281738f, + 0.00033569f, 0.01162720f, -0.08474731f, 0.24905396f, 0.74566650f, 0.13397217f, + -0.06872559f, 0.01284790f, 0.00027466f, 0.01177979f, -0.08419800f, 0.24322510f, + 0.74667358f, 0.13903809f, -0.06967163f, 0.01287842f, 0.00024414f, 0.01193237f, + -0.08364868f, 0.23742676f, 0.74755859f, 0.14416504f, -0.07058716f, 0.01290894f, + 0.00021362f, 0.01205444f, -0.08303833f, 0.23165894f, 0.74838257f, 0.14932251f, + -0.07150269f, 0.01290894f, 0.00018311f, 0.01217651f, -0.08242798f, 0.22592163f, + 0.74908447f, 0.15454102f, -0.07241821f, 0.01290894f, 0.00015259f, 0.01229858f, + -0.08178711f, 0.22021484f, 0.74966431f, 0.15979004f, -0.07330322f, 0.01290894f, + 0.00012207f, 0.01239014f, -0.08111572f, 0.21453857f, 0.75018311f, 0.16510010f, + -0.07415771f, 0.01290894f, 0.00009155f, 0.01248169f, -0.08041382f, 0.20892334f, + 0.75057983f, 0.17044067f, -0.07501221f, 0.01287842f, 0.00006104f, 0.01257324f, + -0.07971191f, 0.20330811f, 0.75088501f, 0.17581177f, -0.07583618f, 0.01284790f, + 0.00003052f, 0.01266479f, -0.07897949f, 0.19772339f, 0.75106812f, 0.18124390f, + -0.07666016f, 0.01281738f, 0.00000000f, 0.01272583f, -0.07821655f, 0.19219971f, + 0.75119019f, 0.18670654f, -0.07745361f, 0.01275635f, + }; + + static constexpr std::array<f32, 1024> lut2 = { + -0.00036621f, 0.00143433f, -0.00408936f, 0.99996948f, 0.00247192f, -0.00048828f, + 0.00006104f, 0.00000000f, -0.00079346f, 0.00329590f, -0.01052856f, 0.99975586f, + 0.00918579f, -0.00241089f, 0.00051880f, -0.00003052f, -0.00122070f, 0.00512695f, + -0.01684570f, 0.99929810f, 0.01605225f, -0.00439453f, 0.00097656f, -0.00006104f, + -0.00161743f, 0.00689697f, -0.02297974f, 0.99862671f, 0.02304077f, -0.00640869f, + 0.00143433f, -0.00009155f, -0.00201416f, 0.00866699f, -0.02899170f, 0.99774170f, + 0.03018188f, -0.00845337f, 0.00192261f, -0.00015259f, -0.00238037f, 0.01037598f, + -0.03488159f, 0.99664307f, 0.03741455f, -0.01055908f, 0.00241089f, -0.00018311f, + -0.00274658f, 0.01202393f, -0.04061890f, 0.99533081f, 0.04483032f, -0.01266479f, + 0.00292969f, -0.00024414f, -0.00308228f, 0.01364136f, -0.04620361f, 0.99377441f, + 0.05233765f, -0.01483154f, 0.00344849f, -0.00027466f, -0.00341797f, 0.01522827f, + -0.05163574f, 0.99200439f, 0.05999756f, -0.01699829f, 0.00396729f, -0.00033569f, + -0.00375366f, 0.01678467f, -0.05691528f, 0.99002075f, 0.06777954f, -0.01922607f, + 0.00451660f, -0.00039673f, -0.00405884f, 0.01828003f, -0.06207275f, 0.98782349f, + 0.07568359f, -0.02145386f, 0.00506592f, -0.00042725f, -0.00436401f, 0.01971436f, + -0.06707764f, 0.98541260f, 0.08370972f, -0.02374268f, 0.00564575f, -0.00048828f, + -0.00463867f, 0.02114868f, -0.07192993f, 0.98278809f, 0.09185791f, -0.02603149f, + 0.00622559f, -0.00054932f, -0.00494385f, 0.02252197f, -0.07666016f, 0.97991943f, + 0.10012817f, -0.02835083f, 0.00680542f, -0.00061035f, -0.00518799f, 0.02383423f, + -0.08123779f, 0.97686768f, 0.10848999f, -0.03073120f, 0.00738525f, -0.00070190f, + -0.00543213f, 0.02511597f, -0.08566284f, 0.97360229f, 0.11700439f, -0.03308105f, + 0.00799561f, -0.00076294f, -0.00567627f, 0.02636719f, -0.08993530f, 0.97012329f, + 0.12561035f, -0.03549194f, 0.00860596f, -0.00082397f, -0.00592041f, 0.02755737f, + -0.09405518f, 0.96643066f, 0.13436890f, -0.03790283f, 0.00924683f, -0.00091553f, + -0.00613403f, 0.02868652f, -0.09805298f, 0.96252441f, 0.14318848f, -0.04034424f, + 0.00985718f, -0.00097656f, -0.00631714f, 0.02981567f, -0.10189819f, 0.95843506f, + 0.15213013f, -0.04281616f, 0.01049805f, -0.00106812f, -0.00653076f, 0.03085327f, + -0.10559082f, 0.95413208f, 0.16119385f, -0.04528809f, 0.01113892f, -0.00112915f, + -0.00671387f, 0.03189087f, -0.10916138f, 0.94961548f, 0.17034912f, -0.04779053f, + 0.01181030f, -0.00122070f, -0.00686646f, 0.03286743f, -0.11254883f, 0.94491577f, + 0.17959595f, -0.05029297f, 0.01248169f, -0.00131226f, -0.00701904f, 0.03378296f, + -0.11584473f, 0.94000244f, 0.18893433f, -0.05279541f, 0.01315308f, -0.00140381f, + -0.00717163f, 0.03466797f, -0.11895752f, 0.93490601f, 0.19839478f, -0.05532837f, + 0.01382446f, -0.00149536f, -0.00732422f, 0.03552246f, -0.12194824f, 0.92962646f, + 0.20791626f, -0.05786133f, 0.01449585f, -0.00158691f, -0.00744629f, 0.03631592f, + -0.12478638f, 0.92413330f, 0.21752930f, -0.06042480f, 0.01519775f, -0.00167847f, + -0.00753784f, 0.03707886f, -0.12750244f, 0.91848755f, 0.22723389f, -0.06298828f, + 0.01586914f, -0.00177002f, -0.00765991f, 0.03781128f, -0.13006592f, 0.91262817f, + 0.23703003f, -0.06555176f, 0.01657104f, -0.00189209f, -0.00775146f, 0.03848267f, + -0.13250732f, 0.90658569f, 0.24691772f, -0.06808472f, 0.01727295f, -0.00198364f, + -0.00784302f, 0.03909302f, -0.13479614f, 0.90036011f, 0.25683594f, -0.07064819f, + 0.01797485f, -0.00210571f, -0.00790405f, 0.03970337f, -0.13696289f, 0.89395142f, + 0.26687622f, -0.07321167f, 0.01870728f, -0.00219727f, -0.00796509f, 0.04025269f, + -0.13900757f, 0.88739014f, 0.27694702f, -0.07577515f, 0.01940918f, -0.00231934f, + -0.00802612f, 0.04077148f, -0.14089966f, 0.88064575f, 0.28710938f, -0.07833862f, + 0.02011108f, -0.00244141f, -0.00808716f, 0.04122925f, -0.14263916f, 0.87374878f, + 0.29733276f, -0.08090210f, 0.02084351f, -0.00253296f, -0.00811768f, 0.04165649f, + -0.14428711f, 0.86666870f, 0.30761719f, -0.08343506f, 0.02154541f, -0.00265503f, + -0.00814819f, 0.04205322f, -0.14578247f, 0.85940552f, 0.31793213f, -0.08596802f, + 0.02227783f, -0.00277710f, -0.00814819f, 0.04238892f, -0.14715576f, 0.85202026f, + 0.32833862f, -0.08847046f, 0.02297974f, -0.00289917f, -0.00817871f, 0.04272461f, + -0.14840698f, 0.84445190f, 0.33874512f, -0.09097290f, 0.02371216f, -0.00302124f, + -0.00817871f, 0.04299927f, -0.14953613f, 0.83673096f, 0.34924316f, -0.09347534f, + 0.02441406f, -0.00314331f, -0.00817871f, 0.04321289f, -0.15054321f, 0.82888794f, + 0.35977173f, -0.09594727f, 0.02514648f, -0.00326538f, -0.00814819f, 0.04342651f, + -0.15142822f, 0.82086182f, 0.37033081f, -0.09838867f, 0.02584839f, -0.00341797f, + -0.00814819f, 0.04357910f, -0.15219116f, 0.81271362f, 0.38092041f, -0.10079956f, + 0.02655029f, -0.00354004f, -0.00811768f, 0.04373169f, -0.15283203f, 0.80441284f, + 0.39154053f, -0.10321045f, 0.02725220f, -0.00366211f, -0.00808716f, 0.04382324f, + -0.15338135f, 0.79598999f, 0.40219116f, -0.10559082f, 0.02795410f, -0.00381470f, + -0.00805664f, 0.04388428f, -0.15377808f, 0.78741455f, 0.41287231f, -0.10794067f, + 0.02865601f, -0.00393677f, -0.00799561f, 0.04388428f, -0.15408325f, 0.77871704f, + 0.42358398f, -0.11026001f, 0.02935791f, -0.00405884f, -0.00793457f, 0.04388428f, + -0.15426636f, 0.76989746f, 0.43429565f, -0.11251831f, 0.03002930f, -0.00421143f, + -0.00787354f, 0.04385376f, -0.15435791f, 0.76095581f, 0.44500732f, -0.11477661f, + 0.03070068f, -0.00433350f, -0.00781250f, 0.04379272f, -0.15435791f, 0.75192261f, + 0.45574951f, -0.11697388f, 0.03137207f, -0.00448608f, -0.00775146f, 0.04367065f, + -0.15420532f, 0.74273682f, 0.46649170f, -0.11914062f, 0.03201294f, -0.00460815f, + -0.00769043f, 0.04354858f, -0.15399170f, 0.73345947f, 0.47723389f, -0.12127686f, + 0.03268433f, -0.00473022f, -0.00759888f, 0.04339600f, -0.15365601f, 0.72406006f, + 0.48794556f, -0.12335205f, 0.03329468f, -0.00488281f, -0.00750732f, 0.04321289f, + -0.15322876f, 0.71456909f, 0.49868774f, -0.12539673f, 0.03393555f, -0.00500488f, + -0.00741577f, 0.04296875f, -0.15270996f, 0.70498657f, 0.50936890f, -0.12738037f, + 0.03454590f, -0.00515747f, -0.00732422f, 0.04272461f, -0.15209961f, 0.69528198f, + 0.52008057f, -0.12930298f, 0.03515625f, -0.00527954f, -0.00723267f, 0.04248047f, + -0.15136719f, 0.68551636f, 0.53076172f, -0.13119507f, 0.03573608f, -0.00543213f, + -0.00714111f, 0.04217529f, -0.15057373f, 0.67565918f, 0.54138184f, -0.13299561f, + 0.03631592f, -0.00555420f, -0.00701904f, 0.04183960f, -0.14968872f, 0.66571045f, + 0.55200195f, -0.13476562f, 0.03689575f, -0.00567627f, -0.00692749f, 0.04150391f, + -0.14871216f, 0.65567017f, 0.56259155f, -0.13647461f, 0.03741455f, -0.00582886f, + -0.00680542f, 0.04113770f, -0.14767456f, 0.64556885f, 0.57315063f, -0.13812256f, + 0.03796387f, -0.00595093f, -0.00668335f, 0.04074097f, -0.14651489f, 0.63540649f, + 0.58364868f, -0.13970947f, 0.03845215f, -0.00607300f, -0.00656128f, 0.04031372f, + -0.14529419f, 0.62518311f, 0.59411621f, -0.14120483f, 0.03897095f, -0.00619507f, + -0.00643921f, 0.03988647f, -0.14401245f, 0.61486816f, 0.60452271f, -0.14263916f, + 0.03942871f, -0.00631714f, -0.00631714f, 0.03942871f, -0.14263916f, 0.60452271f, + 0.61486816f, -0.14401245f, 0.03988647f, -0.00643921f, -0.00619507f, 0.03897095f, + -0.14120483f, 0.59411621f, 0.62518311f, -0.14529419f, 0.04031372f, -0.00656128f, + -0.00607300f, 0.03845215f, -0.13970947f, 0.58364868f, 0.63540649f, -0.14651489f, + 0.04074097f, -0.00668335f, -0.00595093f, 0.03796387f, -0.13812256f, 0.57315063f, + 0.64556885f, -0.14767456f, 0.04113770f, -0.00680542f, -0.00582886f, 0.03741455f, + -0.13647461f, 0.56259155f, 0.65567017f, -0.14871216f, 0.04150391f, -0.00692749f, + -0.00567627f, 0.03689575f, -0.13476562f, 0.55200195f, 0.66571045f, -0.14968872f, + 0.04183960f, -0.00701904f, -0.00555420f, 0.03631592f, -0.13299561f, 0.54138184f, + 0.67565918f, -0.15057373f, 0.04217529f, -0.00714111f, -0.00543213f, 0.03573608f, + -0.13119507f, 0.53076172f, 0.68551636f, -0.15136719f, 0.04248047f, -0.00723267f, + -0.00527954f, 0.03515625f, -0.12930298f, 0.52008057f, 0.69528198f, -0.15209961f, + 0.04272461f, -0.00732422f, -0.00515747f, 0.03454590f, -0.12738037f, 0.50936890f, + 0.70498657f, -0.15270996f, 0.04296875f, -0.00741577f, -0.00500488f, 0.03393555f, + -0.12539673f, 0.49868774f, 0.71456909f, -0.15322876f, 0.04321289f, -0.00750732f, + -0.00488281f, 0.03329468f, -0.12335205f, 0.48794556f, 0.72406006f, -0.15365601f, + 0.04339600f, -0.00759888f, -0.00473022f, 0.03268433f, -0.12127686f, 0.47723389f, + 0.73345947f, -0.15399170f, 0.04354858f, -0.00769043f, -0.00460815f, 0.03201294f, + -0.11914062f, 0.46649170f, 0.74273682f, -0.15420532f, 0.04367065f, -0.00775146f, + -0.00448608f, 0.03137207f, -0.11697388f, 0.45574951f, 0.75192261f, -0.15435791f, + 0.04379272f, -0.00781250f, -0.00433350f, 0.03070068f, -0.11477661f, 0.44500732f, + 0.76095581f, -0.15435791f, 0.04385376f, -0.00787354f, -0.00421143f, 0.03002930f, + -0.11251831f, 0.43429565f, 0.76989746f, -0.15426636f, 0.04388428f, -0.00793457f, + -0.00405884f, 0.02935791f, -0.11026001f, 0.42358398f, 0.77871704f, -0.15408325f, + 0.04388428f, -0.00799561f, -0.00393677f, 0.02865601f, -0.10794067f, 0.41287231f, + 0.78741455f, -0.15377808f, 0.04388428f, -0.00805664f, -0.00381470f, 0.02795410f, + -0.10559082f, 0.40219116f, 0.79598999f, -0.15338135f, 0.04382324f, -0.00808716f, + -0.00366211f, 0.02725220f, -0.10321045f, 0.39154053f, 0.80441284f, -0.15283203f, + 0.04373169f, -0.00811768f, -0.00354004f, 0.02655029f, -0.10079956f, 0.38092041f, + 0.81271362f, -0.15219116f, 0.04357910f, -0.00814819f, -0.00341797f, 0.02584839f, + -0.09838867f, 0.37033081f, 0.82086182f, -0.15142822f, 0.04342651f, -0.00814819f, + -0.00326538f, 0.02514648f, -0.09594727f, 0.35977173f, 0.82888794f, -0.15054321f, + 0.04321289f, -0.00817871f, -0.00314331f, 0.02441406f, -0.09347534f, 0.34924316f, + 0.83673096f, -0.14953613f, 0.04299927f, -0.00817871f, -0.00302124f, 0.02371216f, + -0.09097290f, 0.33874512f, 0.84445190f, -0.14840698f, 0.04272461f, -0.00817871f, + -0.00289917f, 0.02297974f, -0.08847046f, 0.32833862f, 0.85202026f, -0.14715576f, + 0.04238892f, -0.00814819f, -0.00277710f, 0.02227783f, -0.08596802f, 0.31793213f, + 0.85940552f, -0.14578247f, 0.04205322f, -0.00814819f, -0.00265503f, 0.02154541f, + -0.08343506f, 0.30761719f, 0.86666870f, -0.14428711f, 0.04165649f, -0.00811768f, + -0.00253296f, 0.02084351f, -0.08090210f, 0.29733276f, 0.87374878f, -0.14263916f, + 0.04122925f, -0.00808716f, -0.00244141f, 0.02011108f, -0.07833862f, 0.28710938f, + 0.88064575f, -0.14089966f, 0.04077148f, -0.00802612f, -0.00231934f, 0.01940918f, + -0.07577515f, 0.27694702f, 0.88739014f, -0.13900757f, 0.04025269f, -0.00796509f, + -0.00219727f, 0.01870728f, -0.07321167f, 0.26687622f, 0.89395142f, -0.13696289f, + 0.03970337f, -0.00790405f, -0.00210571f, 0.01797485f, -0.07064819f, 0.25683594f, + 0.90036011f, -0.13479614f, 0.03909302f, -0.00784302f, -0.00198364f, 0.01727295f, + -0.06808472f, 0.24691772f, 0.90658569f, -0.13250732f, 0.03848267f, -0.00775146f, + -0.00189209f, 0.01657104f, -0.06555176f, 0.23703003f, 0.91262817f, -0.13006592f, + 0.03781128f, -0.00765991f, -0.00177002f, 0.01586914f, -0.06298828f, 0.22723389f, + 0.91848755f, -0.12750244f, 0.03707886f, -0.00753784f, -0.00167847f, 0.01519775f, + -0.06042480f, 0.21752930f, 0.92413330f, -0.12478638f, 0.03631592f, -0.00744629f, + -0.00158691f, 0.01449585f, -0.05786133f, 0.20791626f, 0.92962646f, -0.12194824f, + 0.03552246f, -0.00732422f, -0.00149536f, 0.01382446f, -0.05532837f, 0.19839478f, + 0.93490601f, -0.11895752f, 0.03466797f, -0.00717163f, -0.00140381f, 0.01315308f, + -0.05279541f, 0.18893433f, 0.94000244f, -0.11584473f, 0.03378296f, -0.00701904f, + -0.00131226f, 0.01248169f, -0.05029297f, 0.17959595f, 0.94491577f, -0.11254883f, + 0.03286743f, -0.00686646f, -0.00122070f, 0.01181030f, -0.04779053f, 0.17034912f, + 0.94961548f, -0.10916138f, 0.03189087f, -0.00671387f, -0.00112915f, 0.01113892f, + -0.04528809f, 0.16119385f, 0.95413208f, -0.10559082f, 0.03085327f, -0.00653076f, + -0.00106812f, 0.01049805f, -0.04281616f, 0.15213013f, 0.95843506f, -0.10189819f, + 0.02981567f, -0.00631714f, -0.00097656f, 0.00985718f, -0.04034424f, 0.14318848f, + 0.96252441f, -0.09805298f, 0.02868652f, -0.00613403f, -0.00091553f, 0.00924683f, + -0.03790283f, 0.13436890f, 0.96643066f, -0.09405518f, 0.02755737f, -0.00592041f, + -0.00082397f, 0.00860596f, -0.03549194f, 0.12561035f, 0.97012329f, -0.08993530f, + 0.02636719f, -0.00567627f, -0.00076294f, 0.00799561f, -0.03308105f, 0.11700439f, + 0.97360229f, -0.08566284f, 0.02511597f, -0.00543213f, -0.00070190f, 0.00738525f, + -0.03073120f, 0.10848999f, 0.97686768f, -0.08123779f, 0.02383423f, -0.00518799f, + -0.00061035f, 0.00680542f, -0.02835083f, 0.10012817f, 0.97991943f, -0.07666016f, + 0.02252197f, -0.00494385f, -0.00054932f, 0.00622559f, -0.02603149f, 0.09185791f, + 0.98278809f, -0.07192993f, 0.02114868f, -0.00463867f, -0.00048828f, 0.00564575f, + -0.02374268f, 0.08370972f, 0.98541260f, -0.06707764f, 0.01971436f, -0.00436401f, + -0.00042725f, 0.00506592f, -0.02145386f, 0.07568359f, 0.98782349f, -0.06207275f, + 0.01828003f, -0.00405884f, -0.00039673f, 0.00451660f, -0.01922607f, 0.06777954f, + 0.99002075f, -0.05691528f, 0.01678467f, -0.00375366f, -0.00033569f, 0.00396729f, + -0.01699829f, 0.05999756f, 0.99200439f, -0.05163574f, 0.01522827f, -0.00341797f, + -0.00027466f, 0.00344849f, -0.01483154f, 0.05233765f, 0.99377441f, -0.04620361f, + 0.01364136f, -0.00308228f, -0.00024414f, 0.00292969f, -0.01266479f, 0.04483032f, + 0.99533081f, -0.04061890f, 0.01202393f, -0.00274658f, -0.00018311f, 0.00241089f, + -0.01055908f, 0.03741455f, 0.99664307f, -0.03488159f, 0.01037598f, -0.00238037f, + -0.00015259f, 0.00192261f, -0.00845337f, 0.03018188f, 0.99774170f, -0.02899170f, + 0.00866699f, -0.00201416f, -0.00009155f, 0.00143433f, -0.00640869f, 0.02304077f, + 0.99862671f, -0.02297974f, 0.00689697f, -0.00161743f, -0.00006104f, 0.00097656f, + -0.00439453f, 0.01605225f, 0.99929810f, -0.01684570f, 0.00512695f, -0.00122070f, + -0.00003052f, 0.00051880f, -0.00241089f, 0.00918579f, 0.99975586f, -0.01052856f, + 0.00329590f, -0.00079346f, 0.00000000f, 0.00006104f, -0.00048828f, 0.00247192f, + 0.99996948f, -0.00408936f, 0.00143433f, -0.00036621f, + }; + + const auto get_lut = [&]() -> std::span<const f32> { + if (sample_rate_ratio <= 1.0f) { + return std::span<const f32>(lut2.data(), lut2.size()); + } else if (sample_rate_ratio < 1.3f) { + return std::span<const f32>(lut1.data(), lut1.size()); + } else { + return std::span<const f32>(lut0.data(), lut0.size()); + } + }; + + auto lut{get_lut()}; + u32 read_index{0}; + for (u32 i = 0; i < samples_to_write; i++) { + const auto lut_index{(fraction.get_frac() >> 8) * 8}; + const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]}; + const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]}; + const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]}; + const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]}; + const Common::FixedPoint<56, 8> sample4{input[read_index + 4] * lut[lut_index + 4]}; + const Common::FixedPoint<56, 8> sample5{input[read_index + 5] * lut[lut_index + 5]}; + const Common::FixedPoint<56, 8> sample6{input[read_index + 6] * lut[lut_index + 6]}; + const Common::FixedPoint<56, 8> sample7{input[read_index + 7] * lut[lut_index + 7]}; + output[i] = (sample0 + sample1 + sample2 + sample3 + sample4 + sample5 + sample6 + sample7) + .to_int_floor(); + fraction += sample_rate_ratio; + read_index += static_cast<u32>(fraction.to_int_floor()); + fraction.clear_int(); + } +} + +void Resample(std::span<s32> output, std::span<const s16> input, + const Common::FixedPoint<49, 15>& sample_rate_ratio, + Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write, + const SrcQuality src_quality) { + + switch (src_quality) { + case SrcQuality::Low: + ResampleLowQuality(output, input, sample_rate_ratio, fraction, samples_to_write); + break; + case SrcQuality::Medium: + ResampleNormalQuality(output, input, sample_rate_ratio, fraction, samples_to_write); + break; + case SrcQuality::High: + ResampleHighQuality(output, input, sample_rate_ratio, fraction, samples_to_write); + break; + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/resample.h b/src/audio_core/renderer/command/resample/resample.h new file mode 100644 index 000000000..ba9209b82 --- /dev/null +++ b/src/audio_core/renderer/command/resample/resample.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "audio_core/common/common.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +/** + * Resample an input buffer into an output buffer, according to the sample_rate_ratio. + * + * @param output - Output buffer. + * @param input - Input buffer. + * @param sample_rate_ratio - Ratio for resampling. + e.g 32000/48000 = 0.666 input samples read per output. + * @param fraction - Current read fraction, written to and should be passed back in for + * multiple calls. + * @param samples_to_write - Number of samples to write. + * @param src_quality - Resampling quality. + */ +void Resample(std::span<s32> output, std::span<const s16> input, + const Common::FixedPoint<49, 15>& sample_rate_ratio, + Common::FixedPoint<49, 15>& fraction, u32 samples_to_write, SrcQuality src_quality); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/upsample.cpp b/src/audio_core/renderer/command/resample/upsample.cpp new file mode 100644 index 000000000..6c3ff31f7 --- /dev/null +++ b/src/audio_core/renderer/command/resample/upsample.cpp @@ -0,0 +1,262 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <array> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/resample/upsample.h" +#include "audio_core/renderer/upsampler/upsampler_info.h" + +namespace AudioCore::AudioRenderer { +/** + * Upsampling impl. Input must be 8K, 16K or 32K, output is 48K. + * + * @param output - Output buffer. + * @param input - Input buffer. + * @param target_sample_count - Number of samples for output. + * @param state - Upsampler state, updated each call. + */ +static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input, + const u32 target_sample_count, const u32 source_sample_count, + UpsamplerState* state) { + constexpr u32 WindowSize = 10; + constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow1{ + 51.93359375f, -18.80078125f, 9.73046875f, -5.33203125f, 2.84375f, + -1.41015625f, 0.62109375f, -0.2265625f, 0.0625f, -0.00390625f, + }; + constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow2{ + 105.35546875f, -24.52734375f, 11.9609375f, -6.515625f, 3.52734375f, + -1.796875f, 0.828125f, -0.32421875f, 0.1015625f, -0.015625f, + }; + constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow3{ + 122.08203125f, -16.47656250f, 7.68359375f, -4.15625000f, 2.26171875f, + -1.16796875f, 0.54687500f, -0.22265625f, 0.07421875f, -0.01171875f, + }; + constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow4{ + 23.73437500f, -9.62109375f, 5.07812500f, -2.78125000f, 1.46875000f, + -0.71484375f, 0.30859375f, -0.10546875f, 0.02734375f, 0.00000000f, + }; + constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow5{ + 80.62500000f, -24.67187500f, 12.44921875f, -6.80859375f, 3.66406250f, + -1.83984375f, 0.83203125f, -0.31640625f, 0.09375000f, -0.01171875f, + }; + + if (!state->initialized) { + switch (source_sample_count) { + case 40: + state->window_size = WindowSize; + state->ratio = 6.0f; + state->history.fill(0); + break; + + case 80: + state->window_size = WindowSize; + state->ratio = 3.0f; + state->history.fill(0); + break; + + case 160: + state->window_size = WindowSize; + state->ratio = 1.5f; + state->history.fill(0); + break; + + default: + LOG_ERROR(Service_Audio, "Invalid upsampling source count {}!", source_sample_count); + // This continues anyway, but let's assume 160 for sanity + state->window_size = WindowSize; + state->ratio = 1.5f; + state->history.fill(0); + break; + } + + state->history_input_index = 0; + state->history_output_index = 9; + state->history_start_index = 0; + state->history_end_index = UpsamplerState::HistorySize - 1; + state->initialized = true; + } + + if (target_sample_count == 0) { + return; + } + + u32 read_index{0}; + + auto increment = [&]() -> void { + state->history[state->history_input_index] = input[read_index++]; + state->history_input_index = + static_cast<u16>((state->history_input_index + 1) % UpsamplerState::HistorySize); + state->history_output_index = + static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize); + }; + + auto calculate_sample = [&state](std::span<const Common::FixedPoint<24, 8>> coeffs1, + std::span<const Common::FixedPoint<24, 8>> coeffs2) -> s32 { + auto output_index{state->history_output_index}; + auto start_pos{output_index - state->history_start_index + 1U}; + auto end_pos{10U}; + + if (start_pos < 10) { + end_pos = start_pos; + } + + u64 prev_contrib{0}; + u32 coeff_index{0}; + for (; coeff_index < end_pos; coeff_index++, output_index--) { + prev_contrib += static_cast<u64>(state->history[output_index].to_raw()) * + coeffs1[coeff_index].to_raw(); + } + + auto end_index{state->history_end_index}; + for (; start_pos < 9; start_pos++, coeff_index++, end_index--) { + prev_contrib += static_cast<u64>(state->history[end_index].to_raw()) * + coeffs1[coeff_index].to_raw(); + } + + output_index = + static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize); + start_pos = state->history_end_index - output_index + 1U; + end_pos = 10U; + + if (start_pos < 10) { + end_pos = start_pos; + } + + u64 next_contrib{0}; + coeff_index = 0; + for (; coeff_index < end_pos; coeff_index++, output_index++) { + next_contrib += static_cast<u64>(state->history[output_index].to_raw()) * + coeffs2[coeff_index].to_raw(); + } + + auto start_index{state->history_start_index}; + for (; start_pos < 9; start_pos++, start_index++, coeff_index++) { + next_contrib += static_cast<u64>(state->history[start_index].to_raw()) * + coeffs2[coeff_index].to_raw(); + } + + return static_cast<s32>(((prev_contrib >> 15) + (next_contrib >> 15)) >> 8); + }; + + switch (state->ratio.to_int_floor()) { + // 40 -> 240 + case 6: + for (u32 write_index = 0; write_index < target_sample_count; write_index++) { + switch (state->sample_index) { + case 0: + increment(); + output[write_index] = state->history[state->history_output_index].to_int_floor(); + break; + + case 1: + output[write_index] = calculate_sample(SincWindow3, SincWindow4); + break; + + case 2: + output[write_index] = calculate_sample(SincWindow2, SincWindow1); + break; + + case 3: + output[write_index] = calculate_sample(SincWindow5, SincWindow5); + break; + + case 4: + output[write_index] = calculate_sample(SincWindow1, SincWindow2); + break; + + case 5: + output[write_index] = calculate_sample(SincWindow4, SincWindow3); + break; + } + state->sample_index = static_cast<u8>((state->sample_index + 1) % 6); + } + break; + + // 80 -> 240 + case 3: + for (u32 write_index = 0; write_index < target_sample_count; write_index++) { + switch (state->sample_index) { + case 0: + increment(); + output[write_index] = state->history[state->history_output_index].to_int_floor(); + break; + + case 1: + output[write_index] = calculate_sample(SincWindow2, SincWindow1); + break; + + case 2: + output[write_index] = calculate_sample(SincWindow1, SincWindow2); + break; + } + state->sample_index = static_cast<u8>((state->sample_index + 1) % 3); + } + break; + + // 160 -> 240 + default: + for (u32 write_index = 0; write_index < target_sample_count; write_index++) { + switch (state->sample_index) { + case 0: + increment(); + output[write_index] = state->history[state->history_output_index].to_int_floor(); + break; + + case 1: + output[write_index] = calculate_sample(SincWindow1, SincWindow2); + break; + + case 2: + increment(); + output[write_index] = calculate_sample(SincWindow2, SincWindow1); + break; + } + state->sample_index = static_cast<u8>((state->sample_index + 1) % 3); + } + + break; + } +} + +auto UpsampleCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) -> void { + string += fmt::format("UpsampleCommand\n\tsource_sample_count {} source_sample_rate {}", + source_sample_count, source_sample_rate); + const auto upsampler{reinterpret_cast<UpsamplerInfo*>(upsampler_info)}; + if (upsampler != nullptr) { + string += fmt::format("\n\tUpsampler\n\t\tenabled {} sample count {}\n\tinputs: ", + upsampler->enabled, upsampler->sample_count); + for (u32 i = 0; i < upsampler->input_count; i++) { + string += fmt::format("{:02X}, ", upsampler->inputs[i]); + } + } + string += "\n"; +} + +void UpsampleCommand::Process(const ADSP::CommandListProcessor& processor) { + const auto info{reinterpret_cast<UpsamplerInfo*>(upsampler_info)}; + const auto input_count{std::min(info->input_count, buffer_count)}; + const std::span<const s16> inputs_{reinterpret_cast<const s16*>(inputs), input_count}; + + for (u32 i = 0; i < input_count; i++) { + const auto channel{inputs_[i]}; + + if (channel >= 0 && channel < static_cast<s16>(processor.buffer_count)) { + auto state{&info->states[i]}; + std::span<s32> output{ + reinterpret_cast<s32*>(samples_buffer + info->sample_count * channel * sizeof(s32)), + info->sample_count}; + auto input{processor.mix_buffers.subspan(channel * processor.sample_count, + processor.sample_count)}; + + SrcProcessFrame(output, input, info->sample_count, source_sample_count, state); + } + } +} + +bool UpsampleCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/upsample.h b/src/audio_core/renderer/command/resample/upsample.h new file mode 100644 index 000000000..bfc94e8af --- /dev/null +++ b/src/audio_core/renderer/command/resample/upsample.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for upsampling a mix buffer to 48Khz. + * Input must be 8Khz, 16Khz or 32Khz, and output will be 48Khz. + */ +struct UpsampleCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Pointer to the output samples buffer. + CpuAddr samples_buffer; + /// Pointer to input mix buffer indexes. + CpuAddr inputs; + /// Number of input mix buffers. + u32 buffer_count; + /// Unknown, unused. + u32 unk_20; + /// Source data sample count. + u32 source_sample_count; + /// Source data sample rate. + u32 source_sample_rate; + /// Pointer to the upsampler info for this command. + CpuAddr upsampler_info; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/sink/circular_buffer.cpp b/src/audio_core/renderer/command/sink/circular_buffer.cpp new file mode 100644 index 000000000..ded5afc94 --- /dev/null +++ b/src/audio_core/renderer/command/sink/circular_buffer.cpp @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <vector> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/sink/circular_buffer.h" +#include "core/memory.h" + +namespace AudioCore::AudioRenderer { + +void CircularBufferSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format( + "CircularBufferSinkCommand\n\tinput_count {} ring size {:04X} ring pos {:04X}\n\tinputs: ", + input_count, size, pos); + for (u32 i = 0; i < input_count; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n"; +} + +void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& processor) { + constexpr s32 min{std::numeric_limits<s16>::min()}; + constexpr s32 max{std::numeric_limits<s16>::max()}; + + std::vector<s16> output(processor.sample_count); + for (u32 channel = 0; channel < input_count; channel++) { + auto input{processor.mix_buffers.subspan(inputs[channel] * processor.sample_count, + processor.sample_count)}; + for (u32 sample_index = 0; sample_index < processor.sample_count; sample_index++) { + output[sample_index] = static_cast<s16>(std::clamp(input[sample_index], min, max)); + } + + processor.memory->WriteBlockUnsafe(address + pos, output.data(), + output.size() * sizeof(s16)); + pos += static_cast<u32>(processor.sample_count * sizeof(s16)); + if (pos >= size) { + pos = 0; + } + } +} + +bool CircularBufferSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/sink/circular_buffer.h b/src/audio_core/renderer/command/sink/circular_buffer.h new file mode 100644 index 000000000..e7d5be26e --- /dev/null +++ b/src/audio_core/renderer/command/sink/circular_buffer.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for sinking samples to a circular buffer. + */ +struct CircularBufferSinkCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Number of input mix buffers + u32 input_count; + /// Input mix buffer indexes + std::array<s16, MaxChannels> inputs; + /// Circular buffer address + CpuAddr address; + /// Circular buffer size + u32 size; + /// Current buffer offset + u32 pos; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/sink/device.cpp b/src/audio_core/renderer/command/sink/device.cpp new file mode 100644 index 000000000..47e0c6722 --- /dev/null +++ b/src/audio_core/renderer/command/sink/device.cpp @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <algorithm> + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/sink/device.h" +#include "audio_core/sink/sink.h" + +namespace AudioCore::AudioRenderer { + +void DeviceSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("DeviceSinkCommand\n\t{} session {} input_count {}\n\tinputs: ", + std::string_view(name), session_id, input_count); + for (u32 i = 0; i < input_count; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n"; +} + +void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) { + constexpr s32 min = std::numeric_limits<s16>::min(); + constexpr s32 max = std::numeric_limits<s16>::max(); + + auto stream{processor.GetOutputSinkStream()}; + stream->SetSystemChannels(input_count); + + Sink::SinkBuffer out_buffer{ + .frames{TargetSampleCount}, + .frames_played{0}, + .tag{0}, + .consumed{false}, + }; + + std::vector<s16> samples(out_buffer.frames * input_count); + + for (u32 channel = 0; channel < input_count; channel++) { + const auto offset{inputs[channel] * out_buffer.frames}; + + for (u32 index = 0; index < out_buffer.frames; index++) { + samples[index * input_count + channel] = + static_cast<s16>(std::clamp(sample_buffer[offset + index], min, max)); + } + } + + out_buffer.tag = reinterpret_cast<u64>(samples.data()); + stream->AppendBuffer(out_buffer, samples); +} + +bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/sink/device.h b/src/audio_core/renderer/command/sink/device.h new file mode 100644 index 000000000..1099bcf8c --- /dev/null +++ b/src/audio_core/renderer/command/sink/device.h @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <span> +#include <string> + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for sinking samples to an output device. + */ +struct DeviceSinkCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Device name + char name[0x100]; + /// System session id (unused) + s32 session_id; + /// Sample buffer to sink + std::span<s32> sample_buffer; + /// Number of input channels + u32 input_count; + /// Mix buffer indexes for each channel + std::array<s16, MaxChannels> inputs; +}; + +} // namespace AudioCore::AudioRenderer |