diff options
| author | 2022-07-16 23:48:45 +0100 | |
|---|---|---|
| committer | 2022-07-22 01:11:32 +0100 | |
| commit | 458da8a94877677f086f06cdeecf959ec4283a33 (patch) | |
| tree | 583166d77602ad90a0d552f37de8729ad80fd6c1 /src/audio_core/renderer/command | |
| parent | Merge pull request #8598 from Link4565/recv-dontwait (diff) | |
| download | yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.gz yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.xz yuzu-458da8a94877677f086f06cdeecf959ec4283a33.zip | |
Project Andio
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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/behavior/behavior_info.h" | ||
| 5 | #include "audio_core/renderer/command/command_buffer.h" | ||
| 6 | #include "audio_core/renderer/command/command_list_header.h" | ||
| 7 | #include "audio_core/renderer/command/command_processing_time_estimator.h" | ||
| 8 | #include "audio_core/renderer/effect/biquad_filter.h" | ||
| 9 | #include "audio_core/renderer/effect/delay.h" | ||
| 10 | #include "audio_core/renderer/effect/reverb.h" | ||
| 11 | #include "audio_core/renderer/memory/memory_pool_info.h" | ||
| 12 | #include "audio_core/renderer/mix/mix_info.h" | ||
| 13 | #include "audio_core/renderer/sink/circular_buffer_sink_info.h" | ||
| 14 | #include "audio_core/renderer/sink/device_sink_info.h" | ||
| 15 | #include "audio_core/renderer/sink/sink_info_base.h" | ||
| 16 | #include "audio_core/renderer/voice/voice_info.h" | ||
| 17 | #include "audio_core/renderer/voice/voice_state.h" | ||
| 18 | |||
| 19 | namespace AudioCore::AudioRenderer { | ||
| 20 | |||
| 21 | template <typename T, CommandId Id> | ||
| 22 | T& CommandBuffer::GenerateStart(const s32 node_id) { | ||
| 23 | if (size + sizeof(T) >= command_list.size_bytes()) { | ||
| 24 | LOG_ERROR( | ||
| 25 | Service_Audio, | ||
| 26 | "Attempting to write commands beyond the end of allocated command buffer memory!"); | ||
| 27 | UNREACHABLE(); | ||
| 28 | } | ||
| 29 | |||
| 30 | auto& cmd{*std::construct_at<T>(reinterpret_cast<T*>(&command_list[size]))}; | ||
| 31 | |||
| 32 | cmd.magic = CommandMagic; | ||
| 33 | cmd.enabled = true; | ||
| 34 | cmd.type = Id; | ||
| 35 | cmd.size = sizeof(T); | ||
| 36 | cmd.node_id = node_id; | ||
| 37 | |||
| 38 | return cmd; | ||
| 39 | } | ||
| 40 | |||
| 41 | template <typename T> | ||
| 42 | void CommandBuffer::GenerateEnd(T& cmd) { | ||
| 43 | cmd.estimated_process_time = time_estimator->Estimate(cmd); | ||
| 44 | estimated_process_time += cmd.estimated_process_time; | ||
| 45 | size += sizeof(T); | ||
| 46 | count++; | ||
| 47 | } | ||
| 48 | |||
| 49 | void CommandBuffer::GeneratePcmInt16Version1Command(const s32 node_id, | ||
| 50 | const MemoryPoolInfo& memory_pool_, | ||
| 51 | VoiceInfo& voice_info, | ||
| 52 | const VoiceState& voice_state, | ||
| 53 | const s16 buffer_count, const s8 channel) { | ||
| 54 | auto& cmd{ | ||
| 55 | GenerateStart<PcmInt16DataSourceVersion1Command, CommandId::DataSourcePcmInt16Version1>( | ||
| 56 | node_id)}; | ||
| 57 | |||
| 58 | cmd.src_quality = voice_info.src_quality; | ||
| 59 | cmd.output_index = buffer_count + channel; | ||
| 60 | cmd.flags = voice_info.flags & 3; | ||
| 61 | cmd.sample_rate = voice_info.sample_rate; | ||
| 62 | cmd.pitch = voice_info.pitch; | ||
| 63 | cmd.channel_index = channel; | ||
| 64 | cmd.channel_count = voice_info.channel_count; | ||
| 65 | |||
| 66 | for (u32 i = 0; i < MaxWaveBuffers; i++) { | ||
| 67 | voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); | ||
| 68 | } | ||
| 69 | |||
| 70 | cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); | ||
| 71 | |||
| 72 | GenerateEnd<PcmInt16DataSourceVersion1Command>(cmd); | ||
| 73 | } | ||
| 74 | |||
| 75 | void CommandBuffer::GeneratePcmInt16Version2Command(const s32 node_id, VoiceInfo& voice_info, | ||
| 76 | const VoiceState& voice_state, | ||
| 77 | const s16 buffer_count, const s8 channel) { | ||
| 78 | auto& cmd{ | ||
| 79 | GenerateStart<PcmInt16DataSourceVersion2Command, CommandId::DataSourcePcmInt16Version2>( | ||
| 80 | node_id)}; | ||
| 81 | |||
| 82 | cmd.src_quality = voice_info.src_quality; | ||
| 83 | cmd.output_index = buffer_count + channel; | ||
| 84 | cmd.flags = voice_info.flags & 3; | ||
| 85 | cmd.sample_rate = voice_info.sample_rate; | ||
| 86 | cmd.pitch = voice_info.pitch; | ||
| 87 | cmd.channel_index = channel; | ||
| 88 | cmd.channel_count = voice_info.channel_count; | ||
| 89 | |||
| 90 | for (u32 i = 0; i < MaxWaveBuffers; i++) { | ||
| 91 | voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); | ||
| 92 | } | ||
| 93 | |||
| 94 | cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); | ||
| 95 | |||
| 96 | GenerateEnd<PcmInt16DataSourceVersion2Command>(cmd); | ||
| 97 | } | ||
| 98 | |||
| 99 | void CommandBuffer::GeneratePcmFloatVersion1Command(const s32 node_id, | ||
| 100 | const MemoryPoolInfo& memory_pool_, | ||
| 101 | VoiceInfo& voice_info, | ||
| 102 | const VoiceState& voice_state, | ||
| 103 | const s16 buffer_count, const s8 channel) { | ||
| 104 | auto& cmd{ | ||
| 105 | GenerateStart<PcmFloatDataSourceVersion1Command, CommandId::DataSourcePcmFloatVersion1>( | ||
| 106 | node_id)}; | ||
| 107 | |||
| 108 | cmd.src_quality = voice_info.src_quality; | ||
| 109 | cmd.output_index = buffer_count + channel; | ||
| 110 | cmd.flags = voice_info.flags & 3; | ||
| 111 | cmd.sample_rate = voice_info.sample_rate; | ||
| 112 | cmd.pitch = voice_info.pitch; | ||
| 113 | cmd.channel_index = channel; | ||
| 114 | cmd.channel_count = voice_info.channel_count; | ||
| 115 | |||
| 116 | for (u32 i = 0; i < MaxWaveBuffers; i++) { | ||
| 117 | voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); | ||
| 118 | } | ||
| 119 | |||
| 120 | cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); | ||
| 121 | |||
| 122 | GenerateEnd<PcmFloatDataSourceVersion1Command>(cmd); | ||
| 123 | } | ||
| 124 | |||
| 125 | void CommandBuffer::GeneratePcmFloatVersion2Command(const s32 node_id, VoiceInfo& voice_info, | ||
| 126 | const VoiceState& voice_state, | ||
| 127 | const s16 buffer_count, const s8 channel) { | ||
| 128 | auto& cmd{ | ||
| 129 | GenerateStart<PcmFloatDataSourceVersion2Command, CommandId::DataSourcePcmFloatVersion2>( | ||
| 130 | node_id)}; | ||
| 131 | |||
| 132 | cmd.src_quality = voice_info.src_quality; | ||
| 133 | cmd.output_index = buffer_count + channel; | ||
| 134 | cmd.flags = voice_info.flags & 3; | ||
| 135 | cmd.sample_rate = voice_info.sample_rate; | ||
| 136 | cmd.pitch = voice_info.pitch; | ||
| 137 | cmd.channel_index = channel; | ||
| 138 | cmd.channel_count = voice_info.channel_count; | ||
| 139 | |||
| 140 | for (u32 i = 0; i < MaxWaveBuffers; i++) { | ||
| 141 | voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); | ||
| 142 | } | ||
| 143 | |||
| 144 | cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); | ||
| 145 | |||
| 146 | GenerateEnd<PcmFloatDataSourceVersion2Command>(cmd); | ||
| 147 | } | ||
| 148 | |||
| 149 | void CommandBuffer::GenerateAdpcmVersion1Command(const s32 node_id, | ||
| 150 | const MemoryPoolInfo& memory_pool_, | ||
| 151 | VoiceInfo& voice_info, | ||
| 152 | const VoiceState& voice_state, | ||
| 153 | const s16 buffer_count, const s8 channel) { | ||
| 154 | auto& cmd{ | ||
| 155 | GenerateStart<AdpcmDataSourceVersion1Command, CommandId::DataSourceAdpcmVersion1>(node_id)}; | ||
| 156 | |||
| 157 | cmd.src_quality = voice_info.src_quality; | ||
| 158 | cmd.output_index = buffer_count + channel; | ||
| 159 | cmd.flags = voice_info.flags & 3; | ||
| 160 | cmd.sample_rate = voice_info.sample_rate; | ||
| 161 | cmd.pitch = voice_info.pitch; | ||
| 162 | |||
| 163 | for (u32 i = 0; i < MaxWaveBuffers; i++) { | ||
| 164 | voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); | ||
| 165 | } | ||
| 166 | |||
| 167 | cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); | ||
| 168 | cmd.data_address = voice_info.data_address.GetReference(true); | ||
| 169 | cmd.data_size = voice_info.data_address.GetSize(); | ||
| 170 | |||
| 171 | GenerateEnd<AdpcmDataSourceVersion1Command>(cmd); | ||
| 172 | } | ||
| 173 | |||
| 174 | void CommandBuffer::GenerateAdpcmVersion2Command(const s32 node_id, VoiceInfo& voice_info, | ||
| 175 | const VoiceState& voice_state, | ||
| 176 | const s16 buffer_count, const s8 channel) { | ||
| 177 | auto& cmd{ | ||
| 178 | GenerateStart<AdpcmDataSourceVersion2Command, CommandId::DataSourceAdpcmVersion2>(node_id)}; | ||
| 179 | |||
| 180 | cmd.src_quality = voice_info.src_quality; | ||
| 181 | cmd.output_index = buffer_count + channel; | ||
| 182 | cmd.flags = voice_info.flags & 3; | ||
| 183 | cmd.sample_rate = voice_info.sample_rate; | ||
| 184 | cmd.pitch = voice_info.pitch; | ||
| 185 | cmd.channel_index = channel; | ||
| 186 | cmd.channel_count = voice_info.channel_count; | ||
| 187 | |||
| 188 | for (u32 i = 0; i < MaxWaveBuffers; i++) { | ||
| 189 | voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); | ||
| 190 | } | ||
| 191 | |||
| 192 | cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); | ||
| 193 | cmd.data_address = voice_info.data_address.GetReference(true); | ||
| 194 | cmd.data_size = voice_info.data_address.GetSize(); | ||
| 195 | |||
| 196 | GenerateEnd<AdpcmDataSourceVersion2Command>(cmd); | ||
| 197 | } | ||
| 198 | |||
| 199 | void CommandBuffer::GenerateVolumeCommand(const s32 node_id, const s16 buffer_offset, | ||
| 200 | const s16 input_index, const f32 volume, | ||
| 201 | const u8 precision) { | ||
| 202 | auto& cmd{GenerateStart<VolumeCommand, CommandId::Volume>(node_id)}; | ||
| 203 | |||
| 204 | cmd.precision = precision; | ||
| 205 | cmd.input_index = buffer_offset + input_index; | ||
| 206 | cmd.output_index = buffer_offset + input_index; | ||
| 207 | cmd.volume = volume; | ||
| 208 | |||
| 209 | GenerateEnd<VolumeCommand>(cmd); | ||
| 210 | } | ||
| 211 | |||
| 212 | void CommandBuffer::GenerateVolumeRampCommand(const s32 node_id, VoiceInfo& voice_info, | ||
| 213 | const s16 buffer_count, const u8 precision) { | ||
| 214 | auto& cmd{GenerateStart<VolumeRampCommand, CommandId::VolumeRamp>(node_id)}; | ||
| 215 | |||
| 216 | cmd.input_index = buffer_count; | ||
| 217 | cmd.output_index = buffer_count; | ||
| 218 | cmd.prev_volume = voice_info.prev_volume; | ||
| 219 | cmd.volume = voice_info.volume; | ||
| 220 | cmd.precision = precision; | ||
| 221 | |||
| 222 | GenerateEnd<VolumeRampCommand>(cmd); | ||
| 223 | } | ||
| 224 | |||
| 225 | void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info, | ||
| 226 | const VoiceState& voice_state, | ||
| 227 | const s16 buffer_count, const s8 channel, | ||
| 228 | const u32 biquad_index, | ||
| 229 | const bool use_float_processing) { | ||
| 230 | auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)}; | ||
| 231 | |||
| 232 | cmd.input = buffer_count + channel; | ||
| 233 | cmd.output = buffer_count + channel; | ||
| 234 | |||
| 235 | cmd.biquad = voice_info.biquads[biquad_index]; | ||
| 236 | |||
| 237 | cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()), | ||
| 238 | MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); | ||
| 239 | |||
| 240 | cmd.needs_init = !voice_info.biquad_initialized[biquad_index]; | ||
| 241 | cmd.use_float_processing = use_float_processing; | ||
| 242 | |||
| 243 | GenerateEnd<BiquadFilterCommand>(cmd); | ||
| 244 | } | ||
| 245 | |||
| 246 | void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBase& effect_info, | ||
| 247 | const s16 buffer_offset, const s8 channel, | ||
| 248 | const bool needs_init, | ||
| 249 | const bool use_float_processing) { | ||
| 250 | auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)}; | ||
| 251 | |||
| 252 | const auto& parameter{ | ||
| 253 | *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||
| 254 | const auto state{ | ||
| 255 | reinterpret_cast<VoiceState::BiquadFilterState*>(effect_info.GetStateBuffer())}; | ||
| 256 | |||
| 257 | cmd.input = buffer_offset + parameter.inputs[channel]; | ||
| 258 | cmd.output = buffer_offset + parameter.outputs[channel]; | ||
| 259 | |||
| 260 | cmd.biquad.b = parameter.b; | ||
| 261 | cmd.biquad.a = parameter.a; | ||
| 262 | |||
| 263 | cmd.state = memory_pool->Translate(CpuAddr(state), | ||
| 264 | MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); | ||
| 265 | |||
| 266 | cmd.needs_init = needs_init; | ||
| 267 | cmd.use_float_processing = use_float_processing; | ||
| 268 | |||
| 269 | GenerateEnd<BiquadFilterCommand>(cmd); | ||
| 270 | } | ||
| 271 | |||
| 272 | void CommandBuffer::GenerateMixCommand(const s32 node_id, const s16 input_index, | ||
| 273 | const s16 output_index, const s16 buffer_offset, | ||
| 274 | const f32 volume, const u8 precision) { | ||
| 275 | auto& cmd{GenerateStart<MixCommand, CommandId::Mix>(node_id)}; | ||
| 276 | |||
| 277 | cmd.input_index = input_index; | ||
| 278 | cmd.output_index = output_index; | ||
| 279 | cmd.volume = volume; | ||
| 280 | cmd.precision = precision; | ||
| 281 | |||
| 282 | GenerateEnd<MixCommand>(cmd); | ||
| 283 | } | ||
| 284 | |||
| 285 | void CommandBuffer::GenerateMixRampCommand(const s32 node_id, | ||
| 286 | [[maybe_unused]] const s16 buffer_count, | ||
| 287 | const s16 input_index, const s16 output_index, | ||
| 288 | const f32 volume, const f32 prev_volume, | ||
| 289 | const CpuAddr prev_samples, const u8 precision) { | ||
| 290 | if (volume == 0.0f && prev_volume == 0.0f) { | ||
| 291 | return; | ||
| 292 | } | ||
| 293 | |||
| 294 | auto& cmd{GenerateStart<MixRampCommand, CommandId::MixRamp>(node_id)}; | ||
| 295 | |||
| 296 | cmd.input_index = input_index; | ||
| 297 | cmd.output_index = output_index; | ||
| 298 | cmd.prev_volume = prev_volume; | ||
| 299 | cmd.volume = volume; | ||
| 300 | cmd.previous_sample = prev_samples; | ||
| 301 | cmd.precision = precision; | ||
| 302 | |||
| 303 | GenerateEnd<MixRampCommand>(cmd); | ||
| 304 | } | ||
| 305 | |||
| 306 | void CommandBuffer::GenerateMixRampGroupedCommand(const s32 node_id, const s16 buffer_count, | ||
| 307 | const s16 input_index, s16 output_index, | ||
| 308 | std::span<const f32> volumes, | ||
| 309 | std::span<const f32> prev_volumes, | ||
| 310 | const CpuAddr prev_samples, const u8 precision) { | ||
| 311 | auto& cmd{GenerateStart<MixRampGroupedCommand, CommandId::MixRampGrouped>(node_id)}; | ||
| 312 | |||
| 313 | cmd.buffer_count = buffer_count; | ||
| 314 | |||
| 315 | for (s32 i = 0; i < buffer_count; i++) { | ||
| 316 | cmd.inputs[i] = input_index; | ||
| 317 | cmd.outputs[i] = output_index++; | ||
| 318 | cmd.prev_volumes[i] = prev_volumes[i]; | ||
| 319 | cmd.volumes[i] = volumes[i]; | ||
| 320 | } | ||
| 321 | |||
| 322 | cmd.previous_samples = prev_samples; | ||
| 323 | cmd.precision = precision; | ||
| 324 | |||
| 325 | GenerateEnd<MixRampGroupedCommand>(cmd); | ||
| 326 | } | ||
| 327 | |||
| 328 | void CommandBuffer::GenerateDepopPrepareCommand(const s32 node_id, const VoiceState& voice_state, | ||
| 329 | std::span<const s32> buffer, const s16 buffer_count, | ||
| 330 | s16 buffer_offset, const bool was_playing) { | ||
| 331 | auto& cmd{GenerateStart<DepopPrepareCommand, CommandId::DepopPrepare>(node_id)}; | ||
| 332 | |||
| 333 | cmd.enabled = was_playing; | ||
| 334 | |||
| 335 | for (u32 i = 0; i < MaxMixBuffers; i++) { | ||
| 336 | cmd.inputs[i] = buffer_offset++; | ||
| 337 | } | ||
| 338 | |||
| 339 | cmd.previous_samples = memory_pool->Translate(CpuAddr(voice_state.previous_samples.data()), | ||
| 340 | MaxMixBuffers * sizeof(s32)); | ||
| 341 | cmd.buffer_count = buffer_count; | ||
| 342 | cmd.depop_buffer = memory_pool->Translate(CpuAddr(buffer.data()), buffer_count * sizeof(s32)); | ||
| 343 | |||
| 344 | GenerateEnd<DepopPrepareCommand>(cmd); | ||
| 345 | } | ||
| 346 | |||
| 347 | void CommandBuffer::GenerateDepopForMixBuffersCommand(const s32 node_id, const MixInfo& mix_info, | ||
| 348 | std::span<const s32> depop_buffer) { | ||
| 349 | auto& cmd{GenerateStart<DepopForMixBuffersCommand, CommandId::DepopForMixBuffers>(node_id)}; | ||
| 350 | |||
| 351 | cmd.input = mix_info.buffer_offset; | ||
| 352 | cmd.count = mix_info.buffer_count; | ||
| 353 | cmd.decay = mix_info.sample_rate == TargetSampleRate ? 0.96218872f : 0.94369507f; | ||
| 354 | cmd.depop_buffer = | ||
| 355 | memory_pool->Translate(CpuAddr(depop_buffer.data()), mix_info.buffer_count * sizeof(s32)); | ||
| 356 | |||
| 357 | GenerateEnd<DepopForMixBuffersCommand>(cmd); | ||
| 358 | } | ||
| 359 | |||
| 360 | void CommandBuffer::GenerateDelayCommand(const s32 node_id, EffectInfoBase& effect_info, | ||
| 361 | const s16 buffer_offset) { | ||
| 362 | auto& cmd{GenerateStart<DelayCommand, CommandId::Delay>(node_id)}; | ||
| 363 | |||
| 364 | const auto& parameter{ | ||
| 365 | *reinterpret_cast<DelayInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||
| 366 | const auto state{effect_info.GetStateBuffer()}; | ||
| 367 | |||
| 368 | if (IsChannelCountValid(parameter.channel_count)) { | ||
| 369 | const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(DelayInfo::State))}; | ||
| 370 | if (state_buffer) { | ||
| 371 | for (s16 channel = 0; channel < parameter.channel_count; channel++) { | ||
| 372 | cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; | ||
| 373 | cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; | ||
| 374 | } | ||
| 375 | |||
| 376 | if (!behavior->IsDelayChannelMappingChanged() && parameter.channel_count == 6) { | ||
| 377 | UseOldChannelMapping(cmd.inputs, cmd.outputs); | ||
| 378 | } | ||
| 379 | |||
| 380 | cmd.parameter = parameter; | ||
| 381 | cmd.effect_enabled = effect_info.IsEnabled(); | ||
| 382 | cmd.state = state_buffer; | ||
| 383 | cmd.workbuffer = effect_info.GetWorkbuffer(-1); | ||
| 384 | } | ||
| 385 | } | ||
| 386 | |||
| 387 | GenerateEnd<DelayCommand>(cmd); | ||
| 388 | } | ||
| 389 | |||
| 390 | void CommandBuffer::GenerateUpsampleCommand(const s32 node_id, const s16 buffer_offset, | ||
| 391 | UpsamplerInfo& upsampler_info, const u32 input_count, | ||
| 392 | std::span<const s8> inputs, const s16 buffer_count, | ||
| 393 | const u32 sample_count_, const u32 sample_rate_) { | ||
| 394 | auto& cmd{GenerateStart<UpsampleCommand, CommandId::Upsample>(node_id)}; | ||
| 395 | |||
| 396 | cmd.samples_buffer = memory_pool->Translate(upsampler_info.samples_pos, | ||
| 397 | upsampler_info.sample_count * sizeof(s32)); | ||
| 398 | cmd.inputs = memory_pool->Translate(CpuAddr(upsampler_info.inputs.data()), MaxChannels); | ||
| 399 | cmd.buffer_count = buffer_count; | ||
| 400 | cmd.unk_20 = 0; | ||
| 401 | cmd.source_sample_count = sample_count_; | ||
| 402 | cmd.source_sample_rate = sample_rate_; | ||
| 403 | |||
| 404 | upsampler_info.input_count = input_count; | ||
| 405 | for (u32 i = 0; i < input_count; i++) { | ||
| 406 | upsampler_info.inputs[i] = buffer_offset + inputs[i]; | ||
| 407 | } | ||
| 408 | |||
| 409 | cmd.upsampler_info = memory_pool->Translate(CpuAddr(&upsampler_info), sizeof(UpsamplerInfo)); | ||
| 410 | |||
| 411 | GenerateEnd<UpsampleCommand>(cmd); | ||
| 412 | } | ||
| 413 | |||
| 414 | void CommandBuffer::GenerateDownMix6chTo2chCommand(const s32 node_id, std::span<const s8> inputs, | ||
| 415 | const s16 buffer_offset, | ||
| 416 | std::span<const f32> downmix_coeff) { | ||
| 417 | auto& cmd{GenerateStart<DownMix6chTo2chCommand, CommandId::DownMix6chTo2ch>(node_id)}; | ||
| 418 | |||
| 419 | for (u32 i = 0; i < MaxChannels; i++) { | ||
| 420 | cmd.inputs[i] = buffer_offset + inputs[i]; | ||
| 421 | cmd.outputs[i] = buffer_offset + inputs[i]; | ||
| 422 | } | ||
| 423 | |||
| 424 | for (u32 i = 0; i < 4; i++) { | ||
| 425 | cmd.down_mix_coeff[i] = downmix_coeff[i]; | ||
| 426 | } | ||
| 427 | |||
| 428 | GenerateEnd<DownMix6chTo2chCommand>(cmd); | ||
| 429 | } | ||
| 430 | |||
| 431 | void CommandBuffer::GenerateAuxCommand(const s32 node_id, EffectInfoBase& effect_info, | ||
| 432 | const s16 input_index, const s16 output_index, | ||
| 433 | const s16 buffer_offset, const u32 update_count, | ||
| 434 | const u32 count_max, const u32 write_offset) { | ||
| 435 | auto& cmd{GenerateStart<AuxCommand, CommandId::Aux>(node_id)}; | ||
| 436 | |||
| 437 | if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) { | ||
| 438 | cmd.input = buffer_offset + input_index; | ||
| 439 | cmd.output = buffer_offset + output_index; | ||
| 440 | cmd.send_buffer_info = effect_info.GetSendBufferInfo(); | ||
| 441 | cmd.send_buffer = effect_info.GetSendBuffer(); | ||
| 442 | cmd.return_buffer_info = effect_info.GetReturnBufferInfo(); | ||
| 443 | cmd.return_buffer = effect_info.GetReturnBuffer(); | ||
| 444 | cmd.count_max = count_max; | ||
| 445 | cmd.write_offset = write_offset; | ||
| 446 | cmd.update_count = update_count; | ||
| 447 | cmd.effect_enabled = effect_info.IsEnabled(); | ||
| 448 | } | ||
| 449 | |||
| 450 | GenerateEnd<AuxCommand>(cmd); | ||
| 451 | } | ||
| 452 | |||
| 453 | void CommandBuffer::GenerateDeviceSinkCommand(const s32 node_id, const s16 buffer_offset, | ||
| 454 | SinkInfoBase& sink_info, const u32 session_id, | ||
| 455 | std::span<s32> samples_buffer) { | ||
| 456 | auto& cmd{GenerateStart<DeviceSinkCommand, CommandId::DeviceSink>(node_id)}; | ||
| 457 | const auto& parameter{ | ||
| 458 | *reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())}; | ||
| 459 | auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())}; | ||
| 460 | |||
| 461 | cmd.session_id = session_id; | ||
| 462 | |||
| 463 | if (state.upsampler_info != nullptr) { | ||
| 464 | const auto size_{state.upsampler_info->sample_count * parameter.input_count}; | ||
| 465 | const auto size_bytes{size_ * sizeof(s32)}; | ||
| 466 | const auto addr{memory_pool->Translate(state.upsampler_info->samples_pos, size_bytes)}; | ||
| 467 | cmd.sample_buffer = {reinterpret_cast<s32*>(addr), | ||
| 468 | parameter.input_count * state.upsampler_info->sample_count}; | ||
| 469 | } else { | ||
| 470 | cmd.sample_buffer = samples_buffer; | ||
| 471 | } | ||
| 472 | |||
| 473 | cmd.input_count = parameter.input_count; | ||
| 474 | for (u32 i = 0; i < parameter.input_count; i++) { | ||
| 475 | cmd.inputs[i] = buffer_offset + parameter.inputs[i]; | ||
| 476 | } | ||
| 477 | |||
| 478 | GenerateEnd<DeviceSinkCommand>(cmd); | ||
| 479 | } | ||
| 480 | |||
| 481 | void CommandBuffer::GenerateCircularBufferSinkCommand(const s32 node_id, SinkInfoBase& sink_info, | ||
| 482 | const s16 buffer_offset) { | ||
| 483 | auto& cmd{GenerateStart<CircularBufferSinkCommand, CommandId::CircularBufferSink>(node_id)}; | ||
| 484 | const auto& parameter{*reinterpret_cast<CircularBufferSinkInfo::CircularBufferInParameter*>( | ||
| 485 | sink_info.GetParameter())}; | ||
| 486 | auto state{ | ||
| 487 | *reinterpret_cast<CircularBufferSinkInfo::CircularBufferState*>(sink_info.GetState())}; | ||
| 488 | |||
| 489 | cmd.input_count = parameter.input_count; | ||
| 490 | for (u32 i = 0; i < parameter.input_count; i++) { | ||
| 491 | cmd.inputs[i] = buffer_offset + parameter.inputs[i]; | ||
| 492 | } | ||
| 493 | |||
| 494 | cmd.address = state.address_info.GetReference(true); | ||
| 495 | cmd.size = parameter.size; | ||
| 496 | cmd.pos = state.current_pos; | ||
| 497 | |||
| 498 | GenerateEnd<CircularBufferSinkCommand>(cmd); | ||
| 499 | } | ||
| 500 | |||
| 501 | void CommandBuffer::GenerateReverbCommand(const s32 node_id, EffectInfoBase& effect_info, | ||
| 502 | const s16 buffer_offset, | ||
| 503 | const bool long_size_pre_delay_supported) { | ||
| 504 | auto& cmd{GenerateStart<ReverbCommand, CommandId::Reverb>(node_id)}; | ||
| 505 | |||
| 506 | const auto& parameter{ | ||
| 507 | *reinterpret_cast<ReverbInfo::ParameterVersion2*>(effect_info.GetParameter())}; | ||
| 508 | const auto state{effect_info.GetStateBuffer()}; | ||
| 509 | |||
| 510 | if (IsChannelCountValid(parameter.channel_count)) { | ||
| 511 | const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(ReverbInfo::State))}; | ||
| 512 | if (state_buffer) { | ||
| 513 | for (s16 channel = 0; channel < parameter.channel_count; channel++) { | ||
| 514 | cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; | ||
| 515 | cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; | ||
| 516 | } | ||
| 517 | |||
| 518 | if (!behavior->IsReverbChannelMappingChanged() && parameter.channel_count == 6) { | ||
| 519 | UseOldChannelMapping(cmd.inputs, cmd.outputs); | ||
| 520 | } | ||
| 521 | |||
| 522 | cmd.parameter = parameter; | ||
| 523 | cmd.effect_enabled = effect_info.IsEnabled(); | ||
| 524 | cmd.state = state_buffer; | ||
| 525 | cmd.workbuffer = effect_info.GetWorkbuffer(-1); | ||
| 526 | cmd.long_size_pre_delay_supported = long_size_pre_delay_supported; | ||
| 527 | } | ||
| 528 | } | ||
| 529 | |||
| 530 | GenerateEnd<ReverbCommand>(cmd); | ||
| 531 | } | ||
| 532 | |||
| 533 | void CommandBuffer::GenerateI3dl2ReverbCommand(const s32 node_id, EffectInfoBase& effect_info, | ||
| 534 | const s16 buffer_offset) { | ||
| 535 | auto& cmd{GenerateStart<I3dl2ReverbCommand, CommandId::I3dl2Reverb>(node_id)}; | ||
| 536 | |||
| 537 | const auto& parameter{ | ||
| 538 | *reinterpret_cast<I3dl2ReverbInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||
| 539 | const auto state{effect_info.GetStateBuffer()}; | ||
| 540 | |||
| 541 | if (IsChannelCountValid(parameter.channel_count)) { | ||
| 542 | const auto state_buffer{ | ||
| 543 | memory_pool->Translate(CpuAddr(state), sizeof(I3dl2ReverbInfo::State))}; | ||
| 544 | if (state_buffer) { | ||
| 545 | for (s16 channel = 0; channel < parameter.channel_count; channel++) { | ||
| 546 | cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; | ||
| 547 | cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; | ||
| 548 | } | ||
| 549 | |||
| 550 | if (!behavior->IsI3dl2ReverbChannelMappingChanged() && parameter.channel_count == 6) { | ||
| 551 | UseOldChannelMapping(cmd.inputs, cmd.outputs); | ||
| 552 | } | ||
| 553 | |||
| 554 | cmd.parameter = parameter; | ||
| 555 | cmd.effect_enabled = effect_info.IsEnabled(); | ||
| 556 | cmd.state = state_buffer; | ||
| 557 | cmd.workbuffer = effect_info.GetWorkbuffer(-1); | ||
| 558 | } | ||
| 559 | } | ||
| 560 | |||
| 561 | GenerateEnd<I3dl2ReverbCommand>(cmd); | ||
| 562 | } | ||
| 563 | |||
| 564 | void CommandBuffer::GeneratePerformanceCommand(const s32 node_id, const PerformanceState state, | ||
| 565 | const PerformanceEntryAddresses& entry_addresses) { | ||
| 566 | auto& cmd{GenerateStart<PerformanceCommand, CommandId::Performance>(node_id)}; | ||
| 567 | |||
| 568 | cmd.state = state; | ||
| 569 | cmd.entry_address = entry_addresses; | ||
| 570 | |||
| 571 | GenerateEnd<PerformanceCommand>(cmd); | ||
| 572 | } | ||
| 573 | |||
| 574 | void CommandBuffer::GenerateClearMixCommand(const s32 node_id) { | ||
| 575 | auto& cmd{GenerateStart<ClearMixBufferCommand, CommandId::ClearMixBuffer>(node_id)}; | ||
| 576 | GenerateEnd<ClearMixBufferCommand>(cmd); | ||
| 577 | } | ||
| 578 | |||
| 579 | void CommandBuffer::GenerateCopyMixBufferCommand(const s32 node_id, EffectInfoBase& effect_info, | ||
| 580 | const s16 buffer_offset, const s8 channel) { | ||
| 581 | auto& cmd{GenerateStart<CopyMixBufferCommand, CommandId::CopyMixBuffer>(node_id)}; | ||
| 582 | |||
| 583 | const auto& parameter{ | ||
| 584 | *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||
| 585 | cmd.input_index = buffer_offset + parameter.inputs[channel]; | ||
| 586 | cmd.output_index = buffer_offset + parameter.outputs[channel]; | ||
| 587 | |||
| 588 | GenerateEnd<CopyMixBufferCommand>(cmd); | ||
| 589 | } | ||
| 590 | |||
| 591 | void CommandBuffer::GenerateLightLimiterCommand( | ||
| 592 | const s32 node_id, const s16 buffer_offset, | ||
| 593 | const LightLimiterInfo::ParameterVersion1& parameter, const LightLimiterInfo::State& state, | ||
| 594 | const bool enabled, const CpuAddr workbuffer) { | ||
| 595 | auto& cmd{GenerateStart<LightLimiterVersion1Command, CommandId::LightLimiterVersion1>(node_id)}; | ||
| 596 | |||
| 597 | if (IsChannelCountValid(parameter.channel_count)) { | ||
| 598 | const auto state_buffer{ | ||
| 599 | memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))}; | ||
| 600 | if (state_buffer) { | ||
| 601 | for (s8 channel = 0; channel < parameter.channel_count; channel++) { | ||
| 602 | cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; | ||
| 603 | cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; | ||
| 604 | } | ||
| 605 | |||
| 606 | std::memcpy(&cmd.parameter, ¶meter, sizeof(LightLimiterInfo::ParameterVersion1)); | ||
| 607 | cmd.effect_enabled = enabled; | ||
| 608 | cmd.state = state_buffer; | ||
| 609 | cmd.workbuffer = workbuffer; | ||
| 610 | } | ||
| 611 | } | ||
| 612 | |||
| 613 | GenerateEnd<LightLimiterVersion1Command>(cmd); | ||
| 614 | } | ||
| 615 | |||
| 616 | void CommandBuffer::GenerateLightLimiterCommand( | ||
| 617 | const s32 node_id, const s16 buffer_offset, | ||
| 618 | const LightLimiterInfo::ParameterVersion2& parameter, | ||
| 619 | const LightLimiterInfo::StatisticsInternal& statistics, const LightLimiterInfo::State& state, | ||
| 620 | const bool enabled, const CpuAddr workbuffer) { | ||
| 621 | auto& cmd{GenerateStart<LightLimiterVersion2Command, CommandId::LightLimiterVersion2>(node_id)}; | ||
| 622 | if (IsChannelCountValid(parameter.channel_count)) { | ||
| 623 | const auto state_buffer{ | ||
| 624 | memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))}; | ||
| 625 | if (state_buffer) { | ||
| 626 | for (s8 channel = 0; channel < parameter.channel_count; channel++) { | ||
| 627 | cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; | ||
| 628 | cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; | ||
| 629 | } | ||
| 630 | |||
| 631 | cmd.parameter = parameter; | ||
| 632 | cmd.effect_enabled = enabled; | ||
| 633 | cmd.state = state_buffer; | ||
| 634 | if (cmd.parameter.statistics_enabled) { | ||
| 635 | cmd.result_state = memory_pool->Translate( | ||
| 636 | CpuAddr(&statistics), sizeof(LightLimiterInfo::StatisticsInternal)); | ||
| 637 | } else { | ||
| 638 | cmd.result_state = 0; | ||
| 639 | } | ||
| 640 | cmd.workbuffer = workbuffer; | ||
| 641 | } | ||
| 642 | } | ||
| 643 | |||
| 644 | GenerateEnd<LightLimiterVersion2Command>(cmd); | ||
| 645 | } | ||
| 646 | |||
| 647 | void CommandBuffer::GenerateMultitapBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info, | ||
| 648 | const VoiceState& voice_state, | ||
| 649 | const s16 buffer_count, const s8 channel) { | ||
| 650 | auto& cmd{GenerateStart<MultiTapBiquadFilterCommand, CommandId::MultiTapBiquadFilter>(node_id)}; | ||
| 651 | |||
| 652 | cmd.input = buffer_count + channel; | ||
| 653 | cmd.output = buffer_count + channel; | ||
| 654 | cmd.biquads = voice_info.biquads; | ||
| 655 | |||
| 656 | cmd.states[0] = | ||
| 657 | memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()), | ||
| 658 | MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); | ||
| 659 | cmd.states[1] = | ||
| 660 | memory_pool->Translate(CpuAddr(voice_state.biquad_states[1].data()), | ||
| 661 | MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); | ||
| 662 | |||
| 663 | cmd.needs_init[0] = !voice_info.biquad_initialized[0]; | ||
| 664 | cmd.needs_init[1] = !voice_info.biquad_initialized[1]; | ||
| 665 | cmd.filter_tap_count = MaxBiquadFilters; | ||
| 666 | |||
| 667 | GenerateEnd<MultiTapBiquadFilterCommand>(cmd); | ||
| 668 | } | ||
| 669 | |||
| 670 | void CommandBuffer::GenerateCaptureCommand(const s32 node_id, EffectInfoBase& effect_info, | ||
| 671 | const s16 input_index, const s16 output_index, | ||
| 672 | const s16 buffer_offset, const u32 update_count, | ||
| 673 | const u32 count_max, const u32 write_offset) { | ||
| 674 | auto& cmd{GenerateStart<CaptureCommand, CommandId::Capture>(node_id)}; | ||
| 675 | |||
| 676 | if (effect_info.GetSendBuffer()) { | ||
| 677 | cmd.input = buffer_offset + input_index; | ||
| 678 | cmd.output = buffer_offset + output_index; | ||
| 679 | cmd.send_buffer_info = effect_info.GetSendBufferInfo(); | ||
| 680 | cmd.send_buffer = effect_info.GetSendBuffer(); | ||
| 681 | cmd.count_max = count_max; | ||
| 682 | cmd.write_offset = write_offset; | ||
| 683 | cmd.update_count = update_count; | ||
| 684 | cmd.effect_enabled = effect_info.IsEnabled(); | ||
| 685 | } | ||
| 686 | |||
| 687 | GenerateEnd<CaptureCommand>(cmd); | ||
| 688 | } | ||
| 689 | |||
| 690 | void CommandBuffer::GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, | ||
| 691 | s32 node_id) { | ||
| 692 | auto& cmd{GenerateStart<CompressorCommand, CommandId::Compressor>(node_id)}; | ||
| 693 | |||
| 694 | auto& parameter{ | ||
| 695 | *reinterpret_cast<CompressorInfo::ParameterVersion2*>(effect_info.GetParameter())}; | ||
| 696 | auto state{reinterpret_cast<CompressorInfo::State*>(effect_info.GetStateBuffer())}; | ||
| 697 | |||
| 698 | if (IsChannelCountValid(parameter.channel_count)) { | ||
| 699 | auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(CompressorInfo::State))}; | ||
| 700 | if (state_buffer) { | ||
| 701 | for (u16 channel = 0; channel < parameter.channel_count; channel++) { | ||
| 702 | cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; | ||
| 703 | cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; | ||
| 704 | } | ||
| 705 | cmd.parameter = parameter; | ||
| 706 | cmd.workbuffer = state_buffer; | ||
| 707 | cmd.enabled = effect_info.IsEnabled(); | ||
| 708 | } | ||
| 709 | } | ||
| 710 | |||
| 711 | GenerateEnd<CompressorCommand>(cmd); | ||
| 712 | } | ||
| 713 | |||
| 714 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <span> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/command/commands.h" | ||
| 9 | #include "audio_core/renderer/effect/light_limiter.h" | ||
| 10 | #include "audio_core/renderer/performance/performance_manager.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | struct UpsamplerInfo; | ||
| 15 | struct VoiceState; | ||
| 16 | class EffectInfoBase; | ||
| 17 | class ICommandProcessingTimeEstimator; | ||
| 18 | class MixInfo; | ||
| 19 | class MemoryPoolInfo; | ||
| 20 | class SinkInfoBase; | ||
| 21 | class VoiceInfo; | ||
| 22 | |||
| 23 | /** | ||
| 24 | * Utility functions to generate and add commands into the current command list. | ||
| 25 | */ | ||
| 26 | class CommandBuffer { | ||
| 27 | public: | ||
| 28 | /** | ||
| 29 | * Generate a PCM s16 version 1 command, adding it to the command list. | ||
| 30 | * | ||
| 31 | * @param node_id - Node id of the voice this command is generated for. | ||
| 32 | * @param memory_pool - Memory pool for translating buffer addresses to the DSP. | ||
| 33 | * @param voice_info - The voice info this command is generated from. | ||
| 34 | * @param voice_state - The voice state the DSP will use for this command. | ||
| 35 | * @param buffer_count - Number of mix buffers in use, | ||
| 36 | * data will be read into this index + channel. | ||
| 37 | * @param channel - Channel index for this command. | ||
| 38 | */ | ||
| 39 | void GeneratePcmInt16Version1Command(s32 node_id, const MemoryPoolInfo& memory_pool, | ||
| 40 | VoiceInfo& voice_info, const VoiceState& voice_state, | ||
| 41 | s16 buffer_count, s8 channel); | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Generate a PCM s16 version 2 command, adding it to the command list. | ||
| 45 | * | ||
| 46 | * @param node_id - Node id of the voice this command is generated for. | ||
| 47 | * @param voice_info - The voice info this command is generated from. | ||
| 48 | * @param voice_state - The voice state the DSP will use for this command. | ||
| 49 | * @param buffer_count - Number of mix buffers in use, | ||
| 50 | * data will be read into this index + channel. | ||
| 51 | * @param channel - Channel index for this command. | ||
| 52 | */ | ||
| 53 | void GeneratePcmInt16Version2Command(s32 node_id, VoiceInfo& voice_info, | ||
| 54 | const VoiceState& voice_state, s16 buffer_count, | ||
| 55 | s8 channel); | ||
| 56 | |||
| 57 | /** | ||
| 58 | * Generate a PCM f32 version 1 command, adding it to the command list. | ||
| 59 | * | ||
| 60 | * @param node_id - Node id of the voice this command is generated for. | ||
| 61 | * @param memory_pool - Memory pool for translating buffer addresses to the DSP. | ||
| 62 | * @param voice_info - The voice info this command is generated from. | ||
| 63 | * @param voice_state - The voice state the DSP will use for this command. | ||
| 64 | * @param buffer_count - Number of mix buffers in use, | ||
| 65 | * data will be read into this index + channel. | ||
| 66 | * @param channel - Channel index for this command. | ||
| 67 | */ | ||
| 68 | void GeneratePcmFloatVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool, | ||
| 69 | VoiceInfo& voice_info, const VoiceState& voice_state, | ||
| 70 | s16 buffer_count, s8 channel); | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Generate a PCM f32 version 2 command, adding it to the command list. | ||
| 74 | * | ||
| 75 | * @param node_id - Node id of the voice this command is generated for. | ||
| 76 | * @param voice_info - The voice info this command is generated from. | ||
| 77 | * @param voice_state - The voice state the DSP will use for this command. | ||
| 78 | * @param buffer_count - Number of mix buffers in use, | ||
| 79 | * data will be read into this index + channel. | ||
| 80 | * @param channel - Channel index for this command. | ||
| 81 | */ | ||
| 82 | void GeneratePcmFloatVersion2Command(s32 node_id, VoiceInfo& voice_info, | ||
| 83 | const VoiceState& voice_state, s16 buffer_count, | ||
| 84 | s8 channel); | ||
| 85 | |||
| 86 | /** | ||
| 87 | * Generate an ADPCM version 1 command, adding it to the command list. | ||
| 88 | * | ||
| 89 | * @param node_id - Node id of the voice this command is generated for. | ||
| 90 | * @param memory_pool - Memory pool for translating buffer addresses to the DSP. | ||
| 91 | * @param voice_info - The voice info this command is generated from. | ||
| 92 | * @param voice_state - The voice state the DSP will use for this command. | ||
| 93 | * @param buffer_count - Number of mix buffers in use, | ||
| 94 | * data will be read into this index + channel. | ||
| 95 | * @param channel - Channel index for this command. | ||
| 96 | */ | ||
| 97 | void GenerateAdpcmVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool, | ||
| 98 | VoiceInfo& voice_info, const VoiceState& voice_state, | ||
| 99 | s16 buffer_count, s8 channel); | ||
| 100 | |||
| 101 | /** | ||
| 102 | * Generate an ADPCM version 2 command, adding it to the command list. | ||
| 103 | * | ||
| 104 | * @param node_id - Node id of the voice this command is generated for. | ||
| 105 | * @param voice_info - The voice info this command is generated from. | ||
| 106 | * @param voice_state - The voice state the DSP will use for this command. | ||
| 107 | * @param buffer_count - Number of mix buffers in use, | ||
| 108 | * data will be read into this index + channel. | ||
| 109 | * @param channel - Channel index for this command. | ||
| 110 | */ | ||
| 111 | void GenerateAdpcmVersion2Command(s32 node_id, VoiceInfo& voice_info, | ||
| 112 | const VoiceState& voice_state, s16 buffer_count, s8 channel); | ||
| 113 | |||
| 114 | /** | ||
| 115 | * Generate a volume command, adding it to the command list. | ||
| 116 | * | ||
| 117 | * @param node_id - Node id of the voice this command is generated for. | ||
| 118 | * @param buffer_offset - Base mix buffer index to generate this command at. | ||
| 119 | * @param input_index - Channel index and mix buffer offset for this command. | ||
| 120 | * @param volume - Mix volume added to the input samples. | ||
| 121 | * @param precision - Number of decimal bits for fixed point operations. | ||
| 122 | */ | ||
| 123 | void GenerateVolumeCommand(s32 node_id, s16 buffer_offset, s16 input_index, f32 volume, | ||
| 124 | u8 precision); | ||
| 125 | |||
| 126 | /** | ||
| 127 | * Generate a volume ramp command, adding it to the command list. | ||
| 128 | * | ||
| 129 | * @param node_id - Node id of the voice this command is generated for. | ||
| 130 | * @param voice_info - The voice info this command takes its volumes from. | ||
| 131 | * @param buffer_count - Number of active mix buffers, command will generate at this index. | ||
| 132 | * @param precision - Number of decimal bits for fixed point operations. | ||
| 133 | */ | ||
| 134 | void GenerateVolumeRampCommand(s32 node_id, VoiceInfo& voice_info, s16 buffer_count, | ||
| 135 | u8 precision); | ||
| 136 | |||
| 137 | /** | ||
| 138 | * Generate a biquad filter command from a voice, adding it to the command list. | ||
| 139 | * | ||
| 140 | * @param node_id - Node id of the voice this command is generated for. | ||
| 141 | * @param voice_info - The voice info this command takes biquad parameters from. | ||
| 142 | * @param voice_state - Used by the AudioRenderer to track previous samples. | ||
| 143 | * @param buffer_count - Number of active mix buffers, | ||
| 144 | * command will generate at this index + channel. | ||
| 145 | * @param channel - Channel index for this filter to work on. | ||
| 146 | * @param biquad_index - Which biquad filter to use for this command (0-1). | ||
| 147 | * @param use_float_processing - Should int or float processing be used? | ||
| 148 | */ | ||
| 149 | void GenerateBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info, | ||
| 150 | const VoiceState& voice_state, s16 buffer_count, s8 channel, | ||
| 151 | u32 biquad_index, bool use_float_processing); | ||
| 152 | |||
| 153 | /** | ||
| 154 | * Generate a biquad filter effect command, adding it to the command list. | ||
| 155 | * | ||
| 156 | * @param node_id - Node id of the voice this command is generated for. | ||
| 157 | * @param effect_info - The effect info this command takes biquad parameters from. | ||
| 158 | * @param buffer_offset - Mix buffer offset this command will use, | ||
| 159 | * command will generate at this index + channel. | ||
| 160 | * @param channel - Channel index for this filter to work on. | ||
| 161 | * @param needs_init - True if the biquad state needs initialisation. | ||
| 162 | * @param use_float_processing - Should int or float processing be used? | ||
| 163 | */ | ||
| 164 | void GenerateBiquadFilterCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset, | ||
| 165 | s8 channel, bool needs_init, bool use_float_processing); | ||
| 166 | |||
| 167 | /** | ||
| 168 | * Generate a mix command, adding it to the command list. | ||
| 169 | * | ||
| 170 | * @param node_id - Node id of the voice this command is generated for. | ||
| 171 | * @param input_index - Input mix buffer index for this command. | ||
| 172 | * Added to the buffer offset. | ||
| 173 | * @param output_index - Output mix buffer index for this command. | ||
| 174 | * Added to the buffer offset. | ||
| 175 | * @param buffer_offset - Mix buffer offset this command will use. | ||
| 176 | * @param volume - Volume to be applied to the input. | ||
| 177 | * @param precision - Number of decimal bits for fixed point operations. | ||
| 178 | */ | ||
| 179 | void GenerateMixCommand(s32 node_id, s16 input_index, s16 output_index, s16 buffer_offset, | ||
| 180 | f32 volume, u8 precision); | ||
| 181 | |||
| 182 | /** | ||
| 183 | * Generate a mix ramp command, adding it to the command list. | ||
| 184 | * | ||
| 185 | * @param node_id - Node id of the voice this command is generated for. | ||
| 186 | * @param buffer_count - Number of active mix buffers. | ||
| 187 | * @param input_index - Input mix buffer index for this command. | ||
| 188 | * Added to buffer_count. | ||
| 189 | * @param output_index - Output mix buffer index for this command. | ||
| 190 | * Added to buffer_count. | ||
| 191 | * @param volume - Current mix volume used for calculating the ramp. | ||
| 192 | * @param prev_volume - Previous mix volume, used for calculating the ramp, | ||
| 193 | * also applied to the input. | ||
| 194 | * @param precision - Number of decimal bits for fixed point operations. | ||
| 195 | */ | ||
| 196 | void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index, | ||
| 197 | f32 volume, f32 prev_volume, CpuAddr prev_samples, u8 precision); | ||
| 198 | |||
| 199 | /** | ||
| 200 | * Generate a mix ramp grouped command, adding it to the command list. | ||
| 201 | * | ||
| 202 | * @param node_id - Node id of the voice this command is generated for. | ||
| 203 | * @param buffer_count - Number of active mix buffers. | ||
| 204 | * @param input_index - Input mix buffer index for this command. | ||
| 205 | * Added to buffer_count. | ||
| 206 | * @param output_index - Output mix buffer index for this command. | ||
| 207 | * Added to buffer_count. | ||
| 208 | * @param volumes - Current mix volumes used for calculating the ramp. | ||
| 209 | * @param prev_volumes - Previous mix volumes, used for calculating the ramp, | ||
| 210 | * also applied to the input. | ||
| 211 | * @param precision - Number of decimal bits for fixed point operations. | ||
| 212 | */ | ||
| 213 | void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index, | ||
| 214 | s16 output_index, std::span<const f32> volumes, | ||
| 215 | std::span<const f32> prev_volumes, CpuAddr prev_samples, | ||
| 216 | u8 precision); | ||
| 217 | |||
| 218 | /** | ||
| 219 | * Generate a depop prepare command, adding it to the command list. | ||
| 220 | * | ||
| 221 | * @param node_id - Node id of the voice this command is generated for. | ||
| 222 | * @param voice_state - State to track the previous depop samples for each mix buffer. | ||
| 223 | * @param buffer - State to track the current depop samples for each mix buffer. | ||
| 224 | * @param buffer_count - Number of active mix buffers. | ||
| 225 | * @param buffer_offset - Base mix buffer index to generate the channel depops at. | ||
| 226 | * @param was_playing - Command only needs to work if the voice was previously playing. | ||
| 227 | */ | ||
| 228 | void GenerateDepopPrepareCommand(s32 node_id, const VoiceState& voice_state, | ||
| 229 | std::span<const s32> buffer, s16 buffer_count, | ||
| 230 | s16 buffer_offset, bool was_playing); | ||
| 231 | |||
| 232 | /** | ||
| 233 | * Generate a depop command, adding it to the command list. | ||
| 234 | * | ||
| 235 | * @param node_id - Node id of the voice this command is generated for. | ||
| 236 | * @param mix_info - Mix info to get the buffer count and base offsets from. | ||
| 237 | * @param depop_buffer - Buffer of current depop sample values to be added to the input | ||
| 238 | * channels. | ||
| 239 | */ | ||
| 240 | void GenerateDepopForMixBuffersCommand(s32 node_id, const MixInfo& mix_info, | ||
| 241 | std::span<const s32> depop_buffer); | ||
| 242 | |||
| 243 | /** | ||
| 244 | * Generate a delay command, adding it to the command list. | ||
| 245 | * | ||
| 246 | * @param node_id - Node id of the voice this command is generated for. | ||
| 247 | * @param effect_info - Delay effect info to generate this command from. | ||
| 248 | * @param buffer_offset - Base mix buffer offset to apply the apply the delay. | ||
| 249 | */ | ||
| 250 | void GenerateDelayCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset); | ||
| 251 | |||
| 252 | /** | ||
| 253 | * Generate an upsample command, adding it to the command list. | ||
| 254 | * | ||
| 255 | * @param node_id - Node id of the voice this command is generated for. | ||
| 256 | * @param buffer_offset - Base mix buffer offset to upsample. | ||
| 257 | * @param upsampler_info - Upsampler info to control the upsampling. | ||
| 258 | * @param input_count - Number of input channels to upsample. | ||
| 259 | * @param inputs - Input mix buffer indexes. | ||
| 260 | * @param buffer_count - Number of active mix buffers. | ||
| 261 | * @param sample_count - Source sample count of the input. | ||
| 262 | * @param sample_rate - Source sample rate of the input. | ||
| 263 | */ | ||
| 264 | void GenerateUpsampleCommand(s32 node_id, s16 buffer_offset, UpsamplerInfo& upsampler_info, | ||
| 265 | u32 input_count, std::span<const s8> inputs, s16 buffer_count, | ||
| 266 | u32 sample_count, u32 sample_rate); | ||
| 267 | |||
| 268 | /** | ||
| 269 | * Generate a downmix 6 -> 2 command, adding it to the command list. | ||
| 270 | * | ||
| 271 | * @param node_id - Node id of the voice this command is generated for. | ||
| 272 | * @param inputs - Input mix buffer indexes. | ||
| 273 | * @param buffer_offset - Base mix buffer offset of the channels to downmix. | ||
| 274 | * @param downmix_coeff - Downmixing coefficients. | ||
| 275 | */ | ||
| 276 | void GenerateDownMix6chTo2chCommand(s32 node_id, std::span<const s8> inputs, s16 buffer_offset, | ||
| 277 | std::span<const f32> downmix_coeff); | ||
| 278 | |||
| 279 | /** | ||
| 280 | * Generate an aux buffer command, adding it to the command list. | ||
| 281 | * | ||
| 282 | * @param node_id - Node id of the voice this command is generated for. | ||
| 283 | * @param effect_info - Aux effect info to generate this command from. | ||
| 284 | * @param input_index - Input mix buffer index for this command. | ||
| 285 | * Added to buffer_offset. | ||
| 286 | * @param output_index - Output mix buffer index for this command. | ||
| 287 | * Added to buffer_offset. | ||
| 288 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 289 | * @param update_count - Number of samples to write back to the game as updated, can be 0. | ||
| 290 | * @param count_max - Maximum number of samples to read or write. | ||
| 291 | * @param write_offset - Current read or write offset within the buffer. | ||
| 292 | */ | ||
| 293 | void GenerateAuxCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index, | ||
| 294 | s16 output_index, s16 buffer_offset, u32 update_count, u32 count_max, | ||
| 295 | u32 write_offset); | ||
| 296 | |||
| 297 | /** | ||
| 298 | * Generate a device sink command, adding it to the command list. | ||
| 299 | * | ||
| 300 | * @param node_id - Node id of the voice this command is generated for. | ||
| 301 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 302 | * @param sink_info - The sink_info to generate this command from. | ||
| 303 | * @session_id - System session id this command is generated from. | ||
| 304 | * @samples_buffer - The buffer to be sent to the sink if upsampling is not used. | ||
| 305 | */ | ||
| 306 | void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info, | ||
| 307 | u32 session_id, std::span<s32> samples_buffer); | ||
| 308 | |||
| 309 | /** | ||
| 310 | * Generate a circular buffer sink command, adding it to the command list. | ||
| 311 | * | ||
| 312 | * @param node_id - Node id of the voice this command is generated for. | ||
| 313 | * @param sink_info - The sink_info to generate this command from. | ||
| 314 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 315 | */ | ||
| 316 | void GenerateCircularBufferSinkCommand(s32 node_id, SinkInfoBase& sink_info, s16 buffer_offset); | ||
| 317 | |||
| 318 | /** | ||
| 319 | * Generate a reverb command, adding it to the command list. | ||
| 320 | * | ||
| 321 | * @param node_id - Node id of the voice this command is generated for. | ||
| 322 | * @param effect_info - Reverb effect info to generate this command from. | ||
| 323 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 324 | * @param long_size_pre_delay_supported - Should a longer pre-delay time be used before reverb | ||
| 325 | * begins? | ||
| 326 | */ | ||
| 327 | void GenerateReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset, | ||
| 328 | bool long_size_pre_delay_supported); | ||
| 329 | |||
| 330 | /** | ||
| 331 | * Generate an I3DL2 reverb command, adding it to the command list. | ||
| 332 | * | ||
| 333 | * @param node_id - Node id of the voice this command is generated for. | ||
| 334 | * @param effect_info - I3DL2Reverb effect info to generate this command from. | ||
| 335 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 336 | */ | ||
| 337 | void GenerateI3dl2ReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset); | ||
| 338 | |||
| 339 | /** | ||
| 340 | * Generate a performance command, adding it to the command list. | ||
| 341 | * | ||
| 342 | * @param node_id - Node id of the voice this command is generated for. | ||
| 343 | * @param state - State of the performance. | ||
| 344 | * @param entry_addresses - The addresses to be filled in by the AudioRenderer. | ||
| 345 | */ | ||
| 346 | void GeneratePerformanceCommand(s32 node_id, PerformanceState state, | ||
| 347 | const PerformanceEntryAddresses& entry_addresses); | ||
| 348 | |||
| 349 | /** | ||
| 350 | * Generate a clear mix command, adding it to the command list. | ||
| 351 | * | ||
| 352 | * @param node_id - Node id of the voice this command is generated for. | ||
| 353 | */ | ||
| 354 | void GenerateClearMixCommand(s32 node_id); | ||
| 355 | |||
| 356 | /** | ||
| 357 | * Generate a copy mix command, adding it to the command list. | ||
| 358 | * | ||
| 359 | * @param node_id - Node id of the voice this command is generated for. | ||
| 360 | * @param effect_info - BiquadFilter effect info to generate this command from. | ||
| 361 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 362 | * @param channel - Index to the effect's parameters input indexes for this command. | ||
| 363 | */ | ||
| 364 | void GenerateCopyMixBufferCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset, | ||
| 365 | s8 channel); | ||
| 366 | |||
| 367 | /** | ||
| 368 | * Generate a light limiter version 1 command, adding it to the command list. | ||
| 369 | * | ||
| 370 | * @param node_id - Node id of the voice this command is generated for. | ||
| 371 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 372 | * @param parameter - Effect parameter to generate from. | ||
| 373 | * @param state - State used by the AudioRenderer between commands. | ||
| 374 | * @param enabled - Is this command enabled? | ||
| 375 | * @param workbuffer - Game-supplied memory for the state. | ||
| 376 | */ | ||
| 377 | void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset, | ||
| 378 | const LightLimiterInfo::ParameterVersion1& parameter, | ||
| 379 | const LightLimiterInfo::State& state, bool enabled, | ||
| 380 | CpuAddr workbuffer); | ||
| 381 | |||
| 382 | /** | ||
| 383 | * Generate a light limiter version 2 command, adding it to the command list. | ||
| 384 | * | ||
| 385 | * @param node_id - Node id of the voice this command is generated for. | ||
| 386 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 387 | * @param parameter - Effect parameter to generate from. | ||
| 388 | * @param statistics - Statistics reported by the AudioRenderer on the limiter's state. | ||
| 389 | * @param state - State used by the AudioRenderer between commands. | ||
| 390 | * @param enabled - Is this command enabled? | ||
| 391 | * @param workbuffer - Game-supplied memory for the state. | ||
| 392 | */ | ||
| 393 | void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset, | ||
| 394 | const LightLimiterInfo::ParameterVersion2& parameter, | ||
| 395 | const LightLimiterInfo::StatisticsInternal& statistics, | ||
| 396 | const LightLimiterInfo::State& state, bool enabled, | ||
| 397 | CpuAddr workbuffer); | ||
| 398 | |||
| 399 | /** | ||
| 400 | * Generate a multitap biquad filter command, adding it to the command list. | ||
| 401 | * | ||
| 402 | * @param node_id - Node id of the voice this command is generated for. | ||
| 403 | * @param voice_info - The voice info this command takes biquad parameters from. | ||
| 404 | * @param voice_state - Used by the AudioRenderer to track previous samples. | ||
| 405 | * @param buffer_count - Number of active mix buffers, | ||
| 406 | * command will generate at this index + channel. | ||
| 407 | * @param channel - Channel index for this filter to work on. | ||
| 408 | */ | ||
| 409 | void GenerateMultitapBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info, | ||
| 410 | const VoiceState& voice_state, s16 buffer_count, | ||
| 411 | s8 channel); | ||
| 412 | |||
| 413 | /** | ||
| 414 | * Generate a capture command, adding it to the command list. | ||
| 415 | * | ||
| 416 | * @param node_id - Node id of the voice this command is generated for. | ||
| 417 | * @param effect_info - Capture effect info to generate this command from. | ||
| 418 | * @param input_index - Input mix buffer index for this command. | ||
| 419 | * Added to buffer_offset. | ||
| 420 | * @param output_index - Output mix buffer index for this command (unused). | ||
| 421 | * Added to buffer_offset. | ||
| 422 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 423 | * @param update_count - Number of samples to write back to the game as updated, can be 0. | ||
| 424 | * @param count_max - Maximum number of samples to read or write. | ||
| 425 | * @param write_offset - Current read or write offset within the buffer. | ||
| 426 | */ | ||
| 427 | void GenerateCaptureCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index, | ||
| 428 | s16 output_index, s16 buffer_offset, u32 update_count, | ||
| 429 | u32 count_max, u32 write_offset); | ||
| 430 | |||
| 431 | /** | ||
| 432 | * Generate a compressor command, adding it to the command list. | ||
| 433 | * | ||
| 434 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 435 | * @param effect_info - Capture effect info to generate this command from. | ||
| 436 | * @param node_id - Node id of the voice this command is generated for. | ||
| 437 | */ | ||
| 438 | void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); | ||
| 439 | |||
| 440 | /// Command list buffer generated commands will be added to | ||
| 441 | std::span<u8> command_list{}; | ||
| 442 | /// Input sample count, unused | ||
| 443 | u32 sample_count{}; | ||
| 444 | /// Input sample rate, unused | ||
| 445 | u32 sample_rate{}; | ||
| 446 | /// Current size of the command buffer | ||
| 447 | u64 size{}; | ||
| 448 | /// Current number of commands added | ||
| 449 | u32 count{}; | ||
| 450 | /// Current estimated processing time for all commands | ||
| 451 | u32 estimated_process_time{}; | ||
| 452 | /// Used for mapping buffers for the AudioRenderer | ||
| 453 | MemoryPoolInfo* memory_pool{}; | ||
| 454 | /// Used for estimating command process times | ||
| 455 | ICommandProcessingTimeEstimator* time_estimator{}; | ||
| 456 | /// Used to check which rendering features are currently enabled | ||
| 457 | BehaviorInfo* behavior{}; | ||
| 458 | |||
| 459 | private: | ||
| 460 | template <typename T, CommandId Id> | ||
| 461 | T& GenerateStart(const s32 node_id); | ||
| 462 | template <typename T> | ||
| 463 | void GenerateEnd(T& cmd); | ||
| 464 | }; | ||
| 465 | |||
| 466 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/common/audio_renderer_parameter.h" | ||
| 5 | #include "audio_core/renderer/behavior/behavior_info.h" | ||
| 6 | #include "audio_core/renderer/command/command_buffer.h" | ||
| 7 | #include "audio_core/renderer/command/command_generator.h" | ||
| 8 | #include "audio_core/renderer/command/command_list_header.h" | ||
| 9 | #include "audio_core/renderer/effect/aux_.h" | ||
| 10 | #include "audio_core/renderer/effect/biquad_filter.h" | ||
| 11 | #include "audio_core/renderer/effect/buffer_mixer.h" | ||
| 12 | #include "audio_core/renderer/effect/capture.h" | ||
| 13 | #include "audio_core/renderer/effect/effect_context.h" | ||
| 14 | #include "audio_core/renderer/effect/light_limiter.h" | ||
| 15 | #include "audio_core/renderer/mix/mix_context.h" | ||
| 16 | #include "audio_core/renderer/performance/detail_aspect.h" | ||
| 17 | #include "audio_core/renderer/performance/entry_aspect.h" | ||
| 18 | #include "audio_core/renderer/sink/device_sink_info.h" | ||
| 19 | #include "audio_core/renderer/sink/sink_context.h" | ||
| 20 | #include "audio_core/renderer/splitter/splitter_context.h" | ||
| 21 | #include "audio_core/renderer/voice/voice_context.h" | ||
| 22 | #include "common/alignment.h" | ||
| 23 | |||
| 24 | namespace AudioCore::AudioRenderer { | ||
| 25 | |||
| 26 | CommandGenerator::CommandGenerator(CommandBuffer& command_buffer_, | ||
| 27 | const CommandListHeader& command_list_header_, | ||
| 28 | const AudioRendererSystemContext& render_context_, | ||
| 29 | VoiceContext& voice_context_, MixContext& mix_context_, | ||
| 30 | EffectContext& effect_context_, SinkContext& sink_context_, | ||
| 31 | SplitterContext& splitter_context_, | ||
| 32 | PerformanceManager* performance_manager_) | ||
| 33 | : command_buffer{command_buffer_}, command_header{command_list_header_}, | ||
| 34 | render_context{render_context_}, voice_context{voice_context_}, mix_context{mix_context_}, | ||
| 35 | effect_context{effect_context_}, sink_context{sink_context_}, | ||
| 36 | splitter_context{splitter_context_}, performance_manager{performance_manager_} { | ||
| 37 | command_buffer.GenerateClearMixCommand(InvalidNodeId); | ||
| 38 | } | ||
| 39 | |||
| 40 | void CommandGenerator::GenerateDataSourceCommand(VoiceInfo& voice_info, | ||
| 41 | const VoiceState& voice_state, const s8 channel) { | ||
| 42 | if (voice_info.mix_id == UnusedMixId) { | ||
| 43 | if (voice_info.splitter_id != UnusedSplitterId) { | ||
| 44 | auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, 0)}; | ||
| 45 | u32 dest_id{0}; | ||
| 46 | while (destination != nullptr) { | ||
| 47 | if (destination->IsConfigured()) { | ||
| 48 | auto mix_id{destination->GetMixId()}; | ||
| 49 | if (mix_id < mix_context.GetCount()) { | ||
| 50 | auto mix_info{mix_context.GetInfo(mix_id)}; | ||
| 51 | command_buffer.GenerateDepopPrepareCommand( | ||
| 52 | voice_info.node_id, voice_state, render_context.depop_buffer, | ||
| 53 | mix_info->buffer_count, mix_info->buffer_offset, | ||
| 54 | voice_info.was_playing); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | dest_id++; | ||
| 58 | destination = splitter_context.GetDesintationData(voice_info.splitter_id, dest_id); | ||
| 59 | } | ||
| 60 | } | ||
| 61 | } else { | ||
| 62 | auto mix_info{mix_context.GetInfo(voice_info.mix_id)}; | ||
| 63 | command_buffer.GenerateDepopPrepareCommand( | ||
| 64 | voice_info.node_id, voice_state, render_context.depop_buffer, mix_info->buffer_count, | ||
| 65 | mix_info->buffer_offset, voice_info.was_playing); | ||
| 66 | } | ||
| 67 | |||
| 68 | if (voice_info.was_playing) { | ||
| 69 | return; | ||
| 70 | } | ||
| 71 | |||
| 72 | if (render_context.behavior->IsWaveBufferVer2Supported()) { | ||
| 73 | switch (voice_info.sample_format) { | ||
| 74 | case SampleFormat::PcmInt16: | ||
| 75 | command_buffer.GeneratePcmInt16Version2Command( | ||
| 76 | voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count, | ||
| 77 | channel); | ||
| 78 | break; | ||
| 79 | case SampleFormat::PcmFloat: | ||
| 80 | command_buffer.GeneratePcmFloatVersion2Command( | ||
| 81 | voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count, | ||
| 82 | channel); | ||
| 83 | break; | ||
| 84 | case SampleFormat::Adpcm: | ||
| 85 | command_buffer.GenerateAdpcmVersion2Command(voice_info.node_id, voice_info, voice_state, | ||
| 86 | render_context.mix_buffer_count, channel); | ||
| 87 | break; | ||
| 88 | default: | ||
| 89 | LOG_ERROR(Service_Audio, "Invalid SampleFormat {}", | ||
| 90 | static_cast<u32>(voice_info.sample_format)); | ||
| 91 | break; | ||
| 92 | } | ||
| 93 | } else { | ||
| 94 | switch (voice_info.sample_format) { | ||
| 95 | case SampleFormat::PcmInt16: | ||
| 96 | command_buffer.GeneratePcmInt16Version1Command( | ||
| 97 | voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state, | ||
| 98 | render_context.mix_buffer_count, channel); | ||
| 99 | break; | ||
| 100 | case SampleFormat::PcmFloat: | ||
| 101 | command_buffer.GeneratePcmFloatVersion1Command( | ||
| 102 | voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state, | ||
| 103 | render_context.mix_buffer_count, channel); | ||
| 104 | break; | ||
| 105 | case SampleFormat::Adpcm: | ||
| 106 | command_buffer.GenerateAdpcmVersion1Command( | ||
| 107 | voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state, | ||
| 108 | render_context.mix_buffer_count, channel); | ||
| 109 | break; | ||
| 110 | default: | ||
| 111 | LOG_ERROR(Service_Audio, "Invalid SampleFormat {}", | ||
| 112 | static_cast<u32>(voice_info.sample_format)); | ||
| 113 | break; | ||
| 114 | } | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | void CommandGenerator::GenerateVoiceMixCommand(std::span<const f32> mix_volumes, | ||
| 119 | std::span<const f32> prev_mix_volumes, | ||
| 120 | const VoiceState& voice_state, s16 output_index, | ||
| 121 | const s16 buffer_count, const s16 input_index, | ||
| 122 | const s32 node_id) { | ||
| 123 | u8 precision{15}; | ||
| 124 | if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { | ||
| 125 | precision = 23; | ||
| 126 | } | ||
| 127 | |||
| 128 | if (buffer_count > 8) { | ||
| 129 | const auto prev_samples{render_context.memory_pool_info->Translate( | ||
| 130 | CpuAddr(voice_state.previous_samples.data()), buffer_count * sizeof(s32))}; | ||
| 131 | command_buffer.GenerateMixRampGroupedCommand(node_id, buffer_count, input_index, | ||
| 132 | output_index, mix_volumes, prev_mix_volumes, | ||
| 133 | prev_samples, precision); | ||
| 134 | } else { | ||
| 135 | for (s16 i = 0; i < buffer_count; i++, output_index++) { | ||
| 136 | const auto prev_samples{render_context.memory_pool_info->Translate( | ||
| 137 | CpuAddr(&voice_state.previous_samples[i]), sizeof(s32))}; | ||
| 138 | |||
| 139 | command_buffer.GenerateMixRampCommand(node_id, buffer_count, input_index, output_index, | ||
| 140 | mix_volumes[i], prev_mix_volumes[i], prev_samples, | ||
| 141 | precision); | ||
| 142 | } | ||
| 143 | } | ||
| 144 | } | ||
| 145 | |||
| 146 | void CommandGenerator::GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, | ||
| 147 | const VoiceState& voice_state, | ||
| 148 | const s16 buffer_count, const s8 channel, | ||
| 149 | const s32 node_id) { | ||
| 150 | const bool both_biquads_enabled{voice_info.biquads[0].enabled && voice_info.biquads[1].enabled}; | ||
| 151 | const auto use_float_processing{render_context.behavior->UseBiquadFilterFloatProcessing()}; | ||
| 152 | |||
| 153 | if (both_biquads_enabled && render_context.behavior->UseMultiTapBiquadFilterProcessing() && | ||
| 154 | use_float_processing) { | ||
| 155 | command_buffer.GenerateMultitapBiquadFilterCommand(node_id, voice_info, voice_state, | ||
| 156 | buffer_count, channel); | ||
| 157 | } else { | ||
| 158 | for (u32 i = 0; i < MaxBiquadFilters; i++) { | ||
| 159 | if (voice_info.biquads[i].enabled) { | ||
| 160 | command_buffer.GenerateBiquadFilterCommand(node_id, voice_info, voice_state, | ||
| 161 | buffer_count, channel, i, | ||
| 162 | use_float_processing); | ||
| 163 | } | ||
| 164 | } | ||
| 165 | } | ||
| 166 | } | ||
| 167 | |||
| 168 | void CommandGenerator::GenerateVoiceCommand(VoiceInfo& voice_info) { | ||
| 169 | u8 precision{15}; | ||
| 170 | if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { | ||
| 171 | precision = 23; | ||
| 172 | } | ||
| 173 | |||
| 174 | for (s8 channel = 0; channel < voice_info.channel_count; channel++) { | ||
| 175 | const auto resource_id{voice_info.channel_resource_ids[channel]}; | ||
| 176 | auto& voice_state{voice_context.GetDspSharedState(resource_id)}; | ||
| 177 | auto& channel_resource{voice_context.GetChannelResource(resource_id)}; | ||
| 178 | |||
| 179 | PerformanceDetailType detail_type{PerformanceDetailType::Invalid}; | ||
| 180 | switch (voice_info.sample_format) { | ||
| 181 | case SampleFormat::PcmInt16: | ||
| 182 | detail_type = PerformanceDetailType::Unk1; | ||
| 183 | break; | ||
| 184 | case SampleFormat::PcmFloat: | ||
| 185 | detail_type = PerformanceDetailType::Unk10; | ||
| 186 | break; | ||
| 187 | default: | ||
| 188 | detail_type = PerformanceDetailType::Unk2; | ||
| 189 | break; | ||
| 190 | } | ||
| 191 | |||
| 192 | DetailAspect data_source_detail(*this, PerformanceEntryType::Voice, voice_info.node_id, | ||
| 193 | detail_type); | ||
| 194 | GenerateDataSourceCommand(voice_info, voice_state, channel); | ||
| 195 | |||
| 196 | if (data_source_detail.initialized) { | ||
| 197 | command_buffer.GeneratePerformanceCommand(data_source_detail.node_id, | ||
| 198 | PerformanceState::Stop, | ||
| 199 | data_source_detail.performance_entry_address); | ||
| 200 | } | ||
| 201 | |||
| 202 | if (voice_info.was_playing) { | ||
| 203 | voice_info.prev_volume = 0.0f; | ||
| 204 | continue; | ||
| 205 | } | ||
| 206 | |||
| 207 | if (!voice_info.HasAnyConnection()) { | ||
| 208 | continue; | ||
| 209 | } | ||
| 210 | |||
| 211 | DetailAspect biquad_detail_aspect(*this, PerformanceEntryType::Voice, voice_info.node_id, | ||
| 212 | PerformanceDetailType::Unk4); | ||
| 213 | GenerateBiquadFilterCommandForVoice( | ||
| 214 | voice_info, voice_state, render_context.mix_buffer_count, channel, voice_info.node_id); | ||
| 215 | |||
| 216 | if (biquad_detail_aspect.initialized) { | ||
| 217 | command_buffer.GeneratePerformanceCommand( | ||
| 218 | biquad_detail_aspect.node_id, PerformanceState::Stop, | ||
| 219 | biquad_detail_aspect.performance_entry_address); | ||
| 220 | } | ||
| 221 | |||
| 222 | DetailAspect volume_ramp_detail_aspect(*this, PerformanceEntryType::Voice, | ||
| 223 | voice_info.node_id, PerformanceDetailType::Unk3); | ||
| 224 | command_buffer.GenerateVolumeRampCommand( | ||
| 225 | voice_info.node_id, voice_info, render_context.mix_buffer_count + channel, precision); | ||
| 226 | if (volume_ramp_detail_aspect.initialized) { | ||
| 227 | command_buffer.GeneratePerformanceCommand( | ||
| 228 | volume_ramp_detail_aspect.node_id, PerformanceState::Stop, | ||
| 229 | volume_ramp_detail_aspect.performance_entry_address); | ||
| 230 | } | ||
| 231 | |||
| 232 | voice_info.prev_volume = voice_info.volume; | ||
| 233 | |||
| 234 | if (voice_info.mix_id == UnusedMixId) { | ||
| 235 | if (voice_info.splitter_id != UnusedSplitterId) { | ||
| 236 | auto i{channel}; | ||
| 237 | auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, i)}; | ||
| 238 | while (destination != nullptr) { | ||
| 239 | if (destination->IsConfigured()) { | ||
| 240 | const auto mix_id{destination->GetMixId()}; | ||
| 241 | if (mix_id < mix_context.GetCount() && | ||
| 242 | static_cast<s32>(mix_id) != UnusedSplitterId) { | ||
| 243 | auto mix_info{mix_context.GetInfo(mix_id)}; | ||
| 244 | GenerateVoiceMixCommand( | ||
| 245 | destination->GetMixVolume(), destination->GetMixVolumePrev(), | ||
| 246 | voice_state, mix_info->buffer_offset, mix_info->buffer_count, | ||
| 247 | render_context.mix_buffer_count + channel, voice_info.node_id); | ||
| 248 | destination->MarkAsNeedToUpdateInternalState(); | ||
| 249 | } | ||
| 250 | } | ||
| 251 | i += voice_info.channel_count; | ||
| 252 | destination = splitter_context.GetDesintationData(voice_info.splitter_id, i); | ||
| 253 | } | ||
| 254 | } | ||
| 255 | } else { | ||
| 256 | DetailAspect volume_mix_detail_aspect(*this, PerformanceEntryType::Voice, | ||
| 257 | voice_info.node_id, PerformanceDetailType::Unk3); | ||
| 258 | auto mix_info{mix_context.GetInfo(voice_info.mix_id)}; | ||
| 259 | GenerateVoiceMixCommand(channel_resource.mix_volumes, channel_resource.prev_mix_volumes, | ||
| 260 | voice_state, mix_info->buffer_offset, mix_info->buffer_count, | ||
| 261 | render_context.mix_buffer_count + channel, voice_info.node_id); | ||
| 262 | if (volume_mix_detail_aspect.initialized) { | ||
| 263 | command_buffer.GeneratePerformanceCommand( | ||
| 264 | volume_mix_detail_aspect.node_id, PerformanceState::Stop, | ||
| 265 | volume_mix_detail_aspect.performance_entry_address); | ||
| 266 | } | ||
| 267 | |||
| 268 | channel_resource.prev_mix_volumes = channel_resource.mix_volumes; | ||
| 269 | } | ||
| 270 | voice_info.biquad_initialized[0] = voice_info.biquads[0].enabled; | ||
| 271 | voice_info.biquad_initialized[1] = voice_info.biquads[1].enabled; | ||
| 272 | } | ||
| 273 | } | ||
| 274 | |||
| 275 | void CommandGenerator::GenerateVoiceCommands() { | ||
| 276 | const auto voice_count{voice_context.GetCount()}; | ||
| 277 | |||
| 278 | for (u32 i = 0; i < voice_count; i++) { | ||
| 279 | auto sorted_info{voice_context.GetSortedInfo(i)}; | ||
| 280 | |||
| 281 | if (sorted_info->ShouldSkip() || !sorted_info->UpdateForCommandGeneration(voice_context)) { | ||
| 282 | continue; | ||
| 283 | } | ||
| 284 | |||
| 285 | EntryAspect voice_entry_aspect(*this, PerformanceEntryType::Voice, sorted_info->node_id); | ||
| 286 | |||
| 287 | GenerateVoiceCommand(*sorted_info); | ||
| 288 | |||
| 289 | if (voice_entry_aspect.initialized) { | ||
| 290 | command_buffer.GeneratePerformanceCommand(voice_entry_aspect.node_id, | ||
| 291 | PerformanceState::Stop, | ||
| 292 | voice_entry_aspect.performance_entry_address); | ||
| 293 | } | ||
| 294 | } | ||
| 295 | |||
| 296 | splitter_context.UpdateInternalState(); | ||
| 297 | } | ||
| 298 | |||
| 299 | void CommandGenerator::GenerateBufferMixerCommand(const s16 buffer_offset, | ||
| 300 | EffectInfoBase& effect_info, const s32 node_id) { | ||
| 301 | u8 precision{15}; | ||
| 302 | if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { | ||
| 303 | precision = 23; | ||
| 304 | } | ||
| 305 | |||
| 306 | if (effect_info.IsEnabled()) { | ||
| 307 | const auto& parameter{ | ||
| 308 | *reinterpret_cast<BufferMixerInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||
| 309 | for (u32 i = 0; i < parameter.mix_count; i++) { | ||
| 310 | if (parameter.volumes[i] != 0.0f) { | ||
| 311 | command_buffer.GenerateMixCommand(node_id, buffer_offset + parameter.inputs[i], | ||
| 312 | buffer_offset + parameter.outputs[i], | ||
| 313 | buffer_offset, parameter.volumes[i], precision); | ||
| 314 | } | ||
| 315 | } | ||
| 316 | } | ||
| 317 | } | ||
| 318 | |||
| 319 | void CommandGenerator::GenerateDelayCommand(const s16 buffer_offset, EffectInfoBase& effect_info, | ||
| 320 | const s32 node_id) { | ||
| 321 | command_buffer.GenerateDelayCommand(node_id, effect_info, buffer_offset); | ||
| 322 | } | ||
| 323 | |||
| 324 | void CommandGenerator::GenerateReverbCommand(const s16 buffer_offset, EffectInfoBase& effect_info, | ||
| 325 | const s32 node_id, | ||
| 326 | const bool long_size_pre_delay_supported) { | ||
| 327 | command_buffer.GenerateReverbCommand(node_id, effect_info, buffer_offset, | ||
| 328 | long_size_pre_delay_supported); | ||
| 329 | } | ||
| 330 | |||
| 331 | void CommandGenerator::GenerateI3dl2ReverbEffectCommand(const s16 buffer_offset, | ||
| 332 | EffectInfoBase& effect_info, | ||
| 333 | const s32 node_id) { | ||
| 334 | command_buffer.GenerateI3dl2ReverbCommand(node_id, effect_info, buffer_offset); | ||
| 335 | } | ||
| 336 | |||
| 337 | void CommandGenerator::GenerateAuxCommand(const s16 buffer_offset, EffectInfoBase& effect_info, | ||
| 338 | const s32 node_id) { | ||
| 339 | |||
| 340 | if (effect_info.IsEnabled()) { | ||
| 341 | effect_info.GetWorkbuffer(0); | ||
| 342 | effect_info.GetWorkbuffer(1); | ||
| 343 | } | ||
| 344 | |||
| 345 | if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) { | ||
| 346 | const auto& parameter{ | ||
| 347 | *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||
| 348 | auto channel_index{parameter.mix_buffer_count - 1}; | ||
| 349 | u32 write_offset{0}; | ||
| 350 | for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) { | ||
| 351 | auto new_update_count{command_header.sample_count + write_offset}; | ||
| 352 | const auto update_count{channel_index > 0 ? 0 : new_update_count}; | ||
| 353 | command_buffer.GenerateAuxCommand(node_id, effect_info, parameter.inputs[i], | ||
| 354 | parameter.outputs[i], buffer_offset, update_count, | ||
| 355 | parameter.count_max, write_offset); | ||
| 356 | write_offset = new_update_count; | ||
| 357 | } | ||
| 358 | } | ||
| 359 | } | ||
| 360 | |||
| 361 | void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset, | ||
| 362 | EffectInfoBase& effect_info, | ||
| 363 | const s32 node_id) { | ||
| 364 | const auto& parameter{ | ||
| 365 | *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||
| 366 | if (effect_info.IsEnabled()) { | ||
| 367 | bool needs_init{false}; | ||
| 368 | |||
| 369 | switch (parameter.state) { | ||
| 370 | case EffectInfoBase::ParameterState::Initialized: | ||
| 371 | needs_init = true; | ||
| 372 | break; | ||
| 373 | case EffectInfoBase::ParameterState::Updating: | ||
| 374 | case EffectInfoBase::ParameterState::Updated: | ||
| 375 | if (render_context.behavior->IsBiquadFilterEffectStateClearBugFixed()) { | ||
| 376 | needs_init = false; | ||
| 377 | } else { | ||
| 378 | needs_init = parameter.state == EffectInfoBase::ParameterState::Updating; | ||
| 379 | } | ||
| 380 | break; | ||
| 381 | default: | ||
| 382 | LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}", | ||
| 383 | static_cast<u32>(parameter.state)); | ||
| 384 | break; | ||
| 385 | } | ||
| 386 | |||
| 387 | for (s8 channel = 0; channel < parameter.channel_count; channel++) { | ||
| 388 | command_buffer.GenerateBiquadFilterCommand( | ||
| 389 | node_id, effect_info, buffer_offset, channel, needs_init, | ||
| 390 | render_context.behavior->UseBiquadFilterFloatProcessing()); | ||
| 391 | } | ||
| 392 | } else { | ||
| 393 | for (s8 channel = 0; channel < parameter.channel_count; channel++) { | ||
| 394 | command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset, | ||
| 395 | channel); | ||
| 396 | } | ||
| 397 | } | ||
| 398 | } | ||
| 399 | |||
| 400 | void CommandGenerator::GenerateLightLimiterEffectCommand(const s16 buffer_offset, | ||
| 401 | EffectInfoBase& effect_info, | ||
| 402 | const s32 node_id, | ||
| 403 | const u32 effect_index) { | ||
| 404 | |||
| 405 | const auto& state{*reinterpret_cast<LightLimiterInfo::State*>(effect_info.GetStateBuffer())}; | ||
| 406 | |||
| 407 | if (render_context.behavior->IsEffectInfoVersion2Supported()) { | ||
| 408 | const auto& parameter{ | ||
| 409 | *reinterpret_cast<LightLimiterInfo::ParameterVersion2*>(effect_info.GetParameter())}; | ||
| 410 | const auto& result_state{*reinterpret_cast<LightLimiterInfo::StatisticsInternal*>( | ||
| 411 | &effect_context.GetDspSharedResultState(effect_index))}; | ||
| 412 | command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, result_state, | ||
| 413 | state, effect_info.IsEnabled(), | ||
| 414 | effect_info.GetWorkbuffer(-1)); | ||
| 415 | } else { | ||
| 416 | const auto& parameter{ | ||
| 417 | *reinterpret_cast<LightLimiterInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||
| 418 | command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, state, | ||
| 419 | effect_info.IsEnabled(), | ||
| 420 | effect_info.GetWorkbuffer(-1)); | ||
| 421 | } | ||
| 422 | } | ||
| 423 | |||
| 424 | void CommandGenerator::GenerateCaptureCommand(const s16 buffer_offset, EffectInfoBase& effect_info, | ||
| 425 | const s32 node_id) { | ||
| 426 | if (effect_info.IsEnabled()) { | ||
| 427 | effect_info.GetWorkbuffer(0); | ||
| 428 | } | ||
| 429 | |||
| 430 | if (effect_info.GetSendBuffer()) { | ||
| 431 | const auto& parameter{ | ||
| 432 | *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||
| 433 | auto channel_index{parameter.mix_buffer_count - 1}; | ||
| 434 | u32 write_offset{0}; | ||
| 435 | for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) { | ||
| 436 | auto new_update_count{command_header.sample_count + write_offset}; | ||
| 437 | const auto update_count{channel_index > 0 ? 0 : new_update_count}; | ||
| 438 | command_buffer.GenerateCaptureCommand(node_id, effect_info, parameter.inputs[i], | ||
| 439 | parameter.outputs[i], buffer_offset, update_count, | ||
| 440 | parameter.count_max, write_offset); | ||
| 441 | write_offset = new_update_count; | ||
| 442 | } | ||
| 443 | } | ||
| 444 | } | ||
| 445 | |||
| 446 | void CommandGenerator::GenerateCompressorCommand(const s16 buffer_offset, | ||
| 447 | EffectInfoBase& effect_info, const s32 node_id) { | ||
| 448 | command_buffer.GenerateCompressorCommand(buffer_offset, effect_info, node_id); | ||
| 449 | } | ||
| 450 | |||
| 451 | void CommandGenerator::GenerateEffectCommand(MixInfo& mix_info) { | ||
| 452 | const auto effect_count{effect_context.GetCount()}; | ||
| 453 | for (u32 i = 0; i < effect_count; i++) { | ||
| 454 | const auto effect_index{mix_info.effect_order_buffer[i]}; | ||
| 455 | if (effect_index == -1) { | ||
| 456 | break; | ||
| 457 | } | ||
| 458 | |||
| 459 | auto& effect_info = effect_context.GetInfo(effect_index); | ||
| 460 | if (effect_info.ShouldSkip()) { | ||
| 461 | continue; | ||
| 462 | } | ||
| 463 | |||
| 464 | const auto entry_type{mix_info.mix_id == FinalMixId ? PerformanceEntryType::FinalMix | ||
| 465 | : PerformanceEntryType::SubMix}; | ||
| 466 | |||
| 467 | switch (effect_info.GetType()) { | ||
| 468 | case EffectInfoBase::Type::Mix: { | ||
| 469 | DetailAspect mix_detail_aspect(*this, entry_type, mix_info.node_id, | ||
| 470 | PerformanceDetailType::Unk5); | ||
| 471 | GenerateBufferMixerCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); | ||
| 472 | if (mix_detail_aspect.initialized) { | ||
| 473 | command_buffer.GeneratePerformanceCommand( | ||
| 474 | mix_detail_aspect.node_id, PerformanceState::Stop, | ||
| 475 | mix_detail_aspect.performance_entry_address); | ||
| 476 | } | ||
| 477 | } break; | ||
| 478 | |||
| 479 | case EffectInfoBase::Type::Aux: { | ||
| 480 | DetailAspect aux_detail_aspect(*this, entry_type, mix_info.node_id, | ||
| 481 | PerformanceDetailType::Unk7); | ||
| 482 | GenerateAuxCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); | ||
| 483 | if (aux_detail_aspect.initialized) { | ||
| 484 | command_buffer.GeneratePerformanceCommand( | ||
| 485 | aux_detail_aspect.node_id, PerformanceState::Stop, | ||
| 486 | aux_detail_aspect.performance_entry_address); | ||
| 487 | } | ||
| 488 | } break; | ||
| 489 | |||
| 490 | case EffectInfoBase::Type::Delay: { | ||
| 491 | DetailAspect delay_detail_aspect(*this, entry_type, mix_info.node_id, | ||
| 492 | PerformanceDetailType::Unk6); | ||
| 493 | GenerateDelayCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); | ||
| 494 | if (delay_detail_aspect.initialized) { | ||
| 495 | command_buffer.GeneratePerformanceCommand( | ||
| 496 | delay_detail_aspect.node_id, PerformanceState::Stop, | ||
| 497 | delay_detail_aspect.performance_entry_address); | ||
| 498 | } | ||
| 499 | } break; | ||
| 500 | |||
| 501 | case EffectInfoBase::Type::Reverb: { | ||
| 502 | DetailAspect reverb_detail_aspect(*this, entry_type, mix_info.node_id, | ||
| 503 | PerformanceDetailType::Unk8); | ||
| 504 | GenerateReverbCommand(mix_info.buffer_offset, effect_info, mix_info.node_id, | ||
| 505 | render_context.behavior->IsLongSizePreDelaySupported()); | ||
| 506 | if (reverb_detail_aspect.initialized) { | ||
| 507 | command_buffer.GeneratePerformanceCommand( | ||
| 508 | reverb_detail_aspect.node_id, PerformanceState::Stop, | ||
| 509 | reverb_detail_aspect.performance_entry_address); | ||
| 510 | } | ||
| 511 | } break; | ||
| 512 | |||
| 513 | case EffectInfoBase::Type::I3dl2Reverb: { | ||
| 514 | DetailAspect i3dl2_detail_aspect(*this, entry_type, mix_info.node_id, | ||
| 515 | PerformanceDetailType::Unk9); | ||
| 516 | GenerateI3dl2ReverbEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); | ||
| 517 | if (i3dl2_detail_aspect.initialized) { | ||
| 518 | command_buffer.GeneratePerformanceCommand( | ||
| 519 | i3dl2_detail_aspect.node_id, PerformanceState::Stop, | ||
| 520 | i3dl2_detail_aspect.performance_entry_address); | ||
| 521 | } | ||
| 522 | } break; | ||
| 523 | |||
| 524 | case EffectInfoBase::Type::BiquadFilter: { | ||
| 525 | DetailAspect biquad_detail_aspect(*this, entry_type, mix_info.node_id, | ||
| 526 | PerformanceDetailType::Unk4); | ||
| 527 | GenerateBiquadFilterEffectCommand(mix_info.buffer_offset, effect_info, | ||
| 528 | mix_info.node_id); | ||
| 529 | if (biquad_detail_aspect.initialized) { | ||
| 530 | command_buffer.GeneratePerformanceCommand( | ||
| 531 | biquad_detail_aspect.node_id, PerformanceState::Stop, | ||
| 532 | biquad_detail_aspect.performance_entry_address); | ||
| 533 | } | ||
| 534 | } break; | ||
| 535 | |||
| 536 | case EffectInfoBase::Type::LightLimiter: { | ||
| 537 | DetailAspect light_limiter_detail_aspect(*this, entry_type, mix_info.node_id, | ||
| 538 | PerformanceDetailType::Unk11); | ||
| 539 | GenerateLightLimiterEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id, | ||
| 540 | effect_index); | ||
| 541 | if (light_limiter_detail_aspect.initialized) { | ||
| 542 | command_buffer.GeneratePerformanceCommand( | ||
| 543 | light_limiter_detail_aspect.node_id, PerformanceState::Stop, | ||
| 544 | light_limiter_detail_aspect.performance_entry_address); | ||
| 545 | } | ||
| 546 | } break; | ||
| 547 | |||
| 548 | case EffectInfoBase::Type::Capture: { | ||
| 549 | DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id, | ||
| 550 | PerformanceDetailType::Unk12); | ||
| 551 | GenerateCaptureCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); | ||
| 552 | if (capture_detail_aspect.initialized) { | ||
| 553 | command_buffer.GeneratePerformanceCommand( | ||
| 554 | capture_detail_aspect.node_id, PerformanceState::Stop, | ||
| 555 | capture_detail_aspect.performance_entry_address); | ||
| 556 | } | ||
| 557 | } break; | ||
| 558 | |||
| 559 | case EffectInfoBase::Type::Compressor: { | ||
| 560 | DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id, | ||
| 561 | PerformanceDetailType::Unk13); | ||
| 562 | GenerateCompressorCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); | ||
| 563 | if (capture_detail_aspect.initialized) { | ||
| 564 | command_buffer.GeneratePerformanceCommand( | ||
| 565 | capture_detail_aspect.node_id, PerformanceState::Stop, | ||
| 566 | capture_detail_aspect.performance_entry_address); | ||
| 567 | } | ||
| 568 | } break; | ||
| 569 | |||
| 570 | default: | ||
| 571 | LOG_ERROR(Service_Audio, "Invalid effect type {}", | ||
| 572 | static_cast<u32>(effect_info.GetType())); | ||
| 573 | break; | ||
| 574 | } | ||
| 575 | |||
| 576 | effect_info.UpdateForCommandGeneration(); | ||
| 577 | } | ||
| 578 | } | ||
| 579 | |||
| 580 | void CommandGenerator::GenerateMixCommands(MixInfo& mix_info) { | ||
| 581 | u8 precision{15}; | ||
| 582 | if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { | ||
| 583 | precision = 23; | ||
| 584 | } | ||
| 585 | |||
| 586 | if (!mix_info.HasAnyConnection()) { | ||
| 587 | return; | ||
| 588 | } | ||
| 589 | |||
| 590 | if (mix_info.dst_mix_id == UnusedMixId) { | ||
| 591 | if (mix_info.dst_splitter_id != UnusedSplitterId) { | ||
| 592 | s16 dest_id{0}; | ||
| 593 | auto destination{ | ||
| 594 | splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id)}; | ||
| 595 | while (destination != nullptr) { | ||
| 596 | if (destination->IsConfigured()) { | ||
| 597 | auto splitter_mix_id{destination->GetMixId()}; | ||
| 598 | if (splitter_mix_id < mix_context.GetCount()) { | ||
| 599 | auto splitter_mix_info{mix_context.GetInfo(splitter_mix_id)}; | ||
| 600 | const s16 input_index{static_cast<s16>(mix_info.buffer_offset + | ||
| 601 | (dest_id % mix_info.buffer_count))}; | ||
| 602 | for (s16 i = 0; i < splitter_mix_info->buffer_count; i++) { | ||
| 603 | auto volume{mix_info.volume * destination->GetMixVolume(i)}; | ||
| 604 | if (volume != 0.0f) { | ||
| 605 | command_buffer.GenerateMixCommand( | ||
| 606 | mix_info.node_id, input_index, | ||
| 607 | splitter_mix_info->buffer_offset + i, mix_info.buffer_offset, | ||
| 608 | volume, precision); | ||
| 609 | } | ||
| 610 | } | ||
| 611 | } | ||
| 612 | } | ||
| 613 | dest_id++; | ||
| 614 | destination = | ||
| 615 | splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id); | ||
| 616 | } | ||
| 617 | } | ||
| 618 | } else { | ||
| 619 | auto dest_mix_info{mix_context.GetInfo(mix_info.dst_mix_id)}; | ||
| 620 | for (s16 i = 0; i < mix_info.buffer_count; i++) { | ||
| 621 | for (s16 j = 0; j < dest_mix_info->buffer_count; j++) { | ||
| 622 | auto volume{mix_info.volume * mix_info.mix_volumes[i][j]}; | ||
| 623 | if (volume != 0.0f) { | ||
| 624 | command_buffer.GenerateMixCommand(mix_info.node_id, mix_info.buffer_offset + i, | ||
| 625 | dest_mix_info->buffer_offset + j, | ||
| 626 | mix_info.buffer_offset, volume, precision); | ||
| 627 | } | ||
| 628 | } | ||
| 629 | } | ||
| 630 | } | ||
| 631 | } | ||
| 632 | |||
| 633 | void CommandGenerator::GenerateSubMixCommand(MixInfo& mix_info) { | ||
| 634 | command_buffer.GenerateDepopForMixBuffersCommand(mix_info.node_id, mix_info, | ||
| 635 | render_context.depop_buffer); | ||
| 636 | GenerateEffectCommand(mix_info); | ||
| 637 | |||
| 638 | DetailAspect mix_detail_aspect(*this, PerformanceEntryType::SubMix, mix_info.node_id, | ||
| 639 | PerformanceDetailType::Unk5); | ||
| 640 | |||
| 641 | GenerateMixCommands(mix_info); | ||
| 642 | |||
| 643 | if (mix_detail_aspect.initialized) { | ||
| 644 | command_buffer.GeneratePerformanceCommand(mix_detail_aspect.node_id, PerformanceState::Stop, | ||
| 645 | mix_detail_aspect.performance_entry_address); | ||
| 646 | } | ||
| 647 | } | ||
| 648 | |||
| 649 | void CommandGenerator::GenerateSubMixCommands() { | ||
| 650 | const auto submix_count{mix_context.GetCount()}; | ||
| 651 | for (s32 i = 0; i < submix_count; i++) { | ||
| 652 | auto sorted_info{mix_context.GetSortedInfo(i)}; | ||
| 653 | if (!sorted_info->in_use || sorted_info->mix_id == FinalMixId) { | ||
| 654 | continue; | ||
| 655 | } | ||
| 656 | |||
| 657 | EntryAspect submix_entry_aspect(*this, PerformanceEntryType::SubMix, sorted_info->node_id); | ||
| 658 | |||
| 659 | GenerateSubMixCommand(*sorted_info); | ||
| 660 | |||
| 661 | if (submix_entry_aspect.initialized) { | ||
| 662 | command_buffer.GeneratePerformanceCommand( | ||
| 663 | submix_entry_aspect.node_id, PerformanceState::Stop, | ||
| 664 | submix_entry_aspect.performance_entry_address); | ||
| 665 | } | ||
| 666 | } | ||
| 667 | } | ||
| 668 | |||
| 669 | void CommandGenerator::GenerateFinalMixCommand() { | ||
| 670 | auto& final_mix_info{*mix_context.GetFinalMixInfo()}; | ||
| 671 | |||
| 672 | command_buffer.GenerateDepopForMixBuffersCommand(final_mix_info.node_id, final_mix_info, | ||
| 673 | render_context.depop_buffer); | ||
| 674 | GenerateEffectCommand(final_mix_info); | ||
| 675 | |||
| 676 | u8 precision{15}; | ||
| 677 | if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { | ||
| 678 | precision = 23; | ||
| 679 | } | ||
| 680 | |||
| 681 | for (s16 i = 0; i < final_mix_info.buffer_count; i++) { | ||
| 682 | DetailAspect volume_aspect(*this, PerformanceEntryType::FinalMix, final_mix_info.node_id, | ||
| 683 | PerformanceDetailType::Unk3); | ||
| 684 | command_buffer.GenerateVolumeCommand(final_mix_info.node_id, final_mix_info.buffer_offset, | ||
| 685 | i, final_mix_info.volume, precision); | ||
| 686 | if (volume_aspect.initialized) { | ||
| 687 | command_buffer.GeneratePerformanceCommand(volume_aspect.node_id, PerformanceState::Stop, | ||
| 688 | volume_aspect.performance_entry_address); | ||
| 689 | } | ||
| 690 | } | ||
| 691 | } | ||
| 692 | |||
| 693 | void CommandGenerator::GenerateFinalMixCommands() { | ||
| 694 | auto final_mix_info{mix_context.GetFinalMixInfo()}; | ||
| 695 | EntryAspect final_mix_entry(*this, PerformanceEntryType::FinalMix, final_mix_info->node_id); | ||
| 696 | GenerateFinalMixCommand(); | ||
| 697 | if (final_mix_entry.initialized) { | ||
| 698 | command_buffer.GeneratePerformanceCommand(final_mix_entry.node_id, PerformanceState::Stop, | ||
| 699 | final_mix_entry.performance_entry_address); | ||
| 700 | } | ||
| 701 | } | ||
| 702 | |||
| 703 | void CommandGenerator::GenerateSinkCommands() { | ||
| 704 | const auto sink_count{sink_context.GetCount()}; | ||
| 705 | |||
| 706 | for (u32 i = 0; i < sink_count; i++) { | ||
| 707 | auto sink_info{sink_context.GetInfo(i)}; | ||
| 708 | if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::DeviceSink) { | ||
| 709 | auto state{reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info->GetState())}; | ||
| 710 | if (command_header.sample_rate != TargetSampleRate && | ||
| 711 | state->upsampler_info == nullptr) { | ||
| 712 | auto device_state{sink_info->GetDeviceState()}; | ||
| 713 | device_state->upsampler_info = render_context.upsampler_manager->Allocate(); | ||
| 714 | } | ||
| 715 | |||
| 716 | EntryAspect device_sink_entry(*this, PerformanceEntryType::Sink, | ||
| 717 | sink_info->GetNodeId()); | ||
| 718 | auto final_mix{mix_context.GetFinalMixInfo()}; | ||
| 719 | GenerateSinkCommand(final_mix->buffer_offset, *sink_info); | ||
| 720 | |||
| 721 | if (device_sink_entry.initialized) { | ||
| 722 | command_buffer.GeneratePerformanceCommand( | ||
| 723 | device_sink_entry.node_id, PerformanceState::Stop, | ||
| 724 | device_sink_entry.performance_entry_address); | ||
| 725 | } | ||
| 726 | } | ||
| 727 | } | ||
| 728 | |||
| 729 | for (u32 i = 0; i < sink_count; i++) { | ||
| 730 | auto sink_info{sink_context.GetInfo(i)}; | ||
| 731 | if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::CircularBufferSink) { | ||
| 732 | EntryAspect circular_buffer_entry(*this, PerformanceEntryType::Sink, | ||
| 733 | sink_info->GetNodeId()); | ||
| 734 | auto final_mix{mix_context.GetFinalMixInfo()}; | ||
| 735 | GenerateSinkCommand(final_mix->buffer_offset, *sink_info); | ||
| 736 | |||
| 737 | if (circular_buffer_entry.initialized) { | ||
| 738 | command_buffer.GeneratePerformanceCommand( | ||
| 739 | circular_buffer_entry.node_id, PerformanceState::Stop, | ||
| 740 | circular_buffer_entry.performance_entry_address); | ||
| 741 | } | ||
| 742 | } | ||
| 743 | } | ||
| 744 | } | ||
| 745 | |||
| 746 | void CommandGenerator::GenerateSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) { | ||
| 747 | if (sink_info.ShouldSkip()) { | ||
| 748 | return; | ||
| 749 | } | ||
| 750 | |||
| 751 | switch (sink_info.GetType()) { | ||
| 752 | case SinkInfoBase::Type::DeviceSink: | ||
| 753 | GenerateDeviceSinkCommand(buffer_offset, sink_info); | ||
| 754 | break; | ||
| 755 | |||
| 756 | case SinkInfoBase::Type::CircularBufferSink: | ||
| 757 | command_buffer.GenerateCircularBufferSinkCommand(sink_info.GetNodeId(), sink_info, | ||
| 758 | buffer_offset); | ||
| 759 | break; | ||
| 760 | |||
| 761 | default: | ||
| 762 | LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(sink_info.GetType())); | ||
| 763 | break; | ||
| 764 | } | ||
| 765 | |||
| 766 | sink_info.UpdateForCommandGeneration(); | ||
| 767 | } | ||
| 768 | |||
| 769 | void CommandGenerator::GenerateDeviceSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) { | ||
| 770 | auto& parameter{ | ||
| 771 | *reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())}; | ||
| 772 | auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())}; | ||
| 773 | |||
| 774 | if (render_context.channels == 2 && parameter.downmix_enabled) { | ||
| 775 | command_buffer.GenerateDownMix6chTo2chCommand(InvalidNodeId, parameter.inputs, | ||
| 776 | buffer_offset, parameter.downmix_coeff); | ||
| 777 | } | ||
| 778 | |||
| 779 | if (state.upsampler_info != nullptr) { | ||
| 780 | command_buffer.GenerateUpsampleCommand( | ||
| 781 | InvalidNodeId, buffer_offset, *state.upsampler_info, parameter.input_count, | ||
| 782 | parameter.inputs, command_header.buffer_count, command_header.sample_count, | ||
| 783 | command_header.sample_rate); | ||
| 784 | } | ||
| 785 | |||
| 786 | command_buffer.GenerateDeviceSinkCommand(InvalidNodeId, buffer_offset, sink_info, | ||
| 787 | render_context.session_id, | ||
| 788 | command_header.samples_buffer); | ||
| 789 | } | ||
| 790 | |||
| 791 | void CommandGenerator::GeneratePerformanceCommand( | ||
| 792 | s32 node_id, PerformanceState state, const PerformanceEntryAddresses& entry_addresses) { | ||
| 793 | command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses); | ||
| 794 | } | ||
| 795 | |||
| 796 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <span> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/command/commands.h" | ||
| 9 | #include "audio_core/renderer/performance/performance_manager.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore { | ||
| 13 | struct AudioRendererSystemContext; | ||
| 14 | |||
| 15 | namespace AudioRenderer { | ||
| 16 | class CommandBuffer; | ||
| 17 | struct CommandListHeader; | ||
| 18 | class VoiceContext; | ||
| 19 | class MixContext; | ||
| 20 | class EffectContext; | ||
| 21 | class SplitterContext; | ||
| 22 | class SinkContext; | ||
| 23 | class BehaviorInfo; | ||
| 24 | class VoiceInfo; | ||
| 25 | struct VoiceState; | ||
| 26 | class MixInfo; | ||
| 27 | class SinkInfoBase; | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Generates all commands to build up a command list, which are sent to the AudioRender for | ||
| 31 | * processing. | ||
| 32 | */ | ||
| 33 | class CommandGenerator { | ||
| 34 | public: | ||
| 35 | explicit CommandGenerator(CommandBuffer& command_buffer, | ||
| 36 | const CommandListHeader& command_list_header, | ||
| 37 | const AudioRendererSystemContext& render_context, | ||
| 38 | VoiceContext& voice_context, MixContext& mix_context, | ||
| 39 | EffectContext& effect_context, SinkContext& sink_context, | ||
| 40 | SplitterContext& splitter_context, | ||
| 41 | PerformanceManager* performance_manager); | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Calculate the buffer size needed for commands. | ||
| 45 | * | ||
| 46 | * @param behavior - Used to check what features are enabled. | ||
| 47 | * @param params - Input rendering parameters for numbers of voices/mixes/sinks etc. | ||
| 48 | */ | ||
| 49 | static u64 CalculateCommandBufferSize(const BehaviorInfo& behavior, | ||
| 50 | const AudioRendererParameterInternal& params) { | ||
| 51 | u64 size{0}; | ||
| 52 | |||
| 53 | // Effects | ||
| 54 | size += params.effects * sizeof(EffectInfoBase); | ||
| 55 | |||
| 56 | // Voices | ||
| 57 | u64 voice_size{0}; | ||
| 58 | if (behavior.IsWaveBufferVer2Supported()) { | ||
| 59 | voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion2Command), | ||
| 60 | sizeof(PcmInt16DataSourceVersion2Command)), | ||
| 61 | sizeof(PcmFloatDataSourceVersion2Command)); | ||
| 62 | } else { | ||
| 63 | voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion1Command), | ||
| 64 | sizeof(PcmInt16DataSourceVersion1Command)), | ||
| 65 | sizeof(PcmFloatDataSourceVersion1Command)); | ||
| 66 | } | ||
| 67 | voice_size += sizeof(BiquadFilterCommand) * MaxBiquadFilters; | ||
| 68 | voice_size += sizeof(VolumeRampCommand); | ||
| 69 | voice_size += sizeof(MixRampGroupedCommand); | ||
| 70 | |||
| 71 | size += params.voices * (params.splitter_infos * sizeof(DepopPrepareCommand) + voice_size); | ||
| 72 | |||
| 73 | // Sub mixes | ||
| 74 | size += sizeof(DepopForMixBuffersCommand) + | ||
| 75 | (sizeof(MixCommand) * MaxMixBuffers) * MaxMixBuffers; | ||
| 76 | |||
| 77 | // Final mix | ||
| 78 | size += sizeof(DepopForMixBuffersCommand) + sizeof(VolumeCommand) * MaxMixBuffers; | ||
| 79 | |||
| 80 | // Splitters | ||
| 81 | size += params.splitter_destinations * sizeof(MixRampCommand) * MaxMixBuffers; | ||
| 82 | |||
| 83 | // Sinks | ||
| 84 | size += | ||
| 85 | params.sinks * std::max(sizeof(DeviceSinkCommand), sizeof(CircularBufferSinkCommand)); | ||
| 86 | |||
| 87 | // Performance | ||
| 88 | size += (params.effects + params.voices + params.sinks + params.sub_mixes + 1 + | ||
| 89 | PerformanceManager::MaxDetailEntries) * | ||
| 90 | sizeof(PerformanceCommand); | ||
| 91 | return size; | ||
| 92 | } | ||
| 93 | |||
| 94 | /** | ||
| 95 | * Get the current command buffer used to generate commands. | ||
| 96 | * | ||
| 97 | * @return The command buffer. | ||
| 98 | */ | ||
| 99 | CommandBuffer& GetCommandBuffer() { | ||
| 100 | return command_buffer; | ||
| 101 | } | ||
| 102 | |||
| 103 | /** | ||
| 104 | * Get the current performance manager, | ||
| 105 | * | ||
| 106 | * @return The performance manager. May be nullptr. | ||
| 107 | */ | ||
| 108 | PerformanceManager* GetPerformanceManager() { | ||
| 109 | return performance_manager; | ||
| 110 | } | ||
| 111 | |||
| 112 | /** | ||
| 113 | * Generate a data source command. | ||
| 114 | * These are the basis for all audio output. | ||
| 115 | * | ||
| 116 | * @param voice_info - Generate the command from this voice. | ||
| 117 | * @param voice_state - State used by the AudioRenderer across calls. | ||
| 118 | * @param channel - Channel index to generate the command into. | ||
| 119 | */ | ||
| 120 | void GenerateDataSourceCommand(VoiceInfo& voice_info, const VoiceState& voice_state, | ||
| 121 | s8 channel); | ||
| 122 | |||
| 123 | /** | ||
| 124 | * Generate voice mixing commands. | ||
| 125 | * These are used to mix buffers together, to mix one input to many outputs, | ||
| 126 | * and also used as copy commands to move data around and prevent it being accidentally | ||
| 127 | * overwritten, e.g by another data source command into the same channel. | ||
| 128 | * | ||
| 129 | * @param mix_volumes - Current volumes of the mix. | ||
| 130 | * @param prev_mix_volumes - Previous volumes of the mix. | ||
| 131 | * @param voice_state - State used by the AudioRenderer across calls. | ||
| 132 | * @param output_index - Output mix buffer index. | ||
| 133 | * @param buffer_count - Number of active mix buffers. | ||
| 134 | * @param input_index - Input mix buffer index. | ||
| 135 | * @param node_id - Node id of the voice this command is generated for. | ||
| 136 | */ | ||
| 137 | void GenerateVoiceMixCommand(std::span<const f32> mix_volumes, | ||
| 138 | std::span<const f32> prev_mix_volumes, | ||
| 139 | const VoiceState& voice_state, s16 output_index, s16 buffer_count, | ||
| 140 | s16 input_index, s32 node_id); | ||
| 141 | |||
| 142 | /** | ||
| 143 | * Generate a biquad filter command for a voice. | ||
| 144 | * | ||
| 145 | * @param voice_info - Voice info this command is generated from. | ||
| 146 | * @param voice_state - State used by the AudioRenderer across calls. | ||
| 147 | * @param buffer_count - Number of active mix buffers. | ||
| 148 | * @param channel - Channel index of this command. | ||
| 149 | * @param node_id - Node id of the voice this command is generated for. | ||
| 150 | */ | ||
| 151 | void GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, const VoiceState& voice_state, | ||
| 152 | s16 buffer_count, s8 channel, s32 node_id); | ||
| 153 | |||
| 154 | /** | ||
| 155 | * Generate commands for a voice. | ||
| 156 | * Includes a data source, biquad filter, volume and mixing. | ||
| 157 | * | ||
| 158 | * @param voice_info - Voice info these commands are generated from. | ||
| 159 | */ | ||
| 160 | void GenerateVoiceCommand(VoiceInfo& voice_info); | ||
| 161 | |||
| 162 | /** | ||
| 163 | * Generate commands for all voices. | ||
| 164 | */ | ||
| 165 | void GenerateVoiceCommands(); | ||
| 166 | |||
| 167 | /** | ||
| 168 | * Generate a mixing command. | ||
| 169 | * | ||
| 170 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 171 | * @param effect_info_base - BufferMixer effect info. | ||
| 172 | * @param node_id - Node id of the mix this command is generated for. | ||
| 173 | */ | ||
| 174 | void GenerateBufferMixerCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, | ||
| 175 | s32 node_id); | ||
| 176 | |||
| 177 | /** | ||
| 178 | * Generate a delay effect command. | ||
| 179 | * | ||
| 180 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 181 | * @param effect_info_base - Delay effect info. | ||
| 182 | * @param node_id - Node id of the mix this command is generated for. | ||
| 183 | */ | ||
| 184 | void GenerateDelayCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id); | ||
| 185 | |||
| 186 | /** | ||
| 187 | * Generate a reverb effect command. | ||
| 188 | * | ||
| 189 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 190 | * @param effect_info_base - Reverb effect info. | ||
| 191 | * @param node_id - Node id of the mix this command is generated for. | ||
| 192 | * @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb starts. | ||
| 193 | */ | ||
| 194 | void GenerateReverbCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id, | ||
| 195 | bool long_size_pre_delay_supported); | ||
| 196 | |||
| 197 | /** | ||
| 198 | * Generate an I3DL2 reverb effect command. | ||
| 199 | * | ||
| 200 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 201 | * @param effect_info_base - I3DL2Reverb effect info. | ||
| 202 | * @param node_id - Node id of the mix this command is generated for. | ||
| 203 | */ | ||
| 204 | void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, | ||
| 205 | s32 node_id); | ||
| 206 | |||
| 207 | /** | ||
| 208 | * Generate an aux effect command. | ||
| 209 | * | ||
| 210 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 211 | * @param effect_info_base - Aux effect info. | ||
| 212 | * @param node_id - Node id of the mix this command is generated for. | ||
| 213 | */ | ||
| 214 | void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); | ||
| 215 | |||
| 216 | /** | ||
| 217 | * Generate a biquad filter effect command. | ||
| 218 | * | ||
| 219 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 220 | * @param effect_info_base - Aux effect info. | ||
| 221 | * @param node_id - Node id of the mix this command is generated for. | ||
| 222 | */ | ||
| 223 | void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, | ||
| 224 | s32 node_id); | ||
| 225 | |||
| 226 | /** | ||
| 227 | * Generate a light limiter effect command. | ||
| 228 | * | ||
| 229 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 230 | * @param effect_info_base - Limiter effect info. | ||
| 231 | * @param node_id - Node id of the mix this command is generated for. | ||
| 232 | * @param effect_index - Index for the statistics state. | ||
| 233 | */ | ||
| 234 | void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, | ||
| 235 | s32 node_id, u32 effect_index); | ||
| 236 | |||
| 237 | /** | ||
| 238 | * Generate a capture effect command. | ||
| 239 | * Writes a mix buffer back to game memory. | ||
| 240 | * | ||
| 241 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 242 | * @param effect_info_base - Capture effect info. | ||
| 243 | * @param node_id - Node id of the mix this command is generated for. | ||
| 244 | */ | ||
| 245 | void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); | ||
| 246 | |||
| 247 | /** | ||
| 248 | * Generate a compressor effect command. | ||
| 249 | * | ||
| 250 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 251 | * @param effect_info_base - Compressor effect info. | ||
| 252 | * @param node_id - Node id of the mix this command is generated for. | ||
| 253 | */ | ||
| 254 | void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info, | ||
| 255 | const s32 node_id); | ||
| 256 | |||
| 257 | /** | ||
| 258 | * Generate all effect commands for a mix. | ||
| 259 | * | ||
| 260 | * @param mix_info - Mix to generate effects from. | ||
| 261 | */ | ||
| 262 | void GenerateEffectCommand(MixInfo& mix_info); | ||
| 263 | |||
| 264 | /** | ||
| 265 | * Generate all mix commands. | ||
| 266 | * | ||
| 267 | * @param mix_info - Mix to generate effects from. | ||
| 268 | */ | ||
| 269 | void GenerateMixCommands(MixInfo& mix_info); | ||
| 270 | |||
| 271 | /** | ||
| 272 | * Generate a submix command. | ||
| 273 | * Generates all effects and all mixing commands. | ||
| 274 | * | ||
| 275 | * @param mix_info - Mix to generate effects from. | ||
| 276 | */ | ||
| 277 | void GenerateSubMixCommand(MixInfo& mix_info); | ||
| 278 | |||
| 279 | /** | ||
| 280 | * Generate all submix command. | ||
| 281 | */ | ||
| 282 | void GenerateSubMixCommands(); | ||
| 283 | |||
| 284 | /** | ||
| 285 | * Generate the final mix. | ||
| 286 | */ | ||
| 287 | void GenerateFinalMixCommand(); | ||
| 288 | |||
| 289 | /** | ||
| 290 | * Generate the final mix commands. | ||
| 291 | */ | ||
| 292 | void GenerateFinalMixCommands(); | ||
| 293 | |||
| 294 | /** | ||
| 295 | * Generate all sink commands. | ||
| 296 | */ | ||
| 297 | void GenerateSinkCommands(); | ||
| 298 | |||
| 299 | /** | ||
| 300 | * Generate a sink command. | ||
| 301 | * Sends samples out to the backend, or a game-supplied circular buffer. | ||
| 302 | * | ||
| 303 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 304 | * @param sink_info - Sink info to generate the commands from. | ||
| 305 | */ | ||
| 306 | void GenerateSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info); | ||
| 307 | |||
| 308 | /** | ||
| 309 | * Generate a device sink command. | ||
| 310 | * Sends samples out to the backend. | ||
| 311 | * | ||
| 312 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 313 | * @param sink_info - Sink info to generate the commands from. | ||
| 314 | */ | ||
| 315 | void GenerateDeviceSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info); | ||
| 316 | |||
| 317 | /** | ||
| 318 | * Generate a performance command. | ||
| 319 | * Used to report performance metrics of the AudioRenderer back to the game. | ||
| 320 | * | ||
| 321 | * @param buffer_offset - Base mix buffer offset to use. | ||
| 322 | * @param sink_info - Sink info to generate the commands from. | ||
| 323 | */ | ||
| 324 | void GeneratePerformanceCommand(s32 node_id, PerformanceState state, | ||
| 325 | const PerformanceEntryAddresses& entry_addresses); | ||
| 326 | |||
| 327 | private: | ||
| 328 | /// Commands will be written by this buffer | ||
| 329 | CommandBuffer& command_buffer; | ||
| 330 | /// Header information for the commands generated | ||
| 331 | const CommandListHeader& command_header; | ||
| 332 | /// Various things to control generation | ||
| 333 | const AudioRendererSystemContext& render_context; | ||
| 334 | /// Used for generating voices | ||
| 335 | VoiceContext& voice_context; | ||
| 336 | /// Used for generating mixes | ||
| 337 | MixContext& mix_context; | ||
| 338 | /// Used for generating effects | ||
| 339 | EffectContext& effect_context; | ||
| 340 | /// Used for generating sinks | ||
| 341 | SinkContext& sink_context; | ||
| 342 | /// Used for generating submixes | ||
| 343 | SplitterContext& splitter_context; | ||
| 344 | /// Used for generating performance | ||
| 345 | PerformanceManager* performance_manager; | ||
| 346 | }; | ||
| 347 | |||
| 348 | } // namespace AudioRenderer | ||
| 349 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <span> | ||
| 7 | |||
| 8 | #include "audio_core/common/common.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | |||
| 13 | struct CommandListHeader { | ||
| 14 | u64 buffer_size; | ||
| 15 | u32 command_count; | ||
| 16 | std::span<s32> samples_buffer; | ||
| 17 | s16 buffer_count; | ||
| 18 | u32 sample_count; | ||
| 19 | u32 sample_rate; | ||
| 20 | }; | ||
| 21 | |||
| 22 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/command/command_processing_time_estimator.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 9 | const PcmInt16DataSourceVersion1Command& command) const { | ||
| 10 | return static_cast<u32>(command.pitch * 0.25f * 1.2f); | ||
| 11 | } | ||
| 12 | |||
| 13 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 14 | const PcmInt16DataSourceVersion2Command& command) const { | ||
| 15 | return static_cast<u32>(command.pitch * 0.25f * 1.2f); | ||
| 16 | } | ||
| 17 | |||
| 18 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 19 | [[maybe_unused]] const PcmFloatDataSourceVersion1Command& command) const { | ||
| 20 | return 0; | ||
| 21 | } | ||
| 22 | |||
| 23 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 24 | [[maybe_unused]] const PcmFloatDataSourceVersion2Command& command) const { | ||
| 25 | return 0; | ||
| 26 | } | ||
| 27 | |||
| 28 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 29 | const AdpcmDataSourceVersion1Command& command) const { | ||
| 30 | return static_cast<u32>(command.pitch * 0.25f * 1.2f); | ||
| 31 | } | ||
| 32 | |||
| 33 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 34 | const AdpcmDataSourceVersion2Command& command) const { | ||
| 35 | return static_cast<u32>(command.pitch * 0.25f * 1.2f); | ||
| 36 | } | ||
| 37 | |||
| 38 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 39 | [[maybe_unused]] const VolumeCommand& command) const { | ||
| 40 | return static_cast<u32>((static_cast<f32>(sample_count) * 8.8f) * 1.2f); | ||
| 41 | } | ||
| 42 | |||
| 43 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 44 | [[maybe_unused]] const VolumeRampCommand& command) const { | ||
| 45 | return static_cast<u32>((static_cast<f32>(sample_count) * 9.8f) * 1.2f); | ||
| 46 | } | ||
| 47 | |||
| 48 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 49 | [[maybe_unused]] const BiquadFilterCommand& command) const { | ||
| 50 | return static_cast<u32>((static_cast<f32>(sample_count) * 58.0f) * 1.2f); | ||
| 51 | } | ||
| 52 | |||
| 53 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 54 | [[maybe_unused]] const MixCommand& command) const { | ||
| 55 | return static_cast<u32>((static_cast<f32>(sample_count) * 10.0f) * 1.2f); | ||
| 56 | } | ||
| 57 | |||
| 58 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 59 | [[maybe_unused]] const MixRampCommand& command) const { | ||
| 60 | return static_cast<u32>((static_cast<f32>(sample_count) * 14.4f) * 1.2f); | ||
| 61 | } | ||
| 62 | |||
| 63 | u32 CommandProcessingTimeEstimatorVersion1::Estimate(const MixRampGroupedCommand& command) const { | ||
| 64 | u32 count{0}; | ||
| 65 | for (u32 i = 0; i < command.buffer_count; i++) { | ||
| 66 | if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { | ||
| 67 | count++; | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | return static_cast<u32>(((static_cast<f32>(sample_count) * 14.4f) * 1.2f) * | ||
| 72 | static_cast<f32>(count)); | ||
| 73 | } | ||
| 74 | |||
| 75 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 76 | [[maybe_unused]] const DepopPrepareCommand& command) const { | ||
| 77 | return 1080; | ||
| 78 | } | ||
| 79 | |||
| 80 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 81 | const DepopForMixBuffersCommand& command) const { | ||
| 82 | return static_cast<u32>((static_cast<f32>(sample_count) * 8.9f) * | ||
| 83 | static_cast<f32>(command.count)); | ||
| 84 | } | ||
| 85 | |||
| 86 | u32 CommandProcessingTimeEstimatorVersion1::Estimate(const DelayCommand& command) const { | ||
| 87 | return static_cast<u32>((static_cast<f32>(sample_count) * command.parameter.channel_count) * | ||
| 88 | 202.5f); | ||
| 89 | } | ||
| 90 | |||
| 91 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 92 | [[maybe_unused]] const UpsampleCommand& command) const { | ||
| 93 | return 357915; | ||
| 94 | } | ||
| 95 | |||
| 96 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 97 | [[maybe_unused]] const DownMix6chTo2chCommand& command) const { | ||
| 98 | return 16108; | ||
| 99 | } | ||
| 100 | |||
| 101 | u32 CommandProcessingTimeEstimatorVersion1::Estimate(const AuxCommand& command) const { | ||
| 102 | if (command.enabled) { | ||
| 103 | return 15956; | ||
| 104 | } | ||
| 105 | return 3765; | ||
| 106 | } | ||
| 107 | |||
| 108 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 109 | [[maybe_unused]] const DeviceSinkCommand& command) const { | ||
| 110 | return 10042; | ||
| 111 | } | ||
| 112 | |||
| 113 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 114 | [[maybe_unused]] const CircularBufferSinkCommand& command) const { | ||
| 115 | return 55; | ||
| 116 | } | ||
| 117 | |||
| 118 | u32 CommandProcessingTimeEstimatorVersion1::Estimate(const ReverbCommand& command) const { | ||
| 119 | if (command.enabled) { | ||
| 120 | return static_cast<u32>( | ||
| 121 | (command.parameter.channel_count * static_cast<f32>(sample_count) * 750) * 1.2f); | ||
| 122 | } | ||
| 123 | return 0; | ||
| 124 | } | ||
| 125 | |||
| 126 | u32 CommandProcessingTimeEstimatorVersion1::Estimate(const I3dl2ReverbCommand& command) const { | ||
| 127 | if (command.enabled) { | ||
| 128 | return static_cast<u32>( | ||
| 129 | (command.parameter.channel_count * static_cast<f32>(sample_count) * 530) * 1.2f); | ||
| 130 | } | ||
| 131 | return 0; | ||
| 132 | } | ||
| 133 | |||
| 134 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 135 | [[maybe_unused]] const PerformanceCommand& command) const { | ||
| 136 | return 1454; | ||
| 137 | } | ||
| 138 | |||
| 139 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 140 | [[maybe_unused]] const ClearMixBufferCommand& command) const { | ||
| 141 | return static_cast<u32>( | ||
| 142 | ((static_cast<f32>(sample_count) * 0.83f) * static_cast<f32>(buffer_count)) * 1.2f); | ||
| 143 | } | ||
| 144 | |||
| 145 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 146 | [[maybe_unused]] const CopyMixBufferCommand& command) const { | ||
| 147 | return 0; | ||
| 148 | } | ||
| 149 | |||
| 150 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 151 | [[maybe_unused]] const LightLimiterVersion1Command& command) const { | ||
| 152 | return 0; | ||
| 153 | } | ||
| 154 | |||
| 155 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 156 | [[maybe_unused]] const LightLimiterVersion2Command& command) const { | ||
| 157 | return 0; | ||
| 158 | } | ||
| 159 | |||
| 160 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 161 | [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { | ||
| 162 | return 0; | ||
| 163 | } | ||
| 164 | |||
| 165 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 166 | [[maybe_unused]] const CaptureCommand& command) const { | ||
| 167 | return 0; | ||
| 168 | } | ||
| 169 | |||
| 170 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 171 | [[maybe_unused]] const CompressorCommand& command) const { | ||
| 172 | return 0; | ||
| 173 | } | ||
| 174 | |||
| 175 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 176 | const PcmInt16DataSourceVersion1Command& command) const { | ||
| 177 | switch (sample_count) { | ||
| 178 | case 160: | ||
| 179 | return static_cast<u32>( | ||
| 180 | (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 181 | (command.pitch * 2.0f) * 749.269f + | ||
| 182 | 6138.94f); | ||
| 183 | case 240: | ||
| 184 | return static_cast<u32>( | ||
| 185 | (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 186 | (command.pitch * 2.0f) * 1195.456f + | ||
| 187 | 7797.047f); | ||
| 188 | default: | ||
| 189 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 190 | return 0; | ||
| 191 | } | ||
| 192 | } | ||
| 193 | |||
| 194 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 195 | const PcmInt16DataSourceVersion2Command& command) const { | ||
| 196 | switch (sample_count) { | ||
| 197 | case 160: | ||
| 198 | return static_cast<u32>( | ||
| 199 | (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 200 | (command.pitch * 2.0f) * 749.269f + | ||
| 201 | 6138.94f); | ||
| 202 | case 240: | ||
| 203 | return static_cast<u32>( | ||
| 204 | (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 205 | (command.pitch * 2.0f) * 1195.456f + | ||
| 206 | 7797.047f); | ||
| 207 | default: | ||
| 208 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 209 | return 0; | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 214 | const PcmFloatDataSourceVersion1Command& command) const { | ||
| 215 | switch (sample_count) { | ||
| 216 | case 160: | ||
| 217 | return static_cast<u32>( | ||
| 218 | (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 219 | (command.pitch * 2.0f) * 749.269f + | ||
| 220 | 6138.94f); | ||
| 221 | case 240: | ||
| 222 | return static_cast<u32>( | ||
| 223 | (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 224 | (command.pitch * 2.0f) * 1195.456f + | ||
| 225 | 7797.047f); | ||
| 226 | default: | ||
| 227 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 228 | return 0; | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 233 | const PcmFloatDataSourceVersion2Command& command) const { | ||
| 234 | switch (sample_count) { | ||
| 235 | case 160: | ||
| 236 | return static_cast<u32>( | ||
| 237 | (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 238 | (command.pitch * 2.0f) * 749.269f + | ||
| 239 | 6138.94f); | ||
| 240 | case 240: | ||
| 241 | return static_cast<u32>( | ||
| 242 | (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 243 | (command.pitch * 2.0f) * 1195.456f + | ||
| 244 | 7797.047f); | ||
| 245 | default: | ||
| 246 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 247 | return 0; | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 252 | const AdpcmDataSourceVersion1Command& command) const { | ||
| 253 | switch (sample_count) { | ||
| 254 | case 160: | ||
| 255 | return static_cast<u32>( | ||
| 256 | (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 257 | (command.pitch * 2.0f) * 2125.588f + | ||
| 258 | 9039.47f); | ||
| 259 | case 240: | ||
| 260 | return static_cast<u32>( | ||
| 261 | (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 262 | (command.pitch * 2.0f) * 3564.088 + | ||
| 263 | 6225.471); | ||
| 264 | default: | ||
| 265 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 266 | return 0; | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 271 | const AdpcmDataSourceVersion2Command& command) const { | ||
| 272 | switch (sample_count) { | ||
| 273 | case 160: | ||
| 274 | return static_cast<u32>( | ||
| 275 | (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 276 | (command.pitch * 2.0f) * 2125.588f + | ||
| 277 | 9039.47f); | ||
| 278 | case 240: | ||
| 279 | return static_cast<u32>( | ||
| 280 | (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 281 | (command.pitch * 2.0f) * 3564.088 + | ||
| 282 | 6225.471); | ||
| 283 | default: | ||
| 284 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 285 | return 0; | ||
| 286 | } | ||
| 287 | } | ||
| 288 | |||
| 289 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 290 | [[maybe_unused]] const VolumeCommand& command) const { | ||
| 291 | switch (sample_count) { | ||
| 292 | case 160: | ||
| 293 | return static_cast<u32>(1280.3f); | ||
| 294 | case 240: | ||
| 295 | return static_cast<u32>(1737.8f); | ||
| 296 | default: | ||
| 297 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 298 | return 0; | ||
| 299 | } | ||
| 300 | } | ||
| 301 | |||
| 302 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 303 | [[maybe_unused]] const VolumeRampCommand& command) const { | ||
| 304 | switch (sample_count) { | ||
| 305 | case 160: | ||
| 306 | return static_cast<u32>(1403.9f); | ||
| 307 | case 240: | ||
| 308 | return static_cast<u32>(1884.3f); | ||
| 309 | default: | ||
| 310 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 311 | return 0; | ||
| 312 | } | ||
| 313 | } | ||
| 314 | |||
| 315 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 316 | [[maybe_unused]] const BiquadFilterCommand& command) const { | ||
| 317 | switch (sample_count) { | ||
| 318 | case 160: | ||
| 319 | return static_cast<u32>(4813.2f); | ||
| 320 | case 240: | ||
| 321 | return static_cast<u32>(6915.4f); | ||
| 322 | default: | ||
| 323 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 324 | return 0; | ||
| 325 | } | ||
| 326 | } | ||
| 327 | |||
| 328 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 329 | [[maybe_unused]] const MixCommand& command) const { | ||
| 330 | switch (sample_count) { | ||
| 331 | case 160: | ||
| 332 | return static_cast<u32>(1342.2f); | ||
| 333 | case 240: | ||
| 334 | return static_cast<u32>(1833.2f); | ||
| 335 | default: | ||
| 336 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 337 | return 0; | ||
| 338 | } | ||
| 339 | } | ||
| 340 | |||
| 341 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 342 | [[maybe_unused]] const MixRampCommand& command) const { | ||
| 343 | switch (sample_count) { | ||
| 344 | case 160: | ||
| 345 | return static_cast<u32>(1859.0f); | ||
| 346 | case 240: | ||
| 347 | return static_cast<u32>(2286.1f); | ||
| 348 | default: | ||
| 349 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 350 | return 0; | ||
| 351 | } | ||
| 352 | } | ||
| 353 | |||
| 354 | u32 CommandProcessingTimeEstimatorVersion2::Estimate(const MixRampGroupedCommand& command) const { | ||
| 355 | u32 count{0}; | ||
| 356 | for (u32 i = 0; i < command.buffer_count; i++) { | ||
| 357 | if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { | ||
| 358 | count++; | ||
| 359 | } | ||
| 360 | } | ||
| 361 | |||
| 362 | switch (sample_count) { | ||
| 363 | case 160: | ||
| 364 | return static_cast<u32>((static_cast<f32>(sample_count) * 7.245f) * | ||
| 365 | static_cast<f32>(count)); | ||
| 366 | case 240: | ||
| 367 | return static_cast<u32>((static_cast<f32>(sample_count) * 7.245f) * | ||
| 368 | static_cast<f32>(count)); | ||
| 369 | default: | ||
| 370 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 371 | return 0; | ||
| 372 | } | ||
| 373 | } | ||
| 374 | |||
| 375 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 376 | [[maybe_unused]] const DepopPrepareCommand& command) const { | ||
| 377 | switch (sample_count) { | ||
| 378 | case 160: | ||
| 379 | return static_cast<u32>(306.62f); | ||
| 380 | case 240: | ||
| 381 | return static_cast<u32>(293.22f); | ||
| 382 | default: | ||
| 383 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 384 | return 0; | ||
| 385 | } | ||
| 386 | } | ||
| 387 | |||
| 388 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 389 | [[maybe_unused]] const DepopForMixBuffersCommand& command) const { | ||
| 390 | switch (sample_count) { | ||
| 391 | case 160: | ||
| 392 | return static_cast<u32>(762.96f); | ||
| 393 | case 240: | ||
| 394 | return static_cast<u32>(726.96f); | ||
| 395 | default: | ||
| 396 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 397 | return 0; | ||
| 398 | } | ||
| 399 | } | ||
| 400 | |||
| 401 | u32 CommandProcessingTimeEstimatorVersion2::Estimate(const DelayCommand& command) const { | ||
| 402 | switch (sample_count) { | ||
| 403 | case 160: | ||
| 404 | if (command.enabled) { | ||
| 405 | switch (command.parameter.channel_count) { | ||
| 406 | case 1: | ||
| 407 | return static_cast<u32>(41635.555f); | ||
| 408 | case 2: | ||
| 409 | return static_cast<u32>(97861.211f); | ||
| 410 | case 4: | ||
| 411 | return static_cast<u32>(192515.516f); | ||
| 412 | case 6: | ||
| 413 | return static_cast<u32>(301755.969f); | ||
| 414 | default: | ||
| 415 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 416 | command.parameter.channel_count); | ||
| 417 | return 0; | ||
| 418 | } | ||
| 419 | } | ||
| 420 | switch (command.parameter.channel_count) { | ||
| 421 | case 1: | ||
| 422 | return static_cast<u32>(578.529f); | ||
| 423 | case 2: | ||
| 424 | return static_cast<u32>(663.064f); | ||
| 425 | case 4: | ||
| 426 | return static_cast<u32>(703.983f); | ||
| 427 | case 6: | ||
| 428 | return static_cast<u32>(760.032f); | ||
| 429 | default: | ||
| 430 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 431 | return 0; | ||
| 432 | } | ||
| 433 | case 240: | ||
| 434 | if (command.enabled) { | ||
| 435 | switch (command.parameter.channel_count) { | ||
| 436 | case 1: | ||
| 437 | return static_cast<u32>(8770.345f); | ||
| 438 | case 2: | ||
| 439 | return static_cast<u32>(25741.18f); | ||
| 440 | case 4: | ||
| 441 | return static_cast<u32>(47551.168f); | ||
| 442 | case 6: | ||
| 443 | return static_cast<u32>(81629.219f); | ||
| 444 | default: | ||
| 445 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 446 | command.parameter.channel_count); | ||
| 447 | return 0; | ||
| 448 | } | ||
| 449 | } | ||
| 450 | switch (command.parameter.channel_count) { | ||
| 451 | case 1: | ||
| 452 | return static_cast<u32>(521.283f); | ||
| 453 | case 2: | ||
| 454 | return static_cast<u32>(585.396f); | ||
| 455 | case 4: | ||
| 456 | return static_cast<u32>(629.884f); | ||
| 457 | case 6: | ||
| 458 | return static_cast<u32>(713.57f); | ||
| 459 | default: | ||
| 460 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 461 | return 0; | ||
| 462 | } | ||
| 463 | default: | ||
| 464 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 465 | return 0; | ||
| 466 | } | ||
| 467 | } | ||
| 468 | |||
| 469 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 470 | [[maybe_unused]] const UpsampleCommand& command) const { | ||
| 471 | switch (sample_count) { | ||
| 472 | case 160: | ||
| 473 | return static_cast<u32>(292000.0f); | ||
| 474 | case 240: | ||
| 475 | return static_cast<u32>(0.0f); | ||
| 476 | default: | ||
| 477 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 478 | return 0; | ||
| 479 | } | ||
| 480 | } | ||
| 481 | |||
| 482 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 483 | [[maybe_unused]] const DownMix6chTo2chCommand& command) const { | ||
| 484 | switch (sample_count) { | ||
| 485 | case 160: | ||
| 486 | return static_cast<u32>(10009.0f); | ||
| 487 | case 240: | ||
| 488 | return static_cast<u32>(14577.0f); | ||
| 489 | default: | ||
| 490 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 491 | return 0; | ||
| 492 | } | ||
| 493 | } | ||
| 494 | |||
| 495 | u32 CommandProcessingTimeEstimatorVersion2::Estimate(const AuxCommand& command) const { | ||
| 496 | // Is this function bugged, returning the wrong time? | ||
| 497 | // Surely the larger time should be returned when enabled... | ||
| 498 | // CMP W8, #0 | ||
| 499 | // MOV W8, #0x60; // 489.163f | ||
| 500 | // MOV W10, #0x64; // 7177.936f | ||
| 501 | // CSEL X8, X10, X8, EQ | ||
| 502 | |||
| 503 | switch (sample_count) { | ||
| 504 | case 160: | ||
| 505 | if (command.enabled) { | ||
| 506 | return static_cast<u32>(489.163f); | ||
| 507 | } | ||
| 508 | return static_cast<u32>(7177.936f); | ||
| 509 | case 240: | ||
| 510 | if (command.enabled) { | ||
| 511 | return static_cast<u32>(485.562f); | ||
| 512 | } | ||
| 513 | return static_cast<u32>(9499.822f); | ||
| 514 | default: | ||
| 515 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 516 | return 0; | ||
| 517 | } | ||
| 518 | } | ||
| 519 | |||
| 520 | u32 CommandProcessingTimeEstimatorVersion2::Estimate(const DeviceSinkCommand& command) const { | ||
| 521 | switch (command.input_count) { | ||
| 522 | case 2: | ||
| 523 | switch (sample_count) { | ||
| 524 | case 160: | ||
| 525 | return static_cast<u32>(9261.545f); | ||
| 526 | case 240: | ||
| 527 | return static_cast<u32>(9336.054f); | ||
| 528 | default: | ||
| 529 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 530 | return 0; | ||
| 531 | } | ||
| 532 | case 6: | ||
| 533 | switch (sample_count) { | ||
| 534 | case 160: | ||
| 535 | return static_cast<u32>(9336.054f); | ||
| 536 | case 240: | ||
| 537 | return static_cast<u32>(9566.728f); | ||
| 538 | default: | ||
| 539 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 540 | return 0; | ||
| 541 | } | ||
| 542 | default: | ||
| 543 | LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count); | ||
| 544 | return 0; | ||
| 545 | } | ||
| 546 | } | ||
| 547 | |||
| 548 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 549 | const CircularBufferSinkCommand& command) const { | ||
| 550 | switch (sample_count) { | ||
| 551 | case 160: | ||
| 552 | return static_cast<u32>(static_cast<f32>(command.input_count) * 853.629f + 1284.517f); | ||
| 553 | case 240: | ||
| 554 | return static_cast<u32>(static_cast<f32>(command.input_count) * 1726.021f + 1369.683f); | ||
| 555 | default: | ||
| 556 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 557 | return 0; | ||
| 558 | } | ||
| 559 | } | ||
| 560 | |||
| 561 | u32 CommandProcessingTimeEstimatorVersion2::Estimate(const ReverbCommand& command) const { | ||
| 562 | switch (sample_count) { | ||
| 563 | case 160: | ||
| 564 | if (command.enabled) { | ||
| 565 | switch (command.parameter.channel_count) { | ||
| 566 | case 1: | ||
| 567 | return static_cast<u32>(97192.227f); | ||
| 568 | case 2: | ||
| 569 | return static_cast<u32>(103278.555f); | ||
| 570 | case 4: | ||
| 571 | return static_cast<u32>(109579.039f); | ||
| 572 | case 6: | ||
| 573 | return static_cast<u32>(115065.438f); | ||
| 574 | default: | ||
| 575 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 576 | command.parameter.channel_count); | ||
| 577 | return 0; | ||
| 578 | } | ||
| 579 | } | ||
| 580 | switch (command.parameter.channel_count) { | ||
| 581 | case 1: | ||
| 582 | return static_cast<u32>(492.009f); | ||
| 583 | case 2: | ||
| 584 | return static_cast<u32>(554.463f); | ||
| 585 | case 4: | ||
| 586 | return static_cast<u32>(595.864f); | ||
| 587 | case 6: | ||
| 588 | return static_cast<u32>(656.617f); | ||
| 589 | default: | ||
| 590 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 591 | return 0; | ||
| 592 | } | ||
| 593 | case 240: | ||
| 594 | if (command.enabled) { | ||
| 595 | switch (command.parameter.channel_count) { | ||
| 596 | case 1: | ||
| 597 | return static_cast<u32>(136463.641f); | ||
| 598 | case 2: | ||
| 599 | return static_cast<u32>(145749.047f); | ||
| 600 | case 4: | ||
| 601 | return static_cast<u32>(154796.938f); | ||
| 602 | case 6: | ||
| 603 | return static_cast<u32>(161968.406f); | ||
| 604 | default: | ||
| 605 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 606 | command.parameter.channel_count); | ||
| 607 | return 0; | ||
| 608 | } | ||
| 609 | } | ||
| 610 | switch (command.parameter.channel_count) { | ||
| 611 | case 1: | ||
| 612 | return static_cast<u32>(495.789f); | ||
| 613 | case 2: | ||
| 614 | return static_cast<u32>(527.163f); | ||
| 615 | case 4: | ||
| 616 | return static_cast<u32>(598.752f); | ||
| 617 | case 6: | ||
| 618 | return static_cast<u32>(666.025f); | ||
| 619 | default: | ||
| 620 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 621 | return 0; | ||
| 622 | } | ||
| 623 | default: | ||
| 624 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 625 | return 0; | ||
| 626 | } | ||
| 627 | } | ||
| 628 | |||
| 629 | u32 CommandProcessingTimeEstimatorVersion2::Estimate(const I3dl2ReverbCommand& command) const { | ||
| 630 | switch (sample_count) { | ||
| 631 | case 160: | ||
| 632 | if (command.enabled) { | ||
| 633 | switch (command.parameter.channel_count) { | ||
| 634 | case 1: | ||
| 635 | return static_cast<u32>(138836.484f); | ||
| 636 | case 2: | ||
| 637 | return static_cast<u32>(135428.172f); | ||
| 638 | case 4: | ||
| 639 | return static_cast<u32>(199181.844f); | ||
| 640 | case 6: | ||
| 641 | return static_cast<u32>(247345.906f); | ||
| 642 | default: | ||
| 643 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 644 | command.parameter.channel_count); | ||
| 645 | return 0; | ||
| 646 | } | ||
| 647 | } | ||
| 648 | switch (command.parameter.channel_count) { | ||
| 649 | case 1: | ||
| 650 | return static_cast<u32>(718.704f); | ||
| 651 | case 2: | ||
| 652 | return static_cast<u32>(751.296f); | ||
| 653 | case 4: | ||
| 654 | return static_cast<u32>(797.464f); | ||
| 655 | case 6: | ||
| 656 | return static_cast<u32>(867.426f); | ||
| 657 | default: | ||
| 658 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 659 | return 0; | ||
| 660 | } | ||
| 661 | case 240: | ||
| 662 | if (command.enabled) { | ||
| 663 | switch (command.parameter.channel_count) { | ||
| 664 | case 1: | ||
| 665 | return static_cast<u32>(199952.734f); | ||
| 666 | case 2: | ||
| 667 | return static_cast<u32>(195199.5f); | ||
| 668 | case 4: | ||
| 669 | return static_cast<u32>(290575.875f); | ||
| 670 | case 6: | ||
| 671 | return static_cast<u32>(363494.531f); | ||
| 672 | default: | ||
| 673 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 674 | command.parameter.channel_count); | ||
| 675 | return 0; | ||
| 676 | } | ||
| 677 | } | ||
| 678 | switch (command.parameter.channel_count) { | ||
| 679 | case 1: | ||
| 680 | return static_cast<u32>(534.24f); | ||
| 681 | case 2: | ||
| 682 | return static_cast<u32>(570.874f); | ||
| 683 | case 4: | ||
| 684 | return static_cast<u32>(660.933f); | ||
| 685 | case 6: | ||
| 686 | return static_cast<u32>(694.596f); | ||
| 687 | default: | ||
| 688 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 689 | return 0; | ||
| 690 | } | ||
| 691 | default: | ||
| 692 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 693 | return 0; | ||
| 694 | } | ||
| 695 | } | ||
| 696 | |||
| 697 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 698 | [[maybe_unused]] const PerformanceCommand& command) const { | ||
| 699 | switch (sample_count) { | ||
| 700 | case 160: | ||
| 701 | return static_cast<u32>(489.35f); | ||
| 702 | case 240: | ||
| 703 | return static_cast<u32>(491.18f); | ||
| 704 | default: | ||
| 705 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 706 | return 0; | ||
| 707 | } | ||
| 708 | } | ||
| 709 | |||
| 710 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 711 | [[maybe_unused]] const ClearMixBufferCommand& command) const { | ||
| 712 | switch (sample_count) { | ||
| 713 | case 160: | ||
| 714 | return static_cast<u32>(static_cast<f32>(buffer_count) * 260.4f + 139.65f); | ||
| 715 | case 240: | ||
| 716 | return static_cast<u32>(static_cast<f32>(buffer_count) * 668.85f + 193.2f); | ||
| 717 | default: | ||
| 718 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 719 | return 0; | ||
| 720 | } | ||
| 721 | } | ||
| 722 | |||
| 723 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 724 | [[maybe_unused]] const CopyMixBufferCommand& command) const { | ||
| 725 | switch (sample_count) { | ||
| 726 | case 160: | ||
| 727 | return static_cast<u32>(836.32f); | ||
| 728 | case 240: | ||
| 729 | return static_cast<u32>(1000.9f); | ||
| 730 | default: | ||
| 731 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 732 | return 0; | ||
| 733 | } | ||
| 734 | } | ||
| 735 | |||
| 736 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 737 | [[maybe_unused]] const LightLimiterVersion1Command& command) const { | ||
| 738 | return 0; | ||
| 739 | } | ||
| 740 | |||
| 741 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 742 | [[maybe_unused]] const LightLimiterVersion2Command& command) const { | ||
| 743 | return 0; | ||
| 744 | } | ||
| 745 | |||
| 746 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 747 | [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { | ||
| 748 | return 0; | ||
| 749 | } | ||
| 750 | |||
| 751 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 752 | [[maybe_unused]] const CaptureCommand& command) const { | ||
| 753 | return 0; | ||
| 754 | } | ||
| 755 | |||
| 756 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 757 | [[maybe_unused]] const CompressorCommand& command) const { | ||
| 758 | return 0; | ||
| 759 | } | ||
| 760 | |||
| 761 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 762 | const PcmInt16DataSourceVersion1Command& command) const { | ||
| 763 | switch (sample_count) { | ||
| 764 | case 160: | ||
| 765 | return static_cast<u32>( | ||
| 766 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 767 | (command.pitch * 0.000030518f)) * | ||
| 768 | 427.52f + | ||
| 769 | 6329.442f); | ||
| 770 | case 240: | ||
| 771 | return static_cast<u32>( | ||
| 772 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 773 | (command.pitch * 0.000030518f)) * | ||
| 774 | 710.143f + | ||
| 775 | 7853.286f); | ||
| 776 | default: | ||
| 777 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 778 | return 0; | ||
| 779 | } | ||
| 780 | } | ||
| 781 | |||
| 782 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 783 | const PcmInt16DataSourceVersion2Command& command) const { | ||
| 784 | switch (sample_count) { | ||
| 785 | case 160: | ||
| 786 | switch (command.src_quality) { | ||
| 787 | case SrcQuality::Medium: | ||
| 788 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 789 | static_cast<f32>(sample_count)) * | ||
| 790 | (command.pitch * 0.000030518f)) - | ||
| 791 | 1.0f) * | ||
| 792 | 427.52f + | ||
| 793 | 6329.442f); | ||
| 794 | case SrcQuality::High: | ||
| 795 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 796 | static_cast<f32>(sample_count)) * | ||
| 797 | (command.pitch * 0.000030518f)) - | ||
| 798 | 1.0f) * | ||
| 799 | 371.876f + | ||
| 800 | 8049.415f); | ||
| 801 | case SrcQuality::Low: | ||
| 802 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 803 | static_cast<f32>(sample_count)) * | ||
| 804 | (command.pitch * 0.000030518f)) - | ||
| 805 | 1.0f) * | ||
| 806 | 423.43f + | ||
| 807 | 5062.659f); | ||
| 808 | default: | ||
| 809 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 810 | static_cast<u32>(command.src_quality)); | ||
| 811 | return 0; | ||
| 812 | } | ||
| 813 | |||
| 814 | case 240: | ||
| 815 | switch (command.src_quality) { | ||
| 816 | case SrcQuality::Medium: | ||
| 817 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 818 | static_cast<f32>(sample_count)) * | ||
| 819 | (command.pitch * 0.000030518f)) - | ||
| 820 | 1.0f) * | ||
| 821 | 710.143f + | ||
| 822 | 7853.286f); | ||
| 823 | case SrcQuality::High: | ||
| 824 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 825 | static_cast<f32>(sample_count)) * | ||
| 826 | (command.pitch * 0.000030518f)) - | ||
| 827 | 1.0f) * | ||
| 828 | 610.487f + | ||
| 829 | 10138.842f); | ||
| 830 | case SrcQuality::Low: | ||
| 831 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 832 | static_cast<f32>(sample_count)) * | ||
| 833 | (command.pitch * 0.000030518f)) - | ||
| 834 | 1.0f) * | ||
| 835 | 676.722f + | ||
| 836 | 5810.962f); | ||
| 837 | default: | ||
| 838 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 839 | static_cast<u32>(command.src_quality)); | ||
| 840 | return 0; | ||
| 841 | } | ||
| 842 | |||
| 843 | default: | ||
| 844 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 845 | return 0; | ||
| 846 | } | ||
| 847 | } | ||
| 848 | |||
| 849 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 850 | const PcmFloatDataSourceVersion1Command& command) const { | ||
| 851 | switch (sample_count) { | ||
| 852 | case 160: | ||
| 853 | return static_cast<u32>( | ||
| 854 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 855 | (command.pitch * 0.000030518f)) * | ||
| 856 | 1672.026f + | ||
| 857 | 7681.211f); | ||
| 858 | case 240: | ||
| 859 | return static_cast<u32>( | ||
| 860 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 861 | (command.pitch * 0.000030518f)) * | ||
| 862 | 2550.414f + | ||
| 863 | 9663.969f); | ||
| 864 | default: | ||
| 865 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 866 | return 0; | ||
| 867 | } | ||
| 868 | } | ||
| 869 | |||
| 870 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 871 | const PcmFloatDataSourceVersion2Command& command) const { | ||
| 872 | switch (sample_count) { | ||
| 873 | case 160: | ||
| 874 | switch (command.src_quality) { | ||
| 875 | case SrcQuality::Medium: | ||
| 876 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 877 | static_cast<f32>(sample_count)) * | ||
| 878 | (command.pitch * 0.000030518f)) - | ||
| 879 | 1.0f) * | ||
| 880 | 1672.026f + | ||
| 881 | 7681.211f); | ||
| 882 | case SrcQuality::High: | ||
| 883 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 884 | static_cast<f32>(sample_count)) * | ||
| 885 | (command.pitch * 0.000030518f)) - | ||
| 886 | 1.0f) * | ||
| 887 | 1672.982f + | ||
| 888 | 9038.011f); | ||
| 889 | case SrcQuality::Low: | ||
| 890 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 891 | static_cast<f32>(sample_count)) * | ||
| 892 | (command.pitch * 0.000030518f)) - | ||
| 893 | 1.0f) * | ||
| 894 | 1673.216f + | ||
| 895 | 6027.577f); | ||
| 896 | default: | ||
| 897 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 898 | static_cast<u32>(command.src_quality)); | ||
| 899 | return 0; | ||
| 900 | } | ||
| 901 | |||
| 902 | case 240: | ||
| 903 | switch (command.src_quality) { | ||
| 904 | case SrcQuality::Medium: | ||
| 905 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 906 | static_cast<f32>(sample_count)) * | ||
| 907 | (command.pitch * 0.000030518f)) - | ||
| 908 | 1.0f) * | ||
| 909 | 2550.414f + | ||
| 910 | 9663.969f); | ||
| 911 | case SrcQuality::High: | ||
| 912 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 913 | static_cast<f32>(sample_count)) * | ||
| 914 | (command.pitch * 0.000030518f)) - | ||
| 915 | 1.0f) * | ||
| 916 | 2522.303f + | ||
| 917 | 11758.571f); | ||
| 918 | case SrcQuality::Low: | ||
| 919 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 920 | static_cast<f32>(sample_count)) * | ||
| 921 | (command.pitch * 0.000030518f)) - | ||
| 922 | 1.0f) * | ||
| 923 | 2537.061f + | ||
| 924 | 7369.309f); | ||
| 925 | default: | ||
| 926 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 927 | static_cast<u32>(command.src_quality)); | ||
| 928 | return 0; | ||
| 929 | } | ||
| 930 | |||
| 931 | default: | ||
| 932 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 933 | return 0; | ||
| 934 | } | ||
| 935 | } | ||
| 936 | |||
| 937 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 938 | const AdpcmDataSourceVersion1Command& command) const { | ||
| 939 | switch (sample_count) { | ||
| 940 | case 160: | ||
| 941 | return static_cast<u32>( | ||
| 942 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 943 | (command.pitch * 0.000030518f)) * | ||
| 944 | 1827.665f + | ||
| 945 | 7913.808f); | ||
| 946 | case 240: | ||
| 947 | return static_cast<u32>( | ||
| 948 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 949 | (command.pitch * 0.000030518f)) * | ||
| 950 | 2756.372f + | ||
| 951 | 9736.702f); | ||
| 952 | default: | ||
| 953 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 954 | return 0; | ||
| 955 | } | ||
| 956 | } | ||
| 957 | |||
| 958 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 959 | const AdpcmDataSourceVersion2Command& command) const { | ||
| 960 | switch (sample_count) { | ||
| 961 | case 160: | ||
| 962 | switch (command.src_quality) { | ||
| 963 | case SrcQuality::Medium: | ||
| 964 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 965 | static_cast<f32>(sample_count)) * | ||
| 966 | (command.pitch * 0.000030518f)) - | ||
| 967 | 1.0f) * | ||
| 968 | 1827.665f + | ||
| 969 | 7913.808f); | ||
| 970 | case SrcQuality::High: | ||
| 971 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 972 | static_cast<f32>(sample_count)) * | ||
| 973 | (command.pitch * 0.000030518f)) - | ||
| 974 | 1.0f) * | ||
| 975 | 1829.285f + | ||
| 976 | 9607.814f); | ||
| 977 | case SrcQuality::Low: | ||
| 978 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 979 | static_cast<f32>(sample_count)) * | ||
| 980 | (command.pitch * 0.000030518f)) - | ||
| 981 | 1.0f) * | ||
| 982 | 1824.609f + | ||
| 983 | 6517.476f); | ||
| 984 | default: | ||
| 985 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 986 | static_cast<u32>(command.src_quality)); | ||
| 987 | return 0; | ||
| 988 | } | ||
| 989 | |||
| 990 | case 240: | ||
| 991 | switch (command.src_quality) { | ||
| 992 | case SrcQuality::Medium: | ||
| 993 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 994 | static_cast<f32>(sample_count)) * | ||
| 995 | (command.pitch * 0.000030518f)) - | ||
| 996 | 1.0f) * | ||
| 997 | 2756.372f + | ||
| 998 | 9736.702f); | ||
| 999 | case SrcQuality::High: | ||
| 1000 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1001 | static_cast<f32>(sample_count)) * | ||
| 1002 | (command.pitch * 0.000030518f)) - | ||
| 1003 | 1.0f) * | ||
| 1004 | 2731.308f + | ||
| 1005 | 12154.379f); | ||
| 1006 | case SrcQuality::Low: | ||
| 1007 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1008 | static_cast<f32>(sample_count)) * | ||
| 1009 | (command.pitch * 0.000030518f)) - | ||
| 1010 | 1.0f) * | ||
| 1011 | 2732.152f + | ||
| 1012 | 7929.442f); | ||
| 1013 | default: | ||
| 1014 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 1015 | static_cast<u32>(command.src_quality)); | ||
| 1016 | return 0; | ||
| 1017 | } | ||
| 1018 | |||
| 1019 | default: | ||
| 1020 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1021 | return 0; | ||
| 1022 | } | ||
| 1023 | } | ||
| 1024 | |||
| 1025 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1026 | [[maybe_unused]] const VolumeCommand& command) const { | ||
| 1027 | switch (sample_count) { | ||
| 1028 | case 160: | ||
| 1029 | return static_cast<u32>(1311.1f); | ||
| 1030 | case 240: | ||
| 1031 | return static_cast<u32>(1713.6f); | ||
| 1032 | default: | ||
| 1033 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1034 | return 0; | ||
| 1035 | } | ||
| 1036 | } | ||
| 1037 | |||
| 1038 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1039 | [[maybe_unused]] const VolumeRampCommand& command) const { | ||
| 1040 | switch (sample_count) { | ||
| 1041 | case 160: | ||
| 1042 | return static_cast<u32>(1425.3f); | ||
| 1043 | case 240: | ||
| 1044 | return static_cast<u32>(1700.0f); | ||
| 1045 | default: | ||
| 1046 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1047 | return 0; | ||
| 1048 | } | ||
| 1049 | } | ||
| 1050 | |||
| 1051 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1052 | [[maybe_unused]] const BiquadFilterCommand& command) const { | ||
| 1053 | switch (sample_count) { | ||
| 1054 | case 160: | ||
| 1055 | return static_cast<u32>(4173.2f); | ||
| 1056 | case 240: | ||
| 1057 | return static_cast<u32>(5585.1f); | ||
| 1058 | default: | ||
| 1059 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1060 | return 0; | ||
| 1061 | } | ||
| 1062 | } | ||
| 1063 | |||
| 1064 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1065 | [[maybe_unused]] const MixCommand& command) const { | ||
| 1066 | switch (sample_count) { | ||
| 1067 | case 160: | ||
| 1068 | return static_cast<u32>(1402.8f); | ||
| 1069 | case 240: | ||
| 1070 | return static_cast<u32>(1853.2f); | ||
| 1071 | default: | ||
| 1072 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1073 | return 0; | ||
| 1074 | } | ||
| 1075 | } | ||
| 1076 | |||
| 1077 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1078 | [[maybe_unused]] const MixRampCommand& command) const { | ||
| 1079 | switch (sample_count) { | ||
| 1080 | case 160: | ||
| 1081 | return static_cast<u32>(1968.7f); | ||
| 1082 | case 240: | ||
| 1083 | return static_cast<u32>(2459.4f); | ||
| 1084 | default: | ||
| 1085 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1086 | return 0; | ||
| 1087 | } | ||
| 1088 | } | ||
| 1089 | |||
| 1090 | u32 CommandProcessingTimeEstimatorVersion3::Estimate(const MixRampGroupedCommand& command) const { | ||
| 1091 | u32 count{0}; | ||
| 1092 | for (u32 i = 0; i < command.buffer_count; i++) { | ||
| 1093 | if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { | ||
| 1094 | count++; | ||
| 1095 | } | ||
| 1096 | } | ||
| 1097 | |||
| 1098 | switch (sample_count) { | ||
| 1099 | case 160: | ||
| 1100 | return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) * | ||
| 1101 | static_cast<f32>(count)); | ||
| 1102 | case 240: | ||
| 1103 | return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) * | ||
| 1104 | static_cast<f32>(count)); | ||
| 1105 | default: | ||
| 1106 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1107 | return 0; | ||
| 1108 | } | ||
| 1109 | } | ||
| 1110 | |||
| 1111 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1112 | [[maybe_unused]] const DepopPrepareCommand& command) const { | ||
| 1113 | return 0; | ||
| 1114 | } | ||
| 1115 | |||
| 1116 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1117 | [[maybe_unused]] const DepopForMixBuffersCommand& command) const { | ||
| 1118 | switch (sample_count) { | ||
| 1119 | case 160: | ||
| 1120 | return static_cast<u32>(739.64f); | ||
| 1121 | case 240: | ||
| 1122 | return static_cast<u32>(910.97f); | ||
| 1123 | default: | ||
| 1124 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1125 | return 0; | ||
| 1126 | } | ||
| 1127 | } | ||
| 1128 | |||
| 1129 | u32 CommandProcessingTimeEstimatorVersion3::Estimate(const DelayCommand& command) const { | ||
| 1130 | switch (sample_count) { | ||
| 1131 | case 160: | ||
| 1132 | if (command.enabled) { | ||
| 1133 | switch (command.parameter.channel_count) { | ||
| 1134 | case 1: | ||
| 1135 | return static_cast<u32>(8929.042f); | ||
| 1136 | case 2: | ||
| 1137 | return static_cast<u32>(25500.75f); | ||
| 1138 | case 4: | ||
| 1139 | return static_cast<u32>(47759.617f); | ||
| 1140 | case 6: | ||
| 1141 | return static_cast<u32>(82203.07f); | ||
| 1142 | default: | ||
| 1143 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 1144 | command.parameter.channel_count); | ||
| 1145 | return 0; | ||
| 1146 | } | ||
| 1147 | } | ||
| 1148 | switch (command.parameter.channel_count) { | ||
| 1149 | case 1: | ||
| 1150 | return static_cast<u32>(1295.206f); | ||
| 1151 | case 2: | ||
| 1152 | return static_cast<u32>(1213.6f); | ||
| 1153 | case 4: | ||
| 1154 | return static_cast<u32>(942.028f); | ||
| 1155 | case 6: | ||
| 1156 | return static_cast<u32>(1001.553f); | ||
| 1157 | default: | ||
| 1158 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 1159 | return 0; | ||
| 1160 | } | ||
| 1161 | case 240: | ||
| 1162 | if (command.enabled) { | ||
| 1163 | switch (command.parameter.channel_count) { | ||
| 1164 | case 1: | ||
| 1165 | return static_cast<u32>(11941.051f); | ||
| 1166 | case 2: | ||
| 1167 | return static_cast<u32>(37197.371f); | ||
| 1168 | case 4: | ||
| 1169 | return static_cast<u32>(69749.836f); | ||
| 1170 | case 6: | ||
| 1171 | return static_cast<u32>(120042.398f); | ||
| 1172 | default: | ||
| 1173 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 1174 | command.parameter.channel_count); | ||
| 1175 | return 0; | ||
| 1176 | } | ||
| 1177 | } | ||
| 1178 | switch (command.parameter.channel_count) { | ||
| 1179 | case 1: | ||
| 1180 | return static_cast<u32>(997.668f); | ||
| 1181 | case 2: | ||
| 1182 | return static_cast<u32>(977.634f); | ||
| 1183 | case 4: | ||
| 1184 | return static_cast<u32>(792.309f); | ||
| 1185 | case 6: | ||
| 1186 | return static_cast<u32>(875.427f); | ||
| 1187 | default: | ||
| 1188 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 1189 | return 0; | ||
| 1190 | } | ||
| 1191 | default: | ||
| 1192 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1193 | return 0; | ||
| 1194 | } | ||
| 1195 | } | ||
| 1196 | |||
| 1197 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1198 | [[maybe_unused]] const UpsampleCommand& command) const { | ||
| 1199 | switch (sample_count) { | ||
| 1200 | case 160: | ||
| 1201 | return static_cast<u32>(312990.0f); | ||
| 1202 | case 240: | ||
| 1203 | return static_cast<u32>(0.0f); | ||
| 1204 | default: | ||
| 1205 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1206 | return 0; | ||
| 1207 | } | ||
| 1208 | } | ||
| 1209 | |||
| 1210 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1211 | [[maybe_unused]] const DownMix6chTo2chCommand& command) const { | ||
| 1212 | switch (sample_count) { | ||
| 1213 | case 160: | ||
| 1214 | return static_cast<u32>(9949.7f); | ||
| 1215 | case 240: | ||
| 1216 | return static_cast<u32>(14679.0f); | ||
| 1217 | default: | ||
| 1218 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1219 | return 0; | ||
| 1220 | } | ||
| 1221 | } | ||
| 1222 | |||
| 1223 | u32 CommandProcessingTimeEstimatorVersion3::Estimate(const AuxCommand& command) const { | ||
| 1224 | switch (sample_count) { | ||
| 1225 | case 160: | ||
| 1226 | if (command.enabled) { | ||
| 1227 | return static_cast<u32>(7182.136f); | ||
| 1228 | } | ||
| 1229 | return static_cast<u32>(472.111f); | ||
| 1230 | case 240: | ||
| 1231 | if (command.enabled) { | ||
| 1232 | return static_cast<u32>(9435.961f); | ||
| 1233 | } | ||
| 1234 | return static_cast<u32>(462.619f); | ||
| 1235 | default: | ||
| 1236 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1237 | return 0; | ||
| 1238 | } | ||
| 1239 | } | ||
| 1240 | |||
| 1241 | u32 CommandProcessingTimeEstimatorVersion3::Estimate(const DeviceSinkCommand& command) const { | ||
| 1242 | switch (command.input_count) { | ||
| 1243 | case 2: | ||
| 1244 | switch (sample_count) { | ||
| 1245 | case 160: | ||
| 1246 | return static_cast<u32>(8979.956f); | ||
| 1247 | case 240: | ||
| 1248 | return static_cast<u32>(9221.907f); | ||
| 1249 | default: | ||
| 1250 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1251 | return 0; | ||
| 1252 | } | ||
| 1253 | case 6: | ||
| 1254 | switch (sample_count) { | ||
| 1255 | case 160: | ||
| 1256 | return static_cast<u32>(9177.903f); | ||
| 1257 | case 240: | ||
| 1258 | return static_cast<u32>(9725.897f); | ||
| 1259 | default: | ||
| 1260 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1261 | return 0; | ||
| 1262 | } | ||
| 1263 | default: | ||
| 1264 | LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count); | ||
| 1265 | return 0; | ||
| 1266 | } | ||
| 1267 | } | ||
| 1268 | |||
| 1269 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1270 | const CircularBufferSinkCommand& command) const { | ||
| 1271 | switch (sample_count) { | ||
| 1272 | case 160: | ||
| 1273 | return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f); | ||
| 1274 | case 240: | ||
| 1275 | return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f); | ||
| 1276 | default: | ||
| 1277 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1278 | return 0; | ||
| 1279 | } | ||
| 1280 | } | ||
| 1281 | |||
| 1282 | u32 CommandProcessingTimeEstimatorVersion3::Estimate(const ReverbCommand& command) const { | ||
| 1283 | switch (sample_count) { | ||
| 1284 | case 160: | ||
| 1285 | if (command.enabled) { | ||
| 1286 | switch (command.parameter.channel_count) { | ||
| 1287 | case 1: | ||
| 1288 | return static_cast<u32>(81475.055f); | ||
| 1289 | case 2: | ||
| 1290 | return static_cast<u32>(84975.0f); | ||
| 1291 | case 4: | ||
| 1292 | return static_cast<u32>(91625.148f); | ||
| 1293 | case 6: | ||
| 1294 | return static_cast<u32>(95332.266f); | ||
| 1295 | default: | ||
| 1296 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 1297 | command.parameter.channel_count); | ||
| 1298 | return 0; | ||
| 1299 | } | ||
| 1300 | } | ||
| 1301 | switch (command.parameter.channel_count) { | ||
| 1302 | case 1: | ||
| 1303 | return static_cast<u32>(536.298f); | ||
| 1304 | case 2: | ||
| 1305 | return static_cast<u32>(588.798f); | ||
| 1306 | case 4: | ||
| 1307 | return static_cast<u32>(643.702f); | ||
| 1308 | case 6: | ||
| 1309 | return static_cast<u32>(705.999f); | ||
| 1310 | default: | ||
| 1311 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 1312 | return 0; | ||
| 1313 | } | ||
| 1314 | case 240: | ||
| 1315 | if (command.enabled) { | ||
| 1316 | switch (command.parameter.channel_count) { | ||
| 1317 | case 1: | ||
| 1318 | return static_cast<u32>(120174.469f); | ||
| 1319 | case 2: | ||
| 1320 | return static_cast<u32>(125262.219f); | ||
| 1321 | case 4: | ||
| 1322 | return static_cast<u32>(135751.234f); | ||
| 1323 | case 6: | ||
| 1324 | return static_cast<u32>(141129.234f); | ||
| 1325 | default: | ||
| 1326 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 1327 | command.parameter.channel_count); | ||
| 1328 | return 0; | ||
| 1329 | } | ||
| 1330 | } | ||
| 1331 | switch (command.parameter.channel_count) { | ||
| 1332 | case 1: | ||
| 1333 | return static_cast<u32>(617.641f); | ||
| 1334 | case 2: | ||
| 1335 | return static_cast<u32>(659.536f); | ||
| 1336 | case 4: | ||
| 1337 | return static_cast<u32>(711.438f); | ||
| 1338 | case 6: | ||
| 1339 | return static_cast<u32>(778.071f); | ||
| 1340 | default: | ||
| 1341 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 1342 | return 0; | ||
| 1343 | } | ||
| 1344 | default: | ||
| 1345 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1346 | return 0; | ||
| 1347 | } | ||
| 1348 | } | ||
| 1349 | |||
| 1350 | u32 CommandProcessingTimeEstimatorVersion3::Estimate(const I3dl2ReverbCommand& command) const { | ||
| 1351 | switch (sample_count) { | ||
| 1352 | case 160: | ||
| 1353 | if (command.enabled) { | ||
| 1354 | switch (command.parameter.channel_count) { | ||
| 1355 | case 1: | ||
| 1356 | return static_cast<u32>(116754.984f); | ||
| 1357 | case 2: | ||
| 1358 | return static_cast<u32>(125912.055f); | ||
| 1359 | case 4: | ||
| 1360 | return static_cast<u32>(146336.031f); | ||
| 1361 | case 6: | ||
| 1362 | return static_cast<u32>(165812.656f); | ||
| 1363 | default: | ||
| 1364 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 1365 | command.parameter.channel_count); | ||
| 1366 | return 0; | ||
| 1367 | } | ||
| 1368 | } | ||
| 1369 | switch (command.parameter.channel_count) { | ||
| 1370 | case 1: | ||
| 1371 | return static_cast<u32>(735.0f); | ||
| 1372 | case 2: | ||
| 1373 | return static_cast<u32>(766.615f); | ||
| 1374 | case 4: | ||
| 1375 | return static_cast<u32>(834.067f); | ||
| 1376 | case 6: | ||
| 1377 | return static_cast<u32>(875.437f); | ||
| 1378 | default: | ||
| 1379 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 1380 | return 0; | ||
| 1381 | } | ||
| 1382 | case 240: | ||
| 1383 | if (command.enabled) { | ||
| 1384 | switch (command.parameter.channel_count) { | ||
| 1385 | case 1: | ||
| 1386 | return static_cast<u32>(170292.344f); | ||
| 1387 | case 2: | ||
| 1388 | return static_cast<u32>(183875.625f); | ||
| 1389 | case 4: | ||
| 1390 | return static_cast<u32>(214696.188f); | ||
| 1391 | case 6: | ||
| 1392 | return static_cast<u32>(243846.766f); | ||
| 1393 | default: | ||
| 1394 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 1395 | command.parameter.channel_count); | ||
| 1396 | return 0; | ||
| 1397 | } | ||
| 1398 | } | ||
| 1399 | switch (command.parameter.channel_count) { | ||
| 1400 | case 1: | ||
| 1401 | return static_cast<u32>(508.473f); | ||
| 1402 | case 2: | ||
| 1403 | return static_cast<u32>(582.445f); | ||
| 1404 | case 4: | ||
| 1405 | return static_cast<u32>(626.419f); | ||
| 1406 | case 6: | ||
| 1407 | return static_cast<u32>(682.468f); | ||
| 1408 | default: | ||
| 1409 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 1410 | return 0; | ||
| 1411 | } | ||
| 1412 | default: | ||
| 1413 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1414 | return 0; | ||
| 1415 | } | ||
| 1416 | } | ||
| 1417 | |||
| 1418 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1419 | [[maybe_unused]] const PerformanceCommand& command) const { | ||
| 1420 | switch (sample_count) { | ||
| 1421 | case 160: | ||
| 1422 | return static_cast<u32>(498.17f); | ||
| 1423 | case 240: | ||
| 1424 | return static_cast<u32>(489.42f); | ||
| 1425 | default: | ||
| 1426 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1427 | return 0; | ||
| 1428 | } | ||
| 1429 | } | ||
| 1430 | |||
| 1431 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1432 | [[maybe_unused]] const ClearMixBufferCommand& command) const { | ||
| 1433 | switch (sample_count) { | ||
| 1434 | case 160: | ||
| 1435 | return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f); | ||
| 1436 | case 240: | ||
| 1437 | return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f); | ||
| 1438 | default: | ||
| 1439 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1440 | return 0; | ||
| 1441 | } | ||
| 1442 | } | ||
| 1443 | |||
| 1444 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1445 | [[maybe_unused]] const CopyMixBufferCommand& command) const { | ||
| 1446 | switch (sample_count) { | ||
| 1447 | case 160: | ||
| 1448 | return static_cast<u32>(842.59f); | ||
| 1449 | case 240: | ||
| 1450 | return static_cast<u32>(986.72f); | ||
| 1451 | default: | ||
| 1452 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1453 | return 0; | ||
| 1454 | } | ||
| 1455 | } | ||
| 1456 | |||
| 1457 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1458 | const LightLimiterVersion1Command& command) const { | ||
| 1459 | switch (sample_count) { | ||
| 1460 | case 160: | ||
| 1461 | if (command.enabled) { | ||
| 1462 | switch (command.parameter.channel_count) { | ||
| 1463 | case 1: | ||
| 1464 | return static_cast<u32>(21392.383f); | ||
| 1465 | case 2: | ||
| 1466 | return static_cast<u32>(26829.389f); | ||
| 1467 | case 4: | ||
| 1468 | return static_cast<u32>(32405.152f); | ||
| 1469 | case 6: | ||
| 1470 | return static_cast<u32>(52218.586f); | ||
| 1471 | default: | ||
| 1472 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 1473 | command.parameter.channel_count); | ||
| 1474 | return 0; | ||
| 1475 | } | ||
| 1476 | } | ||
| 1477 | switch (command.parameter.channel_count) { | ||
| 1478 | case 1: | ||
| 1479 | return static_cast<u32>(897.004f); | ||
| 1480 | case 2: | ||
| 1481 | return static_cast<u32>(931.549f); | ||
| 1482 | case 4: | ||
| 1483 | return static_cast<u32>(975.387f); | ||
| 1484 | case 6: | ||
| 1485 | return static_cast<u32>(1016.778f); | ||
| 1486 | default: | ||
| 1487 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 1488 | return 0; | ||
| 1489 | } | ||
| 1490 | case 240: | ||
| 1491 | if (command.enabled) { | ||
| 1492 | switch (command.parameter.channel_count) { | ||
| 1493 | case 1: | ||
| 1494 | return static_cast<u32>(30555.504f); | ||
| 1495 | case 2: | ||
| 1496 | return static_cast<u32>(39010.785f); | ||
| 1497 | case 4: | ||
| 1498 | return static_cast<u32>(48270.18f); | ||
| 1499 | case 6: | ||
| 1500 | return static_cast<u32>(76711.875f); | ||
| 1501 | default: | ||
| 1502 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 1503 | command.parameter.channel_count); | ||
| 1504 | return 0; | ||
| 1505 | } | ||
| 1506 | } | ||
| 1507 | switch (command.parameter.channel_count) { | ||
| 1508 | case 1: | ||
| 1509 | return static_cast<u32>(874.429f); | ||
| 1510 | case 2: | ||
| 1511 | return static_cast<u32>(921.553f); | ||
| 1512 | case 4: | ||
| 1513 | return static_cast<u32>(945.262f); | ||
| 1514 | case 6: | ||
| 1515 | return static_cast<u32>(992.26f); | ||
| 1516 | default: | ||
| 1517 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 1518 | return 0; | ||
| 1519 | } | ||
| 1520 | default: | ||
| 1521 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1522 | return 0; | ||
| 1523 | } | ||
| 1524 | } | ||
| 1525 | |||
| 1526 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1527 | const LightLimiterVersion2Command& command) const { | ||
| 1528 | switch (sample_count) { | ||
| 1529 | case 160: | ||
| 1530 | if (command.enabled) { | ||
| 1531 | if (command.parameter.statistics_enabled) { | ||
| 1532 | switch (command.parameter.channel_count) { | ||
| 1533 | case 1: | ||
| 1534 | return static_cast<u32>(23308.928f); | ||
| 1535 | case 2: | ||
| 1536 | return static_cast<u32>(29954.062f); | ||
| 1537 | case 4: | ||
| 1538 | return static_cast<u32>(35807.477f); | ||
| 1539 | case 6: | ||
| 1540 | return static_cast<u32>(58339.773f); | ||
| 1541 | default: | ||
| 1542 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 1543 | command.parameter.channel_count); | ||
| 1544 | return 0; | ||
| 1545 | } | ||
| 1546 | } | ||
| 1547 | switch (command.parameter.channel_count) { | ||
| 1548 | case 1: | ||
| 1549 | return static_cast<u32>(21392.383f); | ||
| 1550 | case 2: | ||
| 1551 | return static_cast<u32>(26829.389f); | ||
| 1552 | case 4: | ||
| 1553 | return static_cast<u32>(32405.152f); | ||
| 1554 | case 6: | ||
| 1555 | return static_cast<u32>(52218.586f); | ||
| 1556 | default: | ||
| 1557 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 1558 | command.parameter.channel_count); | ||
| 1559 | return 0; | ||
| 1560 | } | ||
| 1561 | } | ||
| 1562 | switch (command.parameter.channel_count) { | ||
| 1563 | case 1: | ||
| 1564 | return static_cast<u32>(897.004f); | ||
| 1565 | case 2: | ||
| 1566 | return static_cast<u32>(931.549f); | ||
| 1567 | case 4: | ||
| 1568 | return static_cast<u32>(975.387f); | ||
| 1569 | case 6: | ||
| 1570 | return static_cast<u32>(1016.778f); | ||
| 1571 | default: | ||
| 1572 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 1573 | return 0; | ||
| 1574 | } | ||
| 1575 | case 240: | ||
| 1576 | if (command.enabled) { | ||
| 1577 | if (command.parameter.statistics_enabled) { | ||
| 1578 | switch (command.parameter.channel_count) { | ||
| 1579 | case 1: | ||
| 1580 | return static_cast<u32>(33526.121f); | ||
| 1581 | case 2: | ||
| 1582 | return static_cast<u32>(43549.355f); | ||
| 1583 | case 4: | ||
| 1584 | return static_cast<u32>(52190.281f); | ||
| 1585 | case 6: | ||
| 1586 | return static_cast<u32>(85526.516f); | ||
| 1587 | default: | ||
| 1588 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 1589 | command.parameter.channel_count); | ||
| 1590 | return 0; | ||
| 1591 | } | ||
| 1592 | } | ||
| 1593 | switch (command.parameter.channel_count) { | ||
| 1594 | case 1: | ||
| 1595 | return static_cast<u32>(30555.504f); | ||
| 1596 | case 2: | ||
| 1597 | return static_cast<u32>(39010.785f); | ||
| 1598 | case 4: | ||
| 1599 | return static_cast<u32>(48270.18f); | ||
| 1600 | case 6: | ||
| 1601 | return static_cast<u32>(76711.875f); | ||
| 1602 | default: | ||
| 1603 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 1604 | command.parameter.channel_count); | ||
| 1605 | return 0; | ||
| 1606 | } | ||
| 1607 | } | ||
| 1608 | switch (command.parameter.channel_count) { | ||
| 1609 | case 1: | ||
| 1610 | return static_cast<u32>(874.429f); | ||
| 1611 | case 2: | ||
| 1612 | return static_cast<u32>(921.553f); | ||
| 1613 | case 4: | ||
| 1614 | return static_cast<u32>(945.262f); | ||
| 1615 | case 6: | ||
| 1616 | return static_cast<u32>(992.26f); | ||
| 1617 | default: | ||
| 1618 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 1619 | return 0; | ||
| 1620 | } | ||
| 1621 | default: | ||
| 1622 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1623 | return 0; | ||
| 1624 | } | ||
| 1625 | } | ||
| 1626 | |||
| 1627 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1628 | [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { | ||
| 1629 | return 0; | ||
| 1630 | } | ||
| 1631 | |||
| 1632 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1633 | [[maybe_unused]] const CaptureCommand& command) const { | ||
| 1634 | return 0; | ||
| 1635 | } | ||
| 1636 | |||
| 1637 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1638 | [[maybe_unused]] const CompressorCommand& command) const { | ||
| 1639 | return 0; | ||
| 1640 | } | ||
| 1641 | |||
| 1642 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 1643 | const PcmInt16DataSourceVersion1Command& command) const { | ||
| 1644 | switch (sample_count) { | ||
| 1645 | case 160: | ||
| 1646 | return static_cast<u32>( | ||
| 1647 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 1648 | (command.pitch * 0.000030518f)) * | ||
| 1649 | 427.52f + | ||
| 1650 | 6329.442f); | ||
| 1651 | case 240: | ||
| 1652 | return static_cast<u32>( | ||
| 1653 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 1654 | (command.pitch * 0.000030518f)) * | ||
| 1655 | 710.143f + | ||
| 1656 | 7853.286f); | ||
| 1657 | default: | ||
| 1658 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1659 | return 0; | ||
| 1660 | } | ||
| 1661 | } | ||
| 1662 | |||
| 1663 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 1664 | const PcmInt16DataSourceVersion2Command& command) const { | ||
| 1665 | switch (sample_count) { | ||
| 1666 | case 160: | ||
| 1667 | switch (command.src_quality) { | ||
| 1668 | case SrcQuality::Medium: | ||
| 1669 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1670 | static_cast<f32>(sample_count)) * | ||
| 1671 | (command.pitch * 0.000030518f)) - | ||
| 1672 | 1.0f) * | ||
| 1673 | 427.52f + | ||
| 1674 | 6329.442f); | ||
| 1675 | case SrcQuality::High: | ||
| 1676 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1677 | static_cast<f32>(sample_count)) * | ||
| 1678 | (command.pitch * 0.000030518f)) - | ||
| 1679 | 1.0f) * | ||
| 1680 | 371.876f + | ||
| 1681 | 8049.415f); | ||
| 1682 | case SrcQuality::Low: | ||
| 1683 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1684 | static_cast<f32>(sample_count)) * | ||
| 1685 | (command.pitch * 0.000030518f)) - | ||
| 1686 | 1.0f) * | ||
| 1687 | 423.43f + | ||
| 1688 | 5062.659f); | ||
| 1689 | default: | ||
| 1690 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 1691 | static_cast<u32>(command.src_quality)); | ||
| 1692 | return 0; | ||
| 1693 | } | ||
| 1694 | |||
| 1695 | case 240: | ||
| 1696 | switch (command.src_quality) { | ||
| 1697 | case SrcQuality::Medium: | ||
| 1698 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1699 | static_cast<f32>(sample_count)) * | ||
| 1700 | (command.pitch * 0.000030518f)) - | ||
| 1701 | 1.0f) * | ||
| 1702 | 710.143f + | ||
| 1703 | 7853.286f); | ||
| 1704 | case SrcQuality::High: | ||
| 1705 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1706 | static_cast<f32>(sample_count)) * | ||
| 1707 | (command.pitch * 0.000030518f)) - | ||
| 1708 | 1.0f) * | ||
| 1709 | 610.487f + | ||
| 1710 | 10138.842f); | ||
| 1711 | case SrcQuality::Low: | ||
| 1712 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1713 | static_cast<f32>(sample_count)) * | ||
| 1714 | (command.pitch * 0.000030518f)) - | ||
| 1715 | 1.0f) * | ||
| 1716 | 676.722f + | ||
| 1717 | 5810.962f); | ||
| 1718 | default: | ||
| 1719 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 1720 | static_cast<u32>(command.src_quality)); | ||
| 1721 | return 0; | ||
| 1722 | } | ||
| 1723 | |||
| 1724 | default: | ||
| 1725 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1726 | return 0; | ||
| 1727 | } | ||
| 1728 | } | ||
| 1729 | |||
| 1730 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 1731 | const PcmFloatDataSourceVersion1Command& command) const { | ||
| 1732 | switch (sample_count) { | ||
| 1733 | case 160: | ||
| 1734 | return static_cast<u32>( | ||
| 1735 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 1736 | (command.pitch * 0.000030518f)) * | ||
| 1737 | 1672.026f + | ||
| 1738 | 7681.211f); | ||
| 1739 | case 240: | ||
| 1740 | return static_cast<u32>( | ||
| 1741 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 1742 | (command.pitch * 0.000030518f)) * | ||
| 1743 | 2550.414f + | ||
| 1744 | 9663.969f); | ||
| 1745 | default: | ||
| 1746 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1747 | return 0; | ||
| 1748 | } | ||
| 1749 | } | ||
| 1750 | |||
| 1751 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 1752 | const PcmFloatDataSourceVersion2Command& command) const { | ||
| 1753 | switch (sample_count) { | ||
| 1754 | case 160: | ||
| 1755 | switch (command.src_quality) { | ||
| 1756 | case SrcQuality::Medium: | ||
| 1757 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1758 | static_cast<f32>(sample_count)) * | ||
| 1759 | (command.pitch * 0.000030518f)) - | ||
| 1760 | 1.0f) * | ||
| 1761 | 1672.026f + | ||
| 1762 | 7681.211f); | ||
| 1763 | case SrcQuality::High: | ||
| 1764 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1765 | static_cast<f32>(sample_count)) * | ||
| 1766 | (command.pitch * 0.000030518f)) - | ||
| 1767 | 1.0f) * | ||
| 1768 | 1672.982f + | ||
| 1769 | 9038.011f); | ||
| 1770 | case SrcQuality::Low: | ||
| 1771 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1772 | static_cast<f32>(sample_count)) * | ||
| 1773 | (command.pitch * 0.000030518f)) - | ||
| 1774 | 1.0f) * | ||
| 1775 | 1673.216f + | ||
| 1776 | 6027.577f); | ||
| 1777 | default: | ||
| 1778 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 1779 | static_cast<u32>(command.src_quality)); | ||
| 1780 | return 0; | ||
| 1781 | } | ||
| 1782 | |||
| 1783 | case 240: | ||
| 1784 | switch (command.src_quality) { | ||
| 1785 | case SrcQuality::Medium: | ||
| 1786 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1787 | static_cast<f32>(sample_count)) * | ||
| 1788 | (command.pitch * 0.000030518f)) - | ||
| 1789 | 1.0f) * | ||
| 1790 | 2550.414f + | ||
| 1791 | 9663.969f); | ||
| 1792 | case SrcQuality::High: | ||
| 1793 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1794 | static_cast<f32>(sample_count)) * | ||
| 1795 | (command.pitch * 0.000030518f)) - | ||
| 1796 | 1.0f) * | ||
| 1797 | 2522.303f + | ||
| 1798 | 11758.571f); | ||
| 1799 | case SrcQuality::Low: | ||
| 1800 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1801 | static_cast<f32>(sample_count)) * | ||
| 1802 | (command.pitch * 0.000030518f)) - | ||
| 1803 | 1.0f) * | ||
| 1804 | 2537.061f + | ||
| 1805 | 7369.309f); | ||
| 1806 | default: | ||
| 1807 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 1808 | static_cast<u32>(command.src_quality)); | ||
| 1809 | return 0; | ||
| 1810 | } | ||
| 1811 | |||
| 1812 | default: | ||
| 1813 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1814 | return 0; | ||
| 1815 | } | ||
| 1816 | } | ||
| 1817 | |||
| 1818 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 1819 | const AdpcmDataSourceVersion1Command& command) const { | ||
| 1820 | switch (sample_count) { | ||
| 1821 | case 160: | ||
| 1822 | return static_cast<u32>( | ||
| 1823 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 1824 | (command.pitch * 0.000030518f)) * | ||
| 1825 | 1827.665f + | ||
| 1826 | 7913.808f); | ||
| 1827 | case 240: | ||
| 1828 | return static_cast<u32>( | ||
| 1829 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 1830 | (command.pitch * 0.000030518f)) * | ||
| 1831 | 2756.372f + | ||
| 1832 | 9736.702f); | ||
| 1833 | default: | ||
| 1834 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1835 | return 0; | ||
| 1836 | } | ||
| 1837 | } | ||
| 1838 | |||
| 1839 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 1840 | const AdpcmDataSourceVersion2Command& command) const { | ||
| 1841 | switch (sample_count) { | ||
| 1842 | case 160: | ||
| 1843 | switch (command.src_quality) { | ||
| 1844 | case SrcQuality::Medium: | ||
| 1845 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1846 | static_cast<f32>(sample_count)) * | ||
| 1847 | (command.pitch * 0.000030518f)) - | ||
| 1848 | 1.0f) * | ||
| 1849 | 1827.665f + | ||
| 1850 | 7913.808f); | ||
| 1851 | case SrcQuality::High: | ||
| 1852 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1853 | static_cast<f32>(sample_count)) * | ||
| 1854 | (command.pitch * 0.000030518f)) - | ||
| 1855 | 1.0f) * | ||
| 1856 | 1829.285f + | ||
| 1857 | 9607.814f); | ||
| 1858 | case SrcQuality::Low: | ||
| 1859 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1860 | static_cast<f32>(sample_count)) * | ||
| 1861 | (command.pitch * 0.000030518f)) - | ||
| 1862 | 1.0f) * | ||
| 1863 | 1824.609f + | ||
| 1864 | 6517.476f); | ||
| 1865 | default: | ||
| 1866 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 1867 | static_cast<u32>(command.src_quality)); | ||
| 1868 | return 0; | ||
| 1869 | } | ||
| 1870 | |||
| 1871 | case 240: | ||
| 1872 | switch (command.src_quality) { | ||
| 1873 | case SrcQuality::Medium: | ||
| 1874 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1875 | static_cast<f32>(sample_count)) * | ||
| 1876 | (command.pitch * 0.000030518f)) - | ||
| 1877 | 1.0f) * | ||
| 1878 | 2756.372f + | ||
| 1879 | 9736.702f); | ||
| 1880 | case SrcQuality::High: | ||
| 1881 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1882 | static_cast<f32>(sample_count)) * | ||
| 1883 | (command.pitch * 0.000030518f)) - | ||
| 1884 | 1.0f) * | ||
| 1885 | 2731.308f + | ||
| 1886 | 12154.379f); | ||
| 1887 | case SrcQuality::Low: | ||
| 1888 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 1889 | static_cast<f32>(sample_count)) * | ||
| 1890 | (command.pitch * 0.000030518f)) - | ||
| 1891 | 1.0f) * | ||
| 1892 | 2732.152f + | ||
| 1893 | 7929.442f); | ||
| 1894 | default: | ||
| 1895 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 1896 | static_cast<u32>(command.src_quality)); | ||
| 1897 | return 0; | ||
| 1898 | } | ||
| 1899 | |||
| 1900 | default: | ||
| 1901 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1902 | return 0; | ||
| 1903 | } | ||
| 1904 | } | ||
| 1905 | |||
| 1906 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 1907 | [[maybe_unused]] const VolumeCommand& command) const { | ||
| 1908 | switch (sample_count) { | ||
| 1909 | case 160: | ||
| 1910 | return static_cast<u32>(1311.1f); | ||
| 1911 | case 240: | ||
| 1912 | return static_cast<u32>(1713.6f); | ||
| 1913 | default: | ||
| 1914 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1915 | return 0; | ||
| 1916 | } | ||
| 1917 | } | ||
| 1918 | |||
| 1919 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 1920 | [[maybe_unused]] const VolumeRampCommand& command) const { | ||
| 1921 | switch (sample_count) { | ||
| 1922 | case 160: | ||
| 1923 | return static_cast<u32>(1425.3f); | ||
| 1924 | case 240: | ||
| 1925 | return static_cast<u32>(1700.0f); | ||
| 1926 | default: | ||
| 1927 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1928 | return 0; | ||
| 1929 | } | ||
| 1930 | } | ||
| 1931 | |||
| 1932 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 1933 | [[maybe_unused]] const BiquadFilterCommand& command) const { | ||
| 1934 | switch (sample_count) { | ||
| 1935 | case 160: | ||
| 1936 | return static_cast<u32>(4173.2f); | ||
| 1937 | case 240: | ||
| 1938 | return static_cast<u32>(5585.1f); | ||
| 1939 | default: | ||
| 1940 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1941 | return 0; | ||
| 1942 | } | ||
| 1943 | } | ||
| 1944 | |||
| 1945 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 1946 | [[maybe_unused]] const MixCommand& command) const { | ||
| 1947 | switch (sample_count) { | ||
| 1948 | case 160: | ||
| 1949 | return static_cast<u32>(1402.8f); | ||
| 1950 | case 240: | ||
| 1951 | return static_cast<u32>(1853.2f); | ||
| 1952 | default: | ||
| 1953 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1954 | return 0; | ||
| 1955 | } | ||
| 1956 | } | ||
| 1957 | |||
| 1958 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 1959 | [[maybe_unused]] const MixRampCommand& command) const { | ||
| 1960 | switch (sample_count) { | ||
| 1961 | case 160: | ||
| 1962 | return static_cast<u32>(1968.7f); | ||
| 1963 | case 240: | ||
| 1964 | return static_cast<u32>(2459.4f); | ||
| 1965 | default: | ||
| 1966 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1967 | return 0; | ||
| 1968 | } | ||
| 1969 | } | ||
| 1970 | |||
| 1971 | u32 CommandProcessingTimeEstimatorVersion4::Estimate(const MixRampGroupedCommand& command) const { | ||
| 1972 | u32 count{0}; | ||
| 1973 | for (u32 i = 0; i < command.buffer_count; i++) { | ||
| 1974 | if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { | ||
| 1975 | count++; | ||
| 1976 | } | ||
| 1977 | } | ||
| 1978 | |||
| 1979 | switch (sample_count) { | ||
| 1980 | case 160: | ||
| 1981 | return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) * | ||
| 1982 | static_cast<f32>(count)); | ||
| 1983 | case 240: | ||
| 1984 | return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) * | ||
| 1985 | static_cast<f32>(count)); | ||
| 1986 | default: | ||
| 1987 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 1988 | return 0; | ||
| 1989 | } | ||
| 1990 | } | ||
| 1991 | |||
| 1992 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 1993 | [[maybe_unused]] const DepopPrepareCommand& command) const { | ||
| 1994 | return 0; | ||
| 1995 | } | ||
| 1996 | |||
| 1997 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 1998 | [[maybe_unused]] const DepopForMixBuffersCommand& command) const { | ||
| 1999 | switch (sample_count) { | ||
| 2000 | case 160: | ||
| 2001 | return static_cast<u32>(739.64f); | ||
| 2002 | case 240: | ||
| 2003 | return static_cast<u32>(910.97f); | ||
| 2004 | default: | ||
| 2005 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2006 | return 0; | ||
| 2007 | } | ||
| 2008 | } | ||
| 2009 | |||
| 2010 | u32 CommandProcessingTimeEstimatorVersion4::Estimate(const DelayCommand& command) const { | ||
| 2011 | switch (sample_count) { | ||
| 2012 | case 160: | ||
| 2013 | if (command.enabled) { | ||
| 2014 | switch (command.parameter.channel_count) { | ||
| 2015 | case 1: | ||
| 2016 | return static_cast<u32>(8929.042f); | ||
| 2017 | case 2: | ||
| 2018 | return static_cast<u32>(25500.75f); | ||
| 2019 | case 4: | ||
| 2020 | return static_cast<u32>(47759.617f); | ||
| 2021 | case 6: | ||
| 2022 | return static_cast<u32>(82203.07f); | ||
| 2023 | default: | ||
| 2024 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 2025 | command.parameter.channel_count); | ||
| 2026 | return 0; | ||
| 2027 | } | ||
| 2028 | } | ||
| 2029 | switch (command.parameter.channel_count) { | ||
| 2030 | case 1: | ||
| 2031 | return static_cast<u32>(1295.206f); | ||
| 2032 | case 2: | ||
| 2033 | return static_cast<u32>(1213.6f); | ||
| 2034 | case 4: | ||
| 2035 | return static_cast<u32>(942.028f); | ||
| 2036 | case 6: | ||
| 2037 | return static_cast<u32>(1001.553f); | ||
| 2038 | default: | ||
| 2039 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 2040 | return 0; | ||
| 2041 | } | ||
| 2042 | case 240: | ||
| 2043 | if (command.enabled) { | ||
| 2044 | switch (command.parameter.channel_count) { | ||
| 2045 | case 1: | ||
| 2046 | return static_cast<u32>(11941.051f); | ||
| 2047 | case 2: | ||
| 2048 | return static_cast<u32>(37197.371f); | ||
| 2049 | case 4: | ||
| 2050 | return static_cast<u32>(69749.836f); | ||
| 2051 | case 6: | ||
| 2052 | return static_cast<u32>(120042.398f); | ||
| 2053 | default: | ||
| 2054 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 2055 | command.parameter.channel_count); | ||
| 2056 | return 0; | ||
| 2057 | } | ||
| 2058 | } | ||
| 2059 | switch (command.parameter.channel_count) { | ||
| 2060 | case 1: | ||
| 2061 | return static_cast<u32>(997.668f); | ||
| 2062 | case 2: | ||
| 2063 | return static_cast<u32>(977.634f); | ||
| 2064 | case 4: | ||
| 2065 | return static_cast<u32>(792.309f); | ||
| 2066 | case 6: | ||
| 2067 | return static_cast<u32>(875.427f); | ||
| 2068 | default: | ||
| 2069 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 2070 | return 0; | ||
| 2071 | } | ||
| 2072 | default: | ||
| 2073 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2074 | return 0; | ||
| 2075 | } | ||
| 2076 | } | ||
| 2077 | |||
| 2078 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 2079 | [[maybe_unused]] const UpsampleCommand& command) const { | ||
| 2080 | switch (sample_count) { | ||
| 2081 | case 160: | ||
| 2082 | return static_cast<u32>(312990.0f); | ||
| 2083 | case 240: | ||
| 2084 | return static_cast<u32>(0.0f); | ||
| 2085 | default: | ||
| 2086 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2087 | return 0; | ||
| 2088 | } | ||
| 2089 | } | ||
| 2090 | |||
| 2091 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 2092 | [[maybe_unused]] const DownMix6chTo2chCommand& command) const { | ||
| 2093 | switch (sample_count) { | ||
| 2094 | case 160: | ||
| 2095 | return static_cast<u32>(9949.7f); | ||
| 2096 | case 240: | ||
| 2097 | return static_cast<u32>(14679.0f); | ||
| 2098 | default: | ||
| 2099 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2100 | return 0; | ||
| 2101 | } | ||
| 2102 | } | ||
| 2103 | |||
| 2104 | u32 CommandProcessingTimeEstimatorVersion4::Estimate(const AuxCommand& command) const { | ||
| 2105 | switch (sample_count) { | ||
| 2106 | case 160: | ||
| 2107 | if (command.enabled) { | ||
| 2108 | return static_cast<u32>(7182.136f); | ||
| 2109 | } | ||
| 2110 | return static_cast<u32>(472.111f); | ||
| 2111 | case 240: | ||
| 2112 | if (command.enabled) { | ||
| 2113 | return static_cast<u32>(9435.961f); | ||
| 2114 | } | ||
| 2115 | return static_cast<u32>(462.619f); | ||
| 2116 | default: | ||
| 2117 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2118 | return 0; | ||
| 2119 | } | ||
| 2120 | } | ||
| 2121 | |||
| 2122 | u32 CommandProcessingTimeEstimatorVersion4::Estimate(const DeviceSinkCommand& command) const { | ||
| 2123 | switch (command.input_count) { | ||
| 2124 | case 2: | ||
| 2125 | switch (sample_count) { | ||
| 2126 | case 160: | ||
| 2127 | return static_cast<u32>(8979.956f); | ||
| 2128 | case 240: | ||
| 2129 | return static_cast<u32>(9221.907f); | ||
| 2130 | default: | ||
| 2131 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2132 | return 0; | ||
| 2133 | } | ||
| 2134 | case 6: | ||
| 2135 | switch (sample_count) { | ||
| 2136 | case 160: | ||
| 2137 | return static_cast<u32>(9177.903f); | ||
| 2138 | case 240: | ||
| 2139 | return static_cast<u32>(9725.897f); | ||
| 2140 | default: | ||
| 2141 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2142 | return 0; | ||
| 2143 | } | ||
| 2144 | default: | ||
| 2145 | LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count); | ||
| 2146 | return 0; | ||
| 2147 | } | ||
| 2148 | } | ||
| 2149 | |||
| 2150 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 2151 | const CircularBufferSinkCommand& command) const { | ||
| 2152 | switch (sample_count) { | ||
| 2153 | case 160: | ||
| 2154 | return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f); | ||
| 2155 | case 240: | ||
| 2156 | return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f); | ||
| 2157 | default: | ||
| 2158 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2159 | return 0; | ||
| 2160 | } | ||
| 2161 | } | ||
| 2162 | |||
| 2163 | u32 CommandProcessingTimeEstimatorVersion4::Estimate(const ReverbCommand& command) const { | ||
| 2164 | switch (sample_count) { | ||
| 2165 | case 160: | ||
| 2166 | if (command.enabled) { | ||
| 2167 | switch (command.parameter.channel_count) { | ||
| 2168 | case 1: | ||
| 2169 | return static_cast<u32>(81475.055f); | ||
| 2170 | case 2: | ||
| 2171 | return static_cast<u32>(84975.0f); | ||
| 2172 | case 4: | ||
| 2173 | return static_cast<u32>(91625.148f); | ||
| 2174 | case 6: | ||
| 2175 | return static_cast<u32>(95332.266f); | ||
| 2176 | default: | ||
| 2177 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 2178 | command.parameter.channel_count); | ||
| 2179 | return 0; | ||
| 2180 | } | ||
| 2181 | } | ||
| 2182 | switch (command.parameter.channel_count) { | ||
| 2183 | case 1: | ||
| 2184 | return static_cast<u32>(536.298f); | ||
| 2185 | case 2: | ||
| 2186 | return static_cast<u32>(588.798f); | ||
| 2187 | case 4: | ||
| 2188 | return static_cast<u32>(643.702f); | ||
| 2189 | case 6: | ||
| 2190 | return static_cast<u32>(705.999f); | ||
| 2191 | default: | ||
| 2192 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 2193 | return 0; | ||
| 2194 | } | ||
| 2195 | case 240: | ||
| 2196 | if (command.enabled) { | ||
| 2197 | switch (command.parameter.channel_count) { | ||
| 2198 | case 1: | ||
| 2199 | return static_cast<u32>(120174.469f); | ||
| 2200 | case 2: | ||
| 2201 | return static_cast<u32>(125262.219f); | ||
| 2202 | case 4: | ||
| 2203 | return static_cast<u32>(135751.234f); | ||
| 2204 | case 6: | ||
| 2205 | return static_cast<u32>(141129.234f); | ||
| 2206 | default: | ||
| 2207 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 2208 | command.parameter.channel_count); | ||
| 2209 | return 0; | ||
| 2210 | } | ||
| 2211 | } | ||
| 2212 | switch (command.parameter.channel_count) { | ||
| 2213 | case 1: | ||
| 2214 | return static_cast<u32>(617.641f); | ||
| 2215 | case 2: | ||
| 2216 | return static_cast<u32>(659.536f); | ||
| 2217 | case 4: | ||
| 2218 | return static_cast<u32>(711.438f); | ||
| 2219 | case 6: | ||
| 2220 | return static_cast<u32>(778.071f); | ||
| 2221 | default: | ||
| 2222 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 2223 | return 0; | ||
| 2224 | } | ||
| 2225 | default: | ||
| 2226 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2227 | return 0; | ||
| 2228 | } | ||
| 2229 | } | ||
| 2230 | |||
| 2231 | u32 CommandProcessingTimeEstimatorVersion4::Estimate(const I3dl2ReverbCommand& command) const { | ||
| 2232 | switch (sample_count) { | ||
| 2233 | case 160: | ||
| 2234 | if (command.enabled) { | ||
| 2235 | switch (command.parameter.channel_count) { | ||
| 2236 | case 1: | ||
| 2237 | return static_cast<u32>(116754.984f); | ||
| 2238 | case 2: | ||
| 2239 | return static_cast<u32>(125912.055f); | ||
| 2240 | case 4: | ||
| 2241 | return static_cast<u32>(146336.031f); | ||
| 2242 | case 6: | ||
| 2243 | return static_cast<u32>(165812.656f); | ||
| 2244 | default: | ||
| 2245 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 2246 | command.parameter.channel_count); | ||
| 2247 | return 0; | ||
| 2248 | } | ||
| 2249 | } | ||
| 2250 | switch (command.parameter.channel_count) { | ||
| 2251 | case 1: | ||
| 2252 | return static_cast<u32>(735.0f); | ||
| 2253 | case 2: | ||
| 2254 | return static_cast<u32>(766.615f); | ||
| 2255 | case 4: | ||
| 2256 | return static_cast<u32>(834.067f); | ||
| 2257 | case 6: | ||
| 2258 | return static_cast<u32>(875.437f); | ||
| 2259 | default: | ||
| 2260 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 2261 | return 0; | ||
| 2262 | } | ||
| 2263 | case 240: | ||
| 2264 | if (command.enabled) { | ||
| 2265 | switch (command.parameter.channel_count) { | ||
| 2266 | case 1: | ||
| 2267 | return static_cast<u32>(170292.344f); | ||
| 2268 | case 2: | ||
| 2269 | return static_cast<u32>(183875.625f); | ||
| 2270 | case 4: | ||
| 2271 | return static_cast<u32>(214696.188f); | ||
| 2272 | case 6: | ||
| 2273 | return static_cast<u32>(243846.766f); | ||
| 2274 | default: | ||
| 2275 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 2276 | command.parameter.channel_count); | ||
| 2277 | return 0; | ||
| 2278 | } | ||
| 2279 | } | ||
| 2280 | switch (command.parameter.channel_count) { | ||
| 2281 | case 1: | ||
| 2282 | return static_cast<u32>(508.473f); | ||
| 2283 | case 2: | ||
| 2284 | return static_cast<u32>(582.445f); | ||
| 2285 | case 4: | ||
| 2286 | return static_cast<u32>(626.419f); | ||
| 2287 | case 6: | ||
| 2288 | return static_cast<u32>(682.468f); | ||
| 2289 | default: | ||
| 2290 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 2291 | return 0; | ||
| 2292 | } | ||
| 2293 | default: | ||
| 2294 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2295 | return 0; | ||
| 2296 | } | ||
| 2297 | } | ||
| 2298 | |||
| 2299 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 2300 | [[maybe_unused]] const PerformanceCommand& command) const { | ||
| 2301 | switch (sample_count) { | ||
| 2302 | case 160: | ||
| 2303 | return static_cast<u32>(498.17f); | ||
| 2304 | case 240: | ||
| 2305 | return static_cast<u32>(489.42f); | ||
| 2306 | default: | ||
| 2307 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2308 | return 0; | ||
| 2309 | } | ||
| 2310 | } | ||
| 2311 | |||
| 2312 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 2313 | [[maybe_unused]] const ClearMixBufferCommand& command) const { | ||
| 2314 | switch (sample_count) { | ||
| 2315 | case 160: | ||
| 2316 | return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f); | ||
| 2317 | case 240: | ||
| 2318 | return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f); | ||
| 2319 | default: | ||
| 2320 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2321 | return 0; | ||
| 2322 | } | ||
| 2323 | } | ||
| 2324 | |||
| 2325 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 2326 | [[maybe_unused]] const CopyMixBufferCommand& command) const { | ||
| 2327 | switch (sample_count) { | ||
| 2328 | case 160: | ||
| 2329 | return static_cast<u32>(842.59f); | ||
| 2330 | case 240: | ||
| 2331 | return static_cast<u32>(986.72f); | ||
| 2332 | default: | ||
| 2333 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2334 | return 0; | ||
| 2335 | } | ||
| 2336 | } | ||
| 2337 | |||
| 2338 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 2339 | const LightLimiterVersion1Command& command) const { | ||
| 2340 | switch (sample_count) { | ||
| 2341 | case 160: | ||
| 2342 | if (command.enabled) { | ||
| 2343 | switch (command.parameter.channel_count) { | ||
| 2344 | case 1: | ||
| 2345 | return static_cast<u32>(21392.383f); | ||
| 2346 | case 2: | ||
| 2347 | return static_cast<u32>(26829.389f); | ||
| 2348 | case 4: | ||
| 2349 | return static_cast<u32>(32405.152f); | ||
| 2350 | case 6: | ||
| 2351 | return static_cast<u32>(52218.586f); | ||
| 2352 | default: | ||
| 2353 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 2354 | command.parameter.channel_count); | ||
| 2355 | return 0; | ||
| 2356 | } | ||
| 2357 | } | ||
| 2358 | switch (command.parameter.channel_count) { | ||
| 2359 | case 1: | ||
| 2360 | return static_cast<u32>(897.004f); | ||
| 2361 | case 2: | ||
| 2362 | return static_cast<u32>(931.549f); | ||
| 2363 | case 4: | ||
| 2364 | return static_cast<u32>(975.387f); | ||
| 2365 | case 6: | ||
| 2366 | return static_cast<u32>(1016.778f); | ||
| 2367 | default: | ||
| 2368 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 2369 | return 0; | ||
| 2370 | } | ||
| 2371 | case 240: | ||
| 2372 | if (command.enabled) { | ||
| 2373 | switch (command.parameter.channel_count) { | ||
| 2374 | case 1: | ||
| 2375 | return static_cast<u32>(30555.504f); | ||
| 2376 | case 2: | ||
| 2377 | return static_cast<u32>(39010.785f); | ||
| 2378 | case 4: | ||
| 2379 | return static_cast<u32>(48270.18f); | ||
| 2380 | case 6: | ||
| 2381 | return static_cast<u32>(76711.875f); | ||
| 2382 | default: | ||
| 2383 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 2384 | command.parameter.channel_count); | ||
| 2385 | return 0; | ||
| 2386 | } | ||
| 2387 | } | ||
| 2388 | switch (command.parameter.channel_count) { | ||
| 2389 | case 1: | ||
| 2390 | return static_cast<u32>(874.429f); | ||
| 2391 | case 2: | ||
| 2392 | return static_cast<u32>(921.553f); | ||
| 2393 | case 4: | ||
| 2394 | return static_cast<u32>(945.262f); | ||
| 2395 | case 6: | ||
| 2396 | return static_cast<u32>(992.26f); | ||
| 2397 | default: | ||
| 2398 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 2399 | return 0; | ||
| 2400 | } | ||
| 2401 | default: | ||
| 2402 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2403 | return 0; | ||
| 2404 | } | ||
| 2405 | } | ||
| 2406 | |||
| 2407 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 2408 | const LightLimiterVersion2Command& command) const { | ||
| 2409 | switch (sample_count) { | ||
| 2410 | case 160: | ||
| 2411 | if (command.enabled) { | ||
| 2412 | if (command.parameter.statistics_enabled) { | ||
| 2413 | switch (command.parameter.channel_count) { | ||
| 2414 | case 1: | ||
| 2415 | return static_cast<u32>(23308.928f); | ||
| 2416 | case 2: | ||
| 2417 | return static_cast<u32>(29954.062f); | ||
| 2418 | case 4: | ||
| 2419 | return static_cast<u32>(35807.477f); | ||
| 2420 | case 6: | ||
| 2421 | return static_cast<u32>(58339.773f); | ||
| 2422 | default: | ||
| 2423 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 2424 | command.parameter.channel_count); | ||
| 2425 | return 0; | ||
| 2426 | } | ||
| 2427 | } | ||
| 2428 | switch (command.parameter.channel_count) { | ||
| 2429 | case 1: | ||
| 2430 | return static_cast<u32>(21392.383f); | ||
| 2431 | case 2: | ||
| 2432 | return static_cast<u32>(26829.389f); | ||
| 2433 | case 4: | ||
| 2434 | return static_cast<u32>(32405.152f); | ||
| 2435 | case 6: | ||
| 2436 | return static_cast<u32>(52218.586f); | ||
| 2437 | default: | ||
| 2438 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 2439 | command.parameter.channel_count); | ||
| 2440 | return 0; | ||
| 2441 | } | ||
| 2442 | } | ||
| 2443 | switch (command.parameter.channel_count) { | ||
| 2444 | case 1: | ||
| 2445 | return static_cast<u32>(897.004f); | ||
| 2446 | case 2: | ||
| 2447 | return static_cast<u32>(931.549f); | ||
| 2448 | case 4: | ||
| 2449 | return static_cast<u32>(975.387f); | ||
| 2450 | case 6: | ||
| 2451 | return static_cast<u32>(1016.778f); | ||
| 2452 | default: | ||
| 2453 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 2454 | return 0; | ||
| 2455 | } | ||
| 2456 | case 240: | ||
| 2457 | if (command.enabled) { | ||
| 2458 | if (command.parameter.statistics_enabled) { | ||
| 2459 | switch (command.parameter.channel_count) { | ||
| 2460 | case 1: | ||
| 2461 | return static_cast<u32>(33526.121f); | ||
| 2462 | case 2: | ||
| 2463 | return static_cast<u32>(43549.355f); | ||
| 2464 | case 4: | ||
| 2465 | return static_cast<u32>(52190.281f); | ||
| 2466 | case 6: | ||
| 2467 | return static_cast<u32>(85526.516f); | ||
| 2468 | default: | ||
| 2469 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 2470 | command.parameter.channel_count); | ||
| 2471 | return 0; | ||
| 2472 | } | ||
| 2473 | } | ||
| 2474 | switch (command.parameter.channel_count) { | ||
| 2475 | case 1: | ||
| 2476 | return static_cast<u32>(30555.504f); | ||
| 2477 | case 2: | ||
| 2478 | return static_cast<u32>(39010.785f); | ||
| 2479 | case 4: | ||
| 2480 | return static_cast<u32>(48270.18f); | ||
| 2481 | case 6: | ||
| 2482 | return static_cast<u32>(76711.875f); | ||
| 2483 | default: | ||
| 2484 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 2485 | command.parameter.channel_count); | ||
| 2486 | return 0; | ||
| 2487 | } | ||
| 2488 | } | ||
| 2489 | switch (command.parameter.channel_count) { | ||
| 2490 | case 1: | ||
| 2491 | return static_cast<u32>(874.429f); | ||
| 2492 | case 2: | ||
| 2493 | return static_cast<u32>(921.553f); | ||
| 2494 | case 4: | ||
| 2495 | return static_cast<u32>(945.262f); | ||
| 2496 | case 6: | ||
| 2497 | return static_cast<u32>(992.26f); | ||
| 2498 | default: | ||
| 2499 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 2500 | return 0; | ||
| 2501 | } | ||
| 2502 | default: | ||
| 2503 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2504 | return 0; | ||
| 2505 | } | ||
| 2506 | } | ||
| 2507 | |||
| 2508 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 2509 | [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { | ||
| 2510 | switch (sample_count) { | ||
| 2511 | case 160: | ||
| 2512 | return static_cast<u32>(7424.5f); | ||
| 2513 | case 240: | ||
| 2514 | return static_cast<u32>(9730.4f); | ||
| 2515 | default: | ||
| 2516 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2517 | return 0; | ||
| 2518 | } | ||
| 2519 | } | ||
| 2520 | |||
| 2521 | u32 CommandProcessingTimeEstimatorVersion4::Estimate(const CaptureCommand& command) const { | ||
| 2522 | switch (sample_count) { | ||
| 2523 | case 160: | ||
| 2524 | if (command.enabled) { | ||
| 2525 | return static_cast<u32>(426.982f); | ||
| 2526 | } | ||
| 2527 | return static_cast<u32>(4261.005f); | ||
| 2528 | case 240: | ||
| 2529 | if (command.enabled) { | ||
| 2530 | return static_cast<u32>(435.204f); | ||
| 2531 | } | ||
| 2532 | return static_cast<u32>(5858.265f); | ||
| 2533 | default: | ||
| 2534 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2535 | return 0; | ||
| 2536 | } | ||
| 2537 | } | ||
| 2538 | |||
| 2539 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 2540 | [[maybe_unused]] const CompressorCommand& command) const { | ||
| 2541 | return 0; | ||
| 2542 | } | ||
| 2543 | |||
| 2544 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 2545 | const PcmInt16DataSourceVersion1Command& command) const { | ||
| 2546 | switch (sample_count) { | ||
| 2547 | case 160: | ||
| 2548 | return static_cast<u32>( | ||
| 2549 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 2550 | (command.pitch * 0.000030518f)) * | ||
| 2551 | 427.52f + | ||
| 2552 | 6329.442f); | ||
| 2553 | case 240: | ||
| 2554 | return static_cast<u32>( | ||
| 2555 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 2556 | (command.pitch * 0.000030518f)) * | ||
| 2557 | 710.143f + | ||
| 2558 | 7853.286f); | ||
| 2559 | default: | ||
| 2560 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2561 | return 0; | ||
| 2562 | } | ||
| 2563 | } | ||
| 2564 | |||
| 2565 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 2566 | const PcmInt16DataSourceVersion2Command& command) const { | ||
| 2567 | switch (sample_count) { | ||
| 2568 | case 160: | ||
| 2569 | switch (command.src_quality) { | ||
| 2570 | case SrcQuality::Medium: | ||
| 2571 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2572 | static_cast<f32>(sample_count)) * | ||
| 2573 | (command.pitch * 0.000030518f)) - | ||
| 2574 | 1.0f) * | ||
| 2575 | 427.52f + | ||
| 2576 | 6329.442f); | ||
| 2577 | case SrcQuality::High: | ||
| 2578 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2579 | static_cast<f32>(sample_count)) * | ||
| 2580 | (command.pitch * 0.000030518f)) - | ||
| 2581 | 1.0f) * | ||
| 2582 | 371.876f + | ||
| 2583 | 8049.415f); | ||
| 2584 | case SrcQuality::Low: | ||
| 2585 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2586 | static_cast<f32>(sample_count)) * | ||
| 2587 | (command.pitch * 0.000030518f)) - | ||
| 2588 | 1.0f) * | ||
| 2589 | 423.43f + | ||
| 2590 | 5062.659f); | ||
| 2591 | default: | ||
| 2592 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 2593 | static_cast<u32>(command.src_quality)); | ||
| 2594 | return 0; | ||
| 2595 | } | ||
| 2596 | |||
| 2597 | case 240: | ||
| 2598 | switch (command.src_quality) { | ||
| 2599 | case SrcQuality::Medium: | ||
| 2600 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2601 | static_cast<f32>(sample_count)) * | ||
| 2602 | (command.pitch * 0.000030518f)) - | ||
| 2603 | 1.0f) * | ||
| 2604 | 710.143f + | ||
| 2605 | 7853.286f); | ||
| 2606 | case SrcQuality::High: | ||
| 2607 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2608 | static_cast<f32>(sample_count)) * | ||
| 2609 | (command.pitch * 0.000030518f)) - | ||
| 2610 | 1.0f) * | ||
| 2611 | 610.487f + | ||
| 2612 | 10138.842f); | ||
| 2613 | case SrcQuality::Low: | ||
| 2614 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2615 | static_cast<f32>(sample_count)) * | ||
| 2616 | (command.pitch * 0.000030518f)) - | ||
| 2617 | 1.0f) * | ||
| 2618 | 676.722f + | ||
| 2619 | 5810.962f); | ||
| 2620 | default: | ||
| 2621 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 2622 | static_cast<u32>(command.src_quality)); | ||
| 2623 | return 0; | ||
| 2624 | } | ||
| 2625 | |||
| 2626 | default: | ||
| 2627 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2628 | return 0; | ||
| 2629 | } | ||
| 2630 | } | ||
| 2631 | |||
| 2632 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 2633 | const PcmFloatDataSourceVersion1Command& command) const { | ||
| 2634 | switch (sample_count) { | ||
| 2635 | case 160: | ||
| 2636 | return static_cast<u32>( | ||
| 2637 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 2638 | (command.pitch * 0.000030518f)) * | ||
| 2639 | 1672.026f + | ||
| 2640 | 7681.211f); | ||
| 2641 | case 240: | ||
| 2642 | return static_cast<u32>( | ||
| 2643 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 2644 | (command.pitch * 0.000030518f)) * | ||
| 2645 | 2550.414f + | ||
| 2646 | 9663.969f); | ||
| 2647 | default: | ||
| 2648 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2649 | return 0; | ||
| 2650 | } | ||
| 2651 | } | ||
| 2652 | |||
| 2653 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 2654 | const PcmFloatDataSourceVersion2Command& command) const { | ||
| 2655 | switch (sample_count) { | ||
| 2656 | case 160: | ||
| 2657 | switch (command.src_quality) { | ||
| 2658 | case SrcQuality::Medium: | ||
| 2659 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2660 | static_cast<f32>(sample_count)) * | ||
| 2661 | (command.pitch * 0.000030518f)) - | ||
| 2662 | 1.0f) * | ||
| 2663 | 1672.026f + | ||
| 2664 | 7681.211f); | ||
| 2665 | case SrcQuality::High: | ||
| 2666 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2667 | static_cast<f32>(sample_count)) * | ||
| 2668 | (command.pitch * 0.000030518f)) - | ||
| 2669 | 1.0f) * | ||
| 2670 | 1672.982f + | ||
| 2671 | 9038.011f); | ||
| 2672 | case SrcQuality::Low: | ||
| 2673 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2674 | static_cast<f32>(sample_count)) * | ||
| 2675 | (command.pitch * 0.000030518f)) - | ||
| 2676 | 1.0f) * | ||
| 2677 | 1673.216f + | ||
| 2678 | 6027.577f); | ||
| 2679 | default: | ||
| 2680 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 2681 | static_cast<u32>(command.src_quality)); | ||
| 2682 | return 0; | ||
| 2683 | } | ||
| 2684 | |||
| 2685 | case 240: | ||
| 2686 | switch (command.src_quality) { | ||
| 2687 | case SrcQuality::Medium: | ||
| 2688 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2689 | static_cast<f32>(sample_count)) * | ||
| 2690 | (command.pitch * 0.000030518f)) - | ||
| 2691 | 1.0f) * | ||
| 2692 | 2550.414f + | ||
| 2693 | 9663.969f); | ||
| 2694 | case SrcQuality::High: | ||
| 2695 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2696 | static_cast<f32>(sample_count)) * | ||
| 2697 | (command.pitch * 0.000030518f)) - | ||
| 2698 | 1.0f) * | ||
| 2699 | 2522.303f + | ||
| 2700 | 11758.571f); | ||
| 2701 | case SrcQuality::Low: | ||
| 2702 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2703 | static_cast<f32>(sample_count)) * | ||
| 2704 | (command.pitch * 0.000030518f)) - | ||
| 2705 | 1.0f) * | ||
| 2706 | 2537.061f + | ||
| 2707 | 7369.309f); | ||
| 2708 | default: | ||
| 2709 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 2710 | static_cast<u32>(command.src_quality)); | ||
| 2711 | return 0; | ||
| 2712 | } | ||
| 2713 | |||
| 2714 | default: | ||
| 2715 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2716 | return 0; | ||
| 2717 | } | ||
| 2718 | } | ||
| 2719 | |||
| 2720 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 2721 | const AdpcmDataSourceVersion1Command& command) const { | ||
| 2722 | switch (sample_count) { | ||
| 2723 | case 160: | ||
| 2724 | return static_cast<u32>( | ||
| 2725 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 2726 | (command.pitch * 0.000030518f)) * | ||
| 2727 | 1827.665f + | ||
| 2728 | 7913.808f); | ||
| 2729 | case 240: | ||
| 2730 | return static_cast<u32>( | ||
| 2731 | ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) * | ||
| 2732 | (command.pitch * 0.000030518f)) * | ||
| 2733 | 2756.372f + | ||
| 2734 | 9736.702f); | ||
| 2735 | default: | ||
| 2736 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2737 | return 0; | ||
| 2738 | } | ||
| 2739 | } | ||
| 2740 | |||
| 2741 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 2742 | const AdpcmDataSourceVersion2Command& command) const { | ||
| 2743 | switch (sample_count) { | ||
| 2744 | case 160: | ||
| 2745 | switch (command.src_quality) { | ||
| 2746 | case SrcQuality::Medium: | ||
| 2747 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2748 | static_cast<f32>(sample_count)) * | ||
| 2749 | (command.pitch * 0.000030518f)) - | ||
| 2750 | 1.0f) * | ||
| 2751 | 1827.665f + | ||
| 2752 | 7913.808f); | ||
| 2753 | case SrcQuality::High: | ||
| 2754 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2755 | static_cast<f32>(sample_count)) * | ||
| 2756 | (command.pitch * 0.000030518f)) - | ||
| 2757 | 1.0f) * | ||
| 2758 | 1829.285f + | ||
| 2759 | 9607.814f); | ||
| 2760 | case SrcQuality::Low: | ||
| 2761 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2762 | static_cast<f32>(sample_count)) * | ||
| 2763 | (command.pitch * 0.000030518f)) - | ||
| 2764 | 1.0f) * | ||
| 2765 | 1824.609f + | ||
| 2766 | 6517.476f); | ||
| 2767 | default: | ||
| 2768 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 2769 | static_cast<u32>(command.src_quality)); | ||
| 2770 | return 0; | ||
| 2771 | } | ||
| 2772 | |||
| 2773 | case 240: | ||
| 2774 | switch (command.src_quality) { | ||
| 2775 | case SrcQuality::Medium: | ||
| 2776 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2777 | static_cast<f32>(sample_count)) * | ||
| 2778 | (command.pitch * 0.000030518f)) - | ||
| 2779 | 1.0f) * | ||
| 2780 | 2756.372f + | ||
| 2781 | 9736.702f); | ||
| 2782 | case SrcQuality::High: | ||
| 2783 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2784 | static_cast<f32>(sample_count)) * | ||
| 2785 | (command.pitch * 0.000030518f)) - | ||
| 2786 | 1.0f) * | ||
| 2787 | 2731.308f + | ||
| 2788 | 12154.379f); | ||
| 2789 | case SrcQuality::Low: | ||
| 2790 | return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f / | ||
| 2791 | static_cast<f32>(sample_count)) * | ||
| 2792 | (command.pitch * 0.000030518f)) - | ||
| 2793 | 1.0f) * | ||
| 2794 | 2732.152f + | ||
| 2795 | 7929.442f); | ||
| 2796 | default: | ||
| 2797 | LOG_ERROR(Service_Audio, "Invalid SRC quality {}", | ||
| 2798 | static_cast<u32>(command.src_quality)); | ||
| 2799 | return 0; | ||
| 2800 | } | ||
| 2801 | |||
| 2802 | default: | ||
| 2803 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2804 | return 0; | ||
| 2805 | } | ||
| 2806 | } | ||
| 2807 | |||
| 2808 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 2809 | [[maybe_unused]] const VolumeCommand& command) const { | ||
| 2810 | switch (sample_count) { | ||
| 2811 | case 160: | ||
| 2812 | return static_cast<u32>(1311.1f); | ||
| 2813 | case 240: | ||
| 2814 | return static_cast<u32>(1713.6f); | ||
| 2815 | default: | ||
| 2816 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2817 | return 0; | ||
| 2818 | } | ||
| 2819 | } | ||
| 2820 | |||
| 2821 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 2822 | [[maybe_unused]] const VolumeRampCommand& command) const { | ||
| 2823 | switch (sample_count) { | ||
| 2824 | case 160: | ||
| 2825 | return static_cast<u32>(1425.3f); | ||
| 2826 | case 240: | ||
| 2827 | return static_cast<u32>(1700.0f); | ||
| 2828 | default: | ||
| 2829 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2830 | return 0; | ||
| 2831 | } | ||
| 2832 | } | ||
| 2833 | |||
| 2834 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 2835 | [[maybe_unused]] const BiquadFilterCommand& command) const { | ||
| 2836 | switch (sample_count) { | ||
| 2837 | case 160: | ||
| 2838 | return static_cast<u32>(4173.2f); | ||
| 2839 | case 240: | ||
| 2840 | return static_cast<u32>(5585.1f); | ||
| 2841 | default: | ||
| 2842 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2843 | return 0; | ||
| 2844 | } | ||
| 2845 | } | ||
| 2846 | |||
| 2847 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 2848 | [[maybe_unused]] const MixCommand& command) const { | ||
| 2849 | switch (sample_count) { | ||
| 2850 | case 160: | ||
| 2851 | return static_cast<u32>(1402.8f); | ||
| 2852 | case 240: | ||
| 2853 | return static_cast<u32>(1853.2f); | ||
| 2854 | default: | ||
| 2855 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2856 | return 0; | ||
| 2857 | } | ||
| 2858 | } | ||
| 2859 | |||
| 2860 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 2861 | [[maybe_unused]] const MixRampCommand& command) const { | ||
| 2862 | switch (sample_count) { | ||
| 2863 | case 160: | ||
| 2864 | return static_cast<u32>(1968.7f); | ||
| 2865 | case 240: | ||
| 2866 | return static_cast<u32>(2459.4f); | ||
| 2867 | default: | ||
| 2868 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2869 | return 0; | ||
| 2870 | } | ||
| 2871 | } | ||
| 2872 | |||
| 2873 | u32 CommandProcessingTimeEstimatorVersion5::Estimate(const MixRampGroupedCommand& command) const { | ||
| 2874 | u32 count{0}; | ||
| 2875 | for (u32 i = 0; i < command.buffer_count; i++) { | ||
| 2876 | if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { | ||
| 2877 | count++; | ||
| 2878 | } | ||
| 2879 | } | ||
| 2880 | |||
| 2881 | switch (sample_count) { | ||
| 2882 | case 160: | ||
| 2883 | return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) * | ||
| 2884 | static_cast<f32>(count)); | ||
| 2885 | case 240: | ||
| 2886 | return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) * | ||
| 2887 | static_cast<f32>(count)); | ||
| 2888 | default: | ||
| 2889 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2890 | return 0; | ||
| 2891 | } | ||
| 2892 | } | ||
| 2893 | |||
| 2894 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 2895 | [[maybe_unused]] const DepopPrepareCommand& command) const { | ||
| 2896 | return 0; | ||
| 2897 | } | ||
| 2898 | |||
| 2899 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 2900 | [[maybe_unused]] const DepopForMixBuffersCommand& command) const { | ||
| 2901 | switch (sample_count) { | ||
| 2902 | case 160: | ||
| 2903 | return static_cast<u32>(739.64f); | ||
| 2904 | case 240: | ||
| 2905 | return static_cast<u32>(910.97f); | ||
| 2906 | default: | ||
| 2907 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2908 | return 0; | ||
| 2909 | } | ||
| 2910 | } | ||
| 2911 | |||
| 2912 | u32 CommandProcessingTimeEstimatorVersion5::Estimate(const DelayCommand& command) const { | ||
| 2913 | switch (sample_count) { | ||
| 2914 | case 160: | ||
| 2915 | if (command.enabled) { | ||
| 2916 | switch (command.parameter.channel_count) { | ||
| 2917 | case 1: | ||
| 2918 | return static_cast<u32>(8929.042f); | ||
| 2919 | case 2: | ||
| 2920 | return static_cast<u32>(25500.75f); | ||
| 2921 | case 4: | ||
| 2922 | return static_cast<u32>(47759.617f); | ||
| 2923 | case 6: | ||
| 2924 | return static_cast<u32>(82203.07f); | ||
| 2925 | default: | ||
| 2926 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 2927 | command.parameter.channel_count); | ||
| 2928 | return 0; | ||
| 2929 | } | ||
| 2930 | } | ||
| 2931 | switch (command.parameter.channel_count) { | ||
| 2932 | case 1: | ||
| 2933 | return static_cast<u32>(1295.206f); | ||
| 2934 | case 2: | ||
| 2935 | return static_cast<u32>(1213.6f); | ||
| 2936 | case 4: | ||
| 2937 | return static_cast<u32>(942.028f); | ||
| 2938 | case 6: | ||
| 2939 | return static_cast<u32>(1001.553f); | ||
| 2940 | default: | ||
| 2941 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 2942 | return 0; | ||
| 2943 | } | ||
| 2944 | case 240: | ||
| 2945 | if (command.enabled) { | ||
| 2946 | switch (command.parameter.channel_count) { | ||
| 2947 | case 1: | ||
| 2948 | return static_cast<u32>(11941.051f); | ||
| 2949 | case 2: | ||
| 2950 | return static_cast<u32>(37197.371f); | ||
| 2951 | case 4: | ||
| 2952 | return static_cast<u32>(69749.836f); | ||
| 2953 | case 6: | ||
| 2954 | return static_cast<u32>(120042.398f); | ||
| 2955 | default: | ||
| 2956 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 2957 | command.parameter.channel_count); | ||
| 2958 | return 0; | ||
| 2959 | } | ||
| 2960 | } | ||
| 2961 | switch (command.parameter.channel_count) { | ||
| 2962 | case 1: | ||
| 2963 | return static_cast<u32>(997.668f); | ||
| 2964 | case 2: | ||
| 2965 | return static_cast<u32>(977.634f); | ||
| 2966 | case 4: | ||
| 2967 | return static_cast<u32>(792.309f); | ||
| 2968 | case 6: | ||
| 2969 | return static_cast<u32>(875.427f); | ||
| 2970 | default: | ||
| 2971 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 2972 | return 0; | ||
| 2973 | } | ||
| 2974 | default: | ||
| 2975 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2976 | return 0; | ||
| 2977 | } | ||
| 2978 | } | ||
| 2979 | |||
| 2980 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 2981 | [[maybe_unused]] const UpsampleCommand& command) const { | ||
| 2982 | switch (sample_count) { | ||
| 2983 | case 160: | ||
| 2984 | return static_cast<u32>(312990.0f); | ||
| 2985 | case 240: | ||
| 2986 | return static_cast<u32>(0.0f); | ||
| 2987 | default: | ||
| 2988 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 2989 | return 0; | ||
| 2990 | } | ||
| 2991 | } | ||
| 2992 | |||
| 2993 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 2994 | [[maybe_unused]] const DownMix6chTo2chCommand& command) const { | ||
| 2995 | switch (sample_count) { | ||
| 2996 | case 160: | ||
| 2997 | return static_cast<u32>(9949.7f); | ||
| 2998 | case 240: | ||
| 2999 | return static_cast<u32>(14679.0f); | ||
| 3000 | default: | ||
| 3001 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3002 | return 0; | ||
| 3003 | } | ||
| 3004 | } | ||
| 3005 | |||
| 3006 | u32 CommandProcessingTimeEstimatorVersion5::Estimate(const AuxCommand& command) const { | ||
| 3007 | switch (sample_count) { | ||
| 3008 | case 160: | ||
| 3009 | if (command.enabled) { | ||
| 3010 | return static_cast<u32>(7182.136f); | ||
| 3011 | } | ||
| 3012 | return static_cast<u32>(472.111f); | ||
| 3013 | case 240: | ||
| 3014 | if (command.enabled) { | ||
| 3015 | return static_cast<u32>(9435.961f); | ||
| 3016 | } | ||
| 3017 | return static_cast<u32>(462.619f); | ||
| 3018 | default: | ||
| 3019 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3020 | return 0; | ||
| 3021 | } | ||
| 3022 | } | ||
| 3023 | |||
| 3024 | u32 CommandProcessingTimeEstimatorVersion5::Estimate(const DeviceSinkCommand& command) const { | ||
| 3025 | switch (command.input_count) { | ||
| 3026 | case 2: | ||
| 3027 | switch (sample_count) { | ||
| 3028 | case 160: | ||
| 3029 | return static_cast<u32>(8979.956f); | ||
| 3030 | case 240: | ||
| 3031 | return static_cast<u32>(9221.907f); | ||
| 3032 | default: | ||
| 3033 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3034 | return 0; | ||
| 3035 | } | ||
| 3036 | case 6: | ||
| 3037 | switch (sample_count) { | ||
| 3038 | case 160: | ||
| 3039 | return static_cast<u32>(9177.903f); | ||
| 3040 | case 240: | ||
| 3041 | return static_cast<u32>(9725.897f); | ||
| 3042 | default: | ||
| 3043 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3044 | return 0; | ||
| 3045 | } | ||
| 3046 | default: | ||
| 3047 | LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count); | ||
| 3048 | return 0; | ||
| 3049 | } | ||
| 3050 | } | ||
| 3051 | |||
| 3052 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 3053 | const CircularBufferSinkCommand& command) const { | ||
| 3054 | switch (sample_count) { | ||
| 3055 | case 160: | ||
| 3056 | return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f); | ||
| 3057 | case 240: | ||
| 3058 | return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f); | ||
| 3059 | default: | ||
| 3060 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3061 | return 0; | ||
| 3062 | } | ||
| 3063 | } | ||
| 3064 | |||
| 3065 | u32 CommandProcessingTimeEstimatorVersion5::Estimate(const ReverbCommand& command) const { | ||
| 3066 | switch (sample_count) { | ||
| 3067 | case 160: | ||
| 3068 | if (command.enabled) { | ||
| 3069 | switch (command.parameter.channel_count) { | ||
| 3070 | case 1: | ||
| 3071 | return static_cast<u32>(81475.055f); | ||
| 3072 | case 2: | ||
| 3073 | return static_cast<u32>(84975.0f); | ||
| 3074 | case 4: | ||
| 3075 | return static_cast<u32>(91625.148f); | ||
| 3076 | case 6: | ||
| 3077 | return static_cast<u32>(95332.266f); | ||
| 3078 | default: | ||
| 3079 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 3080 | command.parameter.channel_count); | ||
| 3081 | return 0; | ||
| 3082 | } | ||
| 3083 | } | ||
| 3084 | switch (command.parameter.channel_count) { | ||
| 3085 | case 1: | ||
| 3086 | return static_cast<u32>(536.298f); | ||
| 3087 | case 2: | ||
| 3088 | return static_cast<u32>(588.798f); | ||
| 3089 | case 4: | ||
| 3090 | return static_cast<u32>(643.702f); | ||
| 3091 | case 6: | ||
| 3092 | return static_cast<u32>(705.999f); | ||
| 3093 | default: | ||
| 3094 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 3095 | return 0; | ||
| 3096 | } | ||
| 3097 | case 240: | ||
| 3098 | if (command.enabled) { | ||
| 3099 | switch (command.parameter.channel_count) { | ||
| 3100 | case 1: | ||
| 3101 | return static_cast<u32>(120174.469f); | ||
| 3102 | case 2: | ||
| 3103 | return static_cast<u32>(125262.219f); | ||
| 3104 | case 4: | ||
| 3105 | return static_cast<u32>(135751.234f); | ||
| 3106 | case 6: | ||
| 3107 | return static_cast<u32>(141129.234f); | ||
| 3108 | default: | ||
| 3109 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 3110 | command.parameter.channel_count); | ||
| 3111 | return 0; | ||
| 3112 | } | ||
| 3113 | } | ||
| 3114 | switch (command.parameter.channel_count) { | ||
| 3115 | case 1: | ||
| 3116 | return static_cast<u32>(617.641f); | ||
| 3117 | case 2: | ||
| 3118 | return static_cast<u32>(659.536f); | ||
| 3119 | case 4: | ||
| 3120 | return static_cast<u32>(711.438f); | ||
| 3121 | case 6: | ||
| 3122 | return static_cast<u32>(778.071f); | ||
| 3123 | default: | ||
| 3124 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 3125 | return 0; | ||
| 3126 | } | ||
| 3127 | default: | ||
| 3128 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3129 | return 0; | ||
| 3130 | } | ||
| 3131 | } | ||
| 3132 | |||
| 3133 | u32 CommandProcessingTimeEstimatorVersion5::Estimate(const I3dl2ReverbCommand& command) const { | ||
| 3134 | switch (sample_count) { | ||
| 3135 | case 160: | ||
| 3136 | if (command.enabled) { | ||
| 3137 | switch (command.parameter.channel_count) { | ||
| 3138 | case 1: | ||
| 3139 | return static_cast<u32>(116754.984f); | ||
| 3140 | case 2: | ||
| 3141 | return static_cast<u32>(125912.055f); | ||
| 3142 | case 4: | ||
| 3143 | return static_cast<u32>(146336.031f); | ||
| 3144 | case 6: | ||
| 3145 | return static_cast<u32>(165812.656f); | ||
| 3146 | default: | ||
| 3147 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 3148 | command.parameter.channel_count); | ||
| 3149 | return 0; | ||
| 3150 | } | ||
| 3151 | } | ||
| 3152 | switch (command.parameter.channel_count) { | ||
| 3153 | case 1: | ||
| 3154 | return static_cast<u32>(735.0f); | ||
| 3155 | case 2: | ||
| 3156 | return static_cast<u32>(766.615f); | ||
| 3157 | case 4: | ||
| 3158 | return static_cast<u32>(834.067f); | ||
| 3159 | case 6: | ||
| 3160 | return static_cast<u32>(875.437f); | ||
| 3161 | default: | ||
| 3162 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 3163 | return 0; | ||
| 3164 | } | ||
| 3165 | case 240: | ||
| 3166 | if (command.enabled) { | ||
| 3167 | switch (command.parameter.channel_count) { | ||
| 3168 | case 1: | ||
| 3169 | return static_cast<u32>(170292.344f); | ||
| 3170 | case 2: | ||
| 3171 | return static_cast<u32>(183875.625f); | ||
| 3172 | case 4: | ||
| 3173 | return static_cast<u32>(214696.188f); | ||
| 3174 | case 6: | ||
| 3175 | return static_cast<u32>(243846.766f); | ||
| 3176 | default: | ||
| 3177 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 3178 | command.parameter.channel_count); | ||
| 3179 | return 0; | ||
| 3180 | } | ||
| 3181 | } | ||
| 3182 | switch (command.parameter.channel_count) { | ||
| 3183 | case 1: | ||
| 3184 | return static_cast<u32>(508.473f); | ||
| 3185 | case 2: | ||
| 3186 | return static_cast<u32>(582.445f); | ||
| 3187 | case 4: | ||
| 3188 | return static_cast<u32>(626.419f); | ||
| 3189 | case 6: | ||
| 3190 | return static_cast<u32>(682.468f); | ||
| 3191 | default: | ||
| 3192 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 3193 | return 0; | ||
| 3194 | } | ||
| 3195 | default: | ||
| 3196 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3197 | return 0; | ||
| 3198 | } | ||
| 3199 | } | ||
| 3200 | |||
| 3201 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 3202 | [[maybe_unused]] const PerformanceCommand& command) const { | ||
| 3203 | switch (sample_count) { | ||
| 3204 | case 160: | ||
| 3205 | return static_cast<u32>(498.17f); | ||
| 3206 | case 240: | ||
| 3207 | return static_cast<u32>(489.42f); | ||
| 3208 | default: | ||
| 3209 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3210 | return 0; | ||
| 3211 | } | ||
| 3212 | } | ||
| 3213 | |||
| 3214 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 3215 | [[maybe_unused]] const ClearMixBufferCommand& command) const { | ||
| 3216 | switch (sample_count) { | ||
| 3217 | case 160: | ||
| 3218 | return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f); | ||
| 3219 | case 240: | ||
| 3220 | return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f); | ||
| 3221 | default: | ||
| 3222 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3223 | return 0; | ||
| 3224 | } | ||
| 3225 | } | ||
| 3226 | |||
| 3227 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 3228 | [[maybe_unused]] const CopyMixBufferCommand& command) const { | ||
| 3229 | switch (sample_count) { | ||
| 3230 | case 160: | ||
| 3231 | return static_cast<u32>(842.59f); | ||
| 3232 | case 240: | ||
| 3233 | return static_cast<u32>(986.72f); | ||
| 3234 | default: | ||
| 3235 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3236 | return 0; | ||
| 3237 | } | ||
| 3238 | } | ||
| 3239 | |||
| 3240 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 3241 | const LightLimiterVersion1Command& command) const { | ||
| 3242 | switch (sample_count) { | ||
| 3243 | case 160: | ||
| 3244 | if (command.enabled) { | ||
| 3245 | switch (command.parameter.channel_count) { | ||
| 3246 | case 1: | ||
| 3247 | return static_cast<u32>(21508.01f); | ||
| 3248 | case 2: | ||
| 3249 | return static_cast<u32>(23120.453f); | ||
| 3250 | case 4: | ||
| 3251 | return static_cast<u32>(26270.053f); | ||
| 3252 | case 6: | ||
| 3253 | return static_cast<u32>(40471.902f); | ||
| 3254 | default: | ||
| 3255 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 3256 | command.parameter.channel_count); | ||
| 3257 | return 0; | ||
| 3258 | } | ||
| 3259 | } | ||
| 3260 | switch (command.parameter.channel_count) { | ||
| 3261 | case 1: | ||
| 3262 | return static_cast<u32>(897.004f); | ||
| 3263 | case 2: | ||
| 3264 | return static_cast<u32>(931.549f); | ||
| 3265 | case 4: | ||
| 3266 | return static_cast<u32>(975.387f); | ||
| 3267 | case 6: | ||
| 3268 | return static_cast<u32>(1016.778f); | ||
| 3269 | default: | ||
| 3270 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 3271 | return 0; | ||
| 3272 | } | ||
| 3273 | case 240: | ||
| 3274 | if (command.enabled) { | ||
| 3275 | switch (command.parameter.channel_count) { | ||
| 3276 | case 1: | ||
| 3277 | return static_cast<u32>(30565.961f); | ||
| 3278 | case 2: | ||
| 3279 | return static_cast<u32>(32812.91f); | ||
| 3280 | case 4: | ||
| 3281 | return static_cast<u32>(37354.852f); | ||
| 3282 | case 6: | ||
| 3283 | return static_cast<u32>(58486.699f); | ||
| 3284 | default: | ||
| 3285 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 3286 | command.parameter.channel_count); | ||
| 3287 | return 0; | ||
| 3288 | } | ||
| 3289 | } | ||
| 3290 | switch (command.parameter.channel_count) { | ||
| 3291 | case 1: | ||
| 3292 | return static_cast<u32>(874.429f); | ||
| 3293 | case 2: | ||
| 3294 | return static_cast<u32>(921.553f); | ||
| 3295 | case 4: | ||
| 3296 | return static_cast<u32>(945.262f); | ||
| 3297 | case 6: | ||
| 3298 | return static_cast<u32>(992.26f); | ||
| 3299 | default: | ||
| 3300 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 3301 | return 0; | ||
| 3302 | } | ||
| 3303 | default: | ||
| 3304 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3305 | return 0; | ||
| 3306 | } | ||
| 3307 | } | ||
| 3308 | |||
| 3309 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 3310 | const LightLimiterVersion2Command& command) const { | ||
| 3311 | switch (sample_count) { | ||
| 3312 | case 160: | ||
| 3313 | if (command.enabled) { | ||
| 3314 | if (command.parameter.processing_mode == LightLimiterInfo::ProcessingMode::Mode0) { | ||
| 3315 | if (command.parameter.statistics_enabled) { | ||
| 3316 | switch (command.parameter.channel_count) { | ||
| 3317 | case 1: | ||
| 3318 | return static_cast<u32>(23639.584f); | ||
| 3319 | case 2: | ||
| 3320 | return static_cast<u32>(24666.725f); | ||
| 3321 | case 4: | ||
| 3322 | return static_cast<u32>(28876.459f); | ||
| 3323 | case 6: | ||
| 3324 | return static_cast<u32>(47096.078f); | ||
| 3325 | default: | ||
| 3326 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 3327 | command.parameter.channel_count); | ||
| 3328 | return 0; | ||
| 3329 | } | ||
| 3330 | } else { | ||
| 3331 | if (command.parameter.statistics_enabled) { | ||
| 3332 | switch (command.parameter.channel_count) { | ||
| 3333 | case 1: | ||
| 3334 | return static_cast<u32>(21508.01f); | ||
| 3335 | case 2: | ||
| 3336 | return static_cast<u32>(23120.453f); | ||
| 3337 | case 4: | ||
| 3338 | return static_cast<u32>(26270.053f); | ||
| 3339 | case 6: | ||
| 3340 | return static_cast<u32>(40471.902f); | ||
| 3341 | default: | ||
| 3342 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 3343 | command.parameter.channel_count); | ||
| 3344 | return 0; | ||
| 3345 | } | ||
| 3346 | } | ||
| 3347 | } | ||
| 3348 | } else if (command.parameter.processing_mode == | ||
| 3349 | LightLimiterInfo::ProcessingMode::Mode1) { | ||
| 3350 | if (command.parameter.statistics_enabled) { | ||
| 3351 | switch (command.parameter.channel_count) { | ||
| 3352 | case 1: | ||
| 3353 | return static_cast<u32>(23639.584f); | ||
| 3354 | case 2: | ||
| 3355 | return static_cast<u32>(29954.062f); | ||
| 3356 | case 4: | ||
| 3357 | return static_cast<u32>(35807.477f); | ||
| 3358 | case 6: | ||
| 3359 | return static_cast<u32>(58339.773f); | ||
| 3360 | default: | ||
| 3361 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 3362 | command.parameter.channel_count); | ||
| 3363 | return 0; | ||
| 3364 | } | ||
| 3365 | } else { | ||
| 3366 | if (command.parameter.statistics_enabled) { | ||
| 3367 | switch (command.parameter.channel_count) { | ||
| 3368 | case 1: | ||
| 3369 | return static_cast<u32>(23639.584f); | ||
| 3370 | case 2: | ||
| 3371 | return static_cast<u32>(29954.062f); | ||
| 3372 | case 4: | ||
| 3373 | return static_cast<u32>(35807.477f); | ||
| 3374 | case 6: | ||
| 3375 | return static_cast<u32>(58339.773f); | ||
| 3376 | default: | ||
| 3377 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 3378 | command.parameter.channel_count); | ||
| 3379 | return 0; | ||
| 3380 | } | ||
| 3381 | } | ||
| 3382 | } | ||
| 3383 | } else { | ||
| 3384 | LOG_ERROR(Service_Audio, "Invalid processing mode {}", | ||
| 3385 | command.parameter.processing_mode); | ||
| 3386 | return 0; | ||
| 3387 | } | ||
| 3388 | } | ||
| 3389 | switch (command.parameter.channel_count) { | ||
| 3390 | case 1: | ||
| 3391 | return static_cast<u32>(897.004f); | ||
| 3392 | case 2: | ||
| 3393 | return static_cast<u32>(931.549f); | ||
| 3394 | case 4: | ||
| 3395 | return static_cast<u32>(975.387f); | ||
| 3396 | case 6: | ||
| 3397 | return static_cast<u32>(1016.778f); | ||
| 3398 | default: | ||
| 3399 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 3400 | return 0; | ||
| 3401 | } | ||
| 3402 | case 240: | ||
| 3403 | if (command.enabled) { | ||
| 3404 | if (command.parameter.processing_mode == LightLimiterInfo::ProcessingMode::Mode0) { | ||
| 3405 | if (command.parameter.statistics_enabled) { | ||
| 3406 | switch (command.parameter.channel_count) { | ||
| 3407 | case 1: | ||
| 3408 | return static_cast<u32>(33875.023f); | ||
| 3409 | case 2: | ||
| 3410 | return static_cast<u32>(35199.938f); | ||
| 3411 | case 4: | ||
| 3412 | return static_cast<u32>(41371.230f); | ||
| 3413 | case 6: | ||
| 3414 | return static_cast<u32>(68370.914f); | ||
| 3415 | default: | ||
| 3416 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 3417 | command.parameter.channel_count); | ||
| 3418 | return 0; | ||
| 3419 | } | ||
| 3420 | } else { | ||
| 3421 | switch (command.parameter.channel_count) { | ||
| 3422 | case 1: | ||
| 3423 | return static_cast<u32>(30565.961f); | ||
| 3424 | case 2: | ||
| 3425 | return static_cast<u32>(32812.91f); | ||
| 3426 | case 4: | ||
| 3427 | return static_cast<u32>(37354.852f); | ||
| 3428 | case 6: | ||
| 3429 | return static_cast<u32>(58486.699f); | ||
| 3430 | default: | ||
| 3431 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 3432 | command.parameter.channel_count); | ||
| 3433 | return 0; | ||
| 3434 | } | ||
| 3435 | } | ||
| 3436 | } else if (command.parameter.processing_mode == | ||
| 3437 | LightLimiterInfo::ProcessingMode::Mode1) { | ||
| 3438 | if (command.parameter.statistics_enabled) { | ||
| 3439 | switch (command.parameter.channel_count) { | ||
| 3440 | case 1: | ||
| 3441 | return static_cast<u32>(33942.980f); | ||
| 3442 | case 2: | ||
| 3443 | return static_cast<u32>(28698.893f); | ||
| 3444 | case 4: | ||
| 3445 | return static_cast<u32>(34774.277f); | ||
| 3446 | case 6: | ||
| 3447 | return static_cast<u32>(61897.773f); | ||
| 3448 | default: | ||
| 3449 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 3450 | command.parameter.channel_count); | ||
| 3451 | return 0; | ||
| 3452 | } | ||
| 3453 | } else { | ||
| 3454 | switch (command.parameter.channel_count) { | ||
| 3455 | case 1: | ||
| 3456 | return static_cast<u32>(30610.248f); | ||
| 3457 | case 2: | ||
| 3458 | return static_cast<u32>(26322.408f); | ||
| 3459 | case 4: | ||
| 3460 | return static_cast<u32>(30369.000f); | ||
| 3461 | case 6: | ||
| 3462 | return static_cast<u32>(51892.090f); | ||
| 3463 | default: | ||
| 3464 | LOG_ERROR(Service_Audio, "Invalid channel count {}", | ||
| 3465 | command.parameter.channel_count); | ||
| 3466 | return 0; | ||
| 3467 | } | ||
| 3468 | } | ||
| 3469 | } else { | ||
| 3470 | LOG_ERROR(Service_Audio, "Invalid processing mode {}", | ||
| 3471 | command.parameter.processing_mode); | ||
| 3472 | return 0; | ||
| 3473 | } | ||
| 3474 | } | ||
| 3475 | switch (command.parameter.channel_count) { | ||
| 3476 | case 1: | ||
| 3477 | return static_cast<u32>(874.429f); | ||
| 3478 | case 2: | ||
| 3479 | return static_cast<u32>(921.553f); | ||
| 3480 | case 4: | ||
| 3481 | return static_cast<u32>(945.262f); | ||
| 3482 | case 6: | ||
| 3483 | return static_cast<u32>(992.26f); | ||
| 3484 | default: | ||
| 3485 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 3486 | return 0; | ||
| 3487 | } | ||
| 3488 | default: | ||
| 3489 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3490 | return 0; | ||
| 3491 | } | ||
| 3492 | } | ||
| 3493 | |||
| 3494 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 3495 | [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { | ||
| 3496 | switch (sample_count) { | ||
| 3497 | case 160: | ||
| 3498 | return static_cast<u32>(7424.5f); | ||
| 3499 | case 240: | ||
| 3500 | return static_cast<u32>(9730.4f); | ||
| 3501 | default: | ||
| 3502 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3503 | return 0; | ||
| 3504 | } | ||
| 3505 | } | ||
| 3506 | |||
| 3507 | u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CaptureCommand& command) const { | ||
| 3508 | switch (sample_count) { | ||
| 3509 | case 160: | ||
| 3510 | if (command.enabled) { | ||
| 3511 | return static_cast<u32>(426.982f); | ||
| 3512 | } | ||
| 3513 | return static_cast<u32>(4261.005f); | ||
| 3514 | case 240: | ||
| 3515 | if (command.enabled) { | ||
| 3516 | return static_cast<u32>(435.204f); | ||
| 3517 | } | ||
| 3518 | return static_cast<u32>(5858.265f); | ||
| 3519 | default: | ||
| 3520 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3521 | return 0; | ||
| 3522 | } | ||
| 3523 | } | ||
| 3524 | |||
| 3525 | u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CompressorCommand& command) const { | ||
| 3526 | if (command.enabled) { | ||
| 3527 | switch (command.parameter.channel_count) { | ||
| 3528 | case 1: | ||
| 3529 | switch (sample_count) { | ||
| 3530 | case 160: | ||
| 3531 | return static_cast<u32>(34430.570f); | ||
| 3532 | case 240: | ||
| 3533 | return static_cast<u32>(51095.348f); | ||
| 3534 | default: | ||
| 3535 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3536 | return 0; | ||
| 3537 | } | ||
| 3538 | case 2: | ||
| 3539 | switch (sample_count) { | ||
| 3540 | case 160: | ||
| 3541 | return static_cast<u32>(44253.320f); | ||
| 3542 | case 240: | ||
| 3543 | return static_cast<u32>(65693.094f); | ||
| 3544 | default: | ||
| 3545 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3546 | return 0; | ||
| 3547 | } | ||
| 3548 | case 4: | ||
| 3549 | switch (sample_count) { | ||
| 3550 | case 160: | ||
| 3551 | return static_cast<u32>(63827.457f); | ||
| 3552 | case 240: | ||
| 3553 | return static_cast<u32>(95382.852f); | ||
| 3554 | default: | ||
| 3555 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3556 | return 0; | ||
| 3557 | } | ||
| 3558 | case 6: | ||
| 3559 | switch (sample_count) { | ||
| 3560 | case 160: | ||
| 3561 | return static_cast<u32>(83361.484f); | ||
| 3562 | case 240: | ||
| 3563 | return static_cast<u32>(124509.906f); | ||
| 3564 | default: | ||
| 3565 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3566 | return 0; | ||
| 3567 | } | ||
| 3568 | default: | ||
| 3569 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 3570 | return 0; | ||
| 3571 | } | ||
| 3572 | } | ||
| 3573 | switch (command.parameter.channel_count) { | ||
| 3574 | case 1: | ||
| 3575 | switch (sample_count) { | ||
| 3576 | case 160: | ||
| 3577 | return static_cast<u32>(630.115f); | ||
| 3578 | case 240: | ||
| 3579 | return static_cast<u32>(840.136f); | ||
| 3580 | default: | ||
| 3581 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3582 | return 0; | ||
| 3583 | } | ||
| 3584 | case 2: | ||
| 3585 | switch (sample_count) { | ||
| 3586 | case 160: | ||
| 3587 | return static_cast<u32>(638.274f); | ||
| 3588 | case 240: | ||
| 3589 | return static_cast<u32>(826.098f); | ||
| 3590 | default: | ||
| 3591 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3592 | return 0; | ||
| 3593 | } | ||
| 3594 | case 4: | ||
| 3595 | switch (sample_count) { | ||
| 3596 | case 160: | ||
| 3597 | return static_cast<u32>(705.862f); | ||
| 3598 | case 240: | ||
| 3599 | return static_cast<u32>(901.876f); | ||
| 3600 | default: | ||
| 3601 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3602 | return 0; | ||
| 3603 | } | ||
| 3604 | case 6: | ||
| 3605 | switch (sample_count) { | ||
| 3606 | case 160: | ||
| 3607 | return static_cast<u32>(782.019f); | ||
| 3608 | case 240: | ||
| 3609 | return static_cast<u32>(965.286f); | ||
| 3610 | default: | ||
| 3611 | LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); | ||
| 3612 | return 0; | ||
| 3613 | } | ||
| 3614 | default: | ||
| 3615 | LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); | ||
| 3616 | return 0; | ||
| 3617 | } | ||
| 3618 | } | ||
| 3619 | |||
| 3620 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "audio_core/renderer/command/commands.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | /** | ||
| 11 | * Estimate the processing time required for all commands. | ||
| 12 | */ | ||
| 13 | class ICommandProcessingTimeEstimator { | ||
| 14 | public: | ||
| 15 | virtual ~ICommandProcessingTimeEstimator() = default; | ||
| 16 | |||
| 17 | virtual u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const = 0; | ||
| 18 | virtual u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const = 0; | ||
| 19 | virtual u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const = 0; | ||
| 20 | virtual u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const = 0; | ||
| 21 | virtual u32 Estimate(const AdpcmDataSourceVersion1Command& command) const = 0; | ||
| 22 | virtual u32 Estimate(const AdpcmDataSourceVersion2Command& command) const = 0; | ||
| 23 | virtual u32 Estimate(const VolumeCommand& command) const = 0; | ||
| 24 | virtual u32 Estimate(const VolumeRampCommand& command) const = 0; | ||
| 25 | virtual u32 Estimate(const BiquadFilterCommand& command) const = 0; | ||
| 26 | virtual u32 Estimate(const MixCommand& command) const = 0; | ||
| 27 | virtual u32 Estimate(const MixRampCommand& command) const = 0; | ||
| 28 | virtual u32 Estimate(const MixRampGroupedCommand& command) const = 0; | ||
| 29 | virtual u32 Estimate(const DepopPrepareCommand& command) const = 0; | ||
| 30 | virtual u32 Estimate(const DepopForMixBuffersCommand& command) const = 0; | ||
| 31 | virtual u32 Estimate(const DelayCommand& command) const = 0; | ||
| 32 | virtual u32 Estimate(const UpsampleCommand& command) const = 0; | ||
| 33 | virtual u32 Estimate(const DownMix6chTo2chCommand& command) const = 0; | ||
| 34 | virtual u32 Estimate(const AuxCommand& command) const = 0; | ||
| 35 | virtual u32 Estimate(const DeviceSinkCommand& command) const = 0; | ||
| 36 | virtual u32 Estimate(const CircularBufferSinkCommand& command) const = 0; | ||
| 37 | virtual u32 Estimate(const ReverbCommand& command) const = 0; | ||
| 38 | virtual u32 Estimate(const I3dl2ReverbCommand& command) const = 0; | ||
| 39 | virtual u32 Estimate(const PerformanceCommand& command) const = 0; | ||
| 40 | virtual u32 Estimate(const ClearMixBufferCommand& command) const = 0; | ||
| 41 | virtual u32 Estimate(const CopyMixBufferCommand& command) const = 0; | ||
| 42 | virtual u32 Estimate(const LightLimiterVersion1Command& command) const = 0; | ||
| 43 | virtual u32 Estimate(const LightLimiterVersion2Command& command) const = 0; | ||
| 44 | virtual u32 Estimate(const MultiTapBiquadFilterCommand& command) const = 0; | ||
| 45 | virtual u32 Estimate(const CaptureCommand& command) const = 0; | ||
| 46 | virtual u32 Estimate(const CompressorCommand& command) const = 0; | ||
| 47 | }; | ||
| 48 | |||
| 49 | class CommandProcessingTimeEstimatorVersion1 final : public ICommandProcessingTimeEstimator { | ||
| 50 | public: | ||
| 51 | CommandProcessingTimeEstimatorVersion1(u32 sample_count_, u32 buffer_count_) | ||
| 52 | : sample_count{sample_count_}, buffer_count{buffer_count_} {} | ||
| 53 | |||
| 54 | u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; | ||
| 55 | u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; | ||
| 56 | u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; | ||
| 57 | u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; | ||
| 58 | u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; | ||
| 59 | u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; | ||
| 60 | u32 Estimate(const VolumeCommand& command) const override; | ||
| 61 | u32 Estimate(const VolumeRampCommand& command) const override; | ||
| 62 | u32 Estimate(const BiquadFilterCommand& command) const override; | ||
| 63 | u32 Estimate(const MixCommand& command) const override; | ||
| 64 | u32 Estimate(const MixRampCommand& command) const override; | ||
| 65 | u32 Estimate(const MixRampGroupedCommand& command) const override; | ||
| 66 | u32 Estimate(const DepopPrepareCommand& command) const override; | ||
| 67 | u32 Estimate(const DepopForMixBuffersCommand& command) const override; | ||
| 68 | u32 Estimate(const DelayCommand& command) const override; | ||
| 69 | u32 Estimate(const UpsampleCommand& command) const override; | ||
| 70 | u32 Estimate(const DownMix6chTo2chCommand& command) const override; | ||
| 71 | u32 Estimate(const AuxCommand& command) const override; | ||
| 72 | u32 Estimate(const DeviceSinkCommand& command) const override; | ||
| 73 | u32 Estimate(const CircularBufferSinkCommand& command) const override; | ||
| 74 | u32 Estimate(const ReverbCommand& command) const override; | ||
| 75 | u32 Estimate(const I3dl2ReverbCommand& command) const override; | ||
| 76 | u32 Estimate(const PerformanceCommand& command) const override; | ||
| 77 | u32 Estimate(const ClearMixBufferCommand& command) const override; | ||
| 78 | u32 Estimate(const CopyMixBufferCommand& command) const override; | ||
| 79 | u32 Estimate(const LightLimiterVersion1Command& command) const override; | ||
| 80 | u32 Estimate(const LightLimiterVersion2Command& command) const override; | ||
| 81 | u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; | ||
| 82 | u32 Estimate(const CaptureCommand& command) const override; | ||
| 83 | u32 Estimate(const CompressorCommand& command) const override; | ||
| 84 | |||
| 85 | private: | ||
| 86 | u32 sample_count{}; | ||
| 87 | u32 buffer_count{}; | ||
| 88 | }; | ||
| 89 | |||
| 90 | class CommandProcessingTimeEstimatorVersion2 final : public ICommandProcessingTimeEstimator { | ||
| 91 | public: | ||
| 92 | CommandProcessingTimeEstimatorVersion2(u32 sample_count_, u32 buffer_count_) | ||
| 93 | : sample_count{sample_count_}, buffer_count{buffer_count_} {} | ||
| 94 | |||
| 95 | u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; | ||
| 96 | u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; | ||
| 97 | u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; | ||
| 98 | u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; | ||
| 99 | u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; | ||
| 100 | u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; | ||
| 101 | u32 Estimate(const VolumeCommand& command) const override; | ||
| 102 | u32 Estimate(const VolumeRampCommand& command) const override; | ||
| 103 | u32 Estimate(const BiquadFilterCommand& command) const override; | ||
| 104 | u32 Estimate(const MixCommand& command) const override; | ||
| 105 | u32 Estimate(const MixRampCommand& command) const override; | ||
| 106 | u32 Estimate(const MixRampGroupedCommand& command) const override; | ||
| 107 | u32 Estimate(const DepopPrepareCommand& command) const override; | ||
| 108 | u32 Estimate(const DepopForMixBuffersCommand& command) const override; | ||
| 109 | u32 Estimate(const DelayCommand& command) const override; | ||
| 110 | u32 Estimate(const UpsampleCommand& command) const override; | ||
| 111 | u32 Estimate(const DownMix6chTo2chCommand& command) const override; | ||
| 112 | u32 Estimate(const AuxCommand& command) const override; | ||
| 113 | u32 Estimate(const DeviceSinkCommand& command) const override; | ||
| 114 | u32 Estimate(const CircularBufferSinkCommand& command) const override; | ||
| 115 | u32 Estimate(const ReverbCommand& command) const override; | ||
| 116 | u32 Estimate(const I3dl2ReverbCommand& command) const override; | ||
| 117 | u32 Estimate(const PerformanceCommand& command) const override; | ||
| 118 | u32 Estimate(const ClearMixBufferCommand& command) const override; | ||
| 119 | u32 Estimate(const CopyMixBufferCommand& command) const override; | ||
| 120 | u32 Estimate(const LightLimiterVersion1Command& command) const override; | ||
| 121 | u32 Estimate(const LightLimiterVersion2Command& command) const override; | ||
| 122 | u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; | ||
| 123 | u32 Estimate(const CaptureCommand& command) const override; | ||
| 124 | u32 Estimate(const CompressorCommand& command) const override; | ||
| 125 | |||
| 126 | private: | ||
| 127 | u32 sample_count{}; | ||
| 128 | u32 buffer_count{}; | ||
| 129 | }; | ||
| 130 | |||
| 131 | class CommandProcessingTimeEstimatorVersion3 final : public ICommandProcessingTimeEstimator { | ||
| 132 | public: | ||
| 133 | CommandProcessingTimeEstimatorVersion3(u32 sample_count_, u32 buffer_count_) | ||
| 134 | : sample_count{sample_count_}, buffer_count{buffer_count_} {} | ||
| 135 | |||
| 136 | u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; | ||
| 137 | u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; | ||
| 138 | u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; | ||
| 139 | u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; | ||
| 140 | u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; | ||
| 141 | u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; | ||
| 142 | u32 Estimate(const VolumeCommand& command) const override; | ||
| 143 | u32 Estimate(const VolumeRampCommand& command) const override; | ||
| 144 | u32 Estimate(const BiquadFilterCommand& command) const override; | ||
| 145 | u32 Estimate(const MixCommand& command) const override; | ||
| 146 | u32 Estimate(const MixRampCommand& command) const override; | ||
| 147 | u32 Estimate(const MixRampGroupedCommand& command) const override; | ||
| 148 | u32 Estimate(const DepopPrepareCommand& command) const override; | ||
| 149 | u32 Estimate(const DepopForMixBuffersCommand& command) const override; | ||
| 150 | u32 Estimate(const DelayCommand& command) const override; | ||
| 151 | u32 Estimate(const UpsampleCommand& command) const override; | ||
| 152 | u32 Estimate(const DownMix6chTo2chCommand& command) const override; | ||
| 153 | u32 Estimate(const AuxCommand& command) const override; | ||
| 154 | u32 Estimate(const DeviceSinkCommand& command) const override; | ||
| 155 | u32 Estimate(const CircularBufferSinkCommand& command) const override; | ||
| 156 | u32 Estimate(const ReverbCommand& command) const override; | ||
| 157 | u32 Estimate(const I3dl2ReverbCommand& command) const override; | ||
| 158 | u32 Estimate(const PerformanceCommand& command) const override; | ||
| 159 | u32 Estimate(const ClearMixBufferCommand& command) const override; | ||
| 160 | u32 Estimate(const CopyMixBufferCommand& command) const override; | ||
| 161 | u32 Estimate(const LightLimiterVersion1Command& command) const override; | ||
| 162 | u32 Estimate(const LightLimiterVersion2Command& command) const override; | ||
| 163 | u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; | ||
| 164 | u32 Estimate(const CaptureCommand& command) const override; | ||
| 165 | u32 Estimate(const CompressorCommand& command) const override; | ||
| 166 | |||
| 167 | private: | ||
| 168 | u32 sample_count{}; | ||
| 169 | u32 buffer_count{}; | ||
| 170 | }; | ||
| 171 | |||
| 172 | class CommandProcessingTimeEstimatorVersion4 final : public ICommandProcessingTimeEstimator { | ||
| 173 | public: | ||
| 174 | CommandProcessingTimeEstimatorVersion4(u32 sample_count_, u32 buffer_count_) | ||
| 175 | : sample_count{sample_count_}, buffer_count{buffer_count_} {} | ||
| 176 | |||
| 177 | u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; | ||
| 178 | u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; | ||
| 179 | u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; | ||
| 180 | u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; | ||
| 181 | u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; | ||
| 182 | u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; | ||
| 183 | u32 Estimate(const VolumeCommand& command) const override; | ||
| 184 | u32 Estimate(const VolumeRampCommand& command) const override; | ||
| 185 | u32 Estimate(const BiquadFilterCommand& command) const override; | ||
| 186 | u32 Estimate(const MixCommand& command) const override; | ||
| 187 | u32 Estimate(const MixRampCommand& command) const override; | ||
| 188 | u32 Estimate(const MixRampGroupedCommand& command) const override; | ||
| 189 | u32 Estimate(const DepopPrepareCommand& command) const override; | ||
| 190 | u32 Estimate(const DepopForMixBuffersCommand& command) const override; | ||
| 191 | u32 Estimate(const DelayCommand& command) const override; | ||
| 192 | u32 Estimate(const UpsampleCommand& command) const override; | ||
| 193 | u32 Estimate(const DownMix6chTo2chCommand& command) const override; | ||
| 194 | u32 Estimate(const AuxCommand& command) const override; | ||
| 195 | u32 Estimate(const DeviceSinkCommand& command) const override; | ||
| 196 | u32 Estimate(const CircularBufferSinkCommand& command) const override; | ||
| 197 | u32 Estimate(const ReverbCommand& command) const override; | ||
| 198 | u32 Estimate(const I3dl2ReverbCommand& command) const override; | ||
| 199 | u32 Estimate(const PerformanceCommand& command) const override; | ||
| 200 | u32 Estimate(const ClearMixBufferCommand& command) const override; | ||
| 201 | u32 Estimate(const CopyMixBufferCommand& command) const override; | ||
| 202 | u32 Estimate(const LightLimiterVersion1Command& command) const override; | ||
| 203 | u32 Estimate(const LightLimiterVersion2Command& command) const override; | ||
| 204 | u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; | ||
| 205 | u32 Estimate(const CaptureCommand& command) const override; | ||
| 206 | u32 Estimate(const CompressorCommand& command) const override; | ||
| 207 | |||
| 208 | private: | ||
| 209 | u32 sample_count{}; | ||
| 210 | u32 buffer_count{}; | ||
| 211 | }; | ||
| 212 | |||
| 213 | class CommandProcessingTimeEstimatorVersion5 final : public ICommandProcessingTimeEstimator { | ||
| 214 | public: | ||
| 215 | CommandProcessingTimeEstimatorVersion5(u32 sample_count_, u32 buffer_count_) | ||
| 216 | : sample_count{sample_count_}, buffer_count{buffer_count_} {} | ||
| 217 | |||
| 218 | u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; | ||
| 219 | u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; | ||
| 220 | u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; | ||
| 221 | u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; | ||
| 222 | u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; | ||
| 223 | u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; | ||
| 224 | u32 Estimate(const VolumeCommand& command) const override; | ||
| 225 | u32 Estimate(const VolumeRampCommand& command) const override; | ||
| 226 | u32 Estimate(const BiquadFilterCommand& command) const override; | ||
| 227 | u32 Estimate(const MixCommand& command) const override; | ||
| 228 | u32 Estimate(const MixRampCommand& command) const override; | ||
| 229 | u32 Estimate(const MixRampGroupedCommand& command) const override; | ||
| 230 | u32 Estimate(const DepopPrepareCommand& command) const override; | ||
| 231 | u32 Estimate(const DepopForMixBuffersCommand& command) const override; | ||
| 232 | u32 Estimate(const DelayCommand& command) const override; | ||
| 233 | u32 Estimate(const UpsampleCommand& command) const override; | ||
| 234 | u32 Estimate(const DownMix6chTo2chCommand& command) const override; | ||
| 235 | u32 Estimate(const AuxCommand& command) const override; | ||
| 236 | u32 Estimate(const DeviceSinkCommand& command) const override; | ||
| 237 | u32 Estimate(const CircularBufferSinkCommand& command) const override; | ||
| 238 | u32 Estimate(const ReverbCommand& command) const override; | ||
| 239 | u32 Estimate(const I3dl2ReverbCommand& command) const override; | ||
| 240 | u32 Estimate(const PerformanceCommand& command) const override; | ||
| 241 | u32 Estimate(const ClearMixBufferCommand& command) const override; | ||
| 242 | u32 Estimate(const CopyMixBufferCommand& command) const override; | ||
| 243 | u32 Estimate(const LightLimiterVersion1Command& command) const override; | ||
| 244 | u32 Estimate(const LightLimiterVersion2Command& command) const override; | ||
| 245 | u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; | ||
| 246 | u32 Estimate(const CaptureCommand& command) const override; | ||
| 247 | u32 Estimate(const CompressorCommand& command) const override; | ||
| 248 | |||
| 249 | private: | ||
| 250 | u32 sample_count{}; | ||
| 251 | u32 buffer_count{}; | ||
| 252 | }; | ||
| 253 | |||
| 254 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "audio_core/renderer/command/data_source/adpcm.h" | ||
| 7 | #include "audio_core/renderer/command/data_source/pcm_float.h" | ||
| 8 | #include "audio_core/renderer/command/data_source/pcm_int16.h" | ||
| 9 | #include "audio_core/renderer/command/effect/aux_.h" | ||
| 10 | #include "audio_core/renderer/command/effect/biquad_filter.h" | ||
| 11 | #include "audio_core/renderer/command/effect/capture.h" | ||
| 12 | #include "audio_core/renderer/command/effect/compressor.h" | ||
| 13 | #include "audio_core/renderer/command/effect/delay.h" | ||
| 14 | #include "audio_core/renderer/command/effect/i3dl2_reverb.h" | ||
| 15 | #include "audio_core/renderer/command/effect/light_limiter.h" | ||
| 16 | #include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h" | ||
| 17 | #include "audio_core/renderer/command/effect/reverb.h" | ||
| 18 | #include "audio_core/renderer/command/icommand.h" | ||
| 19 | #include "audio_core/renderer/command/mix/clear_mix.h" | ||
| 20 | #include "audio_core/renderer/command/mix/copy_mix.h" | ||
| 21 | #include "audio_core/renderer/command/mix/depop_for_mix_buffers.h" | ||
| 22 | #include "audio_core/renderer/command/mix/depop_prepare.h" | ||
| 23 | #include "audio_core/renderer/command/mix/mix.h" | ||
| 24 | #include "audio_core/renderer/command/mix/mix_ramp.h" | ||
| 25 | #include "audio_core/renderer/command/mix/mix_ramp_grouped.h" | ||
| 26 | #include "audio_core/renderer/command/mix/volume.h" | ||
| 27 | #include "audio_core/renderer/command/mix/volume_ramp.h" | ||
| 28 | #include "audio_core/renderer/command/performance/performance.h" | ||
| 29 | #include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h" | ||
| 30 | #include "audio_core/renderer/command/resample/upsample.h" | ||
| 31 | #include "audio_core/renderer/command/sink/circular_buffer.h" | ||
| 32 | #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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <span> | ||
| 5 | |||
| 6 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 7 | #include "audio_core/renderer/command/data_source/adpcm.h" | ||
| 8 | #include "audio_core/renderer/command/data_source/decode.h" | ||
| 9 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | void AdpcmDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, | ||
| 13 | std::string& string) { | ||
| 14 | string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample " | ||
| 15 | "rate {} target sample rate {} src quality {}\n", | ||
| 16 | output_index, sample_rate, processor.target_sample_rate, src_quality); | ||
| 17 | } | ||
| 18 | |||
| 19 | void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { | ||
| 20 | auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, | ||
| 21 | processor.sample_count)}; | ||
| 22 | |||
| 23 | DecodeFromWaveBuffersArgs args{ | ||
| 24 | .sample_format{SampleFormat::Adpcm}, | ||
| 25 | .output{out_buffer}, | ||
| 26 | .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, | ||
| 27 | .wave_buffers{wave_buffers}, | ||
| 28 | .channel{0}, | ||
| 29 | .channel_count{1}, | ||
| 30 | .src_quality{src_quality}, | ||
| 31 | .pitch{pitch}, | ||
| 32 | .source_sample_rate{sample_rate}, | ||
| 33 | .target_sample_rate{processor.target_sample_rate}, | ||
| 34 | .sample_count{processor.sample_count}, | ||
| 35 | .data_address{data_address}, | ||
| 36 | .data_size{data_size}, | ||
| 37 | .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, | ||
| 38 | .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, | ||
| 39 | }; | ||
| 40 | |||
| 41 | DecodeFromWaveBuffers(*processor.memory, args); | ||
| 42 | } | ||
| 43 | |||
| 44 | bool AdpcmDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 45 | return true; | ||
| 46 | } | ||
| 47 | |||
| 48 | void AdpcmDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, | ||
| 49 | std::string& string) { | ||
| 50 | string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample " | ||
| 51 | "rate {} target sample rate {} src quality {}\n", | ||
| 52 | output_index, sample_rate, processor.target_sample_rate, src_quality); | ||
| 53 | } | ||
| 54 | |||
| 55 | void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { | ||
| 56 | auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, | ||
| 57 | processor.sample_count)}; | ||
| 58 | |||
| 59 | DecodeFromWaveBuffersArgs args{ | ||
| 60 | .sample_format{SampleFormat::Adpcm}, | ||
| 61 | .output{out_buffer}, | ||
| 62 | .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, | ||
| 63 | .wave_buffers{wave_buffers}, | ||
| 64 | .channel{0}, | ||
| 65 | .channel_count{1}, | ||
| 66 | .src_quality{src_quality}, | ||
| 67 | .pitch{pitch}, | ||
| 68 | .source_sample_rate{sample_rate}, | ||
| 69 | .target_sample_rate{processor.target_sample_rate}, | ||
| 70 | .sample_count{processor.sample_count}, | ||
| 71 | .data_address{data_address}, | ||
| 72 | .data_size{data_size}, | ||
| 73 | .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, | ||
| 74 | .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, | ||
| 75 | }; | ||
| 76 | |||
| 77 | DecodeFromWaveBuffers(*processor.memory, args); | ||
| 78 | } | ||
| 79 | |||
| 80 | bool AdpcmDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 81 | return true; | ||
| 82 | } | ||
| 83 | |||
| 84 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <string> | ||
| 8 | |||
| 9 | #include "audio_core/common/common.h" | ||
| 10 | #include "audio_core/common/wave_buffer.h" | ||
| 11 | #include "audio_core/renderer/command/icommand.h" | ||
| 12 | #include "common/common_types.h" | ||
| 13 | |||
| 14 | namespace AudioCore::AudioRenderer { | ||
| 15 | namespace ADSP { | ||
| 16 | class CommandListProcessor; | ||
| 17 | } | ||
| 18 | |||
| 19 | /** | ||
| 20 | * AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers | ||
| 21 | * into the output_index mix buffer. | ||
| 22 | */ | ||
| 23 | struct AdpcmDataSourceVersion1Command : ICommand { | ||
| 24 | /** | ||
| 25 | * Print this command's information to a string. | ||
| 26 | * | ||
| 27 | * @param processor - The CommandListProcessor processing this command. | ||
| 28 | * @param string - The string to print into. | ||
| 29 | */ | ||
| 30 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Process this command. | ||
| 34 | * | ||
| 35 | * @param processor - The CommandListProcessor processing this command. | ||
| 36 | */ | ||
| 37 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 38 | |||
| 39 | /** | ||
| 40 | * Verify this command's data is valid. | ||
| 41 | * | ||
| 42 | * @param processor - The CommandListProcessor processing this command. | ||
| 43 | * @return True if the command is valid, otherwise false. | ||
| 44 | */ | ||
| 45 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 46 | |||
| 47 | /// Quality used for sample rate conversion | ||
| 48 | SrcQuality src_quality; | ||
| 49 | /// Mix buffer index for decoded samples | ||
| 50 | s16 output_index; | ||
| 51 | /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | ||
| 52 | u16 flags; | ||
| 53 | /// Wavebuffer sample rate | ||
| 54 | u32 sample_rate; | ||
| 55 | /// Pitch used for sample rate conversion | ||
| 56 | f32 pitch; | ||
| 57 | /// Wavebuffers containing the wavebuffer address, context address, looping information etc | ||
| 58 | std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; | ||
| 59 | /// Voice state, updated each call and written back to game | ||
| 60 | CpuAddr voice_state; | ||
| 61 | /// Coefficients data address | ||
| 62 | CpuAddr data_address; | ||
| 63 | /// Coefficients data size | ||
| 64 | u64 data_size; | ||
| 65 | }; | ||
| 66 | |||
| 67 | /** | ||
| 68 | * AudioRenderer command to decode ADPCM-encoded version 2 wavebuffers | ||
| 69 | * into the output_index mix buffer. | ||
| 70 | */ | ||
| 71 | struct AdpcmDataSourceVersion2Command : ICommand { | ||
| 72 | /** | ||
| 73 | * Print this command's information to a string. | ||
| 74 | * | ||
| 75 | * @param processor - The CommandListProcessor processing this command. | ||
| 76 | * @param string - The string to print into. | ||
| 77 | */ | ||
| 78 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 79 | |||
| 80 | /** | ||
| 81 | * Process this command. | ||
| 82 | * | ||
| 83 | * @param processor - The CommandListProcessor processing this command. | ||
| 84 | */ | ||
| 85 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 86 | |||
| 87 | /** | ||
| 88 | * Verify this command's data is valid. | ||
| 89 | * | ||
| 90 | * @param processor - The CommandListProcessor processing this command. | ||
| 91 | * @return True if the command is valid, otherwise false. | ||
| 92 | */ | ||
| 93 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 94 | |||
| 95 | /// Quality used for sample rate conversion | ||
| 96 | SrcQuality src_quality; | ||
| 97 | /// Mix buffer index for decoded samples | ||
| 98 | s16 output_index; | ||
| 99 | /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | ||
| 100 | u16 flags; | ||
| 101 | /// Wavebuffer sample rate | ||
| 102 | u32 sample_rate; | ||
| 103 | /// Pitch used for sample rate conversion | ||
| 104 | f32 pitch; | ||
| 105 | /// Target channel to read within the wavebuffer | ||
| 106 | s8 channel_index; | ||
| 107 | /// Number of channels within the wavebuffer | ||
| 108 | s8 channel_count; | ||
| 109 | /// Wavebuffers containing the wavebuffer address, context address, looping information etc | ||
| 110 | std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; | ||
| 111 | /// Voice state, updated each call and written back to game | ||
| 112 | CpuAddr voice_state; | ||
| 113 | /// Coefficients data address | ||
| 114 | CpuAddr data_address; | ||
| 115 | /// Coefficients data size | ||
| 116 | u64 data_size; | ||
| 117 | }; | ||
| 118 | |||
| 119 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <array> | ||
| 5 | #include <vector> | ||
| 6 | |||
| 7 | #include "audio_core/renderer/command/data_source/decode.h" | ||
| 8 | #include "audio_core/renderer/command/resample/resample.h" | ||
| 9 | #include "common/fixed_point.h" | ||
| 10 | #include "common/logging/log.h" | ||
| 11 | #include "core/memory.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | |||
| 15 | constexpr u32 TempBufferSize = 0x3F00; | ||
| 16 | constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4}; | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Decode PCM data. Only s16 or f32 is supported. | ||
| 20 | * | ||
| 21 | * @tparam T - Type to decode. Only s16 and f32 are supported. | ||
| 22 | * @param memory - Core memory for reading samples. | ||
| 23 | * @param out_buffer - Output mix buffer to receive the samples. | ||
| 24 | * @param req - Information for how to decode. | ||
| 25 | * @return Number of samples decoded. | ||
| 26 | */ | ||
| 27 | template <typename T> | ||
| 28 | static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, | ||
| 29 | const DecodeArg& req) { | ||
| 30 | constexpr s32 min{std::numeric_limits<s16>::min()}; | ||
| 31 | constexpr s32 max{std::numeric_limits<s16>::max()}; | ||
| 32 | |||
| 33 | if (req.buffer == 0 || req.buffer_size == 0) { | ||
| 34 | return 0; | ||
| 35 | } | ||
| 36 | |||
| 37 | if (req.start_offset >= req.end_offset) { | ||
| 38 | return 0; | ||
| 39 | } | ||
| 40 | |||
| 41 | auto samples_to_decode{ | ||
| 42 | std::min(req.samples_to_read, req.end_offset - req.start_offset - req.offset)}; | ||
| 43 | u32 channel_count{static_cast<u32>(req.channel_count)}; | ||
| 44 | |||
| 45 | switch (req.channel_count) { | ||
| 46 | default: { | ||
| 47 | const VAddr source{req.buffer + | ||
| 48 | (((req.start_offset + req.offset) * channel_count) * sizeof(T))}; | ||
| 49 | const u64 size{channel_count * samples_to_decode}; | ||
| 50 | const u64 size_bytes{size * sizeof(T)}; | ||
| 51 | |||
| 52 | std::vector<T> samples(size); | ||
| 53 | memory.ReadBlockUnsafe(source, samples.data(), size_bytes); | ||
| 54 | |||
| 55 | if constexpr (std::is_floating_point_v<T>) { | ||
| 56 | for (u32 i = 0; i < samples_to_decode; i++) { | ||
| 57 | auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] * | ||
| 58 | std::numeric_limits<s16>::max())}; | ||
| 59 | out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max)); | ||
| 60 | } | ||
| 61 | } else { | ||
| 62 | for (u32 i = 0; i < samples_to_decode; i++) { | ||
| 63 | out_buffer[i] = samples[i * channel_count + req.target_channel]; | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } break; | ||
| 67 | |||
| 68 | case 1: | ||
| 69 | if (req.target_channel != 0) { | ||
| 70 | LOG_ERROR(Service_Audio, "Invalid target channel, expected 0, got {}", | ||
| 71 | req.target_channel); | ||
| 72 | return 0; | ||
| 73 | } | ||
| 74 | |||
| 75 | const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))}; | ||
| 76 | std::vector<T> samples(samples_to_decode); | ||
| 77 | memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T)); | ||
| 78 | |||
| 79 | if constexpr (std::is_floating_point_v<T>) { | ||
| 80 | for (u32 i = 0; i < samples_to_decode; i++) { | ||
| 81 | auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] * | ||
| 82 | std::numeric_limits<s16>::max())}; | ||
| 83 | out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max)); | ||
| 84 | } | ||
| 85 | } else { | ||
| 86 | std::memcpy(out_buffer.data(), samples.data(), samples_to_decode * sizeof(s16)); | ||
| 87 | } | ||
| 88 | break; | ||
| 89 | } | ||
| 90 | |||
| 91 | return samples_to_decode; | ||
| 92 | } | ||
| 93 | |||
| 94 | /** | ||
| 95 | * Decode ADPCM data. | ||
| 96 | * | ||
| 97 | * @param memory - Core memory for reading samples. | ||
| 98 | * @param out_buffer - Output mix buffer to receive the samples. | ||
| 99 | * @param req - Information for how to decode. | ||
| 100 | * @return Number of samples decoded. | ||
| 101 | */ | ||
| 102 | static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, | ||
| 103 | const DecodeArg& req) { | ||
| 104 | constexpr u32 SamplesPerFrame{14}; | ||
| 105 | constexpr u32 NibblesPerFrame{16}; | ||
| 106 | |||
| 107 | if (req.buffer == 0 || req.buffer_size == 0) { | ||
| 108 | return 0; | ||
| 109 | } | ||
| 110 | |||
| 111 | if (req.end_offset < req.start_offset) { | ||
| 112 | return 0; | ||
| 113 | } | ||
| 114 | |||
| 115 | auto end{(req.end_offset % SamplesPerFrame) + | ||
| 116 | NibblesPerFrame * (req.end_offset / SamplesPerFrame)}; | ||
| 117 | if (req.end_offset % SamplesPerFrame) { | ||
| 118 | end += 3; | ||
| 119 | } else { | ||
| 120 | end += 1; | ||
| 121 | } | ||
| 122 | |||
| 123 | if (req.buffer_size < end / 2) { | ||
| 124 | return 0; | ||
| 125 | } | ||
| 126 | |||
| 127 | auto samples_to_process{ | ||
| 128 | std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)}; | ||
| 129 | |||
| 130 | auto samples_to_read{samples_to_process}; | ||
| 131 | auto start_pos{req.start_offset + req.offset}; | ||
| 132 | auto samples_remaining_in_frame{start_pos % SamplesPerFrame}; | ||
| 133 | auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame + | ||
| 134 | samples_remaining_in_frame}; | ||
| 135 | |||
| 136 | if (samples_remaining_in_frame) { | ||
| 137 | position_in_frame += 2; | ||
| 138 | } | ||
| 139 | |||
| 140 | const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)}; | ||
| 141 | std::vector<u8> wavebuffer(size); | ||
| 142 | memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(), | ||
| 143 | wavebuffer.size()); | ||
| 144 | |||
| 145 | auto context{req.adpcm_context}; | ||
| 146 | auto header{context->header}; | ||
| 147 | u8 coeff_index{static_cast<u8>((header >> 4U) & 0xFU)}; | ||
| 148 | u8 scale{static_cast<u8>(header & 0xFU)}; | ||
| 149 | s32 coeff0{req.coefficients[coeff_index * 2 + 0]}; | ||
| 150 | s32 coeff1{req.coefficients[coeff_index * 2 + 1]}; | ||
| 151 | |||
| 152 | auto yn0{context->yn0}; | ||
| 153 | auto yn1{context->yn1}; | ||
| 154 | |||
| 155 | static constexpr std::array<s32, 16> Steps{ | ||
| 156 | 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1, | ||
| 157 | }; | ||
| 158 | |||
| 159 | const auto decode_sample = [&](const s32 code) -> s16 { | ||
| 160 | const auto xn = code * (1 << scale); | ||
| 161 | const auto prediction = coeff0 * yn0 + coeff1 * yn1; | ||
| 162 | const auto sample = ((xn << 11) + 0x400 + prediction) >> 11; | ||
| 163 | const auto saturated = std::clamp<s32>(sample, -0x8000, 0x7FFF); | ||
| 164 | yn1 = yn0; | ||
| 165 | yn0 = static_cast<s16>(saturated); | ||
| 166 | return yn0; | ||
| 167 | }; | ||
| 168 | |||
| 169 | u32 read_index{0}; | ||
| 170 | u32 write_index{0}; | ||
| 171 | |||
| 172 | while (samples_to_read > 0) { | ||
| 173 | // Are we at a new frame? | ||
| 174 | if ((position_in_frame % NibblesPerFrame) == 0) { | ||
| 175 | header = wavebuffer[read_index++]; | ||
| 176 | coeff_index = (header >> 4) & 0xF; | ||
| 177 | scale = header & 0xF; | ||
| 178 | coeff0 = req.coefficients[coeff_index * 2 + 0]; | ||
| 179 | coeff1 = req.coefficients[coeff_index * 2 + 1]; | ||
| 180 | position_in_frame += 2; | ||
| 181 | |||
| 182 | // Can we consume all of this frame's samples? | ||
| 183 | if (samples_to_read >= SamplesPerFrame) { | ||
| 184 | // Can grab all samples until the next header | ||
| 185 | for (u32 i = 0; i < SamplesPerFrame / 2; i++) { | ||
| 186 | auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]}; | ||
| 187 | auto code1{Steps[wavebuffer[read_index] & 0xF]}; | ||
| 188 | read_index++; | ||
| 189 | |||
| 190 | out_buffer[write_index++] = decode_sample(code0); | ||
| 191 | out_buffer[write_index++] = decode_sample(code1); | ||
| 192 | } | ||
| 193 | |||
| 194 | position_in_frame += SamplesPerFrame; | ||
| 195 | samples_to_read -= SamplesPerFrame; | ||
| 196 | continue; | ||
| 197 | } | ||
| 198 | } | ||
| 199 | |||
| 200 | // Decode a single sample | ||
| 201 | auto code{wavebuffer[read_index]}; | ||
| 202 | if (position_in_frame & 1) { | ||
| 203 | code &= 0xF; | ||
| 204 | read_index++; | ||
| 205 | } else { | ||
| 206 | code >>= 4; | ||
| 207 | } | ||
| 208 | |||
| 209 | out_buffer[write_index++] = decode_sample(Steps[code]); | ||
| 210 | |||
| 211 | position_in_frame++; | ||
| 212 | samples_to_read--; | ||
| 213 | } | ||
| 214 | |||
| 215 | context->header = header; | ||
| 216 | context->yn0 = yn0; | ||
| 217 | context->yn1 = yn1; | ||
| 218 | |||
| 219 | return samples_to_process; | ||
| 220 | } | ||
| 221 | |||
| 222 | /** | ||
| 223 | * Decode implementation. | ||
| 224 | * Decode wavebuffers according to the given args. | ||
| 225 | * | ||
| 226 | * @param memory - Core memory to read data from. | ||
| 227 | * @param args - The wavebuffer data, and information for how to decode it. | ||
| 228 | */ | ||
| 229 | void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) { | ||
| 230 | auto& voice_state{*args.voice_state}; | ||
| 231 | auto remaining_sample_count{args.sample_count}; | ||
| 232 | auto fraction{voice_state.fraction}; | ||
| 233 | |||
| 234 | const auto sample_rate_ratio{ | ||
| 235 | (Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) * | ||
| 236 | args.pitch}; | ||
| 237 | const auto size_required{fraction + remaining_sample_count * sample_rate_ratio}; | ||
| 238 | |||
| 239 | if (size_required < 0) { | ||
| 240 | return; | ||
| 241 | } | ||
| 242 | |||
| 243 | auto pitch{PitchBySrcQuality[static_cast<u32>(args.src_quality)]}; | ||
| 244 | if (static_cast<u32>(pitch + size_required.to_int_floor()) > TempBufferSize) { | ||
| 245 | return; | ||
| 246 | } | ||
| 247 | |||
| 248 | auto max_remaining_sample_count{ | ||
| 249 | ((Common::FixedPoint<17, 15>(TempBufferSize) - fraction) / sample_rate_ratio) | ||
| 250 | .to_uint_floor()}; | ||
| 251 | max_remaining_sample_count = std::min(max_remaining_sample_count, remaining_sample_count); | ||
| 252 | |||
| 253 | auto wavebuffers_consumed{voice_state.wave_buffers_consumed}; | ||
| 254 | auto wavebuffer_index{voice_state.wave_buffer_index}; | ||
| 255 | auto played_sample_count{voice_state.played_sample_count}; | ||
| 256 | |||
| 257 | bool is_buffer_starved{false}; | ||
| 258 | u32 offset{voice_state.offset}; | ||
| 259 | |||
| 260 | auto output_buffer{args.output}; | ||
| 261 | std::vector<s16> temp_buffer(TempBufferSize, 0); | ||
| 262 | |||
| 263 | while (remaining_sample_count > 0) { | ||
| 264 | const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)}; | ||
| 265 | const auto samples_to_read{ | ||
| 266 | (fraction + samples_to_write * sample_rate_ratio).to_uint_floor()}; | ||
| 267 | |||
| 268 | u32 temp_buffer_pos{0}; | ||
| 269 | |||
| 270 | if (!args.IsVoicePitchAndSrcSkippedSupported) { | ||
| 271 | for (u32 i = 0; i < pitch; i++) { | ||
| 272 | temp_buffer[i] = voice_state.sample_history[i]; | ||
| 273 | } | ||
| 274 | temp_buffer_pos = pitch; | ||
| 275 | } | ||
| 276 | |||
| 277 | u32 samples_read{0}; | ||
| 278 | while (samples_read < samples_to_read) { | ||
| 279 | if (wavebuffer_index >= MaxWaveBuffers) { | ||
| 280 | LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index); | ||
| 281 | wavebuffer_index = 0; | ||
| 282 | voice_state.wave_buffer_valid.fill(false); | ||
| 283 | wavebuffers_consumed = MaxWaveBuffers; | ||
| 284 | } | ||
| 285 | |||
| 286 | if (!voice_state.wave_buffer_valid[wavebuffer_index]) { | ||
| 287 | is_buffer_starved = true; | ||
| 288 | break; | ||
| 289 | } | ||
| 290 | |||
| 291 | auto& wavebuffer{args.wave_buffers[wavebuffer_index]}; | ||
| 292 | |||
| 293 | if (offset == 0 && args.sample_format == SampleFormat::Adpcm && | ||
| 294 | wavebuffer.context != 0) { | ||
| 295 | memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context, | ||
| 296 | wavebuffer.context_size); | ||
| 297 | } | ||
| 298 | |||
| 299 | auto start_offset{wavebuffer.start_offset}; | ||
| 300 | auto end_offset{wavebuffer.end_offset}; | ||
| 301 | |||
| 302 | if (wavebuffer.loop && voice_state.loop_count > 0 && | ||
| 303 | wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 && | ||
| 304 | wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) { | ||
| 305 | start_offset = wavebuffer.loop_start_offset; | ||
| 306 | end_offset = wavebuffer.loop_end_offset; | ||
| 307 | } | ||
| 308 | |||
| 309 | DecodeArg decode_arg{.buffer{wavebuffer.buffer}, | ||
| 310 | .buffer_size{wavebuffer.buffer_size}, | ||
| 311 | .start_offset{start_offset}, | ||
| 312 | .end_offset{end_offset}, | ||
| 313 | .channel_count{args.channel_count}, | ||
| 314 | .coefficients{}, | ||
| 315 | .adpcm_context{nullptr}, | ||
| 316 | .target_channel{args.channel}, | ||
| 317 | .offset{offset}, | ||
| 318 | .samples_to_read{samples_to_read - samples_read}}; | ||
| 319 | |||
| 320 | s32 samples_decoded{0}; | ||
| 321 | |||
| 322 | switch (args.sample_format) { | ||
| 323 | case SampleFormat::PcmInt16: | ||
| 324 | samples_decoded = DecodePcm<s16>( | ||
| 325 | memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, | ||
| 326 | decode_arg); | ||
| 327 | break; | ||
| 328 | |||
| 329 | case SampleFormat::PcmFloat: | ||
| 330 | samples_decoded = DecodePcm<f32>( | ||
| 331 | memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, | ||
| 332 | decode_arg); | ||
| 333 | break; | ||
| 334 | |||
| 335 | case SampleFormat::Adpcm: { | ||
| 336 | decode_arg.adpcm_context = &voice_state.adpcm_context; | ||
| 337 | memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size); | ||
| 338 | samples_decoded = DecodeAdpcm( | ||
| 339 | memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, | ||
| 340 | decode_arg); | ||
| 341 | } break; | ||
| 342 | |||
| 343 | default: | ||
| 344 | LOG_ERROR(Service_Audio, "Invalid sample format to decode {}", | ||
| 345 | static_cast<u32>(args.sample_format)); | ||
| 346 | samples_decoded = 0; | ||
| 347 | break; | ||
| 348 | } | ||
| 349 | |||
| 350 | played_sample_count += samples_decoded; | ||
| 351 | samples_read += samples_decoded; | ||
| 352 | temp_buffer_pos += samples_decoded; | ||
| 353 | offset += samples_decoded; | ||
| 354 | |||
| 355 | if (samples_decoded == 0 || offset >= end_offset - start_offset) { | ||
| 356 | offset = 0; | ||
| 357 | if (!wavebuffer.loop) { | ||
| 358 | voice_state.wave_buffer_valid[wavebuffer_index] = false; | ||
| 359 | voice_state.loop_count = 0; | ||
| 360 | |||
| 361 | if (wavebuffer.stream_ended) { | ||
| 362 | played_sample_count = 0; | ||
| 363 | } | ||
| 364 | |||
| 365 | wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; | ||
| 366 | wavebuffers_consumed++; | ||
| 367 | } else { | ||
| 368 | voice_state.loop_count++; | ||
| 369 | if (wavebuffer.loop_count > 0 && | ||
| 370 | (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) { | ||
| 371 | voice_state.wave_buffer_valid[wavebuffer_index] = false; | ||
| 372 | voice_state.loop_count = 0; | ||
| 373 | |||
| 374 | if (wavebuffer.stream_ended) { | ||
| 375 | played_sample_count = 0; | ||
| 376 | } | ||
| 377 | |||
| 378 | wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; | ||
| 379 | wavebuffers_consumed++; | ||
| 380 | } | ||
| 381 | |||
| 382 | if (samples_decoded == 0) { | ||
| 383 | is_buffer_starved = true; | ||
| 384 | break; | ||
| 385 | } | ||
| 386 | |||
| 387 | if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) { | ||
| 388 | played_sample_count = 0; | ||
| 389 | } | ||
| 390 | } | ||
| 391 | } | ||
| 392 | } | ||
| 393 | |||
| 394 | if (args.IsVoicePitchAndSrcSkippedSupported) { | ||
| 395 | if (samples_read > output_buffer.size()) { | ||
| 396 | LOG_ERROR(Service_Audio, "Attempting to write past the end of output buffer!"); | ||
| 397 | } | ||
| 398 | for (u32 i = 0; i < samples_read; i++) { | ||
| 399 | output_buffer[i] = temp_buffer[i]; | ||
| 400 | } | ||
| 401 | } else { | ||
| 402 | std::memset(&temp_buffer[temp_buffer_pos], 0, | ||
| 403 | (samples_to_read - samples_read) * sizeof(s16)); | ||
| 404 | |||
| 405 | Resample(output_buffer, temp_buffer, sample_rate_ratio, fraction, samples_to_write, | ||
| 406 | args.src_quality); | ||
| 407 | |||
| 408 | std::memcpy(voice_state.sample_history.data(), &temp_buffer[samples_to_read], | ||
| 409 | pitch * sizeof(s16)); | ||
| 410 | } | ||
| 411 | |||
| 412 | remaining_sample_count -= samples_to_write; | ||
| 413 | if (remaining_sample_count != 0 && is_buffer_starved) { | ||
| 414 | LOG_ERROR(Service_Audio, "Samples remaining but buffer is starving??"); | ||
| 415 | break; | ||
| 416 | } | ||
| 417 | |||
| 418 | output_buffer = output_buffer.subspan(samples_to_write); | ||
| 419 | } | ||
| 420 | |||
| 421 | voice_state.wave_buffers_consumed = wavebuffers_consumed; | ||
| 422 | voice_state.played_sample_count = played_sample_count; | ||
| 423 | voice_state.wave_buffer_index = wavebuffer_index; | ||
| 424 | voice_state.offset = offset; | ||
| 425 | voice_state.fraction = fraction; | ||
| 426 | } | ||
| 427 | |||
| 428 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <span> | ||
| 8 | |||
| 9 | #include "audio_core/common/common.h" | ||
| 10 | #include "audio_core/common/wave_buffer.h" | ||
| 11 | #include "audio_core/renderer/voice/voice_state.h" | ||
| 12 | #include "common/common_types.h" | ||
| 13 | |||
| 14 | namespace Core::Memory { | ||
| 15 | class Memory; | ||
| 16 | } | ||
| 17 | |||
| 18 | namespace AudioCore::AudioRenderer { | ||
| 19 | |||
| 20 | struct DecodeFromWaveBuffersArgs { | ||
| 21 | SampleFormat sample_format; | ||
| 22 | std::span<s32> output; | ||
| 23 | VoiceState* voice_state; | ||
| 24 | std::span<WaveBufferVersion2> wave_buffers; | ||
| 25 | s8 channel; | ||
| 26 | s8 channel_count; | ||
| 27 | SrcQuality src_quality; | ||
| 28 | f32 pitch; | ||
| 29 | u32 source_sample_rate; | ||
| 30 | u32 target_sample_rate; | ||
| 31 | u32 sample_count; | ||
| 32 | CpuAddr data_address; | ||
| 33 | u64 data_size; | ||
| 34 | bool IsVoicePlayedSampleCountResetAtLoopPointSupported; | ||
| 35 | bool IsVoicePitchAndSrcSkippedSupported; | ||
| 36 | }; | ||
| 37 | |||
| 38 | struct DecodeArg { | ||
| 39 | CpuAddr buffer; | ||
| 40 | u64 buffer_size; | ||
| 41 | u32 start_offset; | ||
| 42 | u32 end_offset; | ||
| 43 | s8 channel_count; | ||
| 44 | std::array<s16, 16> coefficients; | ||
| 45 | VoiceState::AdpcmContext* adpcm_context; | ||
| 46 | s8 target_channel; | ||
| 47 | u32 offset; | ||
| 48 | u32 samples_to_read; | ||
| 49 | }; | ||
| 50 | |||
| 51 | /** | ||
| 52 | * Decode wavebuffers according to the given args. | ||
| 53 | * | ||
| 54 | * @param memory - Core memory to read data from. | ||
| 55 | * @param args - The wavebuffer data, and information for how to decode it. | ||
| 56 | */ | ||
| 57 | void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args); | ||
| 58 | |||
| 59 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 5 | #include "audio_core/renderer/command/data_source/decode.h" | ||
| 6 | #include "audio_core/renderer/command/data_source/pcm_float.h" | ||
| 7 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, | ||
| 11 | std::string& string) { | ||
| 12 | string += | ||
| 13 | fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} " | ||
| 14 | "channel count {} source sample rate {} target sample rate {} src quality {}\n", | ||
| 15 | output_index, channel_index, channel_count, sample_rate, | ||
| 16 | processor.target_sample_rate, src_quality); | ||
| 17 | } | ||
| 18 | |||
| 19 | void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { | ||
| 20 | auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, | ||
| 21 | processor.sample_count); | ||
| 22 | |||
| 23 | DecodeFromWaveBuffersArgs args{ | ||
| 24 | .sample_format{SampleFormat::PcmFloat}, | ||
| 25 | .output{out_buffer}, | ||
| 26 | .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, | ||
| 27 | .wave_buffers{wave_buffers}, | ||
| 28 | .channel{channel_index}, | ||
| 29 | .channel_count{channel_count}, | ||
| 30 | .src_quality{src_quality}, | ||
| 31 | .pitch{pitch}, | ||
| 32 | .source_sample_rate{sample_rate}, | ||
| 33 | .target_sample_rate{processor.target_sample_rate}, | ||
| 34 | .sample_count{processor.sample_count}, | ||
| 35 | .data_address{0}, | ||
| 36 | .data_size{0}, | ||
| 37 | .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, | ||
| 38 | .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, | ||
| 39 | }; | ||
| 40 | |||
| 41 | DecodeFromWaveBuffers(*processor.memory, args); | ||
| 42 | } | ||
| 43 | |||
| 44 | bool PcmFloatDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 45 | return true; | ||
| 46 | } | ||
| 47 | |||
| 48 | void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, | ||
| 49 | std::string& string) { | ||
| 50 | string += | ||
| 51 | fmt::format("PcmFloatDataSourceVersion2Command\n\toutput_index {:02X} channel {} " | ||
| 52 | "channel count {} source sample rate {} target sample rate {} src quality {}\n", | ||
| 53 | output_index, channel_index, channel_count, sample_rate, | ||
| 54 | processor.target_sample_rate, src_quality); | ||
| 55 | } | ||
| 56 | |||
| 57 | void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { | ||
| 58 | auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, | ||
| 59 | processor.sample_count); | ||
| 60 | |||
| 61 | DecodeFromWaveBuffersArgs args{ | ||
| 62 | .sample_format{SampleFormat::PcmFloat}, | ||
| 63 | .output{out_buffer}, | ||
| 64 | .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, | ||
| 65 | .wave_buffers{wave_buffers}, | ||
| 66 | .channel{channel_index}, | ||
| 67 | .channel_count{channel_count}, | ||
| 68 | .src_quality{src_quality}, | ||
| 69 | .pitch{pitch}, | ||
| 70 | .source_sample_rate{sample_rate}, | ||
| 71 | .target_sample_rate{processor.target_sample_rate}, | ||
| 72 | .sample_count{processor.sample_count}, | ||
| 73 | .data_address{0}, | ||
| 74 | .data_size{0}, | ||
| 75 | .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, | ||
| 76 | .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, | ||
| 77 | }; | ||
| 78 | |||
| 79 | DecodeFromWaveBuffers(*processor.memory, args); | ||
| 80 | } | ||
| 81 | |||
| 82 | bool PcmFloatDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 83 | return true; | ||
| 84 | } | ||
| 85 | |||
| 86 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include "audio_core/common/wave_buffer.h" | ||
| 9 | #include "audio_core/renderer/command/icommand.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | namespace ADSP { | ||
| 14 | class CommandListProcessor; | ||
| 15 | } | ||
| 16 | |||
| 17 | /** | ||
| 18 | * AudioRenderer command to decode PCM float-encoded version 1 wavebuffers | ||
| 19 | * into the output_index mix buffer. | ||
| 20 | */ | ||
| 21 | struct PcmFloatDataSourceVersion1Command : ICommand { | ||
| 22 | /** | ||
| 23 | * Print this command's information to a string. | ||
| 24 | * | ||
| 25 | * @param processor - The CommandListProcessor processing this command. | ||
| 26 | * @param string - The string to print into. | ||
| 27 | */ | ||
| 28 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Process this command. | ||
| 32 | * | ||
| 33 | * @param processor - The CommandListProcessor processing this command. | ||
| 34 | */ | ||
| 35 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Verify this command's data is valid. | ||
| 39 | * | ||
| 40 | * @param processor - The CommandListProcessor processing this command. | ||
| 41 | * @return True if the command is valid, otherwise false. | ||
| 42 | */ | ||
| 43 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 44 | |||
| 45 | /// Quality used for sample rate conversion | ||
| 46 | SrcQuality src_quality; | ||
| 47 | /// Mix buffer index for decoded samples | ||
| 48 | s16 output_index; | ||
| 49 | /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | ||
| 50 | u16 flags; | ||
| 51 | /// Wavebuffer sample rate | ||
| 52 | u32 sample_rate; | ||
| 53 | /// Pitch used for sample rate conversion | ||
| 54 | f32 pitch; | ||
| 55 | /// Target channel to read within the wavebuffer | ||
| 56 | s8 channel_index; | ||
| 57 | /// Number of channels within the wavebuffer | ||
| 58 | s8 channel_count; | ||
| 59 | /// Wavebuffers containing the wavebuffer address, context address, looping information etc | ||
| 60 | std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; | ||
| 61 | /// Voice state, updated each call and written back to game | ||
| 62 | CpuAddr voice_state; | ||
| 63 | }; | ||
| 64 | |||
| 65 | /** | ||
| 66 | * AudioRenderer command to decode PCM float-encoded version 2 wavebuffers | ||
| 67 | * into the output_index mix buffer. | ||
| 68 | */ | ||
| 69 | struct PcmFloatDataSourceVersion2Command : ICommand { | ||
| 70 | /** | ||
| 71 | * Print this command's information to a string. | ||
| 72 | * | ||
| 73 | * @param processor - The CommandListProcessor processing this command. | ||
| 74 | * @param string - The string to print into. | ||
| 75 | */ | ||
| 76 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 77 | |||
| 78 | /** | ||
| 79 | * Process this command. | ||
| 80 | * | ||
| 81 | * @param processor - The CommandListProcessor processing this command. | ||
| 82 | */ | ||
| 83 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 84 | |||
| 85 | /** | ||
| 86 | * Verify this command's data is valid. | ||
| 87 | * | ||
| 88 | * @param processor - The CommandListProcessor processing this command. | ||
| 89 | * @return True if the command is valid, otherwise false. | ||
| 90 | */ | ||
| 91 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 92 | |||
| 93 | /// Quality used for sample rate conversion | ||
| 94 | SrcQuality src_quality; | ||
| 95 | /// Mix buffer index for decoded samples | ||
| 96 | s16 output_index; | ||
| 97 | /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | ||
| 98 | u16 flags; | ||
| 99 | /// Wavebuffer sample rate | ||
| 100 | u32 sample_rate; | ||
| 101 | /// Pitch used for sample rate conversion | ||
| 102 | f32 pitch; | ||
| 103 | /// Target channel to read within the wavebuffer | ||
| 104 | s8 channel_index; | ||
| 105 | /// Number of channels within the wavebuffer | ||
| 106 | s8 channel_count; | ||
| 107 | /// Wavebuffers containing the wavebuffer address, context address, looping information etc | ||
| 108 | std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; | ||
| 109 | /// Voice state, updated each call and written back to game | ||
| 110 | CpuAddr voice_state; | ||
| 111 | }; | ||
| 112 | |||
| 113 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <span> | ||
| 5 | |||
| 6 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 7 | #include "audio_core/renderer/command/data_source/decode.h" | ||
| 8 | #include "audio_core/renderer/command/data_source/pcm_int16.h" | ||
| 9 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, | ||
| 13 | std::string& string) { | ||
| 14 | string += | ||
| 15 | fmt::format("PcmInt16DataSourceVersion1Command\n\toutput_index {:02X} channel {} " | ||
| 16 | "channel count {} source sample rate {} target sample rate {} src quality {}\n", | ||
| 17 | output_index, channel_index, channel_count, sample_rate, | ||
| 18 | processor.target_sample_rate, src_quality); | ||
| 19 | } | ||
| 20 | |||
| 21 | void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { | ||
| 22 | auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, | ||
| 23 | processor.sample_count); | ||
| 24 | |||
| 25 | DecodeFromWaveBuffersArgs args{ | ||
| 26 | .sample_format{SampleFormat::PcmInt16}, | ||
| 27 | .output{out_buffer}, | ||
| 28 | .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, | ||
| 29 | .wave_buffers{wave_buffers}, | ||
| 30 | .channel{channel_index}, | ||
| 31 | .channel_count{channel_count}, | ||
| 32 | .src_quality{src_quality}, | ||
| 33 | .pitch{pitch}, | ||
| 34 | .source_sample_rate{sample_rate}, | ||
| 35 | .target_sample_rate{processor.target_sample_rate}, | ||
| 36 | .sample_count{processor.sample_count}, | ||
| 37 | .data_address{0}, | ||
| 38 | .data_size{0}, | ||
| 39 | .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, | ||
| 40 | .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, | ||
| 41 | }; | ||
| 42 | |||
| 43 | DecodeFromWaveBuffers(*processor.memory, args); | ||
| 44 | } | ||
| 45 | |||
| 46 | bool PcmInt16DataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 47 | return true; | ||
| 48 | } | ||
| 49 | |||
| 50 | void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, | ||
| 51 | std::string& string) { | ||
| 52 | string += | ||
| 53 | fmt::format("PcmInt16DataSourceVersion2Command\n\toutput_index {:02X} channel {} " | ||
| 54 | "channel count {} source sample rate {} target sample rate {} src quality {}\n", | ||
| 55 | output_index, channel_index, channel_count, sample_rate, | ||
| 56 | processor.target_sample_rate, src_quality); | ||
| 57 | } | ||
| 58 | |||
| 59 | void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { | ||
| 60 | auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, | ||
| 61 | processor.sample_count); | ||
| 62 | DecodeFromWaveBuffersArgs args{ | ||
| 63 | .sample_format{SampleFormat::PcmInt16}, | ||
| 64 | .output{out_buffer}, | ||
| 65 | .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, | ||
| 66 | .wave_buffers{wave_buffers}, | ||
| 67 | .channel{channel_index}, | ||
| 68 | .channel_count{channel_count}, | ||
| 69 | .src_quality{src_quality}, | ||
| 70 | .pitch{pitch}, | ||
| 71 | .source_sample_rate{sample_rate}, | ||
| 72 | .target_sample_rate{processor.target_sample_rate}, | ||
| 73 | .sample_count{processor.sample_count}, | ||
| 74 | .data_address{0}, | ||
| 75 | .data_size{0}, | ||
| 76 | .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, | ||
| 77 | .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, | ||
| 78 | }; | ||
| 79 | |||
| 80 | DecodeFromWaveBuffers(*processor.memory, args); | ||
| 81 | } | ||
| 82 | |||
| 83 | bool PcmInt16DataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 84 | return true; | ||
| 85 | } | ||
| 86 | |||
| 87 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include "audio_core/common/wave_buffer.h" | ||
| 9 | #include "audio_core/renderer/command/icommand.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | namespace ADSP { | ||
| 14 | class CommandListProcessor; | ||
| 15 | } | ||
| 16 | |||
| 17 | /** | ||
| 18 | * AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers | ||
| 19 | * into the output_index mix buffer. | ||
| 20 | */ | ||
| 21 | struct PcmInt16DataSourceVersion1Command : ICommand { | ||
| 22 | /** | ||
| 23 | * Print this command's information to a string. | ||
| 24 | * | ||
| 25 | * @param processor - The CommandListProcessor processing this command. | ||
| 26 | * @param string - The string to print into. | ||
| 27 | */ | ||
| 28 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Process this command. | ||
| 32 | * | ||
| 33 | * @param processor - The CommandListProcessor processing this command. | ||
| 34 | */ | ||
| 35 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Verify this command's data is valid. | ||
| 39 | * | ||
| 40 | * @param processor - The CommandListProcessor processing this command. | ||
| 41 | * @return True if the command is valid, otherwise false. | ||
| 42 | */ | ||
| 43 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 44 | |||
| 45 | /// Quality used for sample rate conversion | ||
| 46 | SrcQuality src_quality; | ||
| 47 | /// Mix buffer index for decoded samples | ||
| 48 | s16 output_index; | ||
| 49 | /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | ||
| 50 | u16 flags; | ||
| 51 | /// Wavebuffer sample rate | ||
| 52 | u32 sample_rate; | ||
| 53 | /// Pitch used for sample rate conversion | ||
| 54 | f32 pitch; | ||
| 55 | /// Target channel to read within the wavebuffer | ||
| 56 | s8 channel_index; | ||
| 57 | /// Number of channels within the wavebuffer | ||
| 58 | s8 channel_count; | ||
| 59 | /// Wavebuffers containing the wavebuffer address, context address, looping information etc | ||
| 60 | std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; | ||
| 61 | /// Voice state, updated each call and written back to game | ||
| 62 | CpuAddr voice_state; | ||
| 63 | }; | ||
| 64 | |||
| 65 | /** | ||
| 66 | * AudioRenderer command to decode PCM s16-encoded version 2 wavebuffers | ||
| 67 | * into the output_index mix buffer. | ||
| 68 | */ | ||
| 69 | struct PcmInt16DataSourceVersion2Command : ICommand { | ||
| 70 | /** | ||
| 71 | * Print this command's information to a string. | ||
| 72 | * @param processor - The CommandListProcessor processing this command. | ||
| 73 | * @param string - The string to print into. | ||
| 74 | */ | ||
| 75 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 76 | |||
| 77 | /** | ||
| 78 | * Process this command. | ||
| 79 | * @param processor - The CommandListProcessor processing this command. | ||
| 80 | */ | ||
| 81 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 82 | |||
| 83 | /** | ||
| 84 | * Verify this command's data is valid. | ||
| 85 | * @param processor - The CommandListProcessor processing this command. | ||
| 86 | * @return True if the command is valid, otherwise false. | ||
| 87 | */ | ||
| 88 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 89 | |||
| 90 | /// Quality used for sample rate conversion | ||
| 91 | SrcQuality src_quality; | ||
| 92 | /// Mix buffer index for decoded samples | ||
| 93 | s16 output_index; | ||
| 94 | /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | ||
| 95 | u16 flags; | ||
| 96 | /// Wavebuffer sample rate | ||
| 97 | u32 sample_rate; | ||
| 98 | /// Pitch used for sample rate conversion | ||
| 99 | f32 pitch; | ||
| 100 | /// Target channel to read within the wavebuffer | ||
| 101 | s8 channel_index; | ||
| 102 | /// Number of channels within the wavebuffer | ||
| 103 | s8 channel_count; | ||
| 104 | /// Wavebuffers containing the wavebuffer address, context address, looping information etc | ||
| 105 | std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; | ||
| 106 | /// Voice state, updated each call and written back to game | ||
| 107 | CpuAddr voice_state; | ||
| 108 | }; | ||
| 109 | |||
| 110 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 5 | #include "audio_core/renderer/command/effect/aux_.h" | ||
| 6 | #include "audio_core/renderer/effect/aux_.h" | ||
| 7 | #include "core/memory.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | /** | ||
| 11 | * Reset an AuxBuffer. | ||
| 12 | * | ||
| 13 | * @param memory - Core memory for writing. | ||
| 14 | * @param aux_info - Memory address pointing to the AuxInfo to reset. | ||
| 15 | */ | ||
| 16 | static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) { | ||
| 17 | if (aux_info == 0) { | ||
| 18 | LOG_ERROR(Service_Audio, "Aux info is 0!"); | ||
| 19 | return; | ||
| 20 | } | ||
| 21 | |||
| 22 | auto info{reinterpret_cast<AuxInfo::AuxInfoDsp*>(memory.GetPointer(aux_info))}; | ||
| 23 | info->read_offset = 0; | ||
| 24 | info->write_offset = 0; | ||
| 25 | info->total_sample_count = 0; | ||
| 26 | } | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Write the given input mix buffer to the memory at send_buffer, and update send_info_ if | ||
| 30 | * update_count is set, to notify the game that an update happened. | ||
| 31 | * | ||
| 32 | * @param memory - Core memory for writing. | ||
| 33 | * @param send_info_ - Meta information for where to write the mix buffer. | ||
| 34 | * @param sample_count - Unused. | ||
| 35 | * @param send_buffer - Memory address to write the mix buffer to. | ||
| 36 | * @param count_max - Maximum number of samples in the receiving buffer. | ||
| 37 | * @param input - Input mix buffer to write. | ||
| 38 | * @param write_count_ - Number of samples to write. | ||
| 39 | * @param write_offset - Current offset to begin writing the receiving buffer at. | ||
| 40 | * @param update_count - If non-zero, send_info_ will be updated. | ||
| 41 | * @return Number of samples written. | ||
| 42 | */ | ||
| 43 | static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_, | ||
| 44 | [[maybe_unused]] u32 sample_count, const CpuAddr send_buffer, | ||
| 45 | const u32 count_max, std::span<const s32> input, | ||
| 46 | const u32 write_count_, const u32 write_offset, | ||
| 47 | const u32 update_count) { | ||
| 48 | if (write_count_ > count_max) { | ||
| 49 | LOG_ERROR(Service_Audio, | ||
| 50 | "write_count must be smaller than count_max! write_count {}, count_max {}", | ||
| 51 | write_count_, count_max); | ||
| 52 | return 0; | ||
| 53 | } | ||
| 54 | |||
| 55 | if (input.empty()) { | ||
| 56 | LOG_ERROR(Service_Audio, "input buffer is empty!"); | ||
| 57 | return 0; | ||
| 58 | } | ||
| 59 | |||
| 60 | if (send_buffer == 0) { | ||
| 61 | LOG_ERROR(Service_Audio, "send_buffer is 0!"); | ||
| 62 | return 0; | ||
| 63 | } | ||
| 64 | |||
| 65 | if (count_max == 0) { | ||
| 66 | return 0; | ||
| 67 | } | ||
| 68 | |||
| 69 | AuxInfo::AuxInfoDsp send_info{}; | ||
| 70 | memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp)); | ||
| 71 | |||
| 72 | u32 target_write_offset{send_info.write_offset + write_offset}; | ||
| 73 | if (target_write_offset > count_max || write_count_ == 0) { | ||
| 74 | return 0; | ||
| 75 | } | ||
| 76 | |||
| 77 | u32 write_count{write_count_}; | ||
| 78 | u32 write_pos{0}; | ||
| 79 | while (write_count > 0) { | ||
| 80 | u32 to_write{std::min(count_max - target_write_offset, write_count)}; | ||
| 81 | |||
| 82 | if (to_write > 0) { | ||
| 83 | memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32), | ||
| 84 | &input[write_pos], to_write * sizeof(s32)); | ||
| 85 | } | ||
| 86 | |||
| 87 | target_write_offset = (target_write_offset + to_write) % count_max; | ||
| 88 | write_count -= to_write; | ||
| 89 | write_pos += to_write; | ||
| 90 | } | ||
| 91 | |||
| 92 | if (update_count) { | ||
| 93 | send_info.write_offset = (send_info.write_offset + update_count) % count_max; | ||
| 94 | } | ||
| 95 | |||
| 96 | memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp)); | ||
| 97 | |||
| 98 | return write_count_; | ||
| 99 | } | ||
| 100 | |||
| 101 | /** | ||
| 102 | * Read the given memory at return_buffer into the output mix buffer, and update return_info_ if | ||
| 103 | * update_count is set, to notify the game that an update happened. | ||
| 104 | * | ||
| 105 | * @param memory - Core memory for writing. | ||
| 106 | * @param return_info_ - Meta information for where to read the mix buffer. | ||
| 107 | * @param return_buffer - Memory address to read the samples from. | ||
| 108 | * @param count_max - Maximum number of samples in the receiving buffer. | ||
| 109 | * @param output - Output mix buffer which will receive the samples. | ||
| 110 | * @param count_ - Number of samples to read. | ||
| 111 | * @param read_offset - Current offset to begin reading the return_buffer at. | ||
| 112 | * @param update_count - If non-zero, send_info_ will be updated. | ||
| 113 | * @return Number of samples read. | ||
| 114 | */ | ||
| 115 | static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr return_info_, | ||
| 116 | const CpuAddr return_buffer, const u32 count_max, std::span<s32> output, | ||
| 117 | const u32 count_, const u32 read_offset, const u32 update_count) { | ||
| 118 | if (count_max == 0) { | ||
| 119 | return 0; | ||
| 120 | } | ||
| 121 | |||
| 122 | if (count_ > count_max) { | ||
| 123 | LOG_ERROR(Service_Audio, "count must be smaller than count_max! count {}, count_max {}", | ||
| 124 | count_, count_max); | ||
| 125 | return 0; | ||
| 126 | } | ||
| 127 | |||
| 128 | if (output.empty()) { | ||
| 129 | LOG_ERROR(Service_Audio, "output buffer is empty!"); | ||
| 130 | return 0; | ||
| 131 | } | ||
| 132 | |||
| 133 | if (return_buffer == 0) { | ||
| 134 | LOG_ERROR(Service_Audio, "return_buffer is 0!"); | ||
| 135 | return 0; | ||
| 136 | } | ||
| 137 | |||
| 138 | AuxInfo::AuxInfoDsp return_info{}; | ||
| 139 | memory.ReadBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp)); | ||
| 140 | |||
| 141 | u32 target_read_offset{return_info.read_offset + read_offset}; | ||
| 142 | if (target_read_offset > count_max) { | ||
| 143 | return 0; | ||
| 144 | } | ||
| 145 | |||
| 146 | u32 read_count{count_}; | ||
| 147 | u32 read_pos{0}; | ||
| 148 | while (read_count > 0) { | ||
| 149 | u32 to_read{std::min(count_max - target_read_offset, read_count)}; | ||
| 150 | |||
| 151 | if (to_read > 0) { | ||
| 152 | memory.ReadBlockUnsafe(return_buffer + target_read_offset * sizeof(s32), | ||
| 153 | &output[read_pos], to_read * sizeof(s32)); | ||
| 154 | } | ||
| 155 | |||
| 156 | target_read_offset = (target_read_offset + to_read) % count_max; | ||
| 157 | read_count -= to_read; | ||
| 158 | read_pos += to_read; | ||
| 159 | } | ||
| 160 | |||
| 161 | if (update_count) { | ||
| 162 | return_info.read_offset = (return_info.read_offset + update_count) % count_max; | ||
| 163 | } | ||
| 164 | |||
| 165 | memory.WriteBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp)); | ||
| 166 | |||
| 167 | return count_; | ||
| 168 | } | ||
| 169 | |||
| 170 | void AuxCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 171 | std::string& string) { | ||
| 172 | string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled, | ||
| 173 | input, output); | ||
| 174 | } | ||
| 175 | |||
| 176 | void AuxCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 177 | auto input_buffer{ | ||
| 178 | processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; | ||
| 179 | auto output_buffer{ | ||
| 180 | processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; | ||
| 181 | |||
| 182 | if (effect_enabled) { | ||
| 183 | WriteAuxBufferDsp(*processor.memory, send_buffer_info, processor.sample_count, send_buffer, | ||
| 184 | count_max, input_buffer, processor.sample_count, write_offset, | ||
| 185 | update_count); | ||
| 186 | |||
| 187 | auto read{ReadAuxBufferDsp(*processor.memory, return_buffer_info, return_buffer, count_max, | ||
| 188 | output_buffer, processor.sample_count, write_offset, | ||
| 189 | update_count)}; | ||
| 190 | |||
| 191 | if (read != processor.sample_count) { | ||
| 192 | std::memset(&output_buffer[read], 0, processor.sample_count - read); | ||
| 193 | } | ||
| 194 | } else { | ||
| 195 | ResetAuxBufferDsp(*processor.memory, send_buffer_info); | ||
| 196 | ResetAuxBufferDsp(*processor.memory, return_buffer_info); | ||
| 197 | if (input != output) { | ||
| 198 | std::memcpy(output_buffer.data(), input_buffer.data(), output_buffer.size_bytes()); | ||
| 199 | } | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | bool AuxCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 204 | return true; | ||
| 205 | } | ||
| 206 | |||
| 207 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | ||
| 14 | } | ||
| 15 | |||
| 16 | /** | ||
| 17 | * AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game | ||
| 18 | * memory, and reading into the output buffer from game memory. | ||
| 19 | */ | ||
| 20 | struct AuxCommand : ICommand { | ||
| 21 | /** | ||
| 22 | * Print this command's information to a string. | ||
| 23 | * | ||
| 24 | * @param processor - The CommandListProcessor processing this command. | ||
| 25 | * @param string - The string to print into. | ||
| 26 | */ | ||
| 27 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Process this command. | ||
| 31 | * | ||
| 32 | * @param processor - The CommandListProcessor processing this command. | ||
| 33 | */ | ||
| 34 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Verify this command's data is valid. | ||
| 38 | * | ||
| 39 | * @param processor - The CommandListProcessor processing this command. | ||
| 40 | * @return True if the command is valid, otherwise false. | ||
| 41 | */ | ||
| 42 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 43 | |||
| 44 | /// Input mix buffer index | ||
| 45 | s16 input; | ||
| 46 | /// Output mix buffer index | ||
| 47 | s16 output; | ||
| 48 | /// Meta info for writing | ||
| 49 | CpuAddr send_buffer_info; | ||
| 50 | /// Meta info for reading | ||
| 51 | CpuAddr return_buffer_info; | ||
| 52 | /// Game memory write buffer | ||
| 53 | CpuAddr send_buffer; | ||
| 54 | /// Game memory read buffer | ||
| 55 | CpuAddr return_buffer; | ||
| 56 | /// Max samples to read/write | ||
| 57 | u32 count_max; | ||
| 58 | /// Current read/write offset | ||
| 59 | u32 write_offset; | ||
| 60 | /// Number of samples to update per call | ||
| 61 | u32 update_count; | ||
| 62 | /// is this effect enabled? | ||
| 63 | bool effect_enabled; | ||
| 64 | }; | ||
| 65 | |||
| 66 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 5 | #include "audio_core/renderer/command/effect/biquad_filter.h" | ||
| 6 | #include "audio_core/renderer/voice/voice_state.h" | ||
| 7 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | /** | ||
| 10 | * Biquad filter float implementation. | ||
| 11 | * | ||
| 12 | * @param output - Output container for filtered samples. | ||
| 13 | * @param input - Input container for samples to be filtered. | ||
| 14 | * @param b - Feedforward coefficients. | ||
| 15 | * @param a - Feedback coefficients. | ||
| 16 | * @param state - State to track previous samples between calls. | ||
| 17 | * @param sample_count - Number of samples to process. | ||
| 18 | */ | ||
| 19 | void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input, | ||
| 20 | std::array<s16, 3>& b_, std::array<s16, 2>& a_, | ||
| 21 | VoiceState::BiquadFilterState& state, const u32 sample_count) { | ||
| 22 | constexpr s64 min{std::numeric_limits<s32>::min()}; | ||
| 23 | constexpr s64 max{std::numeric_limits<s32>::max()}; | ||
| 24 | std::array<f64, 3> b{Common::FixedPoint<50, 14>::from_base(b_[0]).to_double(), | ||
| 25 | Common::FixedPoint<50, 14>::from_base(b_[1]).to_double(), | ||
| 26 | Common::FixedPoint<50, 14>::from_base(b_[2]).to_double()}; | ||
| 27 | std::array<f64, 2> a{Common::FixedPoint<50, 14>::from_base(a_[0]).to_double(), | ||
| 28 | Common::FixedPoint<50, 14>::from_base(a_[1]).to_double()}; | ||
| 29 | std::array<f64, 4> s{state.s0.to_double(), state.s1.to_double(), state.s2.to_double(), | ||
| 30 | state.s3.to_double()}; | ||
| 31 | |||
| 32 | for (u32 i = 0; i < sample_count; i++) { | ||
| 33 | f64 in_sample{static_cast<f64>(input[i])}; | ||
| 34 | auto sample{in_sample * b[0] + s[0] * b[1] + s[1] * b[2] + s[2] * a[0] + s[3] * a[1]}; | ||
| 35 | |||
| 36 | output[i] = static_cast<s32>(std::clamp(static_cast<s64>(sample), min, max)); | ||
| 37 | |||
| 38 | s[1] = s[0]; | ||
| 39 | s[0] = in_sample; | ||
| 40 | s[3] = s[2]; | ||
| 41 | s[2] = sample; | ||
| 42 | } | ||
| 43 | |||
| 44 | state.s0 = s[0]; | ||
| 45 | state.s1 = s[1]; | ||
| 46 | state.s2 = s[2]; | ||
| 47 | state.s3 = s[3]; | ||
| 48 | } | ||
| 49 | |||
| 50 | /** | ||
| 51 | * Biquad filter s32 implementation. | ||
| 52 | * | ||
| 53 | * @param output - Output container for filtered samples. | ||
| 54 | * @param input - Input container for samples to be filtered. | ||
| 55 | * @param b - Feedforward coefficients. | ||
| 56 | * @param a - Feedback coefficients. | ||
| 57 | * @param state - State to track previous samples between calls. | ||
| 58 | * @param sample_count - Number of samples to process. | ||
| 59 | */ | ||
| 60 | static void ApplyBiquadFilterInt(std::span<s32> output, std::span<const s32> input, | ||
| 61 | std::array<s16, 3>& b_, std::array<s16, 2>& a_, | ||
| 62 | VoiceState::BiquadFilterState& state, const u32 sample_count) { | ||
| 63 | constexpr s64 min{std::numeric_limits<s32>::min()}; | ||
| 64 | constexpr s64 max{std::numeric_limits<s32>::max()}; | ||
| 65 | std::array<Common::FixedPoint<50, 14>, 3> b{ | ||
| 66 | Common::FixedPoint<50, 14>::from_base(b_[0]), | ||
| 67 | Common::FixedPoint<50, 14>::from_base(b_[1]), | ||
| 68 | Common::FixedPoint<50, 14>::from_base(b_[2]), | ||
| 69 | }; | ||
| 70 | std::array<Common::FixedPoint<50, 14>, 3> a{ | ||
| 71 | Common::FixedPoint<50, 14>::from_base(a_[0]), | ||
| 72 | Common::FixedPoint<50, 14>::from_base(a_[1]), | ||
| 73 | }; | ||
| 74 | |||
| 75 | for (u32 i = 0; i < sample_count; i++) { | ||
| 76 | s64 in_sample{input[i]}; | ||
| 77 | auto sample{in_sample * b[0] + state.s0}; | ||
| 78 | const auto out_sample{std::clamp(sample.to_long(), min, max)}; | ||
| 79 | |||
| 80 | output[i] = static_cast<s32>(out_sample); | ||
| 81 | |||
| 82 | state.s0 = state.s1 + b[1] * in_sample + a[0] * out_sample; | ||
| 83 | state.s1 = 0 + b[2] * in_sample + a[1] * out_sample; | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | void BiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 88 | std::string& string) { | ||
| 89 | string += fmt::format( | ||
| 90 | "BiquadFilterCommand\n\tinput {:02X} output {:02X} needs_init {} use_float_processing {}\n", | ||
| 91 | input, output, needs_init, use_float_processing); | ||
| 92 | } | ||
| 93 | |||
| 94 | void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 95 | auto state_{reinterpret_cast<VoiceState::BiquadFilterState*>(state)}; | ||
| 96 | if (needs_init) { | ||
| 97 | std::memset(state_, 0, sizeof(VoiceState::BiquadFilterState)); | ||
| 98 | } | ||
| 99 | |||
| 100 | auto input_buffer{ | ||
| 101 | processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; | ||
| 102 | auto output_buffer{ | ||
| 103 | processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; | ||
| 104 | |||
| 105 | if (use_float_processing) { | ||
| 106 | ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_, | ||
| 107 | processor.sample_count); | ||
| 108 | } else { | ||
| 109 | ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_, | ||
| 110 | processor.sample_count); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | bool BiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 115 | return true; | ||
| 116 | } | ||
| 117 | |||
| 118 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | ||
| 9 | #include "audio_core/renderer/voice/voice_info.h" | ||
| 10 | #include "audio_core/renderer/voice/voice_state.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | ||
| 16 | } | ||
| 17 | |||
| 18 | /** | ||
| 19 | * AudioRenderer command for applying a biquad filter to the input mix buffer, saving the results to | ||
| 20 | * the output mix buffer. | ||
| 21 | */ | ||
| 22 | struct BiquadFilterCommand : ICommand { | ||
| 23 | /** | ||
| 24 | * Print this command's information to a string. | ||
| 25 | * | ||
| 26 | * @param processor - The CommandListProcessor processing this command. | ||
| 27 | * @param string - The string to print into. | ||
| 28 | */ | ||
| 29 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Process this command. | ||
| 33 | * | ||
| 34 | * @param processor - The CommandListProcessor processing this command. | ||
| 35 | */ | ||
| 36 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 37 | |||
| 38 | /** | ||
| 39 | * Verify this command's data is valid. | ||
| 40 | * | ||
| 41 | * @param processor - The CommandListProcessor processing this command. | ||
| 42 | * @return True if the command is valid, otherwise false. | ||
| 43 | */ | ||
| 44 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 45 | |||
| 46 | /// Input mix buffer index | ||
| 47 | s16 input; | ||
| 48 | /// Output mix buffer index | ||
| 49 | s16 output; | ||
| 50 | /// Input parameters for biquad | ||
| 51 | VoiceInfo::BiquadFilterParameter biquad; | ||
| 52 | /// Biquad state, updated each call | ||
| 53 | CpuAddr state; | ||
| 54 | /// If true, reset the state | ||
| 55 | bool needs_init; | ||
| 56 | /// If true, use float processing rather than int | ||
| 57 | bool use_float_processing; | ||
| 58 | }; | ||
| 59 | |||
| 60 | /** | ||
| 61 | * Biquad filter float implementation. | ||
| 62 | * | ||
| 63 | * @param output - Output container for filtered samples. | ||
| 64 | * @param input - Input container for samples to be filtered. | ||
| 65 | * @param b - Feedforward coefficients. | ||
| 66 | * @param a - Feedback coefficients. | ||
| 67 | * @param state - State to track previous samples. | ||
| 68 | * @param sample_count - Number of samples to process. | ||
| 69 | */ | ||
| 70 | void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input, | ||
| 71 | std::array<s16, 3>& b, std::array<s16, 2>& a, | ||
| 72 | VoiceState::BiquadFilterState& state, const u32 sample_count); | ||
| 73 | |||
| 74 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 5 | #include "audio_core/renderer/command/effect/capture.h" | ||
| 6 | #include "audio_core/renderer/effect/aux_.h" | ||
| 7 | #include "core/memory.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | /** | ||
| 11 | * Reset an AuxBuffer. | ||
| 12 | * | ||
| 13 | * @param memory - Core memory for writing. | ||
| 14 | * @param aux_info - Memory address pointing to the AuxInfo to reset. | ||
| 15 | */ | ||
| 16 | static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) { | ||
| 17 | if (aux_info == 0) { | ||
| 18 | LOG_ERROR(Service_Audio, "Aux info is 0!"); | ||
| 19 | return; | ||
| 20 | } | ||
| 21 | |||
| 22 | memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, read_offset)), 0); | ||
| 23 | memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, write_offset)), 0); | ||
| 24 | memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, total_sample_count)), 0); | ||
| 25 | } | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Write the given input mix buffer to the memory at send_buffer, and update send_info_ if | ||
| 29 | * update_count is set, to notify the game that an update happened. | ||
| 30 | * | ||
| 31 | * @param memory - Core memory for writing. | ||
| 32 | * @param send_info_ - Header information for where to write the mix buffer. | ||
| 33 | * @param send_buffer - Memory address to write the mix buffer to. | ||
| 34 | * @param count_max - Maximum number of samples in the receiving buffer. | ||
| 35 | * @param input - Input mix buffer to write. | ||
| 36 | * @param write_count_ - Number of samples to write. | ||
| 37 | * @param write_offset - Current offset to begin writing the receiving buffer at. | ||
| 38 | * @param update_count - If non-zero, send_info_ will be updated. | ||
| 39 | * @return Number of samples written. | ||
| 40 | */ | ||
| 41 | static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_, | ||
| 42 | const CpuAddr send_buffer, u32 count_max, std::span<const s32> input, | ||
| 43 | const u32 write_count_, const u32 write_offset, | ||
| 44 | const u32 update_count) { | ||
| 45 | if (write_count_ > count_max) { | ||
| 46 | LOG_ERROR(Service_Audio, | ||
| 47 | "write_count must be smaller than count_max! write_count {}, count_max {}", | ||
| 48 | write_count_, count_max); | ||
| 49 | return 0; | ||
| 50 | } | ||
| 51 | |||
| 52 | if (send_info_ == 0) { | ||
| 53 | LOG_ERROR(Service_Audio, "send_info is 0!"); | ||
| 54 | return 0; | ||
| 55 | } | ||
| 56 | |||
| 57 | if (input.empty()) { | ||
| 58 | LOG_ERROR(Service_Audio, "input buffer is empty!"); | ||
| 59 | return 0; | ||
| 60 | } | ||
| 61 | |||
| 62 | if (send_buffer == 0) { | ||
| 63 | LOG_ERROR(Service_Audio, "send_buffer is 0!"); | ||
| 64 | return 0; | ||
| 65 | } | ||
| 66 | |||
| 67 | if (count_max == 0) { | ||
| 68 | return 0; | ||
| 69 | } | ||
| 70 | |||
| 71 | AuxInfo::AuxBufferInfo send_info{}; | ||
| 72 | memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo)); | ||
| 73 | |||
| 74 | u32 target_write_offset{send_info.dsp_info.write_offset + write_offset}; | ||
| 75 | if (target_write_offset > count_max || write_count_ == 0) { | ||
| 76 | return 0; | ||
| 77 | } | ||
| 78 | |||
| 79 | u32 write_count{write_count_}; | ||
| 80 | u32 write_pos{0}; | ||
| 81 | while (write_count > 0) { | ||
| 82 | u32 to_write{std::min(count_max - target_write_offset, write_count)}; | ||
| 83 | |||
| 84 | if (to_write > 0) { | ||
| 85 | memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32), | ||
| 86 | &input[write_pos], to_write * sizeof(s32)); | ||
| 87 | } | ||
| 88 | |||
| 89 | target_write_offset = (target_write_offset + to_write) % count_max; | ||
| 90 | write_count -= to_write; | ||
| 91 | write_pos += to_write; | ||
| 92 | } | ||
| 93 | |||
| 94 | if (update_count) { | ||
| 95 | const auto count_diff{send_info.dsp_info.total_sample_count - | ||
| 96 | send_info.cpu_info.total_sample_count}; | ||
| 97 | if (count_diff >= count_max) { | ||
| 98 | auto dsp_lost_count{send_info.dsp_info.lost_sample_count + update_count}; | ||
| 99 | if (dsp_lost_count - send_info.cpu_info.lost_sample_count < | ||
| 100 | send_info.dsp_info.lost_sample_count - send_info.cpu_info.lost_sample_count) { | ||
| 101 | dsp_lost_count = send_info.cpu_info.lost_sample_count - 1; | ||
| 102 | } | ||
| 103 | send_info.dsp_info.lost_sample_count = dsp_lost_count; | ||
| 104 | } | ||
| 105 | |||
| 106 | send_info.dsp_info.write_offset = | ||
| 107 | (send_info.dsp_info.write_offset + update_count + count_max) % count_max; | ||
| 108 | |||
| 109 | auto new_sample_count{send_info.dsp_info.total_sample_count + update_count}; | ||
| 110 | if (new_sample_count - send_info.cpu_info.total_sample_count < count_diff) { | ||
| 111 | new_sample_count = send_info.cpu_info.total_sample_count - 1; | ||
| 112 | } | ||
| 113 | send_info.dsp_info.total_sample_count = new_sample_count; | ||
| 114 | } | ||
| 115 | |||
| 116 | memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo)); | ||
| 117 | |||
| 118 | return write_count_; | ||
| 119 | } | ||
| 120 | |||
| 121 | void CaptureCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 122 | std::string& string) { | ||
| 123 | string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled, | ||
| 124 | input, output); | ||
| 125 | } | ||
| 126 | |||
| 127 | void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 128 | if (effect_enabled) { | ||
| 129 | auto input_buffer{ | ||
| 130 | processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; | ||
| 131 | WriteAuxBufferDsp(*processor.memory, send_buffer_info, send_buffer, count_max, input_buffer, | ||
| 132 | processor.sample_count, write_offset, update_count); | ||
| 133 | } else { | ||
| 134 | ResetAuxBufferDsp(*processor.memory, send_buffer_info); | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | bool CaptureCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 139 | return true; | ||
| 140 | } | ||
| 141 | |||
| 142 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | ||
| 14 | } | ||
| 15 | |||
| 16 | /** | ||
| 17 | * AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory | ||
| 18 | * address. | ||
| 19 | */ | ||
| 20 | struct CaptureCommand : ICommand { | ||
| 21 | /** | ||
| 22 | * Print this command's information to a string. | ||
| 23 | * | ||
| 24 | * @param processor - The CommandListProcessor processing this command. | ||
| 25 | * @param string - The string to print into. | ||
| 26 | */ | ||
| 27 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Process this command. | ||
| 31 | * | ||
| 32 | * @param processor - The CommandListProcessor processing this command. | ||
| 33 | */ | ||
| 34 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Verify this command's data is valid. | ||
| 38 | * | ||
| 39 | * @param processor - The CommandListProcessor processing this command. | ||
| 40 | * @return True if the command is valid, otherwise false. | ||
| 41 | */ | ||
| 42 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 43 | |||
| 44 | /// Input mix buffer index | ||
| 45 | s16 input; | ||
| 46 | /// Output mix buffer index | ||
| 47 | s16 output; | ||
| 48 | /// Meta info for writing | ||
| 49 | CpuAddr send_buffer_info; | ||
| 50 | /// Game memory write buffer | ||
| 51 | CpuAddr send_buffer; | ||
| 52 | /// Max samples to read/write | ||
| 53 | u32 count_max; | ||
| 54 | /// Current read/write offset | ||
| 55 | u32 write_offset; | ||
| 56 | /// Number of samples to update per call | ||
| 57 | u32 update_count; | ||
| 58 | /// is this effect enabled? | ||
| 59 | bool effect_enabled; | ||
| 60 | }; | ||
| 61 | |||
| 62 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <cmath> | ||
| 5 | #include <span> | ||
| 6 | #include <vector> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 9 | #include "audio_core/renderer/command/effect/compressor.h" | ||
| 10 | #include "audio_core/renderer/effect/compressor.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | |||
| 14 | static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params, | ||
| 15 | CompressorInfo::State& state) { | ||
| 16 | const auto ratio{1.0f / params.compressor_ratio}; | ||
| 17 | auto makeup_gain{0.0f}; | ||
| 18 | if (params.makeup_gain_enabled) { | ||
| 19 | makeup_gain = (params.threshold * 0.5f) * (ratio - 1.0f) - 3.0f; | ||
| 20 | } | ||
| 21 | state.makeup_gain = makeup_gain; | ||
| 22 | state.unk_18 = params.unk_28; | ||
| 23 | |||
| 24 | const auto a{(params.out_gain + makeup_gain) / 20.0f * 3.3219f}; | ||
| 25 | const auto b{(a - std::trunc(a)) * 0.69315f}; | ||
| 26 | const auto c{std::pow(2.0f, b)}; | ||
| 27 | |||
| 28 | state.unk_0C = (1.0f - ratio) / 6.0f; | ||
| 29 | state.unk_14 = params.threshold + 1.5f; | ||
| 30 | state.unk_10 = params.threshold - 1.5f; | ||
| 31 | state.unk_20 = c; | ||
| 32 | } | ||
| 33 | |||
| 34 | static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params, | ||
| 35 | CompressorInfo::State& state) { | ||
| 36 | std::memset(&state, 0, sizeof(CompressorInfo::State)); | ||
| 37 | |||
| 38 | state.unk_00 = 0; | ||
| 39 | state.unk_04 = 1.0f; | ||
| 40 | state.unk_08 = 1.0f; | ||
| 41 | |||
| 42 | SetCompressorEffectParameter(params, state); | ||
| 43 | } | ||
| 44 | |||
| 45 | static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params, | ||
| 46 | CompressorInfo::State& state, bool enabled, | ||
| 47 | std::vector<std::span<const s32>> input_buffers, | ||
| 48 | std::vector<std::span<s32>> output_buffers, u32 sample_count) { | ||
| 49 | if (enabled) { | ||
| 50 | auto state_00{state.unk_00}; | ||
| 51 | auto state_04{state.unk_04}; | ||
| 52 | auto state_08{state.unk_08}; | ||
| 53 | auto state_18{state.unk_18}; | ||
| 54 | |||
| 55 | for (u32 i = 0; i < sample_count; i++) { | ||
| 56 | auto a{0.0f}; | ||
| 57 | for (s16 channel = 0; channel < params.channel_count; channel++) { | ||
| 58 | const auto input_sample{Common::FixedPoint<49, 15>(input_buffers[channel][i])}; | ||
| 59 | a += (input_sample * input_sample).to_float(); | ||
| 60 | } | ||
| 61 | |||
| 62 | state_00 += params.unk_24 * ((a / params.channel_count) - state.unk_00); | ||
| 63 | |||
| 64 | auto b{-100.0f}; | ||
| 65 | auto c{0.0f}; | ||
| 66 | if (state_00 >= 1.0e-10) { | ||
| 67 | b = std::log10(state_00) * 10.0f; | ||
| 68 | c = 1.0f; | ||
| 69 | } | ||
| 70 | |||
| 71 | if (b >= state.unk_10) { | ||
| 72 | const auto d{b >= state.unk_14 | ||
| 73 | ? ((1.0f / params.compressor_ratio) - 1.0f) * | ||
| 74 | (b - params.threshold) | ||
| 75 | : (b - state.unk_10) * (b - state.unk_10) * -state.unk_0C}; | ||
| 76 | const auto e{d / 20.0f * 3.3219f}; | ||
| 77 | const auto f{(e - std::trunc(e)) * 0.69315f}; | ||
| 78 | c = std::pow(2.0f, f); | ||
| 79 | } | ||
| 80 | |||
| 81 | state_18 = params.unk_28; | ||
| 82 | auto tmp{c}; | ||
| 83 | if ((state_04 - c) <= 0.08f) { | ||
| 84 | state_18 = params.unk_2C; | ||
| 85 | if (((state_04 - c) >= -0.08f) && (std::abs(state_08 - c) >= 0.001f)) { | ||
| 86 | tmp = state_04; | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | state_04 = tmp; | ||
| 91 | state_08 += (c - state_08) * state_18; | ||
| 92 | |||
| 93 | for (s16 channel = 0; channel < params.channel_count; channel++) { | ||
| 94 | output_buffers[channel][i] = static_cast<s32>( | ||
| 95 | static_cast<f32>(input_buffers[channel][i]) * state_08 * state.unk_20); | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | state.unk_00 = state_00; | ||
| 100 | state.unk_04 = state_04; | ||
| 101 | state.unk_08 = state_08; | ||
| 102 | state.unk_18 = state_18; | ||
| 103 | } else { | ||
| 104 | for (s16 channel = 0; channel < params.channel_count; channel++) { | ||
| 105 | if (params.inputs[channel] != params.outputs[channel]) { | ||
| 106 | std::memcpy((char*)output_buffers[channel].data(), | ||
| 107 | (char*)input_buffers[channel].data(), | ||
| 108 | output_buffers[channel].size_bytes()); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 115 | std::string& string) { | ||
| 116 | string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled); | ||
| 117 | for (s16 i = 0; i < parameter.channel_count; i++) { | ||
| 118 | string += fmt::format("{:02X}, ", inputs[i]); | ||
| 119 | } | ||
| 120 | string += "\n\toutputs: "; | ||
| 121 | for (s16 i = 0; i < parameter.channel_count; i++) { | ||
| 122 | string += fmt::format("{:02X}, ", outputs[i]); | ||
| 123 | } | ||
| 124 | string += "\n"; | ||
| 125 | } | ||
| 126 | |||
| 127 | void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 128 | std::vector<std::span<const s32>> input_buffers(parameter.channel_count); | ||
| 129 | std::vector<std::span<s32>> output_buffers(parameter.channel_count); | ||
| 130 | |||
| 131 | for (s16 i = 0; i < parameter.channel_count; i++) { | ||
| 132 | input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, | ||
| 133 | processor.sample_count); | ||
| 134 | output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, | ||
| 135 | processor.sample_count); | ||
| 136 | } | ||
| 137 | |||
| 138 | auto state_{reinterpret_cast<CompressorInfo::State*>(state)}; | ||
| 139 | |||
| 140 | if (effect_enabled) { | ||
| 141 | if (parameter.state == CompressorInfo::ParameterState::Updating) { | ||
| 142 | SetCompressorEffectParameter(parameter, *state_); | ||
| 143 | } else if (parameter.state == CompressorInfo::ParameterState::Initialized) { | ||
| 144 | InitializeCompressorEffect(parameter, *state_); | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | ApplyCompressorEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, | ||
| 149 | processor.sample_count); | ||
| 150 | } | ||
| 151 | |||
| 152 | bool CompressorCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 153 | return true; | ||
| 154 | } | ||
| 155 | |||
| 156 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <string> | ||
| 8 | |||
| 9 | #include "audio_core/renderer/command/icommand.h" | ||
| 10 | #include "audio_core/renderer/effect/compressor.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | ||
| 16 | } | ||
| 17 | |||
| 18 | /** | ||
| 19 | * AudioRenderer command for limiting volume between a high and low threshold. | ||
| 20 | * Version 1. | ||
| 21 | */ | ||
| 22 | struct CompressorCommand : ICommand { | ||
| 23 | /** | ||
| 24 | * Print this command's information to a string. | ||
| 25 | * | ||
| 26 | * @param processor - The CommandListProcessor processing this command. | ||
| 27 | * @param string - The string to print into. | ||
| 28 | */ | ||
| 29 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Process this command. | ||
| 33 | * | ||
| 34 | * @param processor - The CommandListProcessor processing this command. | ||
| 35 | */ | ||
| 36 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 37 | |||
| 38 | /** | ||
| 39 | * Verify this command's data is valid. | ||
| 40 | * | ||
| 41 | * @param processor - The CommandListProcessor processing this command. | ||
| 42 | * @return True if the command is valid, otherwise false. | ||
| 43 | */ | ||
| 44 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 45 | |||
| 46 | /// Input mix buffer offsets for each channel | ||
| 47 | std::array<s16, MaxChannels> inputs; | ||
| 48 | /// Output mix buffer offsets for each channel | ||
| 49 | std::array<s16, MaxChannels> outputs; | ||
| 50 | /// Input parameters | ||
| 51 | CompressorInfo::ParameterVersion2 parameter; | ||
| 52 | /// State, updated each call | ||
| 53 | CpuAddr state; | ||
| 54 | /// Game-supplied workbuffer (Unused) | ||
| 55 | CpuAddr workbuffer; | ||
| 56 | /// Is this effect enabled? | ||
| 57 | bool effect_enabled; | ||
| 58 | }; | ||
| 59 | |||
| 60 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 5 | #include "audio_core/renderer/command/effect/delay.h" | ||
| 6 | |||
| 7 | namespace AudioCore::AudioRenderer { | ||
| 8 | /** | ||
| 9 | * Update the DelayInfo state according to the given parameters. | ||
| 10 | * | ||
| 11 | * @param params - Input parameters to update the state. | ||
| 12 | * @param state - State to be updated. | ||
| 13 | */ | ||
| 14 | static void SetDelayEffectParameter(const DelayInfo::ParameterVersion1& params, | ||
| 15 | DelayInfo::State& state) { | ||
| 16 | auto channel_spread{params.channel_spread}; | ||
| 17 | state.feedback_gain = params.feedback_gain * 0.97998046875f; | ||
| 18 | state.delay_feedback_gain = state.feedback_gain * (1.0f - channel_spread); | ||
| 19 | if (params.channel_count == 4 || params.channel_count == 6) { | ||
| 20 | channel_spread >>= 1; | ||
| 21 | } | ||
| 22 | state.delay_feedback_cross_gain = channel_spread * state.feedback_gain; | ||
| 23 | state.lowpass_feedback_gain = params.lowpass_amount * 0.949951171875f; | ||
| 24 | state.lowpass_gain = 1.0f - state.lowpass_feedback_gain; | ||
| 25 | } | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Initialize a new DelayInfo state according to the given parameters. | ||
| 29 | * | ||
| 30 | * @param params - Input parameters to update the state. | ||
| 31 | * @param state - State to be updated. | ||
| 32 | * @param workbuffer - Game-supplied memory for the state. (Unused) | ||
| 33 | */ | ||
| 34 | static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params, | ||
| 35 | DelayInfo::State& state, | ||
| 36 | [[maybe_unused]] const CpuAddr workbuffer) { | ||
| 37 | state = {}; | ||
| 38 | |||
| 39 | for (u32 channel = 0; channel < params.channel_count; channel++) { | ||
| 40 | Common::FixedPoint<32, 32> sample_count_max{0.064f}; | ||
| 41 | sample_count_max *= params.sample_rate.to_int_floor() * params.delay_time_max; | ||
| 42 | |||
| 43 | Common::FixedPoint<18, 14> delay_time{params.delay_time}; | ||
| 44 | delay_time *= params.sample_rate / 1000; | ||
| 45 | Common::FixedPoint<32, 32> sample_count{delay_time}; | ||
| 46 | |||
| 47 | if (sample_count > sample_count_max) { | ||
| 48 | sample_count = sample_count_max; | ||
| 49 | } | ||
| 50 | |||
| 51 | state.delay_lines[channel].sample_count_max = sample_count_max.to_int_floor(); | ||
| 52 | state.delay_lines[channel].sample_count = sample_count.to_int_floor(); | ||
| 53 | state.delay_lines[channel].buffer.resize(state.delay_lines[channel].sample_count, 0); | ||
| 54 | if (state.delay_lines[channel].buffer.size() == 0) { | ||
| 55 | state.delay_lines[channel].buffer.push_back(0); | ||
| 56 | } | ||
| 57 | state.delay_lines[channel].buffer_pos = 0; | ||
| 58 | state.delay_lines[channel].decay_rate = 1.0f; | ||
| 59 | } | ||
| 60 | |||
| 61 | SetDelayEffectParameter(params, state); | ||
| 62 | } | ||
| 63 | |||
| 64 | /** | ||
| 65 | * Delay effect impl, according to the parameters and current state, on the input mix buffers, | ||
| 66 | * saving the results to the output mix buffers. | ||
| 67 | * | ||
| 68 | * @tparam NumChannels - Number of channels to process. 1-6. | ||
| 69 | * @param params - Input parameters to use. | ||
| 70 | * @param state - State to use, must be initialized (see InitializeDelayEffect). | ||
| 71 | * @param inputs - Input mix buffers to performan the delay on. | ||
| 72 | * @param outputs - Output mix buffers to receive the delayed samples. | ||
| 73 | * @param sample_count - Number of samples to process. | ||
| 74 | */ | ||
| 75 | template <size_t NumChannels> | ||
| 76 | static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state, | ||
| 77 | std::vector<std::span<const s32>>& inputs, | ||
| 78 | std::vector<std::span<s32>>& outputs, const u32 sample_count) { | ||
| 79 | for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { | ||
| 80 | std::array<Common::FixedPoint<50, 14>, NumChannels> input_samples{}; | ||
| 81 | for (u32 channel = 0; channel < NumChannels; channel++) { | ||
| 82 | input_samples[channel] = inputs[channel][sample_index] * 64; | ||
| 83 | } | ||
| 84 | |||
| 85 | std::array<Common::FixedPoint<50, 14>, NumChannels> delay_samples{}; | ||
| 86 | for (u32 channel = 0; channel < NumChannels; channel++) { | ||
| 87 | delay_samples[channel] = state.delay_lines[channel].Read(); | ||
| 88 | } | ||
| 89 | |||
| 90 | // clang-format off | ||
| 91 | std::array<std::array<Common::FixedPoint<18, 14>, NumChannels>, NumChannels> matrix{}; | ||
| 92 | if constexpr (NumChannels == 1) { | ||
| 93 | matrix = {{ | ||
| 94 | {state.feedback_gain}, | ||
| 95 | }}; | ||
| 96 | } else if constexpr (NumChannels == 2) { | ||
| 97 | matrix = {{ | ||
| 98 | {state.delay_feedback_gain, state.delay_feedback_cross_gain}, | ||
| 99 | {state.delay_feedback_cross_gain, state.delay_feedback_gain}, | ||
| 100 | }}; | ||
| 101 | } else if constexpr (NumChannels == 4) { | ||
| 102 | matrix = {{ | ||
| 103 | {state.delay_feedback_gain, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, 0.0f}, | ||
| 104 | {state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain}, | ||
| 105 | {state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain}, | ||
| 106 | {0.0f, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain}, | ||
| 107 | }}; | ||
| 108 | } else if constexpr (NumChannels == 6) { | ||
| 109 | matrix = {{ | ||
| 110 | {state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f}, | ||
| 111 | {0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain}, | ||
| 112 | {state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, 0.0f, 0.0f}, | ||
| 113 | {0.0f, 0.0f, 0.0f, params.feedback_gain, 0.0f, 0.0f}, | ||
| 114 | {state.delay_feedback_cross_gain, 0.0f, 0.0f, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain}, | ||
| 115 | {0.0f, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain, state.delay_feedback_gain}, | ||
| 116 | }}; | ||
| 117 | } | ||
| 118 | // clang-format on | ||
| 119 | |||
| 120 | std::array<Common::FixedPoint<50, 14>, NumChannels> gained_samples{}; | ||
| 121 | for (u32 channel = 0; channel < NumChannels; channel++) { | ||
| 122 | Common::FixedPoint<50, 14> delay{}; | ||
| 123 | for (u32 j = 0; j < NumChannels; j++) { | ||
| 124 | delay += delay_samples[j] * matrix[j][channel]; | ||
| 125 | } | ||
| 126 | gained_samples[channel] = input_samples[channel] * params.in_gain + delay; | ||
| 127 | } | ||
| 128 | |||
| 129 | for (u32 channel = 0; channel < NumChannels; channel++) { | ||
| 130 | state.lowpass_z[channel] = gained_samples[channel] * state.lowpass_gain + | ||
| 131 | state.lowpass_z[channel] * state.lowpass_feedback_gain; | ||
| 132 | state.delay_lines[channel].Write(state.lowpass_z[channel]); | ||
| 133 | } | ||
| 134 | |||
| 135 | for (u32 channel = 0; channel < NumChannels; channel++) { | ||
| 136 | outputs[channel][sample_index] = (input_samples[channel] * params.dry_gain + | ||
| 137 | delay_samples[channel] * params.wet_gain) | ||
| 138 | .to_int_floor() / | ||
| 139 | 64; | ||
| 140 | } | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | /** | ||
| 145 | * Apply a delay effect if enabled, according to the parameters and current state, on the input mix | ||
| 146 | * buffers, saving the results to the output mix buffers. | ||
| 147 | * | ||
| 148 | * @param params - Input parameters to use. | ||
| 149 | * @param state - State to use, must be initialized (see InitializeDelayEffect). | ||
| 150 | * @param enabled - If enabled, delay will be applied, otherwise input is copied to output. | ||
| 151 | * @param inputs - Input mix buffers to performan the delay on. | ||
| 152 | * @param outputs - Output mix buffers to receive the delayed samples. | ||
| 153 | * @param sample_count - Number of samples to process. | ||
| 154 | */ | ||
| 155 | static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state, | ||
| 156 | const bool enabled, std::vector<std::span<const s32>>& inputs, | ||
| 157 | std::vector<std::span<s32>>& outputs, const u32 sample_count) { | ||
| 158 | |||
| 159 | if (!IsChannelCountValid(params.channel_count)) { | ||
| 160 | LOG_ERROR(Service_Audio, "Invalid delay channels {}", params.channel_count); | ||
| 161 | return; | ||
| 162 | } | ||
| 163 | |||
| 164 | if (enabled) { | ||
| 165 | switch (params.channel_count) { | ||
| 166 | case 1: | ||
| 167 | ApplyDelay<1>(params, state, inputs, outputs, sample_count); | ||
| 168 | break; | ||
| 169 | case 2: | ||
| 170 | ApplyDelay<2>(params, state, inputs, outputs, sample_count); | ||
| 171 | break; | ||
| 172 | case 4: | ||
| 173 | ApplyDelay<4>(params, state, inputs, outputs, sample_count); | ||
| 174 | break; | ||
| 175 | case 6: | ||
| 176 | ApplyDelay<6>(params, state, inputs, outputs, sample_count); | ||
| 177 | break; | ||
| 178 | default: | ||
| 179 | for (u32 channel = 0; channel < params.channel_count; channel++) { | ||
| 180 | if (inputs[channel].data() != outputs[channel].data()) { | ||
| 181 | std::memcpy(outputs[channel].data(), inputs[channel].data(), | ||
| 182 | sample_count * sizeof(s32)); | ||
| 183 | } | ||
| 184 | } | ||
| 185 | break; | ||
| 186 | } | ||
| 187 | } else { | ||
| 188 | for (u32 channel = 0; channel < params.channel_count; channel++) { | ||
| 189 | if (inputs[channel].data() != outputs[channel].data()) { | ||
| 190 | std::memcpy(outputs[channel].data(), inputs[channel].data(), | ||
| 191 | sample_count * sizeof(s32)); | ||
| 192 | } | ||
| 193 | } | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 198 | std::string& string) { | ||
| 199 | string += fmt::format("DelayCommand\n\tenabled {} \n\tinputs: ", effect_enabled); | ||
| 200 | for (u32 i = 0; i < MaxChannels; i++) { | ||
| 201 | string += fmt::format("{:02X}, ", inputs[i]); | ||
| 202 | } | ||
| 203 | string += "\n\toutputs: "; | ||
| 204 | for (u32 i = 0; i < MaxChannels; i++) { | ||
| 205 | string += fmt::format("{:02X}, ", outputs[i]); | ||
| 206 | } | ||
| 207 | string += "\n"; | ||
| 208 | } | ||
| 209 | |||
| 210 | void DelayCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 211 | std::vector<std::span<const s32>> input_buffers(parameter.channel_count); | ||
| 212 | std::vector<std::span<s32>> output_buffers(parameter.channel_count); | ||
| 213 | |||
| 214 | for (s16 i = 0; i < parameter.channel_count; i++) { | ||
| 215 | input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, | ||
| 216 | processor.sample_count); | ||
| 217 | output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, | ||
| 218 | processor.sample_count); | ||
| 219 | } | ||
| 220 | |||
| 221 | auto state_{reinterpret_cast<DelayInfo::State*>(state)}; | ||
| 222 | |||
| 223 | if (effect_enabled) { | ||
| 224 | if (parameter.state == DelayInfo::ParameterState::Updating) { | ||
| 225 | SetDelayEffectParameter(parameter, *state_); | ||
| 226 | } else if (parameter.state == DelayInfo::ParameterState::Initialized) { | ||
| 227 | InitializeDelayEffect(parameter, *state_, workbuffer); | ||
| 228 | } | ||
| 229 | } | ||
| 230 | ApplyDelayEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, | ||
| 231 | processor.sample_count); | ||
| 232 | } | ||
| 233 | |||
| 234 | bool DelayCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 235 | return true; | ||
| 236 | } | ||
| 237 | |||
| 238 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <string> | ||
| 8 | |||
| 9 | #include "audio_core/renderer/command/icommand.h" | ||
| 10 | #include "audio_core/renderer/effect/delay.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | ||
| 16 | } | ||
| 17 | |||
| 18 | /** | ||
| 19 | * AudioRenderer command for a delay effect. Delays inputs mix buffers according to the parameters | ||
| 20 | * and state, outputs receives the delayed samples. | ||
| 21 | */ | ||
| 22 | struct DelayCommand : ICommand { | ||
| 23 | /** | ||
| 24 | * Print this command's information to a string. | ||
| 25 | * | ||
| 26 | * @param processor - The CommandListProcessor processing this command. | ||
| 27 | * @param string - The string to print into. | ||
| 28 | */ | ||
| 29 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Process this command. | ||
| 33 | * | ||
| 34 | * @param processor - The CommandListProcessor processing this command. | ||
| 35 | */ | ||
| 36 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 37 | |||
| 38 | /** | ||
| 39 | * Verify this command's data is valid. | ||
| 40 | * | ||
| 41 | * @param processor - The CommandListProcessor processing this command. | ||
| 42 | * @return True if the command is valid, otherwise false. | ||
| 43 | */ | ||
| 44 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 45 | |||
| 46 | /// Input mix buffer offsets for each channel | ||
| 47 | std::array<s16, MaxChannels> inputs; | ||
| 48 | /// Output mix buffer offsets for each channel | ||
| 49 | std::array<s16, MaxChannels> outputs; | ||
| 50 | /// Input parameters | ||
| 51 | DelayInfo::ParameterVersion1 parameter; | ||
| 52 | /// State, updated each call | ||
| 53 | CpuAddr state; | ||
| 54 | /// Game-supplied workbuffer (Unused) | ||
| 55 | CpuAddr workbuffer; | ||
| 56 | /// Is this effect enabled? | ||
| 57 | bool effect_enabled; | ||
| 58 | }; | ||
| 59 | |||
| 60 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <numbers> | ||
| 5 | |||
| 6 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 7 | #include "audio_core/renderer/command/effect/i3dl2_reverb.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MinDelayLineTimes{ | ||
| 12 | 5.0f, | ||
| 13 | 6.0f, | ||
| 14 | 13.0f, | ||
| 15 | 14.0f, | ||
| 16 | }; | ||
| 17 | constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MaxDelayLineTimes{ | ||
| 18 | 45.7042007446f, | ||
| 19 | 82.7817001343f, | ||
| 20 | 149.938293457f, | ||
| 21 | 271.575805664f, | ||
| 22 | }; | ||
| 23 | constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay0MaxDelayLineTimes{17.0f, 13.0f, | ||
| 24 | 9.0f, 7.0f}; | ||
| 25 | constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay1MaxDelayLineTimes{19.0f, 11.0f, | ||
| 26 | 10.0f, 6.0f}; | ||
| 27 | constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyTapTimes{ | ||
| 28 | 0.0171360000968f, | ||
| 29 | 0.0591540001333f, | ||
| 30 | 0.161733001471f, | ||
| 31 | 0.390186011791f, | ||
| 32 | 0.425262004137f, | ||
| 33 | 0.455410987139f, | ||
| 34 | 0.689737021923f, | ||
| 35 | 0.74590998888f, | ||
| 36 | 0.833844006062f, | ||
| 37 | 0.859502017498f, | ||
| 38 | 0.0f, | ||
| 39 | 0.0750240013003f, | ||
| 40 | 0.168788000941f, | ||
| 41 | 0.299901008606f, | ||
| 42 | 0.337442994118f, | ||
| 43 | 0.371903002262f, | ||
| 44 | 0.599011003971f, | ||
| 45 | 0.716741025448f, | ||
| 46 | 0.817858994007f, | ||
| 47 | 0.85166400671f, | ||
| 48 | }; | ||
| 49 | |||
| 50 | constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyGains{ | ||
| 51 | 0.67096f, 0.61027f, 1.0f, 0.3568f, 0.68361f, 0.65978f, 0.51939f, | ||
| 52 | 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.3827f, | ||
| 53 | 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f}; | ||
| 54 | |||
| 55 | /** | ||
| 56 | * Update the I3dl2ReverbInfo state according to the given parameters. | ||
| 57 | * | ||
| 58 | * @param params - Input parameters to update the state. | ||
| 59 | * @param state - State to be updated. | ||
| 60 | * @param reset - If enabled, the state buffers will be reset. Only set this on initialize. | ||
| 61 | */ | ||
| 62 | static void UpdateI3dl2ReverbEffectParameter(const I3dl2ReverbInfo::ParameterVersion1& params, | ||
| 63 | I3dl2ReverbInfo::State& state, const bool reset) { | ||
| 64 | const auto pow_10 = [](f32 val) -> f32 { | ||
| 65 | return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val); | ||
| 66 | }; | ||
| 67 | const auto sin = [](f32 degrees) -> f32 { | ||
| 68 | return std::sin(degrees * std::numbers::pi_v<f32> / 180.0f); | ||
| 69 | }; | ||
| 70 | const auto cos = [](f32 degrees) -> f32 { | ||
| 71 | return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f); | ||
| 72 | }; | ||
| 73 | |||
| 74 | Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000.0f}; | ||
| 75 | |||
| 76 | state.dry_gain = params.dry_gain; | ||
| 77 | Common::FixedPoint<50, 14> early_gain{ | ||
| 78 | std::min(params.room_gain + params.reflection_gain, 5000.0f) / 2000.0f}; | ||
| 79 | state.early_gain = pow_10(early_gain.to_float()); | ||
| 80 | Common::FixedPoint<50, 14> late_gain{std::min(params.room_gain + params.reverb_gain, 5000.0f) / | ||
| 81 | 2000.0f}; | ||
| 82 | state.late_gain = pow_10(late_gain.to_float()); | ||
| 83 | |||
| 84 | Common::FixedPoint<50, 14> hf_gain{pow_10(params.room_HF_gain / 2000.0f)}; | ||
| 85 | if (hf_gain >= 1.0f) { | ||
| 86 | state.lowpass_1 = 0.0f; | ||
| 87 | state.lowpass_2 = 1.0f; | ||
| 88 | } else { | ||
| 89 | const auto reference_hf{(params.reference_HF * 256.0f) / | ||
| 90 | static_cast<f32>(params.sample_rate)}; | ||
| 91 | const Common::FixedPoint<50, 14> a{1.0f - hf_gain.to_float()}; | ||
| 92 | const Common::FixedPoint<50, 14> b{2.0f + (-cos(reference_hf) * (hf_gain * 2.0f))}; | ||
| 93 | const Common::FixedPoint<50, 14> c{ | ||
| 94 | std::sqrt(std::pow(b.to_float(), 2.0f) + (std::pow(a.to_float(), 2.0f) * -4.0f))}; | ||
| 95 | |||
| 96 | state.lowpass_1 = std::min(((b - c) / (a * 2.0f)).to_float(), 0.99723f); | ||
| 97 | state.lowpass_2 = 1.0f - state.lowpass_1; | ||
| 98 | } | ||
| 99 | |||
| 100 | state.early_to_late_taps = | ||
| 101 | (((params.reflection_delay + params.late_reverb_delay_time) * 1000.0f) * delay).to_int(); | ||
| 102 | state.last_reverb_echo = params.late_reverb_diffusion * 0.6f * 0.01f; | ||
| 103 | |||
| 104 | for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) { | ||
| 105 | auto curr_delay{ | ||
| 106 | ((MinDelayLineTimes[i] + (params.late_reverb_density / 100.0f) * | ||
| 107 | (MaxDelayLineTimes[i] - MinDelayLineTimes[i])) * | ||
| 108 | delay) | ||
| 109 | .to_int()}; | ||
| 110 | state.fdn_delay_lines[i].SetDelay(curr_delay); | ||
| 111 | |||
| 112 | const auto a{ | ||
| 113 | (static_cast<f32>(state.fdn_delay_lines[i].delay + state.decay_delay_lines0[i].delay + | ||
| 114 | state.decay_delay_lines1[i].delay) * | ||
| 115 | -60.0f) / | ||
| 116 | (params.late_reverb_decay_time * static_cast<f32>(params.sample_rate))}; | ||
| 117 | const auto b{a / params.late_reverb_HF_decay_ratio}; | ||
| 118 | const auto c{ | ||
| 119 | cos(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate)) / | ||
| 120 | sin(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate))}; | ||
| 121 | const auto d{pow_10((b - a) / 40.0f)}; | ||
| 122 | const auto e{pow_10((b + a) / 40.0f) * 0.7071f}; | ||
| 123 | |||
| 124 | state.lowpass_coeff[i][0] = ((c * d + 1.0f) * e) / (c + d); | ||
| 125 | state.lowpass_coeff[i][1] = ((1.0f - (c * d)) * e) / (c + d); | ||
| 126 | state.lowpass_coeff[i][2] = (c - d) / (c + d); | ||
| 127 | |||
| 128 | state.decay_delay_lines0[i].wet_gain = state.last_reverb_echo; | ||
| 129 | state.decay_delay_lines1[i].wet_gain = state.last_reverb_echo * -0.9f; | ||
| 130 | } | ||
| 131 | |||
| 132 | if (reset) { | ||
| 133 | state.shelf_filter.fill(0.0f); | ||
| 134 | state.lowpass_0 = 0.0f; | ||
| 135 | for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) { | ||
| 136 | std::ranges::fill(state.fdn_delay_lines[i].buffer, 0); | ||
| 137 | std::ranges::fill(state.decay_delay_lines0[i].buffer, 0); | ||
| 138 | std::ranges::fill(state.decay_delay_lines1[i].buffer, 0); | ||
| 139 | } | ||
| 140 | std::ranges::fill(state.center_delay_line.buffer, 0); | ||
| 141 | std::ranges::fill(state.early_delay_line.buffer, 0); | ||
| 142 | } | ||
| 143 | |||
| 144 | const auto reflection_time{(params.late_reverb_delay_time * 0.9998f + 0.02f) * 1000.0f}; | ||
| 145 | const auto reflection_delay{params.reflection_delay * 1000.0f}; | ||
| 146 | for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayTaps; i++) { | ||
| 147 | auto length{((reflection_delay + reflection_time * EarlyTapTimes[i]) * delay).to_int()}; | ||
| 148 | if (length >= state.early_delay_line.max_delay) { | ||
| 149 | length = state.early_delay_line.max_delay; | ||
| 150 | } | ||
| 151 | state.early_tap_steps[i] = length; | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | /** | ||
| 156 | * Initialize a new I3dl2ReverbInfo state according to the given parameters. | ||
| 157 | * | ||
| 158 | * @param params - Input parameters to update the state. | ||
| 159 | * @param state - State to be updated. | ||
| 160 | * @param workbuffer - Game-supplied memory for the state. (Unused) | ||
| 161 | */ | ||
| 162 | static void InitializeI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params, | ||
| 163 | I3dl2ReverbInfo::State& state, const CpuAddr workbuffer) { | ||
| 164 | state = {}; | ||
| 165 | Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000}; | ||
| 166 | |||
| 167 | for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) { | ||
| 168 | auto fdn_delay_time{(MaxDelayLineTimes[i] * delay).to_uint_floor()}; | ||
| 169 | state.fdn_delay_lines[i].Initialize(fdn_delay_time); | ||
| 170 | |||
| 171 | auto decay0_delay_time{(Decay0MaxDelayLineTimes[i] * delay).to_uint_floor()}; | ||
| 172 | state.decay_delay_lines0[i].Initialize(decay0_delay_time); | ||
| 173 | |||
| 174 | auto decay1_delay_time{(Decay1MaxDelayLineTimes[i] * delay).to_uint_floor()}; | ||
| 175 | state.decay_delay_lines1[i].Initialize(decay1_delay_time); | ||
| 176 | } | ||
| 177 | |||
| 178 | const auto center_delay_time{(5 * delay).to_uint_floor()}; | ||
| 179 | state.center_delay_line.Initialize(center_delay_time); | ||
| 180 | |||
| 181 | const auto early_delay_time{(400 * delay).to_uint_floor()}; | ||
| 182 | state.early_delay_line.Initialize(early_delay_time); | ||
| 183 | |||
| 184 | UpdateI3dl2ReverbEffectParameter(params, state, true); | ||
| 185 | } | ||
| 186 | |||
| 187 | /** | ||
| 188 | * Pass-through the effect, copying input to output directly, with no reverb applied. | ||
| 189 | * | ||
| 190 | * @param inputs - Array of input mix buffers to copy. | ||
| 191 | * @param outputs - Array of output mix buffers to receive copy. | ||
| 192 | * @param channel_count - Number of channels in inputs and outputs. | ||
| 193 | * @param sample_count - Number of samples within each channel (unused). | ||
| 194 | */ | ||
| 195 | static void ApplyI3dl2ReverbEffectBypass(std::span<std::span<const s32>> inputs, | ||
| 196 | std::span<std::span<s32>> outputs, const u32 channel_count, | ||
| 197 | [[maybe_unused]] const u32 sample_count) { | ||
| 198 | for (u32 i = 0; i < channel_count; i++) { | ||
| 199 | if (inputs[i].data() != outputs[i].data()) { | ||
| 200 | std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes()); | ||
| 201 | } | ||
| 202 | } | ||
| 203 | } | ||
| 204 | |||
| 205 | /** | ||
| 206 | * Tick the delay lines, reading and returning their current output, and writing a new decaying | ||
| 207 | * sample (mix). | ||
| 208 | * | ||
| 209 | * @param decay0 - The first decay line. | ||
| 210 | * @param decay1 - The second decay line. | ||
| 211 | * @param fdn - Feedback delay network. | ||
| 212 | * @param mix - The new calculated sample to be written and decayed. | ||
| 213 | * @return The next delayed and decayed sample. | ||
| 214 | */ | ||
| 215 | static Common::FixedPoint<50, 14> Axfx2AllPassTick(I3dl2ReverbInfo::I3dl2DelayLine& decay0, | ||
| 216 | I3dl2ReverbInfo::I3dl2DelayLine& decay1, | ||
| 217 | I3dl2ReverbInfo::I3dl2DelayLine& fdn, | ||
| 218 | const Common::FixedPoint<50, 14> mix) { | ||
| 219 | auto val{decay0.Read()}; | ||
| 220 | auto mixed{mix - (val * decay0.wet_gain)}; | ||
| 221 | auto out{decay0.Tick(mixed) + (mixed * decay0.wet_gain)}; | ||
| 222 | |||
| 223 | val = decay1.Read(); | ||
| 224 | mixed = out - (val * decay1.wet_gain); | ||
| 225 | out = decay1.Tick(mixed) + (mixed * decay1.wet_gain); | ||
| 226 | |||
| 227 | fdn.Tick(out); | ||
| 228 | return out; | ||
| 229 | } | ||
| 230 | |||
| 231 | /** | ||
| 232 | * Impl. Apply a I3DL2 reverb according to the current state, on the input mix buffers, | ||
| 233 | * saving the results to the output mix buffers. | ||
| 234 | * | ||
| 235 | * @tparam NumChannels - Number of channels to process. 1-6. | ||
| 236 | Inputs/outputs should have this many buffers. | ||
| 237 | * @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect). | ||
| 238 | * @param inputs - Input mix buffers to perform the reverb on. | ||
| 239 | * @param outputs - Output mix buffers to receive the reverbed samples. | ||
| 240 | * @param sample_count - Number of samples to process. | ||
| 241 | */ | ||
| 242 | template <size_t NumChannels> | ||
| 243 | static void ApplyI3dl2ReverbEffect(I3dl2ReverbInfo::State& state, | ||
| 244 | std::span<std::span<const s32>> inputs, | ||
| 245 | std::span<std::span<s32>> outputs, const u32 sample_count) { | ||
| 246 | constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{ | ||
| 247 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 248 | }; | ||
| 249 | constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{ | ||
| 250 | 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, | ||
| 251 | }; | ||
| 252 | constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{ | ||
| 253 | 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3, | ||
| 254 | }; | ||
| 255 | constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{ | ||
| 256 | 2, 0, 0, 1, 1, 1, 1, 4, 4, 4, 1, 1, 1, 0, 0, 0, 0, 5, 5, 5, | ||
| 257 | }; | ||
| 258 | |||
| 259 | std::span<const u8> tap_indexes{}; | ||
| 260 | if constexpr (NumChannels == 1) { | ||
| 261 | tap_indexes = OutTapIndexes1Ch; | ||
| 262 | } else if constexpr (NumChannels == 2) { | ||
| 263 | tap_indexes = OutTapIndexes2Ch; | ||
| 264 | } else if constexpr (NumChannels == 4) { | ||
| 265 | tap_indexes = OutTapIndexes4Ch; | ||
| 266 | } else if constexpr (NumChannels == 6) { | ||
| 267 | tap_indexes = OutTapIndexes6Ch; | ||
| 268 | } | ||
| 269 | |||
| 270 | for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { | ||
| 271 | Common::FixedPoint<50, 14> early_to_late_tap{ | ||
| 272 | state.early_delay_line.TapOut(state.early_to_late_taps)}; | ||
| 273 | std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{}; | ||
| 274 | |||
| 275 | for (u32 early_tap = 0; early_tap < I3dl2ReverbInfo::MaxDelayTaps; early_tap++) { | ||
| 276 | output_samples[tap_indexes[early_tap]] += | ||
| 277 | state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) * | ||
| 278 | EarlyGains[early_tap]; | ||
| 279 | if constexpr (NumChannels == 6) { | ||
| 280 | output_samples[static_cast<u32>(Channels::LFE)] += | ||
| 281 | state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) * | ||
| 282 | EarlyGains[early_tap]; | ||
| 283 | } | ||
| 284 | } | ||
| 285 | |||
| 286 | Common::FixedPoint<50, 14> current_sample{}; | ||
| 287 | for (u32 channel = 0; channel < NumChannels; channel++) { | ||
| 288 | current_sample += inputs[channel][sample_index]; | ||
| 289 | } | ||
| 290 | |||
| 291 | state.lowpass_0 = | ||
| 292 | (current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1).to_float(); | ||
| 293 | state.early_delay_line.Tick(state.lowpass_0); | ||
| 294 | |||
| 295 | for (u32 channel = 0; channel < NumChannels; channel++) { | ||
| 296 | output_samples[channel] *= state.early_gain; | ||
| 297 | } | ||
| 298 | |||
| 299 | std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> filtered_samples{}; | ||
| 300 | for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) { | ||
| 301 | filtered_samples[delay_line] = | ||
| 302 | state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][0] + | ||
| 303 | state.shelf_filter[delay_line]; | ||
| 304 | state.shelf_filter[delay_line] = | ||
| 305 | (filtered_samples[delay_line] * state.lowpass_coeff[delay_line][2] + | ||
| 306 | state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][1]) | ||
| 307 | .to_float(); | ||
| 308 | } | ||
| 309 | |||
| 310 | const std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> mix_matrix{ | ||
| 311 | filtered_samples[1] + filtered_samples[2] + early_to_late_tap * state.late_gain, | ||
| 312 | -filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain, | ||
| 313 | filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain, | ||
| 314 | filtered_samples[1] - filtered_samples[2] + early_to_late_tap * state.late_gain, | ||
| 315 | }; | ||
| 316 | |||
| 317 | std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> allpass_samples{}; | ||
| 318 | for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) { | ||
| 319 | allpass_samples[delay_line] = Axfx2AllPassTick( | ||
| 320 | state.decay_delay_lines0[delay_line], state.decay_delay_lines1[delay_line], | ||
| 321 | state.fdn_delay_lines[delay_line], mix_matrix[delay_line]); | ||
| 322 | } | ||
| 323 | |||
| 324 | if constexpr (NumChannels == 6) { | ||
| 325 | const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{ | ||
| 326 | allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3], | ||
| 327 | allpass_samples[3], allpass_samples[2], allpass_samples[3], | ||
| 328 | }; | ||
| 329 | |||
| 330 | for (u32 channel = 0; channel < NumChannels; channel++) { | ||
| 331 | Common::FixedPoint<50, 14> allpass{}; | ||
| 332 | |||
| 333 | if (channel == static_cast<u32>(Channels::Center)) { | ||
| 334 | allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f); | ||
| 335 | } else { | ||
| 336 | allpass = allpass_outputs[channel]; | ||
| 337 | } | ||
| 338 | |||
| 339 | auto out_sample{output_samples[channel] + allpass + | ||
| 340 | state.dry_gain * static_cast<f32>(inputs[channel][sample_index])}; | ||
| 341 | |||
| 342 | outputs[channel][sample_index] = | ||
| 343 | static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f)); | ||
| 344 | } | ||
| 345 | } else { | ||
| 346 | for (u32 channel = 0; channel < NumChannels; channel++) { | ||
| 347 | auto out_sample{output_samples[channel] + allpass_samples[channel] + | ||
| 348 | state.dry_gain * static_cast<f32>(inputs[channel][sample_index])}; | ||
| 349 | outputs[channel][sample_index] = | ||
| 350 | static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f)); | ||
| 351 | } | ||
| 352 | } | ||
| 353 | } | ||
| 354 | } | ||
| 355 | |||
| 356 | /** | ||
| 357 | * Apply a I3DL2 reverb if enabled, according to the current state, on the input mix buffers, | ||
| 358 | * saving the results to the output mix buffers. | ||
| 359 | * | ||
| 360 | * @param params - Input parameters to use. | ||
| 361 | * @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect). | ||
| 362 | * @param enabled - If enabled, delay will be applied, otherwise input is copied to output. | ||
| 363 | * @param inputs - Input mix buffers to performan the delay on. | ||
| 364 | * @param outputs - Output mix buffers to receive the delayed samples. | ||
| 365 | * @param sample_count - Number of samples to process. | ||
| 366 | */ | ||
| 367 | static void ApplyI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params, | ||
| 368 | I3dl2ReverbInfo::State& state, const bool enabled, | ||
| 369 | std::span<std::span<const s32>> inputs, | ||
| 370 | std::span<std::span<s32>> outputs, const u32 sample_count) { | ||
| 371 | if (enabled) { | ||
| 372 | switch (params.channel_count) { | ||
| 373 | case 0: | ||
| 374 | return; | ||
| 375 | case 1: | ||
| 376 | ApplyI3dl2ReverbEffect<1>(state, inputs, outputs, sample_count); | ||
| 377 | break; | ||
| 378 | case 2: | ||
| 379 | ApplyI3dl2ReverbEffect<2>(state, inputs, outputs, sample_count); | ||
| 380 | break; | ||
| 381 | case 4: | ||
| 382 | ApplyI3dl2ReverbEffect<4>(state, inputs, outputs, sample_count); | ||
| 383 | break; | ||
| 384 | case 6: | ||
| 385 | ApplyI3dl2ReverbEffect<6>(state, inputs, outputs, sample_count); | ||
| 386 | break; | ||
| 387 | default: | ||
| 388 | ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count); | ||
| 389 | break; | ||
| 390 | } | ||
| 391 | } else { | ||
| 392 | ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count); | ||
| 393 | } | ||
| 394 | } | ||
| 395 | |||
| 396 | void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 397 | std::string& string) { | ||
| 398 | string += fmt::format("I3dl2ReverbCommand\n\tenabled {} \n\tinputs: ", effect_enabled); | ||
| 399 | for (u32 i = 0; i < parameter.channel_count; i++) { | ||
| 400 | string += fmt::format("{:02X}, ", inputs[i]); | ||
| 401 | } | ||
| 402 | string += "\n\toutputs: "; | ||
| 403 | for (u32 i = 0; i < parameter.channel_count; i++) { | ||
| 404 | string += fmt::format("{:02X}, ", outputs[i]); | ||
| 405 | } | ||
| 406 | string += "\n"; | ||
| 407 | } | ||
| 408 | |||
| 409 | void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 410 | std::vector<std::span<const s32>> input_buffers(parameter.channel_count); | ||
| 411 | std::vector<std::span<s32>> output_buffers(parameter.channel_count); | ||
| 412 | |||
| 413 | for (u32 i = 0; i < parameter.channel_count; i++) { | ||
| 414 | input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, | ||
| 415 | processor.sample_count); | ||
| 416 | output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, | ||
| 417 | processor.sample_count); | ||
| 418 | } | ||
| 419 | |||
| 420 | auto state_{reinterpret_cast<I3dl2ReverbInfo::State*>(state)}; | ||
| 421 | |||
| 422 | if (effect_enabled) { | ||
| 423 | if (parameter.state == I3dl2ReverbInfo::ParameterState::Updating) { | ||
| 424 | UpdateI3dl2ReverbEffectParameter(parameter, *state_, false); | ||
| 425 | } else if (parameter.state == I3dl2ReverbInfo::ParameterState::Initialized) { | ||
| 426 | InitializeI3dl2ReverbEffect(parameter, *state_, workbuffer); | ||
| 427 | } | ||
| 428 | } | ||
| 429 | ApplyI3dl2ReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, | ||
| 430 | processor.sample_count); | ||
| 431 | } | ||
| 432 | |||
| 433 | bool I3dl2ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 434 | return true; | ||
| 435 | } | ||
| 436 | |||
| 437 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <string> | ||
| 8 | |||
| 9 | #include "audio_core/renderer/command/icommand.h" | ||
| 10 | #include "audio_core/renderer/effect/i3dl2.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | ||
| 16 | } | ||
| 17 | |||
| 18 | /** | ||
| 19 | * AudioRenderer command for a I3DL2Reverb effect. Apply a reverb to inputs mix buffer according to | ||
| 20 | * the I3DL2 spec, outputs receives the results. | ||
| 21 | */ | ||
| 22 | struct I3dl2ReverbCommand : ICommand { | ||
| 23 | /** | ||
| 24 | * Print this command's information to a string. | ||
| 25 | * | ||
| 26 | * @param processor - The CommandListProcessor processing this command. | ||
| 27 | * @param string - The string to print into. | ||
| 28 | */ | ||
| 29 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Process this command. | ||
| 33 | * | ||
| 34 | * @param processor - The CommandListProcessor processing this command. | ||
| 35 | */ | ||
| 36 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 37 | |||
| 38 | /** | ||
| 39 | * Verify this command's data is valid. | ||
| 40 | * | ||
| 41 | * @param processor - The CommandListProcessor processing this command. | ||
| 42 | * @return True if the command is valid, otherwise false. | ||
| 43 | */ | ||
| 44 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 45 | |||
| 46 | /// Input mix buffer offsets for each channel | ||
| 47 | std::array<s16, MaxChannels> inputs; | ||
| 48 | /// Output mix buffer offsets for each channel | ||
| 49 | std::array<s16, MaxChannels> outputs; | ||
| 50 | /// Input parameters | ||
| 51 | I3dl2ReverbInfo::ParameterVersion1 parameter; | ||
| 52 | /// State, updated each call | ||
| 53 | CpuAddr state; | ||
| 54 | /// Game-supplied workbuffer (Unused) | ||
| 55 | CpuAddr workbuffer; | ||
| 56 | /// Is this effect enabled? | ||
| 57 | bool effect_enabled; | ||
| 58 | }; | ||
| 59 | |||
| 60 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 5 | #include "audio_core/renderer/command/effect/light_limiter.h" | ||
| 6 | |||
| 7 | namespace AudioCore::AudioRenderer { | ||
| 8 | /** | ||
| 9 | * Update the LightLimiterInfo state according to the given parameters. | ||
| 10 | * A no-op. | ||
| 11 | * | ||
| 12 | * @param params - Input parameters to update the state. | ||
| 13 | * @param state - State to be updated. | ||
| 14 | */ | ||
| 15 | static void UpdateLightLimiterEffectParameter(const LightLimiterInfo::ParameterVersion2& params, | ||
| 16 | LightLimiterInfo::State& state) {} | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Initialize a new LightLimiterInfo state according to the given parameters. | ||
| 20 | * | ||
| 21 | * @param params - Input parameters to update the state. | ||
| 22 | * @param state - State to be updated. | ||
| 23 | * @param workbuffer - Game-supplied memory for the state. (Unused) | ||
| 24 | */ | ||
| 25 | static void InitializeLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params, | ||
| 26 | LightLimiterInfo::State& state, const CpuAddr workbuffer) { | ||
| 27 | state = {}; | ||
| 28 | state.samples_average.fill(0.0f); | ||
| 29 | state.compression_gain.fill(1.0f); | ||
| 30 | state.look_ahead_sample_offsets.fill(0); | ||
| 31 | for (u32 i = 0; i < params.channel_count; i++) { | ||
| 32 | state.look_ahead_sample_buffers[i].resize(params.look_ahead_samples_max, 0.0f); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Apply a light limiter effect if enabled, according to the current state, on the input mix | ||
| 38 | * buffers, saving the results to the output mix buffers. | ||
| 39 | * | ||
| 40 | * @param params - Input parameters to use. | ||
| 41 | * @param state - State to use, must be initialized (see InitializeLightLimiterEffect). | ||
| 42 | * @param enabled - If enabled, limiter will be applied, otherwise input is copied to output. | ||
| 43 | * @param inputs - Input mix buffers to perform the limiter on. | ||
| 44 | * @param outputs - Output mix buffers to receive the limited samples. | ||
| 45 | * @param sample_count - Number of samples to process. | ||
| 46 | * @params statistics - Optional output statistics, only used with version 2. | ||
| 47 | */ | ||
| 48 | static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params, | ||
| 49 | LightLimiterInfo::State& state, const bool enabled, | ||
| 50 | std::vector<std::span<const s32>>& inputs, | ||
| 51 | std::vector<std::span<s32>>& outputs, const u32 sample_count, | ||
| 52 | LightLimiterInfo::StatisticsInternal* statistics) { | ||
| 53 | constexpr s64 min{std::numeric_limits<s32>::min()}; | ||
| 54 | constexpr s64 max{std::numeric_limits<s32>::max()}; | ||
| 55 | |||
| 56 | const auto recip_estimate = [](f64 a) -> f64 { | ||
| 57 | s32 q, s; | ||
| 58 | f64 r; | ||
| 59 | q = (s32)(a * 512.0); /* a in units of 1/512 rounded down */ | ||
| 60 | r = 1.0 / (((f64)q + 0.5) / 512.0); /* reciprocal r */ | ||
| 61 | s = (s32)(256.0 * r + 0.5); /* r in units of 1/256 rounded to nearest */ | ||
| 62 | return ((f64)s / 256.0); | ||
| 63 | }; | ||
| 64 | |||
| 65 | if (enabled) { | ||
| 66 | if (statistics && params.statistics_reset_required) { | ||
| 67 | for (u32 i = 0; i < params.channel_count; i++) { | ||
| 68 | statistics->channel_compression_gain_min[i] = 1.0f; | ||
| 69 | statistics->channel_max_sample[i] = 0; | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { | ||
| 74 | for (u32 channel = 0; channel < params.channel_count; channel++) { | ||
| 75 | auto sample{(Common::FixedPoint<49, 15>(inputs[channel][sample_index]) / | ||
| 76 | Common::FixedPoint<49, 15>::one) * | ||
| 77 | params.input_gain}; | ||
| 78 | auto abs_sample{sample}; | ||
| 79 | if (sample < 0.0f) { | ||
| 80 | abs_sample = -sample; | ||
| 81 | } | ||
| 82 | auto coeff{abs_sample > state.samples_average[channel] ? params.attack_coeff | ||
| 83 | : params.release_coeff}; | ||
| 84 | state.samples_average[channel] += | ||
| 85 | ((abs_sample - state.samples_average[channel]) * coeff).to_float(); | ||
| 86 | |||
| 87 | // Reciprocal estimate | ||
| 88 | auto new_average_sample{Common::FixedPoint<49, 15>( | ||
| 89 | recip_estimate(state.samples_average[channel].to_double()))}; | ||
| 90 | if (params.processing_mode != LightLimiterInfo::ProcessingMode::Mode1) { | ||
| 91 | // Two Newton-Raphson steps | ||
| 92 | auto temp{2.0 - (state.samples_average[channel] * new_average_sample)}; | ||
| 93 | new_average_sample = 2.0 - (state.samples_average[channel] * temp); | ||
| 94 | } | ||
| 95 | |||
| 96 | auto above_threshold{state.samples_average[channel] > params.threshold}; | ||
| 97 | auto attenuation{above_threshold ? params.threshold * new_average_sample : 1.0f}; | ||
| 98 | coeff = attenuation < state.compression_gain[channel] ? params.attack_coeff | ||
| 99 | : params.release_coeff; | ||
| 100 | state.compression_gain[channel] += | ||
| 101 | (attenuation - state.compression_gain[channel]) * coeff; | ||
| 102 | |||
| 103 | auto lookahead_sample{ | ||
| 104 | state.look_ahead_sample_buffers[channel] | ||
| 105 | [state.look_ahead_sample_offsets[channel]]}; | ||
| 106 | |||
| 107 | state.look_ahead_sample_buffers[channel][state.look_ahead_sample_offsets[channel]] = | ||
| 108 | sample; | ||
| 109 | state.look_ahead_sample_offsets[channel] = | ||
| 110 | (state.look_ahead_sample_offsets[channel] + 1) % params.look_ahead_samples_min; | ||
| 111 | |||
| 112 | outputs[channel][sample_index] = static_cast<s32>( | ||
| 113 | std::clamp((lookahead_sample * state.compression_gain[channel] * | ||
| 114 | params.output_gain * Common::FixedPoint<49, 15>::one) | ||
| 115 | .to_long(), | ||
| 116 | min, max)); | ||
| 117 | |||
| 118 | if (statistics) { | ||
| 119 | statistics->channel_max_sample[channel] = | ||
| 120 | std::max(statistics->channel_max_sample[channel], abs_sample.to_float()); | ||
| 121 | statistics->channel_compression_gain_min[channel] = | ||
| 122 | std::min(statistics->channel_compression_gain_min[channel], | ||
| 123 | state.compression_gain[channel].to_float()); | ||
| 124 | } | ||
| 125 | } | ||
| 126 | } | ||
| 127 | } else { | ||
| 128 | for (u32 i = 0; i < params.channel_count; i++) { | ||
| 129 | if (params.inputs[i] != params.outputs[i]) { | ||
| 130 | std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes()); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 137 | std::string& string) { | ||
| 138 | string += fmt::format("LightLimiterVersion1Command\n\tinputs: "); | ||
| 139 | for (u32 i = 0; i < MaxChannels; i++) { | ||
| 140 | string += fmt::format("{:02X}, ", inputs[i]); | ||
| 141 | } | ||
| 142 | string += "\n\toutputs: "; | ||
| 143 | for (u32 i = 0; i < MaxChannels; i++) { | ||
| 144 | string += fmt::format("{:02X}, ", outputs[i]); | ||
| 145 | } | ||
| 146 | string += "\n"; | ||
| 147 | } | ||
| 148 | |||
| 149 | void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) { | ||
| 150 | std::vector<std::span<const s32>> input_buffers(parameter.channel_count); | ||
| 151 | std::vector<std::span<s32>> output_buffers(parameter.channel_count); | ||
| 152 | |||
| 153 | for (u32 i = 0; i < parameter.channel_count; i++) { | ||
| 154 | input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, | ||
| 155 | processor.sample_count); | ||
| 156 | output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, | ||
| 157 | processor.sample_count); | ||
| 158 | } | ||
| 159 | |||
| 160 | auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)}; | ||
| 161 | |||
| 162 | if (effect_enabled) { | ||
| 163 | if (parameter.state == LightLimiterInfo::ParameterState::Updating) { | ||
| 164 | UpdateLightLimiterEffectParameter(parameter, *state_); | ||
| 165 | } else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) { | ||
| 166 | InitializeLightLimiterEffect(parameter, *state_, workbuffer); | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | LightLimiterInfo::StatisticsInternal* statistics{nullptr}; | ||
| 171 | ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, | ||
| 172 | processor.sample_count, statistics); | ||
| 173 | } | ||
| 174 | |||
| 175 | bool LightLimiterVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 176 | return true; | ||
| 177 | } | ||
| 178 | |||
| 179 | void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 180 | std::string& string) { | ||
| 181 | string += fmt::format("LightLimiterVersion2Command\n\tinputs: \n"); | ||
| 182 | for (u32 i = 0; i < MaxChannels; i++) { | ||
| 183 | string += fmt::format("{:02X}, ", inputs[i]); | ||
| 184 | } | ||
| 185 | string += "\n\toutputs: "; | ||
| 186 | for (u32 i = 0; i < MaxChannels; i++) { | ||
| 187 | string += fmt::format("{:02X}, ", outputs[i]); | ||
| 188 | } | ||
| 189 | string += "\n"; | ||
| 190 | } | ||
| 191 | |||
| 192 | void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) { | ||
| 193 | std::vector<std::span<const s32>> input_buffers(parameter.channel_count); | ||
| 194 | std::vector<std::span<s32>> output_buffers(parameter.channel_count); | ||
| 195 | |||
| 196 | for (u32 i = 0; i < parameter.channel_count; i++) { | ||
| 197 | input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, | ||
| 198 | processor.sample_count); | ||
| 199 | output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, | ||
| 200 | processor.sample_count); | ||
| 201 | } | ||
| 202 | |||
| 203 | auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)}; | ||
| 204 | |||
| 205 | if (effect_enabled) { | ||
| 206 | if (parameter.state == LightLimiterInfo::ParameterState::Updating) { | ||
| 207 | UpdateLightLimiterEffectParameter(parameter, *state_); | ||
| 208 | } else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) { | ||
| 209 | InitializeLightLimiterEffect(parameter, *state_, workbuffer); | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | auto statistics{reinterpret_cast<LightLimiterInfo::StatisticsInternal*>(result_state)}; | ||
| 214 | ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, | ||
| 215 | processor.sample_count, statistics); | ||
| 216 | } | ||
| 217 | |||
| 218 | bool LightLimiterVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 219 | return true; | ||
| 220 | } | ||
| 221 | |||
| 222 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <string> | ||
| 8 | |||
| 9 | #include "audio_core/renderer/command/icommand.h" | ||
| 10 | #include "audio_core/renderer/effect/light_limiter.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | ||
| 16 | } | ||
| 17 | |||
| 18 | /** | ||
| 19 | * AudioRenderer command for limiting volume between a high and low threshold. | ||
| 20 | * Version 1. | ||
| 21 | */ | ||
| 22 | struct LightLimiterVersion1Command : ICommand { | ||
| 23 | /** | ||
| 24 | * Print this command's information to a string. | ||
| 25 | * | ||
| 26 | * @param processor - The CommandListProcessor processing this command. | ||
| 27 | * @param string - The string to print into. | ||
| 28 | */ | ||
| 29 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Process this command. | ||
| 33 | * | ||
| 34 | * @param processor - The CommandListProcessor processing this command. | ||
| 35 | */ | ||
| 36 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 37 | |||
| 38 | /** | ||
| 39 | * Verify this command's data is valid. | ||
| 40 | * | ||
| 41 | * @param processor - The CommandListProcessor processing this command. | ||
| 42 | * @return True if the command is valid, otherwise false. | ||
| 43 | */ | ||
| 44 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 45 | |||
| 46 | /// Input mix buffer offsets for each channel | ||
| 47 | std::array<s16, MaxChannels> inputs; | ||
| 48 | /// Output mix buffer offsets for each channel | ||
| 49 | std::array<s16, MaxChannels> outputs; | ||
| 50 | /// Input parameters | ||
| 51 | LightLimiterInfo::ParameterVersion2 parameter; | ||
| 52 | /// State, updated each call | ||
| 53 | CpuAddr state; | ||
| 54 | /// Game-supplied workbuffer (Unused) | ||
| 55 | CpuAddr workbuffer; | ||
| 56 | /// Is this effect enabled? | ||
| 57 | bool effect_enabled; | ||
| 58 | }; | ||
| 59 | |||
| 60 | /** | ||
| 61 | * AudioRenderer command for limiting volume between a high and low threshold. | ||
| 62 | * Version 2 with output statistics. | ||
| 63 | */ | ||
| 64 | struct LightLimiterVersion2Command : ICommand { | ||
| 65 | /** | ||
| 66 | * Print this command's information to a string. | ||
| 67 | * | ||
| 68 | * @param processor - The CommandListProcessor processing this command. | ||
| 69 | * @param string - The string to print into. | ||
| 70 | */ | ||
| 71 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 72 | |||
| 73 | /** | ||
| 74 | * Process this command. | ||
| 75 | * | ||
| 76 | * @param processor - The CommandListProcessor processing this command. | ||
| 77 | */ | ||
| 78 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 79 | |||
| 80 | /** | ||
| 81 | * Verify this command's data is valid. | ||
| 82 | * | ||
| 83 | * @param processor - The CommandListProcessor processing this command. | ||
| 84 | */ | ||
| 85 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 86 | |||
| 87 | /// Input mix buffer offsets for each channel | ||
| 88 | std::array<s16, MaxChannels> inputs; | ||
| 89 | /// Output mix buffer offsets for each channel | ||
| 90 | std::array<s16, MaxChannels> outputs; | ||
| 91 | /// Input parameters | ||
| 92 | LightLimiterInfo::ParameterVersion2 parameter; | ||
| 93 | /// State, updated each call | ||
| 94 | CpuAddr state; | ||
| 95 | /// Game-supplied workbuffer (Unused) | ||
| 96 | CpuAddr workbuffer; | ||
| 97 | /// Optional statistics, sent back to the sysmodule | ||
| 98 | CpuAddr result_state; | ||
| 99 | /// Is this effect enabled? | ||
| 100 | bool effect_enabled; | ||
| 101 | }; | ||
| 102 | |||
| 103 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 5 | #include "audio_core/renderer/command/effect/biquad_filter.h" | ||
| 6 | #include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h" | ||
| 7 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | void MultiTapBiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 11 | std::string& string) { | ||
| 12 | string += fmt::format( | ||
| 13 | "MultiTapBiquadFilterCommand\n\tinput {:02X}\n\toutput {:02X}\n\tneeds_init ({}, {})\n", | ||
| 14 | input, output, needs_init[0], needs_init[1]); | ||
| 15 | } | ||
| 16 | |||
| 17 | void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 18 | if (filter_tap_count > MaxBiquadFilters) { | ||
| 19 | LOG_ERROR(Service_Audio, "Too many filter taps! {}", filter_tap_count); | ||
| 20 | filter_tap_count = MaxBiquadFilters; | ||
| 21 | } | ||
| 22 | |||
| 23 | auto input_buffer{ | ||
| 24 | processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; | ||
| 25 | auto output_buffer{ | ||
| 26 | processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; | ||
| 27 | |||
| 28 | // TODO: Fix this, currently just applies the filter to the input twice, | ||
| 29 | // and doesn't chain the biquads together at all. | ||
| 30 | for (u32 i = 0; i < filter_tap_count; i++) { | ||
| 31 | auto state{reinterpret_cast<VoiceState::BiquadFilterState*>(states[i])}; | ||
| 32 | if (needs_init[i]) { | ||
| 33 | std::memset(state, 0, sizeof(VoiceState::BiquadFilterState)); | ||
| 34 | } | ||
| 35 | |||
| 36 | ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state, | ||
| 37 | processor.sample_count); | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | bool MultiTapBiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 42 | return true; | ||
| 43 | } | ||
| 44 | |||
| 45 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <string> | ||
| 8 | |||
| 9 | #include "audio_core/renderer/command/icommand.h" | ||
| 10 | #include "audio_core/renderer/voice/voice_info.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | ||
| 16 | } | ||
| 17 | |||
| 18 | /** | ||
| 19 | * AudioRenderer command for applying multiple biquads at once. | ||
| 20 | */ | ||
| 21 | struct MultiTapBiquadFilterCommand : ICommand { | ||
| 22 | /** | ||
| 23 | * Print this command's information to a string. | ||
| 24 | * | ||
| 25 | * @param processor - The CommandListProcessor processing this command. | ||
| 26 | * @param string - The string to print into. | ||
| 27 | */ | ||
| 28 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Process this command. | ||
| 32 | * | ||
| 33 | * @param processor - The CommandListProcessor processing this command. | ||
| 34 | */ | ||
| 35 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Verify this command's data is valid. | ||
| 39 | * | ||
| 40 | * @param processor - The CommandListProcessor processing this command. | ||
| 41 | * @return True if the command is valid, otherwise false. | ||
| 42 | */ | ||
| 43 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 44 | |||
| 45 | /// Input mix buffer index | ||
| 46 | s16 input; | ||
| 47 | /// Output mix buffer index | ||
| 48 | s16 output; | ||
| 49 | /// Biquad parameters | ||
| 50 | std::array<VoiceInfo::BiquadFilterParameter, MaxBiquadFilters> biquads; | ||
| 51 | /// Biquad states, updated each call | ||
| 52 | std::array<CpuAddr, MaxBiquadFilters> states; | ||
| 53 | /// If each biquad needs initialisation | ||
| 54 | std::array<bool, MaxBiquadFilters> needs_init; | ||
| 55 | /// Number of active biquads | ||
| 56 | u8 filter_tap_count; | ||
| 57 | }; | ||
| 58 | |||
| 59 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <numbers> | ||
| 5 | #include <ranges> | ||
| 6 | |||
| 7 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 8 | #include "audio_core/renderer/command/effect/reverb.h" | ||
| 9 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | constexpr std::array<f32, ReverbInfo::MaxDelayLines> FdnMaxDelayLineTimes = { | ||
| 13 | 53.9532470703125f, | ||
| 14 | 79.19256591796875f, | ||
| 15 | 116.23876953125f, | ||
| 16 | 170.61529541015625f, | ||
| 17 | }; | ||
| 18 | |||
| 19 | constexpr std::array<f32, ReverbInfo::MaxDelayLines> DecayMaxDelayLineTimes = { | ||
| 20 | 7.0f, | ||
| 21 | 9.0f, | ||
| 22 | 13.0f, | ||
| 23 | 17.0f, | ||
| 24 | }; | ||
| 25 | |||
| 26 | constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps + 1>, ReverbInfo::NumEarlyModes> | ||
| 27 | EarlyDelayTimes = { | ||
| 28 | {{0.000000f, 3.500000f, 2.799988f, 3.899963f, 2.699951f, 13.399963f, 7.899963f, 8.399963f, | ||
| 29 | 9.899963f, 12.000000f, 12.500000f}, | ||
| 30 | {0.000000f, 11.799988f, 5.500000f, 11.199951f, 10.399963f, 38.099976f, 22.199951f, | ||
| 31 | 29.599976f, 21.199951f, 24.799988f, 40.000000f}, | ||
| 32 | {0.000000f, 41.500000f, 20.500000f, 41.299988f, 0.000000f, 29.500000f, 33.799988f, | ||
| 33 | 45.199951f, 46.799988f, 0.000000f, 50.000000f}, | ||
| 34 | {33.099976f, 43.299988f, 22.799988f, 37.899963f, 14.899963f, 35.299988f, 17.899963f, | ||
| 35 | 34.199951f, 0.000000f, 43.299988f, 50.000000f}, | ||
| 36 | {0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | ||
| 37 | 0.000000f, 0.000000f, 0.000000f}}, | ||
| 38 | }; | ||
| 39 | |||
| 40 | constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps>, ReverbInfo::NumEarlyModes> | ||
| 41 | EarlyDelayGains = {{ | ||
| 42 | {0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, | ||
| 43 | 0.679993f, 0.679993f}, | ||
| 44 | {0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.679993f, 0.679993f, | ||
| 45 | 0.679993f, 0.679993f}, | ||
| 46 | {0.500000f, 0.699951f, 0.699951f, 0.679993f, 0.500000f, 0.679993f, 0.679993f, 0.699951f, | ||
| 47 | 0.679993f, 0.000000f}, | ||
| 48 | {0.929993f, 0.919983f, 0.869995f, 0.859985f, 0.939941f, 0.809998f, 0.799988f, 0.769958f, | ||
| 49 | 0.759949f, 0.649963f}, | ||
| 50 | {0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | ||
| 51 | 0.000000f, 0.000000f}, | ||
| 52 | }}; | ||
| 53 | |||
| 54 | constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes> | ||
| 55 | FdnDelayTimes = {{ | ||
| 56 | {53.953247f, 79.192566f, 116.238770f, 130.615295f}, | ||
| 57 | {53.953247f, 79.192566f, 116.238770f, 170.615295f}, | ||
| 58 | {5.000000f, 10.000000f, 5.000000f, 10.000000f}, | ||
| 59 | {47.029968f, 71.000000f, 103.000000f, 170.000000f}, | ||
| 60 | {53.953247f, 79.192566f, 116.238770f, 170.615295f}, | ||
| 61 | }}; | ||
| 62 | |||
| 63 | constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes> | ||
| 64 | DecayDelayTimes = {{ | ||
| 65 | {7.000000f, 9.000000f, 13.000000f, 17.000000f}, | ||
| 66 | {7.000000f, 9.000000f, 13.000000f, 17.000000f}, | ||
| 67 | {1.000000f, 1.000000f, 1.000000f, 1.000000f}, | ||
| 68 | {7.000000f, 7.000000f, 13.000000f, 9.000000f}, | ||
| 69 | {7.000000f, 9.000000f, 13.000000f, 17.000000f}, | ||
| 70 | }}; | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Update the ReverbInfo state according to the given parameters. | ||
| 74 | * | ||
| 75 | * @param params - Input parameters to update the state. | ||
| 76 | * @param state - State to be updated. | ||
| 77 | */ | ||
| 78 | static void UpdateReverbEffectParameter(const ReverbInfo::ParameterVersion2& params, | ||
| 79 | ReverbInfo::State& state) { | ||
| 80 | const auto pow_10 = [](f32 val) -> f32 { | ||
| 81 | return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val); | ||
| 82 | }; | ||
| 83 | const auto cos = [](f32 degrees) -> f32 { | ||
| 84 | return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f); | ||
| 85 | }; | ||
| 86 | |||
| 87 | static bool unk_initialized{false}; | ||
| 88 | static Common::FixedPoint<50, 14> unk_value{}; | ||
| 89 | |||
| 90 | const auto sample_rate{Common::FixedPoint<50, 14>::from_base(params.sample_rate)}; | ||
| 91 | const auto pre_delay_time{Common::FixedPoint<50, 14>::from_base(params.pre_delay)}; | ||
| 92 | |||
| 93 | for (u32 i = 0; i < ReverbInfo::MaxDelayTaps; i++) { | ||
| 94 | auto early_delay{ | ||
| 95 | ((pre_delay_time + EarlyDelayTimes[params.early_mode][i]) * sample_rate).to_int()}; | ||
| 96 | early_delay = std::min(early_delay, state.pre_delay_line.sample_count_max); | ||
| 97 | state.early_delay_times[i] = early_delay + 1; | ||
| 98 | state.early_gains[i] = Common::FixedPoint<50, 14>::from_base(params.early_gain) * | ||
| 99 | EarlyDelayGains[params.early_mode][i]; | ||
| 100 | } | ||
| 101 | |||
| 102 | if (params.channel_count == 2) { | ||
| 103 | state.early_gains[4] * 0.5f; | ||
| 104 | state.early_gains[5] * 0.5f; | ||
| 105 | } | ||
| 106 | |||
| 107 | auto pre_time{ | ||
| 108 | ((pre_delay_time + EarlyDelayTimes[params.early_mode][10]) * sample_rate).to_int()}; | ||
| 109 | state.pre_delay_time = std::min(pre_time, state.pre_delay_line.sample_count_max); | ||
| 110 | |||
| 111 | if (!unk_initialized) { | ||
| 112 | unk_value = cos((1280.0f / sample_rate).to_float()); | ||
| 113 | unk_initialized = true; | ||
| 114 | } | ||
| 115 | |||
| 116 | for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { | ||
| 117 | const auto fdn_delay{(FdnDelayTimes[params.late_mode][i] * sample_rate).to_int()}; | ||
| 118 | state.fdn_delay_lines[i].sample_count = | ||
| 119 | std::min(fdn_delay, state.fdn_delay_lines[i].sample_count_max); | ||
| 120 | state.fdn_delay_lines[i].buffer_end = | ||
| 121 | &state.fdn_delay_lines[i].buffer[state.fdn_delay_lines[i].sample_count - 1]; | ||
| 122 | |||
| 123 | const auto decay_delay{(DecayDelayTimes[params.late_mode][i] * sample_rate).to_int()}; | ||
| 124 | state.decay_delay_lines[i].sample_count = | ||
| 125 | std::min(decay_delay, state.decay_delay_lines[i].sample_count_max); | ||
| 126 | state.decay_delay_lines[i].buffer_end = | ||
| 127 | &state.decay_delay_lines[i].buffer[state.decay_delay_lines[i].sample_count - 1]; | ||
| 128 | |||
| 129 | state.decay_delay_lines[i].decay = | ||
| 130 | 0.5999755859375f * (1.0f - Common::FixedPoint<50, 14>::from_base(params.colouration)); | ||
| 131 | |||
| 132 | auto a{(Common::FixedPoint<50, 14>(state.fdn_delay_lines[i].sample_count_max) + | ||
| 133 | state.decay_delay_lines[i].sample_count_max) * | ||
| 134 | -3}; | ||
| 135 | auto b{a / (Common::FixedPoint<50, 14>::from_base(params.decay_time) * sample_rate)}; | ||
| 136 | Common::FixedPoint<50, 14> c{0.0f}; | ||
| 137 | Common::FixedPoint<50, 14> d{0.0f}; | ||
| 138 | auto hf_decay_ratio{Common::FixedPoint<50, 14>::from_base(params.high_freq_decay_ratio)}; | ||
| 139 | |||
| 140 | if (hf_decay_ratio > 0.99493408203125f) { | ||
| 141 | c = 0.0f; | ||
| 142 | d = 1.0f; | ||
| 143 | } else { | ||
| 144 | const auto e{ | ||
| 145 | pow_10(((((1.0f / hf_decay_ratio) - 1.0f) * 2) / 100 * (b / 10)).to_float())}; | ||
| 146 | const auto f{1.0f - e}; | ||
| 147 | const auto g{2.0f - (unk_value * e * 2)}; | ||
| 148 | const auto h{std::sqrt(std::pow(g.to_float(), 2.0f) - (std::pow(f, 2.0f) * 4))}; | ||
| 149 | |||
| 150 | c = (g - h) / (f * 2.0f); | ||
| 151 | d = 1.0f - c; | ||
| 152 | } | ||
| 153 | |||
| 154 | state.hf_decay_prev_gain[i] = c; | ||
| 155 | state.hf_decay_gain[i] = pow_10((b / 1000).to_float()) * d * 0.70709228515625f; | ||
| 156 | state.prev_feedback_output[i] = 0; | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | /** | ||
| 161 | * Initialize a new ReverbInfo state according to the given parameters. | ||
| 162 | * | ||
| 163 | * @param params - Input parameters to update the state. | ||
| 164 | * @param state - State to be updated. | ||
| 165 | * @param workbuffer - Game-supplied memory for the state. (Unused) | ||
| 166 | * @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb begins. | ||
| 167 | */ | ||
| 168 | static void InitializeReverbEffect(const ReverbInfo::ParameterVersion2& params, | ||
| 169 | ReverbInfo::State& state, const CpuAddr workbuffer, | ||
| 170 | const bool long_size_pre_delay_supported) { | ||
| 171 | state = {}; | ||
| 172 | |||
| 173 | auto delay{Common::FixedPoint<50, 14>::from_base(params.sample_rate)}; | ||
| 174 | |||
| 175 | for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { | ||
| 176 | auto fdn_delay_time{(FdnMaxDelayLineTimes[i] * delay).to_uint_floor()}; | ||
| 177 | state.fdn_delay_lines[i].Initialize(fdn_delay_time, 1.0f); | ||
| 178 | |||
| 179 | auto decay_delay_time{(DecayMaxDelayLineTimes[i] * delay).to_uint_floor()}; | ||
| 180 | state.decay_delay_lines[i].Initialize(decay_delay_time, 0.0f); | ||
| 181 | } | ||
| 182 | |||
| 183 | const auto pre_delay{long_size_pre_delay_supported ? 350.0f : 150.0f}; | ||
| 184 | const auto pre_delay_line{(pre_delay * delay).to_uint_floor()}; | ||
| 185 | state.pre_delay_line.Initialize(pre_delay_line, 1.0f); | ||
| 186 | |||
| 187 | const auto center_delay_time{(5 * delay).to_uint_floor()}; | ||
| 188 | state.center_delay_line.Initialize(center_delay_time, 1.0f); | ||
| 189 | |||
| 190 | UpdateReverbEffectParameter(params, state); | ||
| 191 | |||
| 192 | for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { | ||
| 193 | std::ranges::fill(state.fdn_delay_lines[i].buffer, 0); | ||
| 194 | std::ranges::fill(state.decay_delay_lines[i].buffer, 0); | ||
| 195 | } | ||
| 196 | std::ranges::fill(state.center_delay_line.buffer, 0); | ||
| 197 | std::ranges::fill(state.pre_delay_line.buffer, 0); | ||
| 198 | } | ||
| 199 | |||
| 200 | /** | ||
| 201 | * Pass-through the effect, copying input to output directly, with no reverb applied. | ||
| 202 | * | ||
| 203 | * @param inputs - Array of input mix buffers to copy. | ||
| 204 | * @param outputs - Array of output mix buffers to receive copy. | ||
| 205 | * @param channel_count - Number of channels in inputs and outputs. | ||
| 206 | * @param sample_count - Number of samples within each channel. | ||
| 207 | */ | ||
| 208 | static void ApplyReverbEffectBypass(std::span<std::span<const s32>> inputs, | ||
| 209 | std::span<std::span<s32>> outputs, const u32 channel_count, | ||
| 210 | const u32 sample_count) { | ||
| 211 | for (u32 i = 0; i < channel_count; i++) { | ||
| 212 | if (inputs[i].data() != outputs[i].data()) { | ||
| 213 | std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes()); | ||
| 214 | } | ||
| 215 | } | ||
| 216 | } | ||
| 217 | |||
| 218 | /** | ||
| 219 | * Tick the delay lines, reading and returning their current output, and writing a new decaying | ||
| 220 | * sample (mix). | ||
| 221 | * | ||
| 222 | * @param decay - The decay line. | ||
| 223 | * @param fdn - Feedback delay network. | ||
| 224 | * @param mix - The new calculated sample to be written and decayed. | ||
| 225 | * @return The next delayed and decayed sample. | ||
| 226 | */ | ||
| 227 | static Common::FixedPoint<50, 14> Axfx2AllPassTick(ReverbInfo::ReverbDelayLine& decay, | ||
| 228 | ReverbInfo::ReverbDelayLine& fdn, | ||
| 229 | const Common::FixedPoint<50, 14> mix) { | ||
| 230 | const auto val{decay.Read()}; | ||
| 231 | const auto mixed{mix - (val * decay.decay)}; | ||
| 232 | const auto out{decay.Tick(mixed) + (mixed * decay.decay)}; | ||
| 233 | |||
| 234 | fdn.Tick(out); | ||
| 235 | return out; | ||
| 236 | } | ||
| 237 | |||
| 238 | /** | ||
| 239 | * Impl. Apply a Reverb according to the current state, on the input mix buffers, | ||
| 240 | * saving the results to the output mix buffers. | ||
| 241 | * | ||
| 242 | * @tparam NumChannels - Number of channels to process. 1-6. | ||
| 243 | Inputs/outputs should have this many buffers. | ||
| 244 | * @param params - Input parameters to update the state. | ||
| 245 | * @param state - State to use, must be initialized (see InitializeReverbEffect). | ||
| 246 | * @param inputs - Input mix buffers to perform the reverb on. | ||
| 247 | * @param outputs - Output mix buffers to receive the reverbed samples. | ||
| 248 | * @param sample_count - Number of samples to process. | ||
| 249 | */ | ||
| 250 | template <size_t NumChannels> | ||
| 251 | static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state, | ||
| 252 | std::vector<std::span<const s32>>& inputs, | ||
| 253 | std::vector<std::span<s32>>& outputs, const u32 sample_count) { | ||
| 254 | constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{ | ||
| 255 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 256 | }; | ||
| 257 | constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{ | ||
| 258 | 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, | ||
| 259 | }; | ||
| 260 | constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{ | ||
| 261 | 0, 0, 1, 1, 0, 1, 2, 2, 3, 3, | ||
| 262 | }; | ||
| 263 | constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{ | ||
| 264 | 0, 0, 1, 1, 2, 2, 4, 4, 5, 5, | ||
| 265 | }; | ||
| 266 | |||
| 267 | std::span<const u8> tap_indexes{}; | ||
| 268 | if constexpr (NumChannels == 1) { | ||
| 269 | tap_indexes = OutTapIndexes1Ch; | ||
| 270 | } else if constexpr (NumChannels == 2) { | ||
| 271 | tap_indexes = OutTapIndexes2Ch; | ||
| 272 | } else if constexpr (NumChannels == 4) { | ||
| 273 | tap_indexes = OutTapIndexes4Ch; | ||
| 274 | } else if constexpr (NumChannels == 6) { | ||
| 275 | tap_indexes = OutTapIndexes6Ch; | ||
| 276 | } | ||
| 277 | |||
| 278 | for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { | ||
| 279 | std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{}; | ||
| 280 | |||
| 281 | for (u32 early_tap = 0; early_tap < ReverbInfo::MaxDelayTaps; early_tap++) { | ||
| 282 | const auto sample{state.pre_delay_line.TapOut(state.early_delay_times[early_tap]) * | ||
| 283 | state.early_gains[early_tap]}; | ||
| 284 | output_samples[tap_indexes[early_tap]] += sample; | ||
| 285 | if constexpr (NumChannels == 6) { | ||
| 286 | output_samples[static_cast<u32>(Channels::LFE)] += sample; | ||
| 287 | } | ||
| 288 | } | ||
| 289 | |||
| 290 | if constexpr (NumChannels == 6) { | ||
| 291 | output_samples[static_cast<u32>(Channels::LFE)] *= 0.2f; | ||
| 292 | } | ||
| 293 | |||
| 294 | Common::FixedPoint<50, 14> input_sample{}; | ||
| 295 | for (u32 channel = 0; channel < NumChannels; channel++) { | ||
| 296 | input_sample += inputs[channel][sample_index]; | ||
| 297 | } | ||
| 298 | |||
| 299 | input_sample *= 64; | ||
| 300 | input_sample *= Common::FixedPoint<50, 14>::from_base(params.base_gain); | ||
| 301 | state.pre_delay_line.Write(input_sample); | ||
| 302 | |||
| 303 | for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { | ||
| 304 | state.prev_feedback_output[i] = | ||
| 305 | state.prev_feedback_output[i] * state.hf_decay_prev_gain[i] + | ||
| 306 | state.fdn_delay_lines[i].Read() * state.hf_decay_gain[i]; | ||
| 307 | } | ||
| 308 | |||
| 309 | Common::FixedPoint<50, 14> pre_delay_sample{ | ||
| 310 | state.pre_delay_line.Read() * Common::FixedPoint<50, 14>::from_base(params.late_gain)}; | ||
| 311 | |||
| 312 | std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> mix_matrix{ | ||
| 313 | state.prev_feedback_output[2] + state.prev_feedback_output[1] + pre_delay_sample, | ||
| 314 | -state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample, | ||
| 315 | state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample, | ||
| 316 | state.prev_feedback_output[1] - state.prev_feedback_output[2] + pre_delay_sample, | ||
| 317 | }; | ||
| 318 | |||
| 319 | std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> allpass_samples{}; | ||
| 320 | for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { | ||
| 321 | allpass_samples[i] = Axfx2AllPassTick(state.decay_delay_lines[i], | ||
| 322 | state.fdn_delay_lines[i], mix_matrix[i]); | ||
| 323 | } | ||
| 324 | |||
| 325 | const auto dry_gain{Common::FixedPoint<50, 14>::from_base(params.dry_gain)}; | ||
| 326 | const auto wet_gain{Common::FixedPoint<50, 14>::from_base(params.wet_gain)}; | ||
| 327 | |||
| 328 | if constexpr (NumChannels == 6) { | ||
| 329 | const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{ | ||
| 330 | allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3], | ||
| 331 | allpass_samples[3], allpass_samples[2], allpass_samples[3], | ||
| 332 | }; | ||
| 333 | |||
| 334 | for (u32 channel = 0; channel < NumChannels; channel++) { | ||
| 335 | auto in_sample{inputs[channel][sample_index] * dry_gain}; | ||
| 336 | |||
| 337 | Common::FixedPoint<50, 14> allpass{}; | ||
| 338 | if (channel == static_cast<u32>(Channels::Center)) { | ||
| 339 | allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f); | ||
| 340 | } else { | ||
| 341 | allpass = allpass_outputs[channel]; | ||
| 342 | } | ||
| 343 | |||
| 344 | auto out_sample{((output_samples[channel] + allpass) * wet_gain) / 64}; | ||
| 345 | outputs[channel][sample_index] = (in_sample + out_sample).to_int(); | ||
| 346 | } | ||
| 347 | } else { | ||
| 348 | for (u32 channel = 0; channel < NumChannels; channel++) { | ||
| 349 | auto in_sample{inputs[channel][sample_index] * dry_gain}; | ||
| 350 | auto out_sample{((output_samples[channel] + allpass_samples[channel]) * wet_gain) / | ||
| 351 | 64}; | ||
| 352 | outputs[channel][sample_index] = (in_sample + out_sample).to_int(); | ||
| 353 | } | ||
| 354 | } | ||
| 355 | } | ||
| 356 | } | ||
| 357 | |||
| 358 | /** | ||
| 359 | * Apply a Reverb if enabled, according to the current state, on the input mix buffers, | ||
| 360 | * saving the results to the output mix buffers. | ||
| 361 | * | ||
| 362 | * @param params - Input parameters to use. | ||
| 363 | * @param state - State to use, must be initialized (see InitializeReverbEffect). | ||
| 364 | * @param enabled - If enabled, delay will be applied, otherwise input is copied to output. | ||
| 365 | * @param inputs - Input mix buffers to performan the reverb on. | ||
| 366 | * @param outputs - Output mix buffers to receive the reverbed samples. | ||
| 367 | * @param sample_count - Number of samples to process. | ||
| 368 | */ | ||
| 369 | static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state, | ||
| 370 | const bool enabled, std::vector<std::span<const s32>>& inputs, | ||
| 371 | std::vector<std::span<s32>>& outputs, const u32 sample_count) { | ||
| 372 | if (enabled) { | ||
| 373 | switch (params.channel_count) { | ||
| 374 | case 0: | ||
| 375 | return; | ||
| 376 | case 1: | ||
| 377 | ApplyReverbEffect<1>(params, state, inputs, outputs, sample_count); | ||
| 378 | break; | ||
| 379 | case 2: | ||
| 380 | ApplyReverbEffect<2>(params, state, inputs, outputs, sample_count); | ||
| 381 | break; | ||
| 382 | case 4: | ||
| 383 | ApplyReverbEffect<4>(params, state, inputs, outputs, sample_count); | ||
| 384 | break; | ||
| 385 | case 6: | ||
| 386 | ApplyReverbEffect<6>(params, state, inputs, outputs, sample_count); | ||
| 387 | break; | ||
| 388 | default: | ||
| 389 | ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count); | ||
| 390 | break; | ||
| 391 | } | ||
| 392 | } else { | ||
| 393 | ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count); | ||
| 394 | } | ||
| 395 | } | ||
| 396 | |||
| 397 | void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 398 | std::string& string) { | ||
| 399 | string += fmt::format( | ||
| 400 | "ReverbCommand\n\tenabled {} long_size_pre_delay_supported {}\n\tinputs: ", effect_enabled, | ||
| 401 | long_size_pre_delay_supported); | ||
| 402 | for (u32 i = 0; i < MaxChannels; i++) { | ||
| 403 | string += fmt::format("{:02X}, ", inputs[i]); | ||
| 404 | } | ||
| 405 | string += "\n\toutputs: "; | ||
| 406 | for (u32 i = 0; i < MaxChannels; i++) { | ||
| 407 | string += fmt::format("{:02X}, ", outputs[i]); | ||
| 408 | } | ||
| 409 | string += "\n"; | ||
| 410 | } | ||
| 411 | |||
| 412 | void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 413 | std::vector<std::span<const s32>> input_buffers(parameter.channel_count); | ||
| 414 | std::vector<std::span<s32>> output_buffers(parameter.channel_count); | ||
| 415 | |||
| 416 | for (u32 i = 0; i < parameter.channel_count; i++) { | ||
| 417 | input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, | ||
| 418 | processor.sample_count); | ||
| 419 | output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, | ||
| 420 | processor.sample_count); | ||
| 421 | } | ||
| 422 | |||
| 423 | auto state_{reinterpret_cast<ReverbInfo::State*>(state)}; | ||
| 424 | |||
| 425 | if (effect_enabled) { | ||
| 426 | if (parameter.state == ReverbInfo::ParameterState::Updating) { | ||
| 427 | UpdateReverbEffectParameter(parameter, *state_); | ||
| 428 | } else if (parameter.state == ReverbInfo::ParameterState::Initialized) { | ||
| 429 | InitializeReverbEffect(parameter, *state_, workbuffer, long_size_pre_delay_supported); | ||
| 430 | } | ||
| 431 | } | ||
| 432 | ApplyReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, | ||
| 433 | processor.sample_count); | ||
| 434 | } | ||
| 435 | |||
| 436 | bool ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 437 | return true; | ||
| 438 | } | ||
| 439 | |||
| 440 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <string> | ||
| 8 | |||
| 9 | #include "audio_core/renderer/command/icommand.h" | ||
| 10 | #include "audio_core/renderer/effect/reverb.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | ||
| 16 | } | ||
| 17 | |||
| 18 | /** | ||
| 19 | * AudioRenderer command for a Reverb effect. Apply a reverb to inputs mix buffer, outputs receives | ||
| 20 | * the results. | ||
| 21 | */ | ||
| 22 | struct ReverbCommand : ICommand { | ||
| 23 | /** | ||
| 24 | * Print this command's information to a string. | ||
| 25 | * | ||
| 26 | * @param processor - The CommandListProcessor processing this command. | ||
| 27 | * @param string - The string to print into. | ||
| 28 | */ | ||
| 29 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Process this command. | ||
| 33 | * | ||
| 34 | * @param processor - The CommandListProcessor processing this command. | ||
| 35 | */ | ||
| 36 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 37 | |||
| 38 | /** | ||
| 39 | * Verify this command's data is valid. | ||
| 40 | * | ||
| 41 | * @param processor - The CommandListProcessor processing this command. | ||
| 42 | * @return True if the command is valid, otherwise false. | ||
| 43 | */ | ||
| 44 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 45 | |||
| 46 | /// Input mix buffer offsets for each channel | ||
| 47 | std::array<s16, MaxChannels> inputs; | ||
| 48 | /// Output mix buffer offsets for each channel | ||
| 49 | std::array<s16, MaxChannels> outputs; | ||
| 50 | /// Input parameters | ||
| 51 | ReverbInfo::ParameterVersion2 parameter; | ||
| 52 | /// State, updated each call | ||
| 53 | CpuAddr state; | ||
| 54 | /// Game-supplied workbuffer (Unused) | ||
| 55 | CpuAddr workbuffer; | ||
| 56 | /// Is this effect enabled? | ||
| 57 | bool effect_enabled; | ||
| 58 | /// Is a longer pre-delay time supported? | ||
| 59 | bool long_size_pre_delay_supported; | ||
| 60 | }; | ||
| 61 | |||
| 62 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "audio_core/common/common.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | namespace ADSP { | ||
| 11 | class CommandListProcessor; | ||
| 12 | } | ||
| 13 | |||
| 14 | enum class CommandId : u8 { | ||
| 15 | /* 0x00 */ Invalid, | ||
| 16 | /* 0x01 */ DataSourcePcmInt16Version1, | ||
| 17 | /* 0x02 */ DataSourcePcmInt16Version2, | ||
| 18 | /* 0x03 */ DataSourcePcmFloatVersion1, | ||
| 19 | /* 0x04 */ DataSourcePcmFloatVersion2, | ||
| 20 | /* 0x05 */ DataSourceAdpcmVersion1, | ||
| 21 | /* 0x06 */ DataSourceAdpcmVersion2, | ||
| 22 | /* 0x07 */ Volume, | ||
| 23 | /* 0x08 */ VolumeRamp, | ||
| 24 | /* 0x09 */ BiquadFilter, | ||
| 25 | /* 0x0A */ Mix, | ||
| 26 | /* 0x0B */ MixRamp, | ||
| 27 | /* 0x0C */ MixRampGrouped, | ||
| 28 | /* 0x0D */ DepopPrepare, | ||
| 29 | /* 0x0E */ DepopForMixBuffers, | ||
| 30 | /* 0x0F */ Delay, | ||
| 31 | /* 0x10 */ Upsample, | ||
| 32 | /* 0x11 */ DownMix6chTo2ch, | ||
| 33 | /* 0x12 */ Aux, | ||
| 34 | /* 0x13 */ DeviceSink, | ||
| 35 | /* 0x14 */ CircularBufferSink, | ||
| 36 | /* 0x15 */ Reverb, | ||
| 37 | /* 0x16 */ I3dl2Reverb, | ||
| 38 | /* 0x17 */ Performance, | ||
| 39 | /* 0x18 */ ClearMixBuffer, | ||
| 40 | /* 0x19 */ CopyMixBuffer, | ||
| 41 | /* 0x1A */ LightLimiterVersion1, | ||
| 42 | /* 0x1B */ LightLimiterVersion2, | ||
| 43 | /* 0x1C */ MultiTapBiquadFilter, | ||
| 44 | /* 0x1D */ Capture, | ||
| 45 | /* 0x1E */ Compressor, | ||
| 46 | }; | ||
| 47 | |||
| 48 | constexpr u32 CommandMagic{0xCAFEBABE}; | ||
| 49 | |||
| 50 | /** | ||
| 51 | * A command, generated by the host, and processed by the ADSP's AudioRenderer. | ||
| 52 | */ | ||
| 53 | struct ICommand { | ||
| 54 | virtual ~ICommand() = default; | ||
| 55 | |||
| 56 | /** | ||
| 57 | * Print this command's information to a string. | ||
| 58 | * | ||
| 59 | * @param processor - The CommandListProcessor processing this command. | ||
| 60 | * @param string - The string to print into. | ||
| 61 | */ | ||
| 62 | virtual void Dump(const ADSP::CommandListProcessor& processor, std::string& string) = 0; | ||
| 63 | |||
| 64 | /** | ||
| 65 | * Process this command. | ||
| 66 | * | ||
| 67 | * @param processor - The CommandListProcessor processing this command. | ||
| 68 | */ | ||
| 69 | virtual void Process(const ADSP::CommandListProcessor& processor) = 0; | ||
| 70 | |||
| 71 | /** | ||
| 72 | * Verify this command's data is valid. | ||
| 73 | * | ||
| 74 | * @param processor - The CommandListProcessor processing this command. | ||
| 75 | * @return True if the command is valid, otherwise false. | ||
| 76 | */ | ||
| 77 | virtual bool Verify(const ADSP::CommandListProcessor& processor) = 0; | ||
| 78 | |||
| 79 | /// Command magic 0xCAFEBABE | ||
| 80 | u32 magic{}; | ||
| 81 | /// Command enabled | ||
| 82 | bool enabled{}; | ||
| 83 | /// Type of this command (see CommandId) | ||
| 84 | CommandId type{}; | ||
| 85 | /// Size of this command | ||
| 86 | s16 size{}; | ||
| 87 | /// Estimated processing time for this command | ||
| 88 | u32 estimated_process_time{}; | ||
| 89 | /// Node id of the voice or mix this command was generated from | ||
| 90 | u32 node_id{}; | ||
| 91 | }; | ||
| 92 | |||
| 93 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <string> | ||
| 5 | |||
| 6 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 7 | #include "audio_core/renderer/command/mix/clear_mix.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | void ClearMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 12 | std::string& string) { | ||
| 13 | string += fmt::format("ClearMixBufferCommand\n"); | ||
| 14 | } | ||
| 15 | |||
| 16 | void ClearMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 17 | memset(processor.mix_buffers.data(), 0, processor.mix_buffers.size_bytes()); | ||
| 18 | } | ||
| 19 | |||
| 20 | bool ClearMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 21 | return true; | ||
| 22 | } | ||
| 23 | |||
| 24 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | ||
| 14 | } | ||
| 15 | |||
| 16 | /** | ||
| 17 | * AudioRenderer command for a clearing the mix buffers. | ||
| 18 | * Used at the start of each command list. | ||
| 19 | */ | ||
| 20 | struct ClearMixBufferCommand : ICommand { | ||
| 21 | /** | ||
| 22 | * Print this command's information to a string. | ||
| 23 | * | ||
| 24 | * @param processor - The CommandListProcessor processing this command. | ||
| 25 | * @param string - The string to print into. | ||
| 26 | */ | ||
| 27 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Process this command. | ||
| 31 | * | ||
| 32 | * @param processor - The CommandListProcessor processing this command. | ||
| 33 | */ | ||
| 34 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Verify this command's data is valid. | ||
| 38 | * | ||
| 39 | * @param processor - The CommandListProcessor processing this command. | ||
| 40 | * @return True if the command is valid, otherwise false. | ||
| 41 | */ | ||
| 42 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 43 | }; | ||
| 44 | |||
| 45 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 5 | #include "audio_core/renderer/command/mix/copy_mix.h" | ||
| 6 | |||
| 7 | namespace AudioCore::AudioRenderer { | ||
| 8 | |||
| 9 | void CopyMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 10 | std::string& string) { | ||
| 11 | string += fmt::format("CopyMixBufferCommand\n\tinput {:02X} output {:02X}\n", input_index, | ||
| 12 | output_index); | ||
| 13 | } | ||
| 14 | |||
| 15 | void CopyMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 16 | auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, | ||
| 17 | processor.sample_count)}; | ||
| 18 | auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, | ||
| 19 | processor.sample_count)}; | ||
| 20 | std::memcpy(output.data(), input.data(), processor.sample_count * sizeof(s32)); | ||
| 21 | } | ||
| 22 | |||
| 23 | bool CopyMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 24 | return true; | ||
| 25 | } | ||
| 26 | |||
| 27 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | ||
| 14 | } | ||
| 15 | |||
| 16 | /** | ||
| 17 | * AudioRenderer command for a copying a mix buffer from input to output. | ||
| 18 | */ | ||
| 19 | struct CopyMixBufferCommand : ICommand { | ||
| 20 | /** | ||
| 21 | * Print this command's information to a string. | ||
| 22 | * | ||
| 23 | * @param processor - The CommandListProcessor processing this command. | ||
| 24 | * @param string - The string to print into. | ||
| 25 | */ | ||
| 26 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Process this command. | ||
| 30 | * | ||
| 31 | * @param processor - The CommandListProcessor processing this command. | ||
| 32 | */ | ||
| 33 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Verify this command's data is valid. | ||
| 37 | * | ||
| 38 | * @param processor - The CommandListProcessor processing this command. | ||
| 39 | * @return True if the command is valid, otherwise false. | ||
| 40 | */ | ||
| 41 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 42 | |||
| 43 | /// Input mix buffer index | ||
| 44 | s16 input_index; | ||
| 45 | /// Output mix buffer index | ||
| 46 | s16 output_index; | ||
| 47 | }; | ||
| 48 | |||
| 49 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/common/common.h" | ||
| 5 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 6 | #include "audio_core/renderer/command/mix/depop_for_mix_buffers.h" | ||
| 7 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | /** | ||
| 10 | * Apply depopping. Add the depopped sample to each incoming new sample, decaying it each time | ||
| 11 | * according to decay. | ||
| 12 | * | ||
| 13 | * @param output - Output buffer to be depopped. | ||
| 14 | * @param depop_sample - Depopped sample to apply to output samples. | ||
| 15 | * @param decay_ - Amount to decay the depopped sample for every output sample. | ||
| 16 | * @param sample_count - Samples to process. | ||
| 17 | * @return Final decayed depop sample. | ||
| 18 | */ | ||
| 19 | static s32 ApplyDepopMix(std::span<s32> output, const s32 depop_sample, | ||
| 20 | Common::FixedPoint<49, 15>& decay_, const u32 sample_count) { | ||
| 21 | auto sample{std::abs(depop_sample)}; | ||
| 22 | auto decay{decay_.to_raw()}; | ||
| 23 | |||
| 24 | if (depop_sample <= 0) { | ||
| 25 | for (u32 i = 0; i < sample_count; i++) { | ||
| 26 | sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15); | ||
| 27 | output[i] -= sample; | ||
| 28 | } | ||
| 29 | return -sample; | ||
| 30 | } else { | ||
| 31 | for (u32 i = 0; i < sample_count; i++) { | ||
| 32 | sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15); | ||
| 33 | output[i] += sample; | ||
| 34 | } | ||
| 35 | return sample; | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | void DepopForMixBuffersCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 40 | std::string& string) { | ||
| 41 | string += fmt::format("DepopForMixBuffersCommand\n\tinput {:02X} count {} decay {}\n", input, | ||
| 42 | count, decay.to_float()); | ||
| 43 | } | ||
| 44 | |||
| 45 | void DepopForMixBuffersCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 46 | auto end_index{std::min(processor.buffer_count, input + count)}; | ||
| 47 | std::span<s32> depop_buff{reinterpret_cast<s32*>(depop_buffer), end_index}; | ||
| 48 | |||
| 49 | for (u32 index = input; index < end_index; index++) { | ||
| 50 | const auto depop_sample{depop_buff[index]}; | ||
| 51 | if (depop_sample != 0) { | ||
| 52 | auto input_buffer{processor.mix_buffers.subspan(index * processor.sample_count, | ||
| 53 | processor.sample_count)}; | ||
| 54 | depop_buff[index] = | ||
| 55 | ApplyDepopMix(input_buffer, depop_sample, decay, processor.sample_count); | ||
| 56 | } | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | bool DepopForMixBuffersCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 61 | return true; | ||
| 62 | } | ||
| 63 | |||
| 64 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | #include "common/fixed_point.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | namespace ADSP { | ||
| 14 | class CommandListProcessor; | ||
| 15 | } | ||
| 16 | |||
| 17 | /** | ||
| 18 | * AudioRenderer command for depopping a mix buffer. | ||
| 19 | * Adds a cumulation of previous samples to the current mix buffer with a decay. | ||
| 20 | */ | ||
| 21 | struct DepopForMixBuffersCommand : ICommand { | ||
| 22 | /** | ||
| 23 | * Print this command's information to a string. | ||
| 24 | * | ||
| 25 | * @param processor - The CommandListProcessor processing this command. | ||
| 26 | * @param string - The string to print into. | ||
| 27 | */ | ||
| 28 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Process this command. | ||
| 32 | * | ||
| 33 | * @param processor - The CommandListProcessor processing this command. | ||
| 34 | */ | ||
| 35 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Verify this command's data is valid. | ||
| 39 | * | ||
| 40 | * @param processor - The CommandListProcessor processing this command. | ||
| 41 | * @return True if the command is valid, otherwise false. | ||
| 42 | */ | ||
| 43 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 44 | |||
| 45 | /// Starting input mix buffer index | ||
| 46 | u32 input; | ||
| 47 | /// Number of mix buffers to depop | ||
| 48 | u32 count; | ||
| 49 | /// Amount to decay the depop sample for each new sample | ||
| 50 | Common::FixedPoint<49, 15> decay; | ||
| 51 | /// Address of the depop buffer, holding the last sample for every mix buffer | ||
| 52 | CpuAddr depop_buffer; | ||
| 53 | }; | ||
| 54 | |||
| 55 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 5 | #include "audio_core/renderer/command/mix/depop_prepare.h" | ||
| 6 | #include "audio_core/renderer/voice/voice_state.h" | ||
| 7 | #include "common/fixed_point.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 12 | std::string& string) { | ||
| 13 | string += fmt::format("DepopPrepareCommand\n\tinputs: "); | ||
| 14 | for (u32 i = 0; i < buffer_count; i++) { | ||
| 15 | string += fmt::format("{:02X}, ", inputs[i]); | ||
| 16 | } | ||
| 17 | string += "\n"; | ||
| 18 | } | ||
| 19 | |||
| 20 | void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 21 | auto samples{reinterpret_cast<s32*>(previous_samples)}; | ||
| 22 | auto buffer{std::span(reinterpret_cast<s32*>(depop_buffer), buffer_count)}; | ||
| 23 | |||
| 24 | for (u32 i = 0; i < buffer_count; i++) { | ||
| 25 | if (samples[i]) { | ||
| 26 | buffer[inputs[i]] += samples[i]; | ||
| 27 | samples[i] = 0; | ||
| 28 | } | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | bool DepopPrepareCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 33 | return true; | ||
| 34 | } | ||
| 35 | |||
| 36 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | ||
| 14 | } | ||
| 15 | |||
| 16 | /** | ||
| 17 | * AudioRenderer command for preparing depop. | ||
| 18 | * Adds the previusly output last samples to the depop buffer. | ||
| 19 | */ | ||
| 20 | struct DepopPrepareCommand : ICommand { | ||
| 21 | /** | ||
| 22 | * Print this command's information to a string. | ||
| 23 | * | ||
| 24 | * @param processor - The CommandListProcessor processing this command. | ||
| 25 | * @param string - The string to print into. | ||
| 26 | */ | ||
| 27 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Process this command. | ||
| 31 | * | ||
| 32 | * @param processor - The CommandListProcessor processing this command. | ||
| 33 | */ | ||
| 34 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Verify this command's data is valid. | ||
| 38 | * | ||
| 39 | * @param processor - The CommandListProcessor processing this command. | ||
| 40 | * @return True if the command is valid, otherwise false. | ||
| 41 | */ | ||
| 42 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 43 | |||
| 44 | /// Depop buffer offset for each mix buffer | ||
| 45 | std::array<s16, MaxMixBuffers> inputs; | ||
| 46 | /// Pointer to the previous mix buffer samples | ||
| 47 | CpuAddr previous_samples; | ||
| 48 | /// Number of mix buffers to use | ||
| 49 | u32 buffer_count; | ||
| 50 | /// Pointer to the current depop values | ||
| 51 | CpuAddr depop_buffer; | ||
| 52 | }; | ||
| 53 | |||
| 54 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <limits> | ||
| 6 | #include <span> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 9 | #include "audio_core/renderer/command/mix/mix.h" | ||
| 10 | #include "common/fixed_point.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | /** | ||
| 14 | * Mix input mix buffer into output mix buffer, with volume applied to the input. | ||
| 15 | * | ||
| 16 | * @tparam Q - Number of bits for fixed point operations. | ||
| 17 | * @param output - Output mix buffer. | ||
| 18 | * @param input - Input mix buffer. | ||
| 19 | * @param volume - Volume applied to the input. | ||
| 20 | * @param sample_count - Number of samples to process. | ||
| 21 | */ | ||
| 22 | template <size_t Q> | ||
| 23 | static void ApplyMix(std::span<s32> output, std::span<const s32> input, const f32 volume_, | ||
| 24 | const u32 sample_count) { | ||
| 25 | const Common::FixedPoint<64 - Q, Q> volume{volume_}; | ||
| 26 | for (u32 i = 0; i < sample_count; i++) { | ||
| 27 | output[i] = (output[i] + input[i] * volume).to_int(); | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | void MixCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 32 | std::string& string) { | ||
| 33 | string += fmt::format("MixCommand"); | ||
| 34 | string += fmt::format("\n\tinput {:02X}", input_index); | ||
| 35 | string += fmt::format("\n\toutput {:02X}", output_index); | ||
| 36 | string += fmt::format("\n\tvolume {:.8f}", volume); | ||
| 37 | string += "\n"; | ||
| 38 | } | ||
| 39 | |||
| 40 | void MixCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 41 | auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, | ||
| 42 | processor.sample_count)}; | ||
| 43 | auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, | ||
| 44 | processor.sample_count)}; | ||
| 45 | |||
| 46 | // If volume is 0, nothing will be added to the output, so just skip. | ||
| 47 | if (volume == 0.0f) { | ||
| 48 | return; | ||
| 49 | } | ||
| 50 | |||
| 51 | switch (precision) { | ||
| 52 | case 15: | ||
| 53 | ApplyMix<15>(output, input, volume, processor.sample_count); | ||
| 54 | break; | ||
| 55 | |||
| 56 | case 23: | ||
| 57 | ApplyMix<23>(output, input, volume, processor.sample_count); | ||
| 58 | break; | ||
| 59 | |||
| 60 | default: | ||
| 61 | LOG_ERROR(Service_Audio, "Invalid precision {}", precision); | ||
| 62 | break; | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | bool MixCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 67 | return true; | ||
| 68 | } | ||
| 69 | |||
| 70 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | ||
| 14 | } | ||
| 15 | |||
| 16 | /** | ||
| 17 | * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume | ||
| 18 | * applied to the input. | ||
| 19 | */ | ||
| 20 | struct MixCommand : ICommand { | ||
| 21 | /** | ||
| 22 | * Print this command's information to a string. | ||
| 23 | * | ||
| 24 | * @param processor - The CommandListProcessor processing this command. | ||
| 25 | * @param string - The string to print into. | ||
| 26 | */ | ||
| 27 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Process this command. | ||
| 31 | * | ||
| 32 | * @param processor - The CommandListProcessor processing this command. | ||
| 33 | */ | ||
| 34 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Verify this command's data is valid. | ||
| 38 | * | ||
| 39 | * @param processor - The CommandListProcessor processing this command. | ||
| 40 | * @return True if the command is valid, otherwise false. | ||
| 41 | */ | ||
| 42 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 43 | |||
| 44 | /// Fixed point precision | ||
| 45 | u8 precision; | ||
| 46 | /// Input mix buffer index | ||
| 47 | s16 input_index; | ||
| 48 | /// Output mix buffer index | ||
| 49 | s16 output_index; | ||
| 50 | /// Mix volume applied to the input | ||
| 51 | f32 volume; | ||
| 52 | }; | ||
| 53 | |||
| 54 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 5 | #include "audio_core/renderer/command/mix/mix_ramp.h" | ||
| 6 | #include "common/fixed_point.h" | ||
| 7 | #include "common/logging/log.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | /** | ||
| 11 | * Mix input mix buffer into output mix buffer, with volume applied to the input. | ||
| 12 | * | ||
| 13 | * @tparam Q - Number of bits for fixed point operations. | ||
| 14 | * @param output - Output mix buffer. | ||
| 15 | * @param input - Input mix buffer. | ||
| 16 | * @param volume - Volume applied to the input. | ||
| 17 | * @param ramp - Ramp applied to volume every sample. | ||
| 18 | * @param sample_count - Number of samples to process. | ||
| 19 | * @return The final gained input sample, used for depopping. | ||
| 20 | */ | ||
| 21 | template <size_t Q> | ||
| 22 | s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, | ||
| 23 | const f32 ramp_, const u32 sample_count) { | ||
| 24 | Common::FixedPoint<64 - Q, Q> volume{volume_}; | ||
| 25 | Common::FixedPoint<64 - Q, Q> sample{0}; | ||
| 26 | |||
| 27 | if (ramp_ == 0.0f) { | ||
| 28 | for (u32 i = 0; i < sample_count; i++) { | ||
| 29 | sample = input[i] * volume; | ||
| 30 | output[i] = (output[i] + sample).to_int(); | ||
| 31 | } | ||
| 32 | } else { | ||
| 33 | Common::FixedPoint<64 - Q, Q> ramp{ramp_}; | ||
| 34 | for (u32 i = 0; i < sample_count; i++) { | ||
| 35 | sample = input[i] * volume; | ||
| 36 | output[i] = (output[i] + sample).to_int(); | ||
| 37 | volume += ramp; | ||
| 38 | } | ||
| 39 | } | ||
| 40 | return sample.to_int(); | ||
| 41 | } | ||
| 42 | |||
| 43 | template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, const f32, const f32, | ||
| 44 | const u32); | ||
| 45 | template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, const f32, const f32, | ||
| 46 | const u32); | ||
| 47 | |||
| 48 | void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { | ||
| 49 | const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; | ||
| 50 | string += fmt::format("MixRampCommand"); | ||
| 51 | string += fmt::format("\n\tinput {:02X}", input_index); | ||
| 52 | string += fmt::format("\n\toutput {:02X}", output_index); | ||
| 53 | string += fmt::format("\n\tvolume {:.8f}", volume); | ||
| 54 | string += fmt::format("\n\tprev_volume {:.8f}", prev_volume); | ||
| 55 | string += fmt::format("\n\tramp {:.8f}", ramp); | ||
| 56 | string += "\n"; | ||
| 57 | } | ||
| 58 | |||
| 59 | void MixRampCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 60 | auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, | ||
| 61 | processor.sample_count)}; | ||
| 62 | auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, | ||
| 63 | processor.sample_count)}; | ||
| 64 | const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; | ||
| 65 | auto prev_sample_ptr{reinterpret_cast<s32*>(previous_sample)}; | ||
| 66 | |||
| 67 | // If previous volume and ramp are both 0, nothing will be added to the output, so just skip. | ||
| 68 | if (prev_volume == 0.0f && ramp == 0.0f) { | ||
| 69 | *prev_sample_ptr = 0; | ||
| 70 | return; | ||
| 71 | } | ||
| 72 | |||
| 73 | switch (precision) { | ||
| 74 | case 15: | ||
| 75 | *prev_sample_ptr = | ||
| 76 | ApplyMixRamp<15>(output, input, prev_volume, ramp, processor.sample_count); | ||
| 77 | break; | ||
| 78 | |||
| 79 | case 23: | ||
| 80 | *prev_sample_ptr = | ||
| 81 | ApplyMixRamp<23>(output, input, prev_volume, ramp, processor.sample_count); | ||
| 82 | break; | ||
| 83 | |||
| 84 | default: | ||
| 85 | LOG_ERROR(Service_Audio, "Invalid precision {}", precision); | ||
| 86 | break; | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | bool MixRampCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 91 | return true; | ||
| 92 | } | ||
| 93 | |||
| 94 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <span> | ||
| 7 | #include <string> | ||
| 8 | |||
| 9 | #include "audio_core/renderer/command/icommand.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | namespace ADSP { | ||
| 14 | class CommandListProcessor; | ||
| 15 | } | ||
| 16 | |||
| 17 | /** | ||
| 18 | * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume | ||
| 19 | * applied to the input, and volume ramping to smooth out the transition. | ||
| 20 | */ | ||
| 21 | struct MixRampCommand : ICommand { | ||
| 22 | /** | ||
| 23 | * Print this command's information to a string. | ||
| 24 | * | ||
| 25 | * @param processor - The CommandListProcessor processing this command. | ||
| 26 | * @param string - The string to print into. | ||
| 27 | */ | ||
| 28 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Process this command. | ||
| 32 | * | ||
| 33 | * @param processor - The CommandListProcessor processing this command. | ||
| 34 | */ | ||
| 35 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Verify this command's data is valid. | ||
| 39 | * | ||
| 40 | * @param processor - The CommandListProcessor processing this command. | ||
| 41 | * @return True if the command is valid, otherwise false. | ||
| 42 | */ | ||
| 43 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 44 | |||
| 45 | /// Fixed point precision | ||
| 46 | u8 precision; | ||
| 47 | /// Input mix buffer index | ||
| 48 | s16 input_index; | ||
| 49 | /// Output mix buffer index | ||
| 50 | s16 output_index; | ||
| 51 | /// Previous mix volume | ||
| 52 | f32 prev_volume; | ||
| 53 | /// Current mix volume | ||
| 54 | f32 volume; | ||
| 55 | /// Pointer to the previous sample buffer, used for depopping | ||
| 56 | CpuAddr previous_sample; | ||
| 57 | }; | ||
| 58 | |||
| 59 | /** | ||
| 60 | * Mix input mix buffer into output mix buffer, with volume applied to the input. | ||
| 61 | * @tparam Q - Number of bits for fixed point operations. | ||
| 62 | * @param output - Output mix buffer. | ||
| 63 | * @param input - Input mix buffer. | ||
| 64 | * @param volume - Volume applied to the input. | ||
| 65 | * @param ramp - Ramp applied to volume every sample. | ||
| 66 | * @param sample_count - Number of samples to process. | ||
| 67 | * @return The final gained input sample, used for depopping. | ||
| 68 | */ | ||
| 69 | template <size_t Q> | ||
| 70 | s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, | ||
| 71 | const f32 ramp_, const u32 sample_count); | ||
| 72 | |||
| 73 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 5 | #include "audio_core/renderer/command/mix/mix_ramp.h" | ||
| 6 | #include "audio_core/renderer/command/mix/mix_ramp_grouped.h" | ||
| 7 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | void MixRampGroupedCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { | ||
| 11 | string += "MixRampGroupedCommand"; | ||
| 12 | for (u32 i = 0; i < buffer_count; i++) { | ||
| 13 | string += fmt::format("\n\t{}", i); | ||
| 14 | const auto ramp{(volumes[i] - prev_volumes[i]) / static_cast<f32>(processor.sample_count)}; | ||
| 15 | string += fmt::format("\n\t\tinput {:02X}", inputs[i]); | ||
| 16 | string += fmt::format("\n\t\toutput {:02X}", outputs[i]); | ||
| 17 | string += fmt::format("\n\t\tvolume {:.8f}", volumes[i]); | ||
| 18 | string += fmt::format("\n\t\tprev_volume {:.8f}", prev_volumes[i]); | ||
| 19 | string += fmt::format("\n\t\tramp {:.8f}", ramp); | ||
| 20 | string += "\n"; | ||
| 21 | } | ||
| 22 | } | ||
| 23 | |||
| 24 | void MixRampGroupedCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 25 | std::span<s32> prev_samples = {reinterpret_cast<s32*>(previous_samples), MaxMixBuffers}; | ||
| 26 | |||
| 27 | for (u32 i = 0; i < buffer_count; i++) { | ||
| 28 | auto last_sample{0}; | ||
| 29 | if (prev_volumes[i] != 0.0f || volumes[i] != 0.0f) { | ||
| 30 | const auto output{processor.mix_buffers.subspan(outputs[i] * processor.sample_count, | ||
| 31 | processor.sample_count)}; | ||
| 32 | const auto input{processor.mix_buffers.subspan(inputs[i] * processor.sample_count, | ||
| 33 | processor.sample_count)}; | ||
| 34 | const auto ramp{(volumes[i] - prev_volumes[i]) / | ||
| 35 | static_cast<f32>(processor.sample_count)}; | ||
| 36 | |||
| 37 | if (prev_volumes[i] == 0.0f && ramp == 0.0f) { | ||
| 38 | prev_samples[i] = 0; | ||
| 39 | continue; | ||
| 40 | } | ||
| 41 | |||
| 42 | switch (precision) { | ||
| 43 | case 15: | ||
| 44 | last_sample = | ||
| 45 | ApplyMixRamp<15>(output, input, prev_volumes[i], ramp, processor.sample_count); | ||
| 46 | break; | ||
| 47 | case 23: | ||
| 48 | last_sample = | ||
| 49 | ApplyMixRamp<23>(output, input, prev_volumes[i], ramp, processor.sample_count); | ||
| 50 | break; | ||
| 51 | default: | ||
| 52 | LOG_ERROR(Service_Audio, "Invalid precision {}", precision); | ||
| 53 | break; | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | prev_samples[i] = last_sample; | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | bool MixRampGroupedCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 62 | return true; | ||
| 63 | } | ||
| 64 | |||
| 65 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <string> | ||
| 8 | |||
| 9 | #include "audio_core/renderer/command/icommand.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | namespace ADSP { | ||
| 14 | class CommandListProcessor; | ||
| 15 | } | ||
| 16 | |||
| 17 | /** | ||
| 18 | * AudioRenderer command for mixing multiple input mix buffers to multiple output mix buffers, with | ||
| 19 | * a volume applied to the input, and volume ramping to smooth out the transition. | ||
| 20 | */ | ||
| 21 | struct MixRampGroupedCommand : ICommand { | ||
| 22 | /** | ||
| 23 | * Print this command's information to a string. | ||
| 24 | * | ||
| 25 | * @param processor - The CommandListProcessor processing this command. | ||
| 26 | * @param string - The string to print into. | ||
| 27 | */ | ||
| 28 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Process this command. | ||
| 32 | * | ||
| 33 | * @param processor - The CommandListProcessor processing this command. | ||
| 34 | */ | ||
| 35 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Verify this command's data is valid. | ||
| 39 | * | ||
| 40 | * @param processor - The CommandListProcessor processing this command. | ||
| 41 | * @return True if the command is valid, otherwise false. | ||
| 42 | */ | ||
| 43 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 44 | |||
| 45 | /// Fixed point precision | ||
| 46 | u8 precision; | ||
| 47 | /// Number of mix buffers to mix | ||
| 48 | u32 buffer_count; | ||
| 49 | /// Input mix buffer indexes for each mix buffer | ||
| 50 | std::array<s16, MaxMixBuffers> inputs; | ||
| 51 | /// Output mix buffer indexes for each mix buffer | ||
| 52 | std::array<s16, MaxMixBuffers> outputs; | ||
| 53 | /// Previous mix vloumes for each mix buffer | ||
| 54 | std::array<f32, MaxMixBuffers> prev_volumes; | ||
| 55 | /// Current mix vloumes for each mix buffer | ||
| 56 | std::array<f32, MaxMixBuffers> volumes; | ||
| 57 | /// Pointer to the previous sample buffer, used for depop | ||
| 58 | CpuAddr previous_samples; | ||
| 59 | }; | ||
| 60 | |||
| 61 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 5 | #include "audio_core/renderer/command/mix/volume.h" | ||
| 6 | #include "common/fixed_point.h" | ||
| 7 | #include "common/logging/log.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | /** | ||
| 11 | * Apply volume to the input mix buffer, saving to the output buffer. | ||
| 12 | * | ||
| 13 | * @tparam Q - Number of bits for fixed point operations. | ||
| 14 | * @param output - Output mix buffer. | ||
| 15 | * @param input - Input mix buffer. | ||
| 16 | * @param volume - Volume applied to the input. | ||
| 17 | * @param sample_count - Number of samples to process. | ||
| 18 | */ | ||
| 19 | template <size_t Q> | ||
| 20 | static void ApplyUniformGain(std::span<s32> output, std::span<const s32> input, const f32 volume, | ||
| 21 | const u32 sample_count) { | ||
| 22 | if (volume == 1.0f) { | ||
| 23 | std::memcpy(output.data(), input.data(), input.size_bytes()); | ||
| 24 | } else { | ||
| 25 | const Common::FixedPoint<64 - Q, Q> gain{volume}; | ||
| 26 | for (u32 i = 0; i < sample_count; i++) { | ||
| 27 | output[i] = (input[i] * gain).to_int(); | ||
| 28 | } | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | void VolumeCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 33 | std::string& string) { | ||
| 34 | string += fmt::format("VolumeCommand"); | ||
| 35 | string += fmt::format("\n\tinput {:02X}", input_index); | ||
| 36 | string += fmt::format("\n\toutput {:02X}", output_index); | ||
| 37 | string += fmt::format("\n\tvolume {:.8f}", volume); | ||
| 38 | string += "\n"; | ||
| 39 | } | ||
| 40 | |||
| 41 | void VolumeCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 42 | // If input and output buffers are the same, and the volume is 1.0f, this won't do | ||
| 43 | // anything, so just skip. | ||
| 44 | if (input_index == output_index && volume == 1.0f) { | ||
| 45 | return; | ||
| 46 | } | ||
| 47 | |||
| 48 | auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, | ||
| 49 | processor.sample_count)}; | ||
| 50 | auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, | ||
| 51 | processor.sample_count)}; | ||
| 52 | |||
| 53 | switch (precision) { | ||
| 54 | case 15: | ||
| 55 | ApplyUniformGain<15>(output, input, volume, processor.sample_count); | ||
| 56 | break; | ||
| 57 | |||
| 58 | case 23: | ||
| 59 | ApplyUniformGain<23>(output, input, volume, processor.sample_count); | ||
| 60 | break; | ||
| 61 | |||
| 62 | default: | ||
| 63 | LOG_ERROR(Service_Audio, "Invalid precision {}", precision); | ||
| 64 | break; | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | bool VolumeCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 69 | return true; | ||
| 70 | } | ||
| 71 | |||
| 72 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | ||
| 14 | } | ||
| 15 | |||
| 16 | /** | ||
| 17 | * AudioRenderer command for applying volume to a mix buffer. | ||
| 18 | */ | ||
| 19 | struct VolumeCommand : ICommand { | ||
| 20 | /** | ||
| 21 | * Print this command's information to a string. | ||
| 22 | * | ||
| 23 | * @param processor - The CommandListProcessor processing this command. | ||
| 24 | * @param string - The string to print into. | ||
| 25 | */ | ||
| 26 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Process this command. | ||
| 30 | * | ||
| 31 | * @param processor - The CommandListProcessor processing this command. | ||
| 32 | */ | ||
| 33 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Verify this command's data is valid. | ||
| 37 | * | ||
| 38 | * @param processor - The CommandListProcessor processing this command. | ||
| 39 | * @return True if the command is valid, otherwise false. | ||
| 40 | */ | ||
| 41 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 42 | |||
| 43 | /// Fixed point precision | ||
| 44 | u8 precision; | ||
| 45 | /// Input mix buffer index | ||
| 46 | s16 input_index; | ||
| 47 | /// Output mix buffer index | ||
| 48 | s16 output_index; | ||
| 49 | /// Mix volume applied to the input | ||
| 50 | f32 volume; | ||
| 51 | }; | ||
| 52 | |||
| 53 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 5 | #include "audio_core/renderer/command/mix/volume_ramp.h" | ||
| 6 | #include "common/fixed_point.h" | ||
| 7 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | /** | ||
| 10 | * Apply volume with ramping to the input mix buffer, saving to the output buffer. | ||
| 11 | * | ||
| 12 | * @tparam Q - Number of bits for fixed point operations. | ||
| 13 | * @param output - Output mix buffers. | ||
| 14 | * @param input - Input mix buffers. | ||
| 15 | * @param volume - Volume applied to the input. | ||
| 16 | * @param ramp - Ramp applied to volume every sample. | ||
| 17 | * @param sample_count - Number of samples to process. | ||
| 18 | */ | ||
| 19 | template <size_t Q> | ||
| 20 | static void ApplyLinearEnvelopeGain(std::span<s32> output, std::span<const s32> input, | ||
| 21 | const f32 volume, const f32 ramp_, const u32 sample_count) { | ||
| 22 | if (volume == 0.0f && ramp_ == 0.0f) { | ||
| 23 | std::memset(output.data(), 0, output.size_bytes()); | ||
| 24 | } else if (volume == 1.0f && ramp_ == 0.0f) { | ||
| 25 | std::memcpy(output.data(), input.data(), output.size_bytes()); | ||
| 26 | } else if (ramp_ == 0.0f) { | ||
| 27 | const Common::FixedPoint<64 - Q, Q> gain{volume}; | ||
| 28 | for (u32 i = 0; i < sample_count; i++) { | ||
| 29 | output[i] = (input[i] * gain).to_int(); | ||
| 30 | } | ||
| 31 | } else { | ||
| 32 | Common::FixedPoint<64 - Q, Q> gain{volume}; | ||
| 33 | const Common::FixedPoint<64 - Q, Q> ramp{ramp_}; | ||
| 34 | for (u32 i = 0; i < sample_count; i++) { | ||
| 35 | output[i] = (input[i] * gain).to_int(); | ||
| 36 | gain += ramp; | ||
| 37 | } | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | void VolumeRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { | ||
| 42 | const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; | ||
| 43 | string += fmt::format("VolumeRampCommand"); | ||
| 44 | string += fmt::format("\n\tinput {:02X}", input_index); | ||
| 45 | string += fmt::format("\n\toutput {:02X}", output_index); | ||
| 46 | string += fmt::format("\n\tvolume {:.8f}", volume); | ||
| 47 | string += fmt::format("\n\tprev_volume {:.8f}", prev_volume); | ||
| 48 | string += fmt::format("\n\tramp {:.8f}", ramp); | ||
| 49 | string += "\n"; | ||
| 50 | } | ||
| 51 | |||
| 52 | void VolumeRampCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 53 | auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, | ||
| 54 | processor.sample_count)}; | ||
| 55 | auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, | ||
| 56 | processor.sample_count)}; | ||
| 57 | const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; | ||
| 58 | |||
| 59 | // If input and output buffers are the same, and the volume is 1.0f, and there's no ramping, | ||
| 60 | // this won't do anything, so just skip. | ||
| 61 | if (input_index == output_index && prev_volume == 1.0f && ramp == 0.0f) { | ||
| 62 | return; | ||
| 63 | } | ||
| 64 | |||
| 65 | switch (precision) { | ||
| 66 | case 15: | ||
| 67 | ApplyLinearEnvelopeGain<15>(output, input, prev_volume, ramp, processor.sample_count); | ||
| 68 | break; | ||
| 69 | |||
| 70 | case 23: | ||
| 71 | ApplyLinearEnvelopeGain<23>(output, input, prev_volume, ramp, processor.sample_count); | ||
| 72 | break; | ||
| 73 | |||
| 74 | default: | ||
| 75 | LOG_ERROR(Service_Audio, "Invalid precision {}", precision); | ||
| 76 | break; | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | bool VolumeRampCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 81 | return true; | ||
| 82 | } | ||
| 83 | |||
| 84 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | ||
| 14 | } | ||
| 15 | |||
| 16 | /** | ||
| 17 | * AudioRenderer command for applying volume to a mix buffer, with ramping for the volume to smooth | ||
| 18 | * out the transition. | ||
| 19 | */ | ||
| 20 | struct VolumeRampCommand : ICommand { | ||
| 21 | /** | ||
| 22 | * Print this command's information to a string. | ||
| 23 | * | ||
| 24 | * @param processor - The CommandListProcessor processing this command. | ||
| 25 | * @param string - The string to print into. | ||
| 26 | */ | ||
| 27 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Process this command. | ||
| 31 | * | ||
| 32 | * @param processor - The CommandListProcessor processing this command. | ||
| 33 | */ | ||
| 34 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Verify this command's data is valid. | ||
| 38 | * | ||
| 39 | * @param processor - The CommandListProcessor processing this command. | ||
| 40 | * @return True if the command is valid, otherwise false. | ||
| 41 | */ | ||
| 42 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 43 | |||
| 44 | /// Fixed point precision | ||
| 45 | u8 precision; | ||
| 46 | /// Input mix buffer index | ||
| 47 | s16 input_index; | ||
| 48 | /// Output mix buffer index | ||
| 49 | s16 output_index; | ||
| 50 | /// Previous mix volume applied to the input | ||
| 51 | f32 prev_volume; | ||
| 52 | /// Current mix volume applied to the input | ||
| 53 | f32 volume; | ||
| 54 | }; | ||
| 55 | |||
| 56 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 5 | #include "audio_core/renderer/command/performance/performance.h" | ||
| 6 | #include "core/core.h" | ||
| 7 | #include "core/core_timing.h" | ||
| 8 | #include "core/core_timing_util.h" | ||
| 9 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | void PerformanceCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 13 | std::string& string) { | ||
| 14 | string += fmt::format("PerformanceCommand\n\tstate {}\n", static_cast<u32>(state)); | ||
| 15 | } | ||
| 16 | |||
| 17 | void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 18 | auto base{entry_address.translated_address}; | ||
| 19 | if (state == PerformanceState::Start) { | ||
| 20 | auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)}; | ||
| 21 | *start_time_ptr = static_cast<u32>( | ||
| 22 | Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() - | ||
| 23 | processor.start_time - processor.current_processing_time) | ||
| 24 | .count()); | ||
| 25 | } else if (state == PerformanceState::Stop) { | ||
| 26 | auto processed_time_ptr{ | ||
| 27 | reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)}; | ||
| 28 | auto entry_count_ptr{ | ||
| 29 | reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)}; | ||
| 30 | |||
| 31 | *processed_time_ptr = static_cast<u32>( | ||
| 32 | Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() - | ||
| 33 | processor.start_time - processor.current_processing_time) | ||
| 34 | .count()); | ||
| 35 | (*entry_count_ptr)++; | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | bool PerformanceCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 40 | return true; | ||
| 41 | } | ||
| 42 | |||
| 43 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | ||
| 9 | #include "audio_core/renderer/performance/performance_entry_addresses.h" | ||
| 10 | #include "audio_core/renderer/performance/performance_manager.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | ||
| 16 | } | ||
| 17 | |||
| 18 | /** | ||
| 19 | * AudioRenderer command for writing AudioRenderer performance metrics back to the sysmodule. | ||
| 20 | */ | ||
| 21 | struct PerformanceCommand : ICommand { | ||
| 22 | /** | ||
| 23 | * Print this command's information to a string. | ||
| 24 | * | ||
| 25 | * @param processor - The CommandListProcessor processing this command. | ||
| 26 | * @param string - The string to print into. | ||
| 27 | */ | ||
| 28 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Process this command. | ||
| 32 | * | ||
| 33 | * @param processor - The CommandListProcessor processing this command. | ||
| 34 | */ | ||
| 35 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Verify this command's data is valid. | ||
| 39 | * | ||
| 40 | * @param processor - The CommandListProcessor processing this command. | ||
| 41 | * @return True if the command is valid, otherwise false. | ||
| 42 | */ | ||
| 43 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 44 | |||
| 45 | /// State of the performance | ||
| 46 | PerformanceState state; | ||
| 47 | /// Pointers to be written | ||
| 48 | PerformanceEntryAddresses entry_address; | ||
| 49 | }; | ||
| 50 | |||
| 51 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 5 | #include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h" | ||
| 6 | |||
| 7 | namespace AudioCore::AudioRenderer { | ||
| 8 | |||
| 9 | void DownMix6chTo2chCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 10 | std::string& string) { | ||
| 11 | string += fmt::format("DownMix6chTo2chCommand\n\tinputs: "); | ||
| 12 | for (u32 i = 0; i < MaxChannels; i++) { | ||
| 13 | string += fmt::format("{:02X}, ", inputs[i]); | ||
| 14 | } | ||
| 15 | string += "\n\toutputs: "; | ||
| 16 | for (u32 i = 0; i < MaxChannels; i++) { | ||
| 17 | string += fmt::format("{:02X}, ", outputs[i]); | ||
| 18 | } | ||
| 19 | string += "\n"; | ||
| 20 | } | ||
| 21 | |||
| 22 | void DownMix6chTo2chCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 23 | auto in_front_left{ | ||
| 24 | processor.mix_buffers.subspan(inputs[0] * processor.sample_count, processor.sample_count)}; | ||
| 25 | auto in_front_right{ | ||
| 26 | processor.mix_buffers.subspan(inputs[1] * processor.sample_count, processor.sample_count)}; | ||
| 27 | auto in_center{ | ||
| 28 | processor.mix_buffers.subspan(inputs[2] * processor.sample_count, processor.sample_count)}; | ||
| 29 | auto in_lfe{ | ||
| 30 | processor.mix_buffers.subspan(inputs[3] * processor.sample_count, processor.sample_count)}; | ||
| 31 | auto in_back_left{ | ||
| 32 | processor.mix_buffers.subspan(inputs[4] * processor.sample_count, processor.sample_count)}; | ||
| 33 | auto in_back_right{ | ||
| 34 | processor.mix_buffers.subspan(inputs[5] * processor.sample_count, processor.sample_count)}; | ||
| 35 | |||
| 36 | auto out_front_left{ | ||
| 37 | processor.mix_buffers.subspan(outputs[0] * processor.sample_count, processor.sample_count)}; | ||
| 38 | auto out_front_right{ | ||
| 39 | processor.mix_buffers.subspan(outputs[1] * processor.sample_count, processor.sample_count)}; | ||
| 40 | auto out_center{ | ||
| 41 | processor.mix_buffers.subspan(outputs[2] * processor.sample_count, processor.sample_count)}; | ||
| 42 | auto out_lfe{ | ||
| 43 | processor.mix_buffers.subspan(outputs[3] * processor.sample_count, processor.sample_count)}; | ||
| 44 | auto out_back_left{ | ||
| 45 | processor.mix_buffers.subspan(outputs[4] * processor.sample_count, processor.sample_count)}; | ||
| 46 | auto out_back_right{ | ||
| 47 | processor.mix_buffers.subspan(outputs[5] * processor.sample_count, processor.sample_count)}; | ||
| 48 | |||
| 49 | for (u32 i = 0; i < processor.sample_count; i++) { | ||
| 50 | const auto left_sample{(in_front_left[i] * down_mix_coeff[0] + | ||
| 51 | in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] + | ||
| 52 | in_back_left[i] * down_mix_coeff[3]) | ||
| 53 | .to_int()}; | ||
| 54 | |||
| 55 | const auto right_sample{(in_front_right[i] * down_mix_coeff[0] + | ||
| 56 | in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] + | ||
| 57 | in_back_right[i] * down_mix_coeff[3]) | ||
| 58 | .to_int()}; | ||
| 59 | |||
| 60 | out_front_left[i] = left_sample; | ||
| 61 | out_front_right[i] = right_sample; | ||
| 62 | } | ||
| 63 | |||
| 64 | std::memset(out_center.data(), 0, out_center.size_bytes()); | ||
| 65 | std::memset(out_lfe.data(), 0, out_lfe.size_bytes()); | ||
| 66 | std::memset(out_back_left.data(), 0, out_back_left.size_bytes()); | ||
| 67 | std::memset(out_back_right.data(), 0, out_back_right.size_bytes()); | ||
| 68 | } | ||
| 69 | |||
| 70 | bool DownMix6chTo2chCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 71 | return true; | ||
| 72 | } | ||
| 73 | |||
| 74 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | #include "common/fixed_point.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | namespace ADSP { | ||
| 14 | class CommandListProcessor; | ||
| 15 | } | ||
| 16 | |||
| 17 | /** | ||
| 18 | * AudioRenderer command for downmixing 6 channels to 2. | ||
| 19 | * Channel layout (SMPTE): | ||
| 20 | * 0 - front left | ||
| 21 | * 1 - front right | ||
| 22 | * 2 - center | ||
| 23 | * 3 - lfe | ||
| 24 | * 4 - back left | ||
| 25 | * 5 - back right | ||
| 26 | */ | ||
| 27 | struct DownMix6chTo2chCommand : ICommand { | ||
| 28 | /** | ||
| 29 | * Print this command's information to a string. | ||
| 30 | * | ||
| 31 | * @param processor - The CommandListProcessor processing this command. | ||
| 32 | * @param string - The string to print into. | ||
| 33 | */ | ||
| 34 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Process this command. | ||
| 38 | * | ||
| 39 | * @param processor - The CommandListProcessor processing this command. | ||
| 40 | */ | ||
| 41 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Verify this command's data is valid. | ||
| 45 | * | ||
| 46 | * @param processor - The CommandListProcessor processing this command. | ||
| 47 | * @return True if the command is valid, otherwise false. | ||
| 48 | */ | ||
| 49 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 50 | |||
| 51 | /// Input mix buffer offsets for each channel | ||
| 52 | std::array<s16, MaxChannels> inputs; | ||
| 53 | /// Output mix buffer offsets for each channel | ||
| 54 | std::array<s16, MaxChannels> outputs; | ||
| 55 | /// Coefficients used for downmixing | ||
| 56 | std::array<Common::FixedPoint<48, 16>, 4> down_mix_coeff; | ||
| 57 | }; | ||
| 58 | |||
| 59 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/command/resample/resample.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input, | ||
| 9 | const Common::FixedPoint<49, 15>& sample_rate_ratio, | ||
| 10 | Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) { | ||
| 11 | if (sample_rate_ratio == 1.0f) { | ||
| 12 | for (u32 i = 0; i < samples_to_write; i++) { | ||
| 13 | output[i] = input[i]; | ||
| 14 | } | ||
| 15 | } else { | ||
| 16 | u32 read_index{0}; | ||
| 17 | for (u32 i = 0; i < samples_to_write; i++) { | ||
| 18 | output[i] = input[read_index + (fraction >= 0.5f)]; | ||
| 19 | fraction += sample_rate_ratio; | ||
| 20 | read_index += static_cast<u32>(fraction.to_int_floor()); | ||
| 21 | fraction.clear_int(); | ||
| 22 | } | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | static void ResampleNormalQuality(std::span<s32> output, std::span<const s16> input, | ||
| 27 | const Common::FixedPoint<49, 15>& sample_rate_ratio, | ||
| 28 | Common::FixedPoint<49, 15>& fraction, | ||
| 29 | const u32 samples_to_write) { | ||
| 30 | static constexpr std::array<f32, 512> lut0 = { | ||
| 31 | 0.20141602f, 0.59283447f, 0.20513916f, 0.00009155f, 0.19772339f, 0.59277344f, 0.20889282f, | ||
| 32 | 0.00027466f, 0.19406128f, 0.59262085f, 0.21264648f, 0.00045776f, 0.19039917f, 0.59240723f, | ||
| 33 | 0.21646118f, 0.00067139f, 0.18679810f, 0.59213257f, 0.22030640f, 0.00085449f, 0.18322754f, | ||
| 34 | 0.59176636f, 0.22415161f, 0.00103760f, 0.17968750f, 0.59133911f, 0.22802734f, 0.00125122f, | ||
| 35 | 0.17617798f, 0.59085083f, 0.23193359f, 0.00146484f, 0.17269897f, 0.59027100f, 0.23583984f, | ||
| 36 | 0.00167847f, 0.16925049f, 0.58963013f, 0.23977661f, 0.00189209f, 0.16583252f, 0.58892822f, | ||
| 37 | 0.24374390f, 0.00210571f, 0.16244507f, 0.58816528f, 0.24774170f, 0.00234985f, 0.15908813f, | ||
| 38 | 0.58731079f, 0.25173950f, 0.00256348f, 0.15576172f, 0.58639526f, 0.25576782f, 0.00280762f, | ||
| 39 | 0.15249634f, 0.58541870f, 0.25979614f, 0.00308228f, 0.14923096f, 0.58435059f, 0.26385498f, | ||
| 40 | 0.00332642f, 0.14602661f, 0.58325195f, 0.26794434f, 0.00360107f, 0.14285278f, 0.58206177f, | ||
| 41 | 0.27203369f, 0.00387573f, 0.13973999f, 0.58078003f, 0.27612305f, 0.00418091f, 0.13662720f, | ||
| 42 | 0.57946777f, 0.28024292f, 0.00448608f, 0.13357544f, 0.57806396f, 0.28436279f, 0.00479126f, | ||
| 43 | 0.13052368f, 0.57662964f, 0.28851318f, 0.00512695f, 0.12753296f, 0.57510376f, 0.29266357f, | ||
| 44 | 0.00546265f, 0.12460327f, 0.57351685f, 0.29681396f, 0.00579834f, 0.12167358f, 0.57183838f, | ||
| 45 | 0.30099487f, 0.00616455f, 0.11880493f, 0.57012939f, 0.30517578f, 0.00656128f, 0.11596680f, | ||
| 46 | 0.56835938f, 0.30935669f, 0.00695801f, 0.11318970f, 0.56649780f, 0.31353760f, 0.00735474f, | ||
| 47 | 0.11041260f, 0.56457520f, 0.31771851f, 0.00778198f, 0.10769653f, 0.56262207f, 0.32192993f, | ||
| 48 | 0.00823975f, 0.10501099f, 0.56057739f, 0.32614136f, 0.00869751f, 0.10238647f, 0.55847168f, | ||
| 49 | 0.33032227f, 0.00915527f, 0.09976196f, 0.55633545f, 0.33453369f, 0.00967407f, 0.09722900f, | ||
| 50 | 0.55410767f, 0.33874512f, 0.01019287f, 0.09469604f, 0.55181885f, 0.34295654f, 0.01071167f, | ||
| 51 | 0.09222412f, 0.54949951f, 0.34713745f, 0.01126099f, 0.08978271f, 0.54708862f, 0.35134888f, | ||
| 52 | 0.01184082f, 0.08737183f, 0.54464722f, 0.35552979f, 0.01245117f, 0.08499146f, 0.54214478f, | ||
| 53 | 0.35974121f, 0.01306152f, 0.08267212f, 0.53958130f, 0.36392212f, 0.01370239f, 0.08041382f, | ||
| 54 | 0.53695679f, 0.36810303f, 0.01437378f, 0.07815552f, 0.53427124f, 0.37225342f, 0.01507568f, | ||
| 55 | 0.07595825f, 0.53155518f, 0.37640381f, 0.01577759f, 0.07379150f, 0.52877808f, 0.38055420f, | ||
| 56 | 0.01651001f, 0.07165527f, 0.52593994f, 0.38470459f, 0.01727295f, 0.06958008f, 0.52307129f, | ||
| 57 | 0.38882446f, 0.01806641f, 0.06753540f, 0.52014160f, 0.39294434f, 0.01889038f, 0.06552124f, | ||
| 58 | 0.51715088f, 0.39703369f, 0.01974487f, 0.06356812f, 0.51409912f, 0.40112305f, 0.02059937f, | ||
| 59 | 0.06164551f, 0.51101685f, 0.40518188f, 0.02148438f, 0.05975342f, 0.50790405f, 0.40921021f, | ||
| 60 | 0.02243042f, 0.05789185f, 0.50473022f, 0.41323853f, 0.02337646f, 0.05609131f, 0.50152588f, | ||
| 61 | 0.41726685f, 0.02435303f, 0.05432129f, 0.49826050f, 0.42123413f, 0.02539062f, 0.05258179f, | ||
| 62 | 0.49493408f, 0.42520142f, 0.02642822f, 0.05087280f, 0.49160767f, 0.42913818f, 0.02749634f, | ||
| 63 | 0.04922485f, 0.48822021f, 0.43307495f, 0.02859497f, 0.04760742f, 0.48477173f, 0.43695068f, | ||
| 64 | 0.02975464f, 0.04602051f, 0.48132324f, 0.44082642f, 0.03091431f, 0.04446411f, 0.47781372f, | ||
| 65 | 0.44467163f, 0.03210449f, 0.04293823f, 0.47424316f, 0.44845581f, 0.03335571f, 0.04147339f, | ||
| 66 | 0.47067261f, 0.45223999f, 0.03460693f, 0.04003906f, 0.46704102f, 0.45599365f, 0.03591919f, | ||
| 67 | 0.03863525f, 0.46340942f, 0.45971680f, 0.03726196f, 0.03726196f, 0.45971680f, 0.46340942f, | ||
| 68 | 0.03863525f, 0.03591919f, 0.45599365f, 0.46704102f, 0.04003906f, 0.03460693f, 0.45223999f, | ||
| 69 | 0.47067261f, 0.04147339f, 0.03335571f, 0.44845581f, 0.47424316f, 0.04293823f, 0.03210449f, | ||
| 70 | 0.44467163f, 0.47781372f, 0.04446411f, 0.03091431f, 0.44082642f, 0.48132324f, 0.04602051f, | ||
| 71 | 0.02975464f, 0.43695068f, 0.48477173f, 0.04760742f, 0.02859497f, 0.43307495f, 0.48822021f, | ||
| 72 | 0.04922485f, 0.02749634f, 0.42913818f, 0.49160767f, 0.05087280f, 0.02642822f, 0.42520142f, | ||
| 73 | 0.49493408f, 0.05258179f, 0.02539062f, 0.42123413f, 0.49826050f, 0.05432129f, 0.02435303f, | ||
| 74 | 0.41726685f, 0.50152588f, 0.05609131f, 0.02337646f, 0.41323853f, 0.50473022f, 0.05789185f, | ||
| 75 | 0.02243042f, 0.40921021f, 0.50790405f, 0.05975342f, 0.02148438f, 0.40518188f, 0.51101685f, | ||
| 76 | 0.06164551f, 0.02059937f, 0.40112305f, 0.51409912f, 0.06356812f, 0.01974487f, 0.39703369f, | ||
| 77 | 0.51715088f, 0.06552124f, 0.01889038f, 0.39294434f, 0.52014160f, 0.06753540f, 0.01806641f, | ||
| 78 | 0.38882446f, 0.52307129f, 0.06958008f, 0.01727295f, 0.38470459f, 0.52593994f, 0.07165527f, | ||
| 79 | 0.01651001f, 0.38055420f, 0.52877808f, 0.07379150f, 0.01577759f, 0.37640381f, 0.53155518f, | ||
| 80 | 0.07595825f, 0.01507568f, 0.37225342f, 0.53427124f, 0.07815552f, 0.01437378f, 0.36810303f, | ||
| 81 | 0.53695679f, 0.08041382f, 0.01370239f, 0.36392212f, 0.53958130f, 0.08267212f, 0.01306152f, | ||
| 82 | 0.35974121f, 0.54214478f, 0.08499146f, 0.01245117f, 0.35552979f, 0.54464722f, 0.08737183f, | ||
| 83 | 0.01184082f, 0.35134888f, 0.54708862f, 0.08978271f, 0.01126099f, 0.34713745f, 0.54949951f, | ||
| 84 | 0.09222412f, 0.01071167f, 0.34295654f, 0.55181885f, 0.09469604f, 0.01019287f, 0.33874512f, | ||
| 85 | 0.55410767f, 0.09722900f, 0.00967407f, 0.33453369f, 0.55633545f, 0.09976196f, 0.00915527f, | ||
| 86 | 0.33032227f, 0.55847168f, 0.10238647f, 0.00869751f, 0.32614136f, 0.56057739f, 0.10501099f, | ||
| 87 | 0.00823975f, 0.32192993f, 0.56262207f, 0.10769653f, 0.00778198f, 0.31771851f, 0.56457520f, | ||
| 88 | 0.11041260f, 0.00735474f, 0.31353760f, 0.56649780f, 0.11318970f, 0.00695801f, 0.30935669f, | ||
| 89 | 0.56835938f, 0.11596680f, 0.00656128f, 0.30517578f, 0.57012939f, 0.11880493f, 0.00616455f, | ||
| 90 | 0.30099487f, 0.57183838f, 0.12167358f, 0.00579834f, 0.29681396f, 0.57351685f, 0.12460327f, | ||
| 91 | 0.00546265f, 0.29266357f, 0.57510376f, 0.12753296f, 0.00512695f, 0.28851318f, 0.57662964f, | ||
| 92 | 0.13052368f, 0.00479126f, 0.28436279f, 0.57806396f, 0.13357544f, 0.00448608f, 0.28024292f, | ||
| 93 | 0.57946777f, 0.13662720f, 0.00418091f, 0.27612305f, 0.58078003f, 0.13973999f, 0.00387573f, | ||
| 94 | 0.27203369f, 0.58206177f, 0.14285278f, 0.00360107f, 0.26794434f, 0.58325195f, 0.14602661f, | ||
| 95 | 0.00332642f, 0.26385498f, 0.58435059f, 0.14923096f, 0.00308228f, 0.25979614f, 0.58541870f, | ||
| 96 | 0.15249634f, 0.00280762f, 0.25576782f, 0.58639526f, 0.15576172f, 0.00256348f, 0.25173950f, | ||
| 97 | 0.58731079f, 0.15908813f, 0.00234985f, 0.24774170f, 0.58816528f, 0.16244507f, 0.00210571f, | ||
| 98 | 0.24374390f, 0.58892822f, 0.16583252f, 0.00189209f, 0.23977661f, 0.58963013f, 0.16925049f, | ||
| 99 | 0.00167847f, 0.23583984f, 0.59027100f, 0.17269897f, 0.00146484f, 0.23193359f, 0.59085083f, | ||
| 100 | 0.17617798f, 0.00125122f, 0.22802734f, 0.59133911f, 0.17968750f, 0.00103760f, 0.22415161f, | ||
| 101 | 0.59176636f, 0.18322754f, 0.00085449f, 0.22030640f, 0.59213257f, 0.18679810f, 0.00067139f, | ||
| 102 | 0.21646118f, 0.59240723f, 0.19039917f, 0.00045776f, 0.21264648f, 0.59262085f, 0.19406128f, | ||
| 103 | 0.00027466f, 0.20889282f, 0.59277344f, 0.19772339f, 0.00009155f, 0.20513916f, 0.59283447f, | ||
| 104 | 0.20141602f, | ||
| 105 | }; | ||
| 106 | |||
| 107 | static constexpr std::array<f32, 512> lut1 = { | ||
| 108 | 0.00207520f, 0.99606323f, 0.00210571f, -0.00015259f, -0.00610352f, 0.99578857f, | ||
| 109 | 0.00646973f, -0.00045776f, -0.01000977f, 0.99526978f, 0.01095581f, -0.00079346f, | ||
| 110 | -0.01373291f, 0.99444580f, 0.01562500f, -0.00109863f, -0.01733398f, 0.99337769f, | ||
| 111 | 0.02041626f, -0.00143433f, -0.02075195f, 0.99203491f, 0.02539062f, -0.00177002f, | ||
| 112 | -0.02404785f, 0.99041748f, 0.03051758f, -0.00210571f, -0.02719116f, 0.98855591f, | ||
| 113 | 0.03582764f, -0.00244141f, -0.03021240f, 0.98641968f, 0.04125977f, -0.00280762f, | ||
| 114 | -0.03308105f, 0.98400879f, 0.04687500f, -0.00314331f, -0.03579712f, 0.98135376f, | ||
| 115 | 0.05261230f, -0.00350952f, -0.03839111f, 0.97842407f, 0.05856323f, -0.00390625f, | ||
| 116 | -0.04083252f, 0.97521973f, 0.06463623f, -0.00427246f, -0.04315186f, 0.97180176f, | ||
| 117 | 0.07086182f, -0.00466919f, -0.04534912f, 0.96810913f, 0.07727051f, -0.00509644f, | ||
| 118 | -0.04742432f, 0.96414185f, 0.08383179f, -0.00549316f, -0.04934692f, 0.95996094f, | ||
| 119 | 0.09054565f, -0.00592041f, -0.05114746f, 0.95550537f, 0.09741211f, -0.00637817f, | ||
| 120 | -0.05285645f, 0.95083618f, 0.10443115f, -0.00683594f, -0.05441284f, 0.94589233f, | ||
| 121 | 0.11160278f, -0.00732422f, -0.05584717f, 0.94073486f, 0.11892700f, -0.00781250f, | ||
| 122 | -0.05718994f, 0.93533325f, 0.12643433f, -0.00830078f, -0.05841064f, 0.92968750f, | ||
| 123 | 0.13406372f, -0.00881958f, -0.05953979f, 0.92382812f, 0.14184570f, -0.00936890f, | ||
| 124 | -0.06054688f, 0.91772461f, 0.14978027f, -0.00991821f, -0.06146240f, 0.91143799f, | ||
| 125 | 0.15783691f, -0.01046753f, -0.06225586f, 0.90490723f, 0.16607666f, -0.01104736f, | ||
| 126 | -0.06295776f, 0.89816284f, 0.17443848f, -0.01165771f, -0.06356812f, 0.89120483f, | ||
| 127 | 0.18292236f, -0.01229858f, -0.06408691f, 0.88403320f, 0.19155884f, -0.01293945f, | ||
| 128 | -0.06451416f, 0.87667847f, 0.20034790f, -0.01358032f, -0.06484985f, 0.86914062f, | ||
| 129 | 0.20925903f, -0.01428223f, -0.06509399f, 0.86138916f, 0.21829224f, -0.01495361f, | ||
| 130 | -0.06527710f, 0.85345459f, 0.22744751f, -0.01568604f, -0.06536865f, 0.84533691f, | ||
| 131 | 0.23675537f, -0.01641846f, -0.06536865f, 0.83703613f, 0.24615479f, -0.01718140f, | ||
| 132 | -0.06533813f, 0.82858276f, 0.25567627f, -0.01794434f, -0.06518555f, 0.81991577f, | ||
| 133 | 0.26531982f, -0.01873779f, -0.06500244f, 0.81112671f, 0.27505493f, -0.01956177f, | ||
| 134 | -0.06472778f, 0.80215454f, 0.28491211f, -0.02038574f, -0.06442261f, 0.79306030f, | ||
| 135 | 0.29489136f, -0.02124023f, -0.06402588f, 0.78378296f, 0.30496216f, -0.02209473f, | ||
| 136 | -0.06359863f, 0.77438354f, 0.31512451f, -0.02297974f, -0.06307983f, 0.76486206f, | ||
| 137 | 0.32537842f, -0.02389526f, -0.06253052f, 0.75518799f, 0.33569336f, -0.02481079f, | ||
| 138 | -0.06195068f, 0.74539185f, 0.34613037f, -0.02575684f, -0.06130981f, 0.73547363f, | ||
| 139 | 0.35662842f, -0.02670288f, -0.06060791f, 0.72543335f, 0.36721802f, -0.02767944f, | ||
| 140 | -0.05987549f, 0.71527100f, 0.37786865f, -0.02865601f, -0.05911255f, 0.70504761f, | ||
| 141 | 0.38858032f, -0.02966309f, -0.05831909f, 0.69470215f, 0.39935303f, -0.03067017f, | ||
| 142 | -0.05746460f, 0.68426514f, 0.41018677f, -0.03170776f, -0.05661011f, 0.67373657f, | ||
| 143 | 0.42108154f, -0.03271484f, -0.05569458f, 0.66311646f, 0.43200684f, -0.03378296f, | ||
| 144 | -0.05477905f, 0.65246582f, 0.44299316f, -0.03482056f, -0.05383301f, 0.64169312f, | ||
| 145 | 0.45401001f, -0.03588867f, -0.05285645f, 0.63088989f, 0.46505737f, -0.03695679f, | ||
| 146 | -0.05187988f, 0.62002563f, 0.47613525f, -0.03802490f, -0.05087280f, 0.60910034f, | ||
| 147 | 0.48721313f, -0.03912354f, -0.04983521f, 0.59814453f, 0.49832153f, -0.04019165f, | ||
| 148 | -0.04879761f, 0.58712769f, 0.50946045f, -0.04129028f, -0.04772949f, 0.57611084f, | ||
| 149 | 0.52056885f, -0.04235840f, -0.04669189f, 0.56503296f, 0.53170776f, -0.04345703f, | ||
| 150 | -0.04562378f, 0.55392456f, 0.54281616f, -0.04452515f, -0.04452515f, 0.54281616f, | ||
| 151 | 0.55392456f, -0.04562378f, -0.04345703f, 0.53170776f, 0.56503296f, -0.04669189f, | ||
| 152 | -0.04235840f, 0.52056885f, 0.57611084f, -0.04772949f, -0.04129028f, 0.50946045f, | ||
| 153 | 0.58712769f, -0.04879761f, -0.04019165f, 0.49832153f, 0.59814453f, -0.04983521f, | ||
| 154 | -0.03912354f, 0.48721313f, 0.60910034f, -0.05087280f, -0.03802490f, 0.47613525f, | ||
| 155 | 0.62002563f, -0.05187988f, -0.03695679f, 0.46505737f, 0.63088989f, -0.05285645f, | ||
| 156 | -0.03588867f, 0.45401001f, 0.64169312f, -0.05383301f, -0.03482056f, 0.44299316f, | ||
| 157 | 0.65246582f, -0.05477905f, -0.03378296f, 0.43200684f, 0.66311646f, -0.05569458f, | ||
| 158 | -0.03271484f, 0.42108154f, 0.67373657f, -0.05661011f, -0.03170776f, 0.41018677f, | ||
| 159 | 0.68426514f, -0.05746460f, -0.03067017f, 0.39935303f, 0.69470215f, -0.05831909f, | ||
| 160 | -0.02966309f, 0.38858032f, 0.70504761f, -0.05911255f, -0.02865601f, 0.37786865f, | ||
| 161 | 0.71527100f, -0.05987549f, -0.02767944f, 0.36721802f, 0.72543335f, -0.06060791f, | ||
| 162 | -0.02670288f, 0.35662842f, 0.73547363f, -0.06130981f, -0.02575684f, 0.34613037f, | ||
| 163 | 0.74539185f, -0.06195068f, -0.02481079f, 0.33569336f, 0.75518799f, -0.06253052f, | ||
| 164 | -0.02389526f, 0.32537842f, 0.76486206f, -0.06307983f, -0.02297974f, 0.31512451f, | ||
| 165 | 0.77438354f, -0.06359863f, -0.02209473f, 0.30496216f, 0.78378296f, -0.06402588f, | ||
| 166 | -0.02124023f, 0.29489136f, 0.79306030f, -0.06442261f, -0.02038574f, 0.28491211f, | ||
| 167 | 0.80215454f, -0.06472778f, -0.01956177f, 0.27505493f, 0.81112671f, -0.06500244f, | ||
| 168 | -0.01873779f, 0.26531982f, 0.81991577f, -0.06518555f, -0.01794434f, 0.25567627f, | ||
| 169 | 0.82858276f, -0.06533813f, -0.01718140f, 0.24615479f, 0.83703613f, -0.06536865f, | ||
| 170 | -0.01641846f, 0.23675537f, 0.84533691f, -0.06536865f, -0.01568604f, 0.22744751f, | ||
| 171 | 0.85345459f, -0.06527710f, -0.01495361f, 0.21829224f, 0.86138916f, -0.06509399f, | ||
| 172 | -0.01428223f, 0.20925903f, 0.86914062f, -0.06484985f, -0.01358032f, 0.20034790f, | ||
| 173 | 0.87667847f, -0.06451416f, -0.01293945f, 0.19155884f, 0.88403320f, -0.06408691f, | ||
| 174 | -0.01229858f, 0.18292236f, 0.89120483f, -0.06356812f, -0.01165771f, 0.17443848f, | ||
| 175 | 0.89816284f, -0.06295776f, -0.01104736f, 0.16607666f, 0.90490723f, -0.06225586f, | ||
| 176 | -0.01046753f, 0.15783691f, 0.91143799f, -0.06146240f, -0.00991821f, 0.14978027f, | ||
| 177 | 0.91772461f, -0.06054688f, -0.00936890f, 0.14184570f, 0.92382812f, -0.05953979f, | ||
| 178 | -0.00881958f, 0.13406372f, 0.92968750f, -0.05841064f, -0.00830078f, 0.12643433f, | ||
| 179 | 0.93533325f, -0.05718994f, -0.00781250f, 0.11892700f, 0.94073486f, -0.05584717f, | ||
| 180 | -0.00732422f, 0.11160278f, 0.94589233f, -0.05441284f, -0.00683594f, 0.10443115f, | ||
| 181 | 0.95083618f, -0.05285645f, -0.00637817f, 0.09741211f, 0.95550537f, -0.05114746f, | ||
| 182 | -0.00592041f, 0.09054565f, 0.95996094f, -0.04934692f, -0.00549316f, 0.08383179f, | ||
| 183 | 0.96414185f, -0.04742432f, -0.00509644f, 0.07727051f, 0.96810913f, -0.04534912f, | ||
| 184 | -0.00466919f, 0.07086182f, 0.97180176f, -0.04315186f, -0.00427246f, 0.06463623f, | ||
| 185 | 0.97521973f, -0.04083252f, -0.00390625f, 0.05856323f, 0.97842407f, -0.03839111f, | ||
| 186 | -0.00350952f, 0.05261230f, 0.98135376f, -0.03579712f, -0.00314331f, 0.04687500f, | ||
| 187 | 0.98400879f, -0.03308105f, -0.00280762f, 0.04125977f, 0.98641968f, -0.03021240f, | ||
| 188 | -0.00244141f, 0.03582764f, 0.98855591f, -0.02719116f, -0.00210571f, 0.03051758f, | ||
| 189 | 0.99041748f, -0.02404785f, -0.00177002f, 0.02539062f, 0.99203491f, -0.02075195f, | ||
| 190 | -0.00143433f, 0.02041626f, 0.99337769f, -0.01733398f, -0.00109863f, 0.01562500f, | ||
| 191 | 0.99444580f, -0.01373291f, -0.00079346f, 0.01095581f, 0.99526978f, -0.01000977f, | ||
| 192 | -0.00045776f, 0.00646973f, 0.99578857f, -0.00610352f, -0.00015259f, 0.00210571f, | ||
| 193 | 0.99606323f, -0.00207520f, | ||
| 194 | }; | ||
| 195 | |||
| 196 | static constexpr std::array<f32, 512> lut2 = { | ||
| 197 | 0.09750366f, 0.80221558f, 0.10159302f, -0.00097656f, 0.09350586f, 0.80203247f, | ||
| 198 | 0.10580444f, -0.00103760f, 0.08959961f, 0.80169678f, 0.11010742f, -0.00115967f, | ||
| 199 | 0.08578491f, 0.80117798f, 0.11447144f, -0.00128174f, 0.08203125f, 0.80047607f, | ||
| 200 | 0.11892700f, -0.00140381f, 0.07836914f, 0.79962158f, 0.12347412f, -0.00152588f, | ||
| 201 | 0.07479858f, 0.79861450f, 0.12814331f, -0.00164795f, 0.07135010f, 0.79742432f, | ||
| 202 | 0.13287354f, -0.00177002f, 0.06796265f, 0.79605103f, 0.13769531f, -0.00192261f, | ||
| 203 | 0.06469727f, 0.79452515f, 0.14260864f, -0.00204468f, 0.06149292f, 0.79284668f, | ||
| 204 | 0.14761353f, -0.00219727f, 0.05834961f, 0.79098511f, 0.15270996f, -0.00231934f, | ||
| 205 | 0.05532837f, 0.78894043f, 0.15789795f, -0.00247192f, 0.05236816f, 0.78674316f, | ||
| 206 | 0.16317749f, -0.00265503f, 0.04949951f, 0.78442383f, 0.16851807f, -0.00280762f, | ||
| 207 | 0.04672241f, 0.78189087f, 0.17398071f, -0.00299072f, 0.04400635f, 0.77920532f, | ||
| 208 | 0.17950439f, -0.00314331f, 0.04141235f, 0.77636719f, 0.18511963f, -0.00332642f, | ||
| 209 | 0.03887939f, 0.77337646f, 0.19082642f, -0.00350952f, 0.03640747f, 0.77023315f, | ||
| 210 | 0.19659424f, -0.00369263f, 0.03402710f, 0.76693726f, 0.20248413f, -0.00387573f, | ||
| 211 | 0.03173828f, 0.76348877f, 0.20843506f, -0.00405884f, 0.02951050f, 0.75985718f, | ||
| 212 | 0.21444702f, -0.00427246f, 0.02737427f, 0.75610352f, 0.22055054f, -0.00445557f, | ||
| 213 | 0.02529907f, 0.75219727f, 0.22674561f, -0.00466919f, 0.02331543f, 0.74816895f, | ||
| 214 | 0.23300171f, -0.00485229f, 0.02139282f, 0.74398804f, 0.23931885f, -0.00506592f, | ||
| 215 | 0.01956177f, 0.73965454f, 0.24572754f, -0.00531006f, 0.01779175f, 0.73519897f, | ||
| 216 | 0.25219727f, -0.00552368f, 0.01605225f, 0.73059082f, 0.25872803f, -0.00570679f, | ||
| 217 | 0.01440430f, 0.72586060f, 0.26535034f, -0.00592041f, 0.01281738f, 0.72100830f, | ||
| 218 | 0.27203369f, -0.00616455f, 0.01132202f, 0.71600342f, 0.27877808f, -0.00637817f, | ||
| 219 | 0.00988770f, 0.71090698f, 0.28558350f, -0.00656128f, 0.00851440f, 0.70565796f, | ||
| 220 | 0.29244995f, -0.00677490f, 0.00720215f, 0.70031738f, 0.29934692f, -0.00701904f, | ||
| 221 | 0.00592041f, 0.69485474f, 0.30633545f, -0.00723267f, 0.00469971f, 0.68927002f, | ||
| 222 | 0.31338501f, -0.00741577f, 0.00357056f, 0.68356323f, 0.32046509f, -0.00762939f, | ||
| 223 | 0.00247192f, 0.67773438f, 0.32760620f, -0.00787354f, 0.00143433f, 0.67184448f, | ||
| 224 | 0.33477783f, -0.00808716f, 0.00045776f, 0.66583252f, 0.34197998f, -0.00827026f, | ||
| 225 | -0.00048828f, 0.65972900f, 0.34924316f, -0.00845337f, -0.00134277f, 0.65353394f, | ||
| 226 | 0.35656738f, -0.00863647f, -0.00216675f, 0.64721680f, 0.36389160f, -0.00885010f, | ||
| 227 | -0.00296021f, 0.64083862f, 0.37127686f, -0.00903320f, -0.00369263f, 0.63433838f, | ||
| 228 | 0.37869263f, -0.00921631f, -0.00436401f, 0.62777710f, 0.38613892f, -0.00933838f, | ||
| 229 | -0.00497437f, 0.62115479f, 0.39361572f, -0.00949097f, -0.00558472f, 0.61444092f, | ||
| 230 | 0.40109253f, -0.00964355f, -0.00613403f, 0.60763550f, 0.40859985f, -0.00979614f, | ||
| 231 | -0.00665283f, 0.60076904f, 0.41610718f, -0.00991821f, -0.00714111f, 0.59384155f, | ||
| 232 | 0.42364502f, -0.01000977f, -0.00756836f, 0.58685303f, 0.43121338f, -0.01013184f, | ||
| 233 | -0.00796509f, 0.57977295f, 0.43875122f, -0.01022339f, -0.00833130f, 0.57266235f, | ||
| 234 | 0.44631958f, -0.01028442f, -0.00866699f, 0.56552124f, 0.45388794f, -0.01034546f, | ||
| 235 | -0.00897217f, 0.55831909f, 0.46145630f, -0.01040649f, -0.00921631f, 0.55105591f, | ||
| 236 | 0.46902466f, -0.01040649f, -0.00946045f, 0.54373169f, 0.47659302f, -0.01040649f, | ||
| 237 | -0.00967407f, 0.53640747f, 0.48413086f, -0.01037598f, -0.00985718f, 0.52902222f, | ||
| 238 | 0.49166870f, -0.01037598f, -0.01000977f, 0.52160645f, 0.49917603f, -0.01031494f, | ||
| 239 | -0.01013184f, 0.51416016f, 0.50668335f, -0.01025391f, -0.01025391f, 0.50668335f, | ||
| 240 | 0.51416016f, -0.01013184f, -0.01031494f, 0.49917603f, 0.52160645f, -0.01000977f, | ||
| 241 | -0.01037598f, 0.49166870f, 0.52902222f, -0.00985718f, -0.01037598f, 0.48413086f, | ||
| 242 | 0.53640747f, -0.00967407f, -0.01040649f, 0.47659302f, 0.54373169f, -0.00946045f, | ||
| 243 | -0.01040649f, 0.46902466f, 0.55105591f, -0.00921631f, -0.01040649f, 0.46145630f, | ||
| 244 | 0.55831909f, -0.00897217f, -0.01034546f, 0.45388794f, 0.56552124f, -0.00866699f, | ||
| 245 | -0.01028442f, 0.44631958f, 0.57266235f, -0.00833130f, -0.01022339f, 0.43875122f, | ||
| 246 | 0.57977295f, -0.00796509f, -0.01013184f, 0.43121338f, 0.58685303f, -0.00756836f, | ||
| 247 | -0.01000977f, 0.42364502f, 0.59384155f, -0.00714111f, -0.00991821f, 0.41610718f, | ||
| 248 | 0.60076904f, -0.00665283f, -0.00979614f, 0.40859985f, 0.60763550f, -0.00613403f, | ||
| 249 | -0.00964355f, 0.40109253f, 0.61444092f, -0.00558472f, -0.00949097f, 0.39361572f, | ||
| 250 | 0.62115479f, -0.00497437f, -0.00933838f, 0.38613892f, 0.62777710f, -0.00436401f, | ||
| 251 | -0.00921631f, 0.37869263f, 0.63433838f, -0.00369263f, -0.00903320f, 0.37127686f, | ||
| 252 | 0.64083862f, -0.00296021f, -0.00885010f, 0.36389160f, 0.64721680f, -0.00216675f, | ||
| 253 | -0.00863647f, 0.35656738f, 0.65353394f, -0.00134277f, -0.00845337f, 0.34924316f, | ||
| 254 | 0.65972900f, -0.00048828f, -0.00827026f, 0.34197998f, 0.66583252f, 0.00045776f, | ||
| 255 | -0.00808716f, 0.33477783f, 0.67184448f, 0.00143433f, -0.00787354f, 0.32760620f, | ||
| 256 | 0.67773438f, 0.00247192f, -0.00762939f, 0.32046509f, 0.68356323f, 0.00357056f, | ||
| 257 | -0.00741577f, 0.31338501f, 0.68927002f, 0.00469971f, -0.00723267f, 0.30633545f, | ||
| 258 | 0.69485474f, 0.00592041f, -0.00701904f, 0.29934692f, 0.70031738f, 0.00720215f, | ||
| 259 | -0.00677490f, 0.29244995f, 0.70565796f, 0.00851440f, -0.00656128f, 0.28558350f, | ||
| 260 | 0.71090698f, 0.00988770f, -0.00637817f, 0.27877808f, 0.71600342f, 0.01132202f, | ||
| 261 | -0.00616455f, 0.27203369f, 0.72100830f, 0.01281738f, -0.00592041f, 0.26535034f, | ||
| 262 | 0.72586060f, 0.01440430f, -0.00570679f, 0.25872803f, 0.73059082f, 0.01605225f, | ||
| 263 | -0.00552368f, 0.25219727f, 0.73519897f, 0.01779175f, -0.00531006f, 0.24572754f, | ||
| 264 | 0.73965454f, 0.01956177f, -0.00506592f, 0.23931885f, 0.74398804f, 0.02139282f, | ||
| 265 | -0.00485229f, 0.23300171f, 0.74816895f, 0.02331543f, -0.00466919f, 0.22674561f, | ||
| 266 | 0.75219727f, 0.02529907f, -0.00445557f, 0.22055054f, 0.75610352f, 0.02737427f, | ||
| 267 | -0.00427246f, 0.21444702f, 0.75985718f, 0.02951050f, -0.00405884f, 0.20843506f, | ||
| 268 | 0.76348877f, 0.03173828f, -0.00387573f, 0.20248413f, 0.76693726f, 0.03402710f, | ||
| 269 | -0.00369263f, 0.19659424f, 0.77023315f, 0.03640747f, -0.00350952f, 0.19082642f, | ||
| 270 | 0.77337646f, 0.03887939f, -0.00332642f, 0.18511963f, 0.77636719f, 0.04141235f, | ||
| 271 | -0.00314331f, 0.17950439f, 0.77920532f, 0.04400635f, -0.00299072f, 0.17398071f, | ||
| 272 | 0.78189087f, 0.04672241f, -0.00280762f, 0.16851807f, 0.78442383f, 0.04949951f, | ||
| 273 | -0.00265503f, 0.16317749f, 0.78674316f, 0.05236816f, -0.00247192f, 0.15789795f, | ||
| 274 | 0.78894043f, 0.05532837f, -0.00231934f, 0.15270996f, 0.79098511f, 0.05834961f, | ||
| 275 | -0.00219727f, 0.14761353f, 0.79284668f, 0.06149292f, -0.00204468f, 0.14260864f, | ||
| 276 | 0.79452515f, 0.06469727f, -0.00192261f, 0.13769531f, 0.79605103f, 0.06796265f, | ||
| 277 | -0.00177002f, 0.13287354f, 0.79742432f, 0.07135010f, -0.00164795f, 0.12814331f, | ||
| 278 | 0.79861450f, 0.07479858f, -0.00152588f, 0.12347412f, 0.79962158f, 0.07836914f, | ||
| 279 | -0.00140381f, 0.11892700f, 0.80047607f, 0.08203125f, -0.00128174f, 0.11447144f, | ||
| 280 | 0.80117798f, 0.08578491f, -0.00115967f, 0.11010742f, 0.80169678f, 0.08959961f, | ||
| 281 | -0.00103760f, 0.10580444f, 0.80203247f, 0.09350586f, -0.00097656f, 0.10159302f, | ||
| 282 | 0.80221558f, 0.09750366f, | ||
| 283 | }; | ||
| 284 | |||
| 285 | const auto get_lut = [&]() -> std::span<const f32> { | ||
| 286 | if (sample_rate_ratio <= 1.0f) { | ||
| 287 | return std::span<const f32>(lut2.data(), lut2.size()); | ||
| 288 | } else if (sample_rate_ratio < 1.3f) { | ||
| 289 | return std::span<const f32>(lut1.data(), lut1.size()); | ||
| 290 | } else { | ||
| 291 | return std::span<const f32>(lut0.data(), lut0.size()); | ||
| 292 | } | ||
| 293 | }; | ||
| 294 | |||
| 295 | auto lut{get_lut()}; | ||
| 296 | u32 read_index{0}; | ||
| 297 | for (u32 i = 0; i < samples_to_write; i++) { | ||
| 298 | const auto lut_index{(fraction.get_frac() >> 8) * 4}; | ||
| 299 | const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]}; | ||
| 300 | const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]}; | ||
| 301 | const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]}; | ||
| 302 | const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]}; | ||
| 303 | output[i] = (sample0 + sample1 + sample2 + sample3).to_int_floor(); | ||
| 304 | fraction += sample_rate_ratio; | ||
| 305 | read_index += static_cast<u32>(fraction.to_int_floor()); | ||
| 306 | fraction.clear_int(); | ||
| 307 | } | ||
| 308 | } | ||
| 309 | |||
| 310 | static void ResampleHighQuality(std::span<s32> output, std::span<const s16> input, | ||
| 311 | const Common::FixedPoint<49, 15>& sample_rate_ratio, | ||
| 312 | Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) { | ||
| 313 | static constexpr std::array<f32, 1024> lut0 = { | ||
| 314 | -0.01776123f, -0.00070190f, 0.26672363f, 0.50006104f, 0.26956177f, 0.00024414f, | ||
| 315 | -0.01800537f, 0.00000000f, -0.01748657f, -0.00164795f, 0.26388550f, 0.50003052f, | ||
| 316 | 0.27236938f, 0.00122070f, -0.01824951f, -0.00003052f, -0.01724243f, -0.00256348f, | ||
| 317 | 0.26107788f, 0.49996948f, 0.27520752f, 0.00219727f, -0.01849365f, -0.00003052f, | ||
| 318 | -0.01699829f, -0.00344849f, 0.25823975f, 0.49984741f, 0.27801514f, 0.00320435f, | ||
| 319 | -0.01873779f, -0.00006104f, -0.01675415f, -0.00433350f, 0.25543213f, 0.49972534f, | ||
| 320 | 0.28085327f, 0.00424194f, -0.01898193f, -0.00006104f, -0.01651001f, -0.00518799f, | ||
| 321 | 0.25259399f, 0.49954224f, 0.28366089f, 0.00527954f, -0.01922607f, -0.00009155f, | ||
| 322 | -0.01626587f, -0.00604248f, 0.24978638f, 0.49932861f, 0.28646851f, 0.00634766f, | ||
| 323 | -0.01947021f, -0.00012207f, -0.01602173f, -0.00686646f, 0.24697876f, 0.49908447f, | ||
| 324 | 0.28930664f, 0.00744629f, -0.01971436f, -0.00015259f, -0.01574707f, -0.00765991f, | ||
| 325 | 0.24414062f, 0.49877930f, 0.29211426f, 0.00854492f, -0.01995850f, -0.00015259f, | ||
| 326 | -0.01550293f, -0.00845337f, 0.24133301f, 0.49847412f, 0.29492188f, 0.00967407f, | ||
| 327 | -0.02020264f, -0.00018311f, -0.01525879f, -0.00921631f, 0.23852539f, 0.49810791f, | ||
| 328 | 0.29772949f, 0.01083374f, -0.02044678f, -0.00021362f, -0.01501465f, -0.00997925f, | ||
| 329 | 0.23571777f, 0.49774170f, 0.30050659f, 0.01199341f, -0.02069092f, -0.00024414f, | ||
| 330 | -0.01477051f, -0.01071167f, 0.23291016f, 0.49731445f, 0.30331421f, 0.01318359f, | ||
| 331 | -0.02093506f, -0.00027466f, -0.01452637f, -0.01141357f, 0.23010254f, 0.49685669f, | ||
| 332 | 0.30609131f, 0.01437378f, -0.02117920f, -0.00030518f, -0.01428223f, -0.01211548f, | ||
| 333 | 0.22732544f, 0.49636841f, 0.30886841f, 0.01559448f, -0.02142334f, -0.00033569f, | ||
| 334 | -0.01403809f, -0.01278687f, 0.22451782f, 0.49581909f, 0.31164551f, 0.01684570f, | ||
| 335 | -0.02163696f, -0.00039673f, -0.01379395f, -0.01345825f, 0.22174072f, 0.49526978f, | ||
| 336 | 0.31442261f, 0.01809692f, -0.02188110f, -0.00042725f, -0.01358032f, -0.01409912f, | ||
| 337 | 0.21896362f, 0.49465942f, 0.31719971f, 0.01937866f, -0.02209473f, -0.00045776f, | ||
| 338 | -0.01333618f, -0.01473999f, 0.21618652f, 0.49404907f, 0.31994629f, 0.02069092f, | ||
| 339 | -0.02233887f, -0.00048828f, -0.01309204f, -0.01535034f, 0.21343994f, 0.49337769f, | ||
| 340 | 0.32269287f, 0.02203369f, -0.02255249f, -0.00054932f, -0.01284790f, -0.01596069f, | ||
| 341 | 0.21066284f, 0.49267578f, 0.32543945f, 0.02337646f, -0.02279663f, -0.00057983f, | ||
| 342 | -0.01263428f, -0.01654053f, 0.20791626f, 0.49194336f, 0.32818604f, 0.02471924f, | ||
| 343 | -0.02301025f, -0.00064087f, -0.01239014f, -0.01708984f, 0.20516968f, 0.49118042f, | ||
| 344 | 0.33090210f, 0.02612305f, -0.02322388f, -0.00067139f, -0.01214600f, -0.01763916f, | ||
| 345 | 0.20242310f, 0.49035645f, 0.33361816f, 0.02752686f, -0.02343750f, -0.00073242f, | ||
| 346 | -0.01193237f, -0.01818848f, 0.19970703f, 0.48953247f, 0.33633423f, 0.02896118f, | ||
| 347 | -0.02365112f, -0.00079346f, -0.01168823f, -0.01867676f, 0.19696045f, 0.48864746f, | ||
| 348 | 0.33901978f, 0.03039551f, -0.02386475f, -0.00082397f, -0.01147461f, -0.01919556f, | ||
| 349 | 0.19427490f, 0.48776245f, 0.34170532f, 0.03186035f, -0.02407837f, -0.00088501f, | ||
| 350 | -0.01123047f, -0.01968384f, 0.19155884f, 0.48681641f, 0.34439087f, 0.03335571f, | ||
| 351 | -0.02429199f, -0.00094604f, -0.01101685f, -0.02014160f, 0.18887329f, 0.48583984f, | ||
| 352 | 0.34704590f, 0.03485107f, -0.02447510f, -0.00100708f, -0.01080322f, -0.02059937f, | ||
| 353 | 0.18615723f, 0.48483276f, 0.34970093f, 0.03637695f, -0.02468872f, -0.00106812f, | ||
| 354 | -0.01058960f, -0.02102661f, 0.18350220f, 0.48379517f, 0.35235596f, 0.03793335f, | ||
| 355 | -0.02487183f, -0.00112915f, -0.01034546f, -0.02145386f, 0.18081665f, 0.48272705f, | ||
| 356 | 0.35498047f, 0.03948975f, -0.02505493f, -0.00119019f, -0.01013184f, -0.02188110f, | ||
| 357 | 0.17816162f, 0.48162842f, 0.35760498f, 0.04107666f, -0.02523804f, -0.00125122f, | ||
| 358 | -0.00991821f, -0.02227783f, 0.17550659f, 0.48049927f, 0.36019897f, 0.04269409f, | ||
| 359 | -0.02542114f, -0.00131226f, -0.00970459f, -0.02264404f, 0.17288208f, 0.47933960f, | ||
| 360 | 0.36279297f, 0.04431152f, -0.02560425f, -0.00140381f, -0.00952148f, -0.02301025f, | ||
| 361 | 0.17025757f, 0.47814941f, 0.36538696f, 0.04595947f, -0.02578735f, -0.00146484f, | ||
| 362 | -0.00930786f, -0.02337646f, 0.16763306f, 0.47689819f, 0.36795044f, 0.04763794f, | ||
| 363 | -0.02593994f, -0.00152588f, -0.00909424f, -0.02371216f, 0.16503906f, 0.47564697f, | ||
| 364 | 0.37048340f, 0.04931641f, -0.02609253f, -0.00161743f, -0.00888062f, -0.02401733f, | ||
| 365 | 0.16244507f, 0.47436523f, 0.37304688f, 0.05102539f, -0.02627563f, -0.00170898f, | ||
| 366 | -0.00869751f, -0.02435303f, 0.15988159f, 0.47302246f, 0.37554932f, 0.05276489f, | ||
| 367 | -0.02642822f, -0.00177002f, -0.00848389f, -0.02462769f, 0.15731812f, 0.47167969f, | ||
| 368 | 0.37805176f, 0.05450439f, -0.02658081f, -0.00186157f, -0.00830078f, -0.02493286f, | ||
| 369 | 0.15475464f, 0.47027588f, 0.38055420f, 0.05627441f, -0.02670288f, -0.00195312f, | ||
| 370 | -0.00808716f, -0.02520752f, 0.15222168f, 0.46887207f, 0.38302612f, 0.05804443f, | ||
| 371 | -0.02685547f, -0.00204468f, -0.00790405f, -0.02545166f, 0.14968872f, 0.46743774f, | ||
| 372 | 0.38546753f, 0.05987549f, -0.02697754f, -0.00213623f, -0.00772095f, -0.02569580f, | ||
| 373 | 0.14718628f, 0.46594238f, 0.38790894f, 0.06170654f, -0.02709961f, -0.00222778f, | ||
| 374 | -0.00753784f, -0.02593994f, 0.14468384f, 0.46444702f, 0.39031982f, 0.06353760f, | ||
| 375 | -0.02722168f, -0.00231934f, -0.00735474f, -0.02615356f, 0.14218140f, 0.46289062f, | ||
| 376 | 0.39273071f, 0.06539917f, -0.02734375f, -0.00241089f, -0.00717163f, -0.02636719f, | ||
| 377 | 0.13970947f, 0.46133423f, 0.39511108f, 0.06729126f, -0.02743530f, -0.00250244f, | ||
| 378 | -0.00698853f, -0.02655029f, 0.13726807f, 0.45974731f, 0.39749146f, 0.06918335f, | ||
| 379 | -0.02755737f, -0.00259399f, -0.00680542f, -0.02673340f, 0.13479614f, 0.45812988f, | ||
| 380 | 0.39984131f, 0.07113647f, -0.02764893f, -0.00271606f, -0.00662231f, -0.02691650f, | ||
| 381 | 0.13238525f, 0.45648193f, 0.40216064f, 0.07305908f, -0.02774048f, -0.00280762f, | ||
| 382 | -0.00643921f, -0.02706909f, 0.12997437f, 0.45480347f, 0.40447998f, 0.07504272f, | ||
| 383 | -0.02780151f, -0.00292969f, -0.00628662f, -0.02722168f, 0.12756348f, 0.45309448f, | ||
| 384 | 0.40676880f, 0.07699585f, -0.02789307f, -0.00305176f, -0.00610352f, -0.02734375f, | ||
| 385 | 0.12518311f, 0.45135498f, 0.40902710f, 0.07901001f, -0.02795410f, -0.00314331f, | ||
| 386 | -0.00595093f, -0.02746582f, 0.12280273f, 0.44958496f, 0.41128540f, 0.08102417f, | ||
| 387 | -0.02801514f, -0.00326538f, -0.00579834f, -0.02758789f, 0.12045288f, 0.44778442f, | ||
| 388 | 0.41351318f, 0.08306885f, -0.02804565f, -0.00338745f, -0.00561523f, -0.02770996f, | ||
| 389 | 0.11813354f, 0.44598389f, 0.41571045f, 0.08511353f, -0.02810669f, -0.00350952f, | ||
| 390 | -0.00546265f, -0.02780151f, 0.11581421f, 0.44412231f, 0.41787720f, 0.08718872f, | ||
| 391 | -0.02813721f, -0.00363159f, -0.00531006f, -0.02786255f, 0.11349487f, 0.44226074f, | ||
| 392 | 0.42004395f, 0.08929443f, -0.02816772f, -0.00375366f, -0.00515747f, -0.02795410f, | ||
| 393 | 0.11120605f, 0.44036865f, 0.42218018f, 0.09140015f, -0.02816772f, -0.00387573f, | ||
| 394 | -0.00500488f, -0.02801514f, 0.10894775f, 0.43844604f, 0.42431641f, 0.09353638f, | ||
| 395 | -0.02819824f, -0.00402832f, -0.00485229f, -0.02807617f, 0.10668945f, 0.43649292f, | ||
| 396 | 0.42639160f, 0.09570312f, -0.02819824f, -0.00415039f, -0.00469971f, -0.02810669f, | ||
| 397 | 0.10446167f, 0.43453979f, 0.42846680f, 0.09786987f, -0.02819824f, -0.00427246f, | ||
| 398 | -0.00457764f, -0.02813721f, 0.10223389f, 0.43252563f, 0.43051147f, 0.10003662f, | ||
| 399 | -0.02816772f, -0.00442505f, -0.00442505f, -0.02816772f, 0.10003662f, 0.43051147f, | ||
| 400 | 0.43252563f, 0.10223389f, -0.02813721f, -0.00457764f, -0.00427246f, -0.02819824f, | ||
| 401 | 0.09786987f, 0.42846680f, 0.43453979f, 0.10446167f, -0.02810669f, -0.00469971f, | ||
| 402 | -0.00415039f, -0.02819824f, 0.09570312f, 0.42639160f, 0.43649292f, 0.10668945f, | ||
| 403 | -0.02807617f, -0.00485229f, -0.00402832f, -0.02819824f, 0.09353638f, 0.42431641f, | ||
| 404 | 0.43844604f, 0.10894775f, -0.02801514f, -0.00500488f, -0.00387573f, -0.02816772f, | ||
| 405 | 0.09140015f, 0.42218018f, 0.44036865f, 0.11120605f, -0.02795410f, -0.00515747f, | ||
| 406 | -0.00375366f, -0.02816772f, 0.08929443f, 0.42004395f, 0.44226074f, 0.11349487f, | ||
| 407 | -0.02786255f, -0.00531006f, -0.00363159f, -0.02813721f, 0.08718872f, 0.41787720f, | ||
| 408 | 0.44412231f, 0.11581421f, -0.02780151f, -0.00546265f, -0.00350952f, -0.02810669f, | ||
| 409 | 0.08511353f, 0.41571045f, 0.44598389f, 0.11813354f, -0.02770996f, -0.00561523f, | ||
| 410 | -0.00338745f, -0.02804565f, 0.08306885f, 0.41351318f, 0.44778442f, 0.12045288f, | ||
| 411 | -0.02758789f, -0.00579834f, -0.00326538f, -0.02801514f, 0.08102417f, 0.41128540f, | ||
| 412 | 0.44958496f, 0.12280273f, -0.02746582f, -0.00595093f, -0.00314331f, -0.02795410f, | ||
| 413 | 0.07901001f, 0.40902710f, 0.45135498f, 0.12518311f, -0.02734375f, -0.00610352f, | ||
| 414 | -0.00305176f, -0.02789307f, 0.07699585f, 0.40676880f, 0.45309448f, 0.12756348f, | ||
| 415 | -0.02722168f, -0.00628662f, -0.00292969f, -0.02780151f, 0.07504272f, 0.40447998f, | ||
| 416 | 0.45480347f, 0.12997437f, -0.02706909f, -0.00643921f, -0.00280762f, -0.02774048f, | ||
| 417 | 0.07305908f, 0.40216064f, 0.45648193f, 0.13238525f, -0.02691650f, -0.00662231f, | ||
| 418 | -0.00271606f, -0.02764893f, 0.07113647f, 0.39984131f, 0.45812988f, 0.13479614f, | ||
| 419 | -0.02673340f, -0.00680542f, -0.00259399f, -0.02755737f, 0.06918335f, 0.39749146f, | ||
| 420 | 0.45974731f, 0.13726807f, -0.02655029f, -0.00698853f, -0.00250244f, -0.02743530f, | ||
| 421 | 0.06729126f, 0.39511108f, 0.46133423f, 0.13970947f, -0.02636719f, -0.00717163f, | ||
| 422 | -0.00241089f, -0.02734375f, 0.06539917f, 0.39273071f, 0.46289062f, 0.14218140f, | ||
| 423 | -0.02615356f, -0.00735474f, -0.00231934f, -0.02722168f, 0.06353760f, 0.39031982f, | ||
| 424 | 0.46444702f, 0.14468384f, -0.02593994f, -0.00753784f, -0.00222778f, -0.02709961f, | ||
| 425 | 0.06170654f, 0.38790894f, 0.46594238f, 0.14718628f, -0.02569580f, -0.00772095f, | ||
| 426 | -0.00213623f, -0.02697754f, 0.05987549f, 0.38546753f, 0.46743774f, 0.14968872f, | ||
| 427 | -0.02545166f, -0.00790405f, -0.00204468f, -0.02685547f, 0.05804443f, 0.38302612f, | ||
| 428 | 0.46887207f, 0.15222168f, -0.02520752f, -0.00808716f, -0.00195312f, -0.02670288f, | ||
| 429 | 0.05627441f, 0.38055420f, 0.47027588f, 0.15475464f, -0.02493286f, -0.00830078f, | ||
| 430 | -0.00186157f, -0.02658081f, 0.05450439f, 0.37805176f, 0.47167969f, 0.15731812f, | ||
| 431 | -0.02462769f, -0.00848389f, -0.00177002f, -0.02642822f, 0.05276489f, 0.37554932f, | ||
| 432 | 0.47302246f, 0.15988159f, -0.02435303f, -0.00869751f, -0.00170898f, -0.02627563f, | ||
| 433 | 0.05102539f, 0.37304688f, 0.47436523f, 0.16244507f, -0.02401733f, -0.00888062f, | ||
| 434 | -0.00161743f, -0.02609253f, 0.04931641f, 0.37048340f, 0.47564697f, 0.16503906f, | ||
| 435 | -0.02371216f, -0.00909424f, -0.00152588f, -0.02593994f, 0.04763794f, 0.36795044f, | ||
| 436 | 0.47689819f, 0.16763306f, -0.02337646f, -0.00930786f, -0.00146484f, -0.02578735f, | ||
| 437 | 0.04595947f, 0.36538696f, 0.47814941f, 0.17025757f, -0.02301025f, -0.00952148f, | ||
| 438 | -0.00140381f, -0.02560425f, 0.04431152f, 0.36279297f, 0.47933960f, 0.17288208f, | ||
| 439 | -0.02264404f, -0.00970459f, -0.00131226f, -0.02542114f, 0.04269409f, 0.36019897f, | ||
| 440 | 0.48049927f, 0.17550659f, -0.02227783f, -0.00991821f, -0.00125122f, -0.02523804f, | ||
| 441 | 0.04107666f, 0.35760498f, 0.48162842f, 0.17816162f, -0.02188110f, -0.01013184f, | ||
| 442 | -0.00119019f, -0.02505493f, 0.03948975f, 0.35498047f, 0.48272705f, 0.18081665f, | ||
| 443 | -0.02145386f, -0.01034546f, -0.00112915f, -0.02487183f, 0.03793335f, 0.35235596f, | ||
| 444 | 0.48379517f, 0.18350220f, -0.02102661f, -0.01058960f, -0.00106812f, -0.02468872f, | ||
| 445 | 0.03637695f, 0.34970093f, 0.48483276f, 0.18615723f, -0.02059937f, -0.01080322f, | ||
| 446 | -0.00100708f, -0.02447510f, 0.03485107f, 0.34704590f, 0.48583984f, 0.18887329f, | ||
| 447 | -0.02014160f, -0.01101685f, -0.00094604f, -0.02429199f, 0.03335571f, 0.34439087f, | ||
| 448 | 0.48681641f, 0.19155884f, -0.01968384f, -0.01123047f, -0.00088501f, -0.02407837f, | ||
| 449 | 0.03186035f, 0.34170532f, 0.48776245f, 0.19427490f, -0.01919556f, -0.01147461f, | ||
| 450 | -0.00082397f, -0.02386475f, 0.03039551f, 0.33901978f, 0.48864746f, 0.19696045f, | ||
| 451 | -0.01867676f, -0.01168823f, -0.00079346f, -0.02365112f, 0.02896118f, 0.33633423f, | ||
| 452 | 0.48953247f, 0.19970703f, -0.01818848f, -0.01193237f, -0.00073242f, -0.02343750f, | ||
| 453 | 0.02752686f, 0.33361816f, 0.49035645f, 0.20242310f, -0.01763916f, -0.01214600f, | ||
| 454 | -0.00067139f, -0.02322388f, 0.02612305f, 0.33090210f, 0.49118042f, 0.20516968f, | ||
| 455 | -0.01708984f, -0.01239014f, -0.00064087f, -0.02301025f, 0.02471924f, 0.32818604f, | ||
| 456 | 0.49194336f, 0.20791626f, -0.01654053f, -0.01263428f, -0.00057983f, -0.02279663f, | ||
| 457 | 0.02337646f, 0.32543945f, 0.49267578f, 0.21066284f, -0.01596069f, -0.01284790f, | ||
| 458 | -0.00054932f, -0.02255249f, 0.02203369f, 0.32269287f, 0.49337769f, 0.21343994f, | ||
| 459 | -0.01535034f, -0.01309204f, -0.00048828f, -0.02233887f, 0.02069092f, 0.31994629f, | ||
| 460 | 0.49404907f, 0.21618652f, -0.01473999f, -0.01333618f, -0.00045776f, -0.02209473f, | ||
| 461 | 0.01937866f, 0.31719971f, 0.49465942f, 0.21896362f, -0.01409912f, -0.01358032f, | ||
| 462 | -0.00042725f, -0.02188110f, 0.01809692f, 0.31442261f, 0.49526978f, 0.22174072f, | ||
| 463 | -0.01345825f, -0.01379395f, -0.00039673f, -0.02163696f, 0.01684570f, 0.31164551f, | ||
| 464 | 0.49581909f, 0.22451782f, -0.01278687f, -0.01403809f, -0.00033569f, -0.02142334f, | ||
| 465 | 0.01559448f, 0.30886841f, 0.49636841f, 0.22732544f, -0.01211548f, -0.01428223f, | ||
| 466 | -0.00030518f, -0.02117920f, 0.01437378f, 0.30609131f, 0.49685669f, 0.23010254f, | ||
| 467 | -0.01141357f, -0.01452637f, -0.00027466f, -0.02093506f, 0.01318359f, 0.30331421f, | ||
| 468 | 0.49731445f, 0.23291016f, -0.01071167f, -0.01477051f, -0.00024414f, -0.02069092f, | ||
| 469 | 0.01199341f, 0.30050659f, 0.49774170f, 0.23571777f, -0.00997925f, -0.01501465f, | ||
| 470 | -0.00021362f, -0.02044678f, 0.01083374f, 0.29772949f, 0.49810791f, 0.23852539f, | ||
| 471 | -0.00921631f, -0.01525879f, -0.00018311f, -0.02020264f, 0.00967407f, 0.29492188f, | ||
| 472 | 0.49847412f, 0.24133301f, -0.00845337f, -0.01550293f, -0.00015259f, -0.01995850f, | ||
| 473 | 0.00854492f, 0.29211426f, 0.49877930f, 0.24414062f, -0.00765991f, -0.01574707f, | ||
| 474 | -0.00015259f, -0.01971436f, 0.00744629f, 0.28930664f, 0.49908447f, 0.24697876f, | ||
| 475 | -0.00686646f, -0.01602173f, -0.00012207f, -0.01947021f, 0.00634766f, 0.28646851f, | ||
| 476 | 0.49932861f, 0.24978638f, -0.00604248f, -0.01626587f, -0.00009155f, -0.01922607f, | ||
| 477 | 0.00527954f, 0.28366089f, 0.49954224f, 0.25259399f, -0.00518799f, -0.01651001f, | ||
| 478 | -0.00006104f, -0.01898193f, 0.00424194f, 0.28085327f, 0.49972534f, 0.25543213f, | ||
| 479 | -0.00433350f, -0.01675415f, -0.00006104f, -0.01873779f, 0.00320435f, 0.27801514f, | ||
| 480 | 0.49984741f, 0.25823975f, -0.00344849f, -0.01699829f, -0.00003052f, -0.01849365f, | ||
| 481 | 0.00219727f, 0.27520752f, 0.49996948f, 0.26107788f, -0.00256348f, -0.01724243f, | ||
| 482 | -0.00003052f, -0.01824951f, 0.00122070f, 0.27236938f, 0.50003052f, 0.26388550f, | ||
| 483 | -0.00164795f, -0.01748657f, 0.00000000f, -0.01800537f, 0.00024414f, 0.26956177f, | ||
| 484 | 0.50006104f, 0.26672363f, -0.00070190f, -0.01776123f, | ||
| 485 | }; | ||
| 486 | |||
| 487 | static constexpr std::array<f32, 1024> lut1 = { | ||
| 488 | 0.01275635f, -0.07745361f, 0.18670654f, 0.75119019f, 0.19219971f, -0.07821655f, | ||
| 489 | 0.01272583f, 0.00000000f, 0.01281738f, -0.07666016f, 0.18124390f, 0.75106812f, | ||
| 490 | 0.19772339f, -0.07897949f, 0.01266479f, 0.00003052f, 0.01284790f, -0.07583618f, | ||
| 491 | 0.17581177f, 0.75088501f, 0.20330811f, -0.07971191f, 0.01257324f, 0.00006104f, | ||
| 492 | 0.01287842f, -0.07501221f, 0.17044067f, 0.75057983f, 0.20892334f, -0.08041382f, | ||
| 493 | 0.01248169f, 0.00009155f, 0.01290894f, -0.07415771f, 0.16510010f, 0.75018311f, | ||
| 494 | 0.21453857f, -0.08111572f, 0.01239014f, 0.00012207f, 0.01290894f, -0.07330322f, | ||
| 495 | 0.15979004f, 0.74966431f, 0.22021484f, -0.08178711f, 0.01229858f, 0.00015259f, | ||
| 496 | 0.01290894f, -0.07241821f, 0.15454102f, 0.74908447f, 0.22592163f, -0.08242798f, | ||
| 497 | 0.01217651f, 0.00018311f, 0.01290894f, -0.07150269f, 0.14932251f, 0.74838257f, | ||
| 498 | 0.23165894f, -0.08303833f, 0.01205444f, 0.00021362f, 0.01290894f, -0.07058716f, | ||
| 499 | 0.14416504f, 0.74755859f, 0.23742676f, -0.08364868f, 0.01193237f, 0.00024414f, | ||
| 500 | 0.01287842f, -0.06967163f, 0.13903809f, 0.74667358f, 0.24322510f, -0.08419800f, | ||
| 501 | 0.01177979f, 0.00027466f, 0.01284790f, -0.06872559f, 0.13397217f, 0.74566650f, | ||
| 502 | 0.24905396f, -0.08474731f, 0.01162720f, 0.00033569f, 0.01281738f, -0.06777954f, | ||
| 503 | 0.12893677f, 0.74456787f, 0.25491333f, -0.08526611f, 0.01147461f, 0.00036621f, | ||
| 504 | 0.01278687f, -0.06683350f, 0.12396240f, 0.74337769f, 0.26077271f, -0.08575439f, | ||
| 505 | 0.01129150f, 0.00042725f, 0.01275635f, -0.06585693f, 0.11901855f, 0.74206543f, | ||
| 506 | 0.26669312f, -0.08621216f, 0.01110840f, 0.00045776f, 0.01269531f, -0.06488037f, | ||
| 507 | 0.11413574f, 0.74069214f, 0.27261353f, -0.08663940f, 0.01092529f, 0.00051880f, | ||
| 508 | 0.01263428f, -0.06387329f, 0.10931396f, 0.73919678f, 0.27853394f, -0.08700562f, | ||
| 509 | 0.01071167f, 0.00057983f, 0.01257324f, -0.06286621f, 0.10452271f, 0.73760986f, | ||
| 510 | 0.28451538f, -0.08737183f, 0.01049805f, 0.00064087f, 0.01251221f, -0.06185913f, | ||
| 511 | 0.09979248f, 0.73593140f, 0.29049683f, -0.08770752f, 0.01025391f, 0.00067139f, | ||
| 512 | 0.01242065f, -0.06082153f, 0.09512329f, 0.73413086f, 0.29647827f, -0.08801270f, | ||
| 513 | 0.01000977f, 0.00073242f, 0.01232910f, -0.05981445f, 0.09051514f, 0.73226929f, | ||
| 514 | 0.30249023f, -0.08828735f, 0.00973511f, 0.00079346f, 0.01226807f, -0.05877686f, | ||
| 515 | 0.08593750f, 0.73028564f, 0.30853271f, -0.08850098f, 0.00949097f, 0.00088501f, | ||
| 516 | 0.01214600f, -0.05773926f, 0.08142090f, 0.72824097f, 0.31457520f, -0.08871460f, | ||
| 517 | 0.00918579f, 0.00094604f, 0.01205444f, -0.05670166f, 0.07696533f, 0.72607422f, | ||
| 518 | 0.32061768f, -0.08886719f, 0.00891113f, 0.00100708f, 0.01196289f, -0.05563354f, | ||
| 519 | 0.07257080f, 0.72381592f, 0.32669067f, -0.08898926f, 0.00860596f, 0.00106812f, | ||
| 520 | 0.01187134f, -0.05459595f, 0.06820679f, 0.72146606f, 0.33276367f, -0.08908081f, | ||
| 521 | 0.00827026f, 0.00115967f, 0.01174927f, -0.05352783f, 0.06393433f, 0.71902466f, | ||
| 522 | 0.33883667f, -0.08911133f, 0.00796509f, 0.00122070f, 0.01162720f, -0.05245972f, | ||
| 523 | 0.05969238f, 0.71649170f, 0.34494019f, -0.08914185f, 0.00759888f, 0.00131226f, | ||
| 524 | 0.01150513f, -0.05139160f, 0.05551147f, 0.71389771f, 0.35101318f, -0.08911133f, | ||
| 525 | 0.00726318f, 0.00137329f, 0.01138306f, -0.05032349f, 0.05139160f, 0.71118164f, | ||
| 526 | 0.35711670f, -0.08901978f, 0.00686646f, 0.00146484f, 0.01126099f, -0.04928589f, | ||
| 527 | 0.04733276f, 0.70837402f, 0.36322021f, -0.08892822f, 0.00650024f, 0.00155640f, | ||
| 528 | 0.01113892f, -0.04821777f, 0.04333496f, 0.70550537f, 0.36932373f, -0.08877563f, | ||
| 529 | 0.00610352f, 0.00164795f, 0.01101685f, -0.04714966f, 0.03939819f, 0.70251465f, | ||
| 530 | 0.37542725f, -0.08856201f, 0.00567627f, 0.00173950f, 0.01086426f, -0.04608154f, | ||
| 531 | 0.03549194f, 0.69946289f, 0.38153076f, -0.08834839f, 0.00527954f, 0.00183105f, | ||
| 532 | 0.01074219f, -0.04501343f, 0.03167725f, 0.69631958f, 0.38763428f, -0.08804321f, | ||
| 533 | 0.00482178f, 0.00192261f, 0.01058960f, -0.04394531f, 0.02792358f, 0.69308472f, | ||
| 534 | 0.39370728f, -0.08773804f, 0.00436401f, 0.00201416f, 0.01043701f, -0.04287720f, | ||
| 535 | 0.02420044f, 0.68975830f, 0.39981079f, -0.08737183f, 0.00390625f, 0.00210571f, | ||
| 536 | 0.01031494f, -0.04180908f, 0.02056885f, 0.68637085f, 0.40588379f, -0.08694458f, | ||
| 537 | 0.00344849f, 0.00222778f, 0.01016235f, -0.04074097f, 0.01699829f, 0.68289185f, | ||
| 538 | 0.41195679f, -0.08648682f, 0.00296021f, 0.00231934f, 0.01000977f, -0.03970337f, | ||
| 539 | 0.01345825f, 0.67932129f, 0.41802979f, -0.08596802f, 0.00244141f, 0.00244141f, | ||
| 540 | 0.00985718f, -0.03863525f, 0.01000977f, 0.67568970f, 0.42407227f, -0.08541870f, | ||
| 541 | 0.00192261f, 0.00253296f, 0.00970459f, -0.03759766f, 0.00662231f, 0.67196655f, | ||
| 542 | 0.43011475f, -0.08480835f, 0.00140381f, 0.00265503f, 0.00955200f, -0.03652954f, | ||
| 543 | 0.00326538f, 0.66815186f, 0.43612671f, -0.08416748f, 0.00085449f, 0.00277710f, | ||
| 544 | 0.00936890f, -0.03549194f, 0.00000000f, 0.66427612f, 0.44213867f, -0.08346558f, | ||
| 545 | 0.00027466f, 0.00289917f, 0.00921631f, -0.03445435f, -0.00320435f, 0.66030884f, | ||
| 546 | 0.44812012f, -0.08270264f, -0.00027466f, 0.00299072f, 0.00906372f, -0.03344727f, | ||
| 547 | -0.00634766f, 0.65631104f, 0.45407104f, -0.08190918f, -0.00088501f, 0.00311279f, | ||
| 548 | 0.00891113f, -0.03240967f, -0.00946045f, 0.65219116f, 0.46002197f, -0.08105469f, | ||
| 549 | -0.00146484f, 0.00323486f, 0.00872803f, -0.03140259f, -0.01248169f, 0.64801025f, | ||
| 550 | 0.46594238f, -0.08013916f, -0.00210571f, 0.00338745f, 0.00857544f, -0.03039551f, | ||
| 551 | -0.01544189f, 0.64376831f, 0.47183228f, -0.07919312f, -0.00271606f, 0.00350952f, | ||
| 552 | 0.00842285f, -0.02938843f, -0.01834106f, 0.63946533f, 0.47772217f, -0.07818604f, | ||
| 553 | -0.00335693f, 0.00363159f, 0.00823975f, -0.02838135f, -0.02117920f, 0.63507080f, | ||
| 554 | 0.48358154f, -0.07711792f, -0.00402832f, 0.00375366f, 0.00808716f, -0.02740479f, | ||
| 555 | -0.02395630f, 0.63061523f, 0.48937988f, -0.07598877f, -0.00469971f, 0.00390625f, | ||
| 556 | 0.00793457f, -0.02642822f, -0.02667236f, 0.62609863f, 0.49517822f, -0.07482910f, | ||
| 557 | -0.00537109f, 0.00402832f, 0.00775146f, -0.02545166f, -0.02932739f, 0.62152100f, | ||
| 558 | 0.50094604f, -0.07357788f, -0.00607300f, 0.00418091f, 0.00759888f, -0.02450562f, | ||
| 559 | -0.03192139f, 0.61685181f, 0.50665283f, -0.07229614f, -0.00677490f, 0.00430298f, | ||
| 560 | 0.00741577f, -0.02352905f, -0.03445435f, 0.61215210f, 0.51235962f, -0.07098389f, | ||
| 561 | -0.00750732f, 0.00445557f, 0.00726318f, -0.02258301f, -0.03689575f, 0.60736084f, | ||
| 562 | 0.51800537f, -0.06958008f, -0.00823975f, 0.00460815f, 0.00711060f, -0.02166748f, | ||
| 563 | -0.03930664f, 0.60253906f, 0.52362061f, -0.06811523f, -0.00897217f, 0.00476074f, | ||
| 564 | 0.00692749f, -0.02075195f, -0.04165649f, 0.59762573f, 0.52920532f, -0.06661987f, | ||
| 565 | -0.00973511f, 0.00488281f, 0.00677490f, -0.01983643f, -0.04394531f, 0.59268188f, | ||
| 566 | 0.53475952f, -0.06506348f, -0.01052856f, 0.00503540f, 0.00662231f, -0.01892090f, | ||
| 567 | -0.04617310f, 0.58767700f, 0.54025269f, -0.06344604f, -0.01129150f, 0.00518799f, | ||
| 568 | 0.00643921f, -0.01803589f, -0.04830933f, 0.58261108f, 0.54571533f, -0.06173706f, | ||
| 569 | -0.01208496f, 0.00534058f, 0.00628662f, -0.01715088f, -0.05041504f, 0.57748413f, | ||
| 570 | 0.55111694f, -0.05999756f, -0.01290894f, 0.00549316f, 0.00613403f, -0.01626587f, | ||
| 571 | -0.05245972f, 0.57232666f, 0.55648804f, -0.05819702f, -0.01373291f, 0.00564575f, | ||
| 572 | 0.00598145f, -0.01541138f, -0.05444336f, 0.56707764f, 0.56182861f, -0.05636597f, | ||
| 573 | -0.01455688f, 0.00582886f, 0.00582886f, -0.01455688f, -0.05636597f, 0.56182861f, | ||
| 574 | 0.56707764f, -0.05444336f, -0.01541138f, 0.00598145f, 0.00564575f, -0.01373291f, | ||
| 575 | -0.05819702f, 0.55648804f, 0.57232666f, -0.05245972f, -0.01626587f, 0.00613403f, | ||
| 576 | 0.00549316f, -0.01290894f, -0.05999756f, 0.55111694f, 0.57748413f, -0.05041504f, | ||
| 577 | -0.01715088f, 0.00628662f, 0.00534058f, -0.01208496f, -0.06173706f, 0.54571533f, | ||
| 578 | 0.58261108f, -0.04830933f, -0.01803589f, 0.00643921f, 0.00518799f, -0.01129150f, | ||
| 579 | -0.06344604f, 0.54025269f, 0.58767700f, -0.04617310f, -0.01892090f, 0.00662231f, | ||
| 580 | 0.00503540f, -0.01052856f, -0.06506348f, 0.53475952f, 0.59268188f, -0.04394531f, | ||
| 581 | -0.01983643f, 0.00677490f, 0.00488281f, -0.00973511f, -0.06661987f, 0.52920532f, | ||
| 582 | 0.59762573f, -0.04165649f, -0.02075195f, 0.00692749f, 0.00476074f, -0.00897217f, | ||
| 583 | -0.06811523f, 0.52362061f, 0.60253906f, -0.03930664f, -0.02166748f, 0.00711060f, | ||
| 584 | 0.00460815f, -0.00823975f, -0.06958008f, 0.51800537f, 0.60736084f, -0.03689575f, | ||
| 585 | -0.02258301f, 0.00726318f, 0.00445557f, -0.00750732f, -0.07098389f, 0.51235962f, | ||
| 586 | 0.61215210f, -0.03445435f, -0.02352905f, 0.00741577f, 0.00430298f, -0.00677490f, | ||
| 587 | -0.07229614f, 0.50665283f, 0.61685181f, -0.03192139f, -0.02450562f, 0.00759888f, | ||
| 588 | 0.00418091f, -0.00607300f, -0.07357788f, 0.50094604f, 0.62152100f, -0.02932739f, | ||
| 589 | -0.02545166f, 0.00775146f, 0.00402832f, -0.00537109f, -0.07482910f, 0.49517822f, | ||
| 590 | 0.62609863f, -0.02667236f, -0.02642822f, 0.00793457f, 0.00390625f, -0.00469971f, | ||
| 591 | -0.07598877f, 0.48937988f, 0.63061523f, -0.02395630f, -0.02740479f, 0.00808716f, | ||
| 592 | 0.00375366f, -0.00402832f, -0.07711792f, 0.48358154f, 0.63507080f, -0.02117920f, | ||
| 593 | -0.02838135f, 0.00823975f, 0.00363159f, -0.00335693f, -0.07818604f, 0.47772217f, | ||
| 594 | 0.63946533f, -0.01834106f, -0.02938843f, 0.00842285f, 0.00350952f, -0.00271606f, | ||
| 595 | -0.07919312f, 0.47183228f, 0.64376831f, -0.01544189f, -0.03039551f, 0.00857544f, | ||
| 596 | 0.00338745f, -0.00210571f, -0.08013916f, 0.46594238f, 0.64801025f, -0.01248169f, | ||
| 597 | -0.03140259f, 0.00872803f, 0.00323486f, -0.00146484f, -0.08105469f, 0.46002197f, | ||
| 598 | 0.65219116f, -0.00946045f, -0.03240967f, 0.00891113f, 0.00311279f, -0.00088501f, | ||
| 599 | -0.08190918f, 0.45407104f, 0.65631104f, -0.00634766f, -0.03344727f, 0.00906372f, | ||
| 600 | 0.00299072f, -0.00027466f, -0.08270264f, 0.44812012f, 0.66030884f, -0.00320435f, | ||
| 601 | -0.03445435f, 0.00921631f, 0.00289917f, 0.00027466f, -0.08346558f, 0.44213867f, | ||
| 602 | 0.66427612f, 0.00000000f, -0.03549194f, 0.00936890f, 0.00277710f, 0.00085449f, | ||
| 603 | -0.08416748f, 0.43612671f, 0.66815186f, 0.00326538f, -0.03652954f, 0.00955200f, | ||
| 604 | 0.00265503f, 0.00140381f, -0.08480835f, 0.43011475f, 0.67196655f, 0.00662231f, | ||
| 605 | -0.03759766f, 0.00970459f, 0.00253296f, 0.00192261f, -0.08541870f, 0.42407227f, | ||
| 606 | 0.67568970f, 0.01000977f, -0.03863525f, 0.00985718f, 0.00244141f, 0.00244141f, | ||
| 607 | -0.08596802f, 0.41802979f, 0.67932129f, 0.01345825f, -0.03970337f, 0.01000977f, | ||
| 608 | 0.00231934f, 0.00296021f, -0.08648682f, 0.41195679f, 0.68289185f, 0.01699829f, | ||
| 609 | -0.04074097f, 0.01016235f, 0.00222778f, 0.00344849f, -0.08694458f, 0.40588379f, | ||
| 610 | 0.68637085f, 0.02056885f, -0.04180908f, 0.01031494f, 0.00210571f, 0.00390625f, | ||
| 611 | -0.08737183f, 0.39981079f, 0.68975830f, 0.02420044f, -0.04287720f, 0.01043701f, | ||
| 612 | 0.00201416f, 0.00436401f, -0.08773804f, 0.39370728f, 0.69308472f, 0.02792358f, | ||
| 613 | -0.04394531f, 0.01058960f, 0.00192261f, 0.00482178f, -0.08804321f, 0.38763428f, | ||
| 614 | 0.69631958f, 0.03167725f, -0.04501343f, 0.01074219f, 0.00183105f, 0.00527954f, | ||
| 615 | -0.08834839f, 0.38153076f, 0.69946289f, 0.03549194f, -0.04608154f, 0.01086426f, | ||
| 616 | 0.00173950f, 0.00567627f, -0.08856201f, 0.37542725f, 0.70251465f, 0.03939819f, | ||
| 617 | -0.04714966f, 0.01101685f, 0.00164795f, 0.00610352f, -0.08877563f, 0.36932373f, | ||
| 618 | 0.70550537f, 0.04333496f, -0.04821777f, 0.01113892f, 0.00155640f, 0.00650024f, | ||
| 619 | -0.08892822f, 0.36322021f, 0.70837402f, 0.04733276f, -0.04928589f, 0.01126099f, | ||
| 620 | 0.00146484f, 0.00686646f, -0.08901978f, 0.35711670f, 0.71118164f, 0.05139160f, | ||
| 621 | -0.05032349f, 0.01138306f, 0.00137329f, 0.00726318f, -0.08911133f, 0.35101318f, | ||
| 622 | 0.71389771f, 0.05551147f, -0.05139160f, 0.01150513f, 0.00131226f, 0.00759888f, | ||
| 623 | -0.08914185f, 0.34494019f, 0.71649170f, 0.05969238f, -0.05245972f, 0.01162720f, | ||
| 624 | 0.00122070f, 0.00796509f, -0.08911133f, 0.33883667f, 0.71902466f, 0.06393433f, | ||
| 625 | -0.05352783f, 0.01174927f, 0.00115967f, 0.00827026f, -0.08908081f, 0.33276367f, | ||
| 626 | 0.72146606f, 0.06820679f, -0.05459595f, 0.01187134f, 0.00106812f, 0.00860596f, | ||
| 627 | -0.08898926f, 0.32669067f, 0.72381592f, 0.07257080f, -0.05563354f, 0.01196289f, | ||
| 628 | 0.00100708f, 0.00891113f, -0.08886719f, 0.32061768f, 0.72607422f, 0.07696533f, | ||
| 629 | -0.05670166f, 0.01205444f, 0.00094604f, 0.00918579f, -0.08871460f, 0.31457520f, | ||
| 630 | 0.72824097f, 0.08142090f, -0.05773926f, 0.01214600f, 0.00088501f, 0.00949097f, | ||
| 631 | -0.08850098f, 0.30853271f, 0.73028564f, 0.08593750f, -0.05877686f, 0.01226807f, | ||
| 632 | 0.00079346f, 0.00973511f, -0.08828735f, 0.30249023f, 0.73226929f, 0.09051514f, | ||
| 633 | -0.05981445f, 0.01232910f, 0.00073242f, 0.01000977f, -0.08801270f, 0.29647827f, | ||
| 634 | 0.73413086f, 0.09512329f, -0.06082153f, 0.01242065f, 0.00067139f, 0.01025391f, | ||
| 635 | -0.08770752f, 0.29049683f, 0.73593140f, 0.09979248f, -0.06185913f, 0.01251221f, | ||
| 636 | 0.00064087f, 0.01049805f, -0.08737183f, 0.28451538f, 0.73760986f, 0.10452271f, | ||
| 637 | -0.06286621f, 0.01257324f, 0.00057983f, 0.01071167f, -0.08700562f, 0.27853394f, | ||
| 638 | 0.73919678f, 0.10931396f, -0.06387329f, 0.01263428f, 0.00051880f, 0.01092529f, | ||
| 639 | -0.08663940f, 0.27261353f, 0.74069214f, 0.11413574f, -0.06488037f, 0.01269531f, | ||
| 640 | 0.00045776f, 0.01110840f, -0.08621216f, 0.26669312f, 0.74206543f, 0.11901855f, | ||
| 641 | -0.06585693f, 0.01275635f, 0.00042725f, 0.01129150f, -0.08575439f, 0.26077271f, | ||
| 642 | 0.74337769f, 0.12396240f, -0.06683350f, 0.01278687f, 0.00036621f, 0.01147461f, | ||
| 643 | -0.08526611f, 0.25491333f, 0.74456787f, 0.12893677f, -0.06777954f, 0.01281738f, | ||
| 644 | 0.00033569f, 0.01162720f, -0.08474731f, 0.24905396f, 0.74566650f, 0.13397217f, | ||
| 645 | -0.06872559f, 0.01284790f, 0.00027466f, 0.01177979f, -0.08419800f, 0.24322510f, | ||
| 646 | 0.74667358f, 0.13903809f, -0.06967163f, 0.01287842f, 0.00024414f, 0.01193237f, | ||
| 647 | -0.08364868f, 0.23742676f, 0.74755859f, 0.14416504f, -0.07058716f, 0.01290894f, | ||
| 648 | 0.00021362f, 0.01205444f, -0.08303833f, 0.23165894f, 0.74838257f, 0.14932251f, | ||
| 649 | -0.07150269f, 0.01290894f, 0.00018311f, 0.01217651f, -0.08242798f, 0.22592163f, | ||
| 650 | 0.74908447f, 0.15454102f, -0.07241821f, 0.01290894f, 0.00015259f, 0.01229858f, | ||
| 651 | -0.08178711f, 0.22021484f, 0.74966431f, 0.15979004f, -0.07330322f, 0.01290894f, | ||
| 652 | 0.00012207f, 0.01239014f, -0.08111572f, 0.21453857f, 0.75018311f, 0.16510010f, | ||
| 653 | -0.07415771f, 0.01290894f, 0.00009155f, 0.01248169f, -0.08041382f, 0.20892334f, | ||
| 654 | 0.75057983f, 0.17044067f, -0.07501221f, 0.01287842f, 0.00006104f, 0.01257324f, | ||
| 655 | -0.07971191f, 0.20330811f, 0.75088501f, 0.17581177f, -0.07583618f, 0.01284790f, | ||
| 656 | 0.00003052f, 0.01266479f, -0.07897949f, 0.19772339f, 0.75106812f, 0.18124390f, | ||
| 657 | -0.07666016f, 0.01281738f, 0.00000000f, 0.01272583f, -0.07821655f, 0.19219971f, | ||
| 658 | 0.75119019f, 0.18670654f, -0.07745361f, 0.01275635f, | ||
| 659 | }; | ||
| 660 | |||
| 661 | static constexpr std::array<f32, 1024> lut2 = { | ||
| 662 | -0.00036621f, 0.00143433f, -0.00408936f, 0.99996948f, 0.00247192f, -0.00048828f, | ||
| 663 | 0.00006104f, 0.00000000f, -0.00079346f, 0.00329590f, -0.01052856f, 0.99975586f, | ||
| 664 | 0.00918579f, -0.00241089f, 0.00051880f, -0.00003052f, -0.00122070f, 0.00512695f, | ||
| 665 | -0.01684570f, 0.99929810f, 0.01605225f, -0.00439453f, 0.00097656f, -0.00006104f, | ||
| 666 | -0.00161743f, 0.00689697f, -0.02297974f, 0.99862671f, 0.02304077f, -0.00640869f, | ||
| 667 | 0.00143433f, -0.00009155f, -0.00201416f, 0.00866699f, -0.02899170f, 0.99774170f, | ||
| 668 | 0.03018188f, -0.00845337f, 0.00192261f, -0.00015259f, -0.00238037f, 0.01037598f, | ||
| 669 | -0.03488159f, 0.99664307f, 0.03741455f, -0.01055908f, 0.00241089f, -0.00018311f, | ||
| 670 | -0.00274658f, 0.01202393f, -0.04061890f, 0.99533081f, 0.04483032f, -0.01266479f, | ||
| 671 | 0.00292969f, -0.00024414f, -0.00308228f, 0.01364136f, -0.04620361f, 0.99377441f, | ||
| 672 | 0.05233765f, -0.01483154f, 0.00344849f, -0.00027466f, -0.00341797f, 0.01522827f, | ||
| 673 | -0.05163574f, 0.99200439f, 0.05999756f, -0.01699829f, 0.00396729f, -0.00033569f, | ||
| 674 | -0.00375366f, 0.01678467f, -0.05691528f, 0.99002075f, 0.06777954f, -0.01922607f, | ||
| 675 | 0.00451660f, -0.00039673f, -0.00405884f, 0.01828003f, -0.06207275f, 0.98782349f, | ||
| 676 | 0.07568359f, -0.02145386f, 0.00506592f, -0.00042725f, -0.00436401f, 0.01971436f, | ||
| 677 | -0.06707764f, 0.98541260f, 0.08370972f, -0.02374268f, 0.00564575f, -0.00048828f, | ||
| 678 | -0.00463867f, 0.02114868f, -0.07192993f, 0.98278809f, 0.09185791f, -0.02603149f, | ||
| 679 | 0.00622559f, -0.00054932f, -0.00494385f, 0.02252197f, -0.07666016f, 0.97991943f, | ||
| 680 | 0.10012817f, -0.02835083f, 0.00680542f, -0.00061035f, -0.00518799f, 0.02383423f, | ||
| 681 | -0.08123779f, 0.97686768f, 0.10848999f, -0.03073120f, 0.00738525f, -0.00070190f, | ||
| 682 | -0.00543213f, 0.02511597f, -0.08566284f, 0.97360229f, 0.11700439f, -0.03308105f, | ||
| 683 | 0.00799561f, -0.00076294f, -0.00567627f, 0.02636719f, -0.08993530f, 0.97012329f, | ||
| 684 | 0.12561035f, -0.03549194f, 0.00860596f, -0.00082397f, -0.00592041f, 0.02755737f, | ||
| 685 | -0.09405518f, 0.96643066f, 0.13436890f, -0.03790283f, 0.00924683f, -0.00091553f, | ||
| 686 | -0.00613403f, 0.02868652f, -0.09805298f, 0.96252441f, 0.14318848f, -0.04034424f, | ||
| 687 | 0.00985718f, -0.00097656f, -0.00631714f, 0.02981567f, -0.10189819f, 0.95843506f, | ||
| 688 | 0.15213013f, -0.04281616f, 0.01049805f, -0.00106812f, -0.00653076f, 0.03085327f, | ||
| 689 | -0.10559082f, 0.95413208f, 0.16119385f, -0.04528809f, 0.01113892f, -0.00112915f, | ||
| 690 | -0.00671387f, 0.03189087f, -0.10916138f, 0.94961548f, 0.17034912f, -0.04779053f, | ||
| 691 | 0.01181030f, -0.00122070f, -0.00686646f, 0.03286743f, -0.11254883f, 0.94491577f, | ||
| 692 | 0.17959595f, -0.05029297f, 0.01248169f, -0.00131226f, -0.00701904f, 0.03378296f, | ||
| 693 | -0.11584473f, 0.94000244f, 0.18893433f, -0.05279541f, 0.01315308f, -0.00140381f, | ||
| 694 | -0.00717163f, 0.03466797f, -0.11895752f, 0.93490601f, 0.19839478f, -0.05532837f, | ||
| 695 | 0.01382446f, -0.00149536f, -0.00732422f, 0.03552246f, -0.12194824f, 0.92962646f, | ||
| 696 | 0.20791626f, -0.05786133f, 0.01449585f, -0.00158691f, -0.00744629f, 0.03631592f, | ||
| 697 | -0.12478638f, 0.92413330f, 0.21752930f, -0.06042480f, 0.01519775f, -0.00167847f, | ||
| 698 | -0.00753784f, 0.03707886f, -0.12750244f, 0.91848755f, 0.22723389f, -0.06298828f, | ||
| 699 | 0.01586914f, -0.00177002f, -0.00765991f, 0.03781128f, -0.13006592f, 0.91262817f, | ||
| 700 | 0.23703003f, -0.06555176f, 0.01657104f, -0.00189209f, -0.00775146f, 0.03848267f, | ||
| 701 | -0.13250732f, 0.90658569f, 0.24691772f, -0.06808472f, 0.01727295f, -0.00198364f, | ||
| 702 | -0.00784302f, 0.03909302f, -0.13479614f, 0.90036011f, 0.25683594f, -0.07064819f, | ||
| 703 | 0.01797485f, -0.00210571f, -0.00790405f, 0.03970337f, -0.13696289f, 0.89395142f, | ||
| 704 | 0.26687622f, -0.07321167f, 0.01870728f, -0.00219727f, -0.00796509f, 0.04025269f, | ||
| 705 | -0.13900757f, 0.88739014f, 0.27694702f, -0.07577515f, 0.01940918f, -0.00231934f, | ||
| 706 | -0.00802612f, 0.04077148f, -0.14089966f, 0.88064575f, 0.28710938f, -0.07833862f, | ||
| 707 | 0.02011108f, -0.00244141f, -0.00808716f, 0.04122925f, -0.14263916f, 0.87374878f, | ||
| 708 | 0.29733276f, -0.08090210f, 0.02084351f, -0.00253296f, -0.00811768f, 0.04165649f, | ||
| 709 | -0.14428711f, 0.86666870f, 0.30761719f, -0.08343506f, 0.02154541f, -0.00265503f, | ||
| 710 | -0.00814819f, 0.04205322f, -0.14578247f, 0.85940552f, 0.31793213f, -0.08596802f, | ||
| 711 | 0.02227783f, -0.00277710f, -0.00814819f, 0.04238892f, -0.14715576f, 0.85202026f, | ||
| 712 | 0.32833862f, -0.08847046f, 0.02297974f, -0.00289917f, -0.00817871f, 0.04272461f, | ||
| 713 | -0.14840698f, 0.84445190f, 0.33874512f, -0.09097290f, 0.02371216f, -0.00302124f, | ||
| 714 | -0.00817871f, 0.04299927f, -0.14953613f, 0.83673096f, 0.34924316f, -0.09347534f, | ||
| 715 | 0.02441406f, -0.00314331f, -0.00817871f, 0.04321289f, -0.15054321f, 0.82888794f, | ||
| 716 | 0.35977173f, -0.09594727f, 0.02514648f, -0.00326538f, -0.00814819f, 0.04342651f, | ||
| 717 | -0.15142822f, 0.82086182f, 0.37033081f, -0.09838867f, 0.02584839f, -0.00341797f, | ||
| 718 | -0.00814819f, 0.04357910f, -0.15219116f, 0.81271362f, 0.38092041f, -0.10079956f, | ||
| 719 | 0.02655029f, -0.00354004f, -0.00811768f, 0.04373169f, -0.15283203f, 0.80441284f, | ||
| 720 | 0.39154053f, -0.10321045f, 0.02725220f, -0.00366211f, -0.00808716f, 0.04382324f, | ||
| 721 | -0.15338135f, 0.79598999f, 0.40219116f, -0.10559082f, 0.02795410f, -0.00381470f, | ||
| 722 | -0.00805664f, 0.04388428f, -0.15377808f, 0.78741455f, 0.41287231f, -0.10794067f, | ||
| 723 | 0.02865601f, -0.00393677f, -0.00799561f, 0.04388428f, -0.15408325f, 0.77871704f, | ||
| 724 | 0.42358398f, -0.11026001f, 0.02935791f, -0.00405884f, -0.00793457f, 0.04388428f, | ||
| 725 | -0.15426636f, 0.76989746f, 0.43429565f, -0.11251831f, 0.03002930f, -0.00421143f, | ||
| 726 | -0.00787354f, 0.04385376f, -0.15435791f, 0.76095581f, 0.44500732f, -0.11477661f, | ||
| 727 | 0.03070068f, -0.00433350f, -0.00781250f, 0.04379272f, -0.15435791f, 0.75192261f, | ||
| 728 | 0.45574951f, -0.11697388f, 0.03137207f, -0.00448608f, -0.00775146f, 0.04367065f, | ||
| 729 | -0.15420532f, 0.74273682f, 0.46649170f, -0.11914062f, 0.03201294f, -0.00460815f, | ||
| 730 | -0.00769043f, 0.04354858f, -0.15399170f, 0.73345947f, 0.47723389f, -0.12127686f, | ||
| 731 | 0.03268433f, -0.00473022f, -0.00759888f, 0.04339600f, -0.15365601f, 0.72406006f, | ||
| 732 | 0.48794556f, -0.12335205f, 0.03329468f, -0.00488281f, -0.00750732f, 0.04321289f, | ||
| 733 | -0.15322876f, 0.71456909f, 0.49868774f, -0.12539673f, 0.03393555f, -0.00500488f, | ||
| 734 | -0.00741577f, 0.04296875f, -0.15270996f, 0.70498657f, 0.50936890f, -0.12738037f, | ||
| 735 | 0.03454590f, -0.00515747f, -0.00732422f, 0.04272461f, -0.15209961f, 0.69528198f, | ||
| 736 | 0.52008057f, -0.12930298f, 0.03515625f, -0.00527954f, -0.00723267f, 0.04248047f, | ||
| 737 | -0.15136719f, 0.68551636f, 0.53076172f, -0.13119507f, 0.03573608f, -0.00543213f, | ||
| 738 | -0.00714111f, 0.04217529f, -0.15057373f, 0.67565918f, 0.54138184f, -0.13299561f, | ||
| 739 | 0.03631592f, -0.00555420f, -0.00701904f, 0.04183960f, -0.14968872f, 0.66571045f, | ||
| 740 | 0.55200195f, -0.13476562f, 0.03689575f, -0.00567627f, -0.00692749f, 0.04150391f, | ||
| 741 | -0.14871216f, 0.65567017f, 0.56259155f, -0.13647461f, 0.03741455f, -0.00582886f, | ||
| 742 | -0.00680542f, 0.04113770f, -0.14767456f, 0.64556885f, 0.57315063f, -0.13812256f, | ||
| 743 | 0.03796387f, -0.00595093f, -0.00668335f, 0.04074097f, -0.14651489f, 0.63540649f, | ||
| 744 | 0.58364868f, -0.13970947f, 0.03845215f, -0.00607300f, -0.00656128f, 0.04031372f, | ||
| 745 | -0.14529419f, 0.62518311f, 0.59411621f, -0.14120483f, 0.03897095f, -0.00619507f, | ||
| 746 | -0.00643921f, 0.03988647f, -0.14401245f, 0.61486816f, 0.60452271f, -0.14263916f, | ||
| 747 | 0.03942871f, -0.00631714f, -0.00631714f, 0.03942871f, -0.14263916f, 0.60452271f, | ||
| 748 | 0.61486816f, -0.14401245f, 0.03988647f, -0.00643921f, -0.00619507f, 0.03897095f, | ||
| 749 | -0.14120483f, 0.59411621f, 0.62518311f, -0.14529419f, 0.04031372f, -0.00656128f, | ||
| 750 | -0.00607300f, 0.03845215f, -0.13970947f, 0.58364868f, 0.63540649f, -0.14651489f, | ||
| 751 | 0.04074097f, -0.00668335f, -0.00595093f, 0.03796387f, -0.13812256f, 0.57315063f, | ||
| 752 | 0.64556885f, -0.14767456f, 0.04113770f, -0.00680542f, -0.00582886f, 0.03741455f, | ||
| 753 | -0.13647461f, 0.56259155f, 0.65567017f, -0.14871216f, 0.04150391f, -0.00692749f, | ||
| 754 | -0.00567627f, 0.03689575f, -0.13476562f, 0.55200195f, 0.66571045f, -0.14968872f, | ||
| 755 | 0.04183960f, -0.00701904f, -0.00555420f, 0.03631592f, -0.13299561f, 0.54138184f, | ||
| 756 | 0.67565918f, -0.15057373f, 0.04217529f, -0.00714111f, -0.00543213f, 0.03573608f, | ||
| 757 | -0.13119507f, 0.53076172f, 0.68551636f, -0.15136719f, 0.04248047f, -0.00723267f, | ||
| 758 | -0.00527954f, 0.03515625f, -0.12930298f, 0.52008057f, 0.69528198f, -0.15209961f, | ||
| 759 | 0.04272461f, -0.00732422f, -0.00515747f, 0.03454590f, -0.12738037f, 0.50936890f, | ||
| 760 | 0.70498657f, -0.15270996f, 0.04296875f, -0.00741577f, -0.00500488f, 0.03393555f, | ||
| 761 | -0.12539673f, 0.49868774f, 0.71456909f, -0.15322876f, 0.04321289f, -0.00750732f, | ||
| 762 | -0.00488281f, 0.03329468f, -0.12335205f, 0.48794556f, 0.72406006f, -0.15365601f, | ||
| 763 | 0.04339600f, -0.00759888f, -0.00473022f, 0.03268433f, -0.12127686f, 0.47723389f, | ||
| 764 | 0.73345947f, -0.15399170f, 0.04354858f, -0.00769043f, -0.00460815f, 0.03201294f, | ||
| 765 | -0.11914062f, 0.46649170f, 0.74273682f, -0.15420532f, 0.04367065f, -0.00775146f, | ||
| 766 | -0.00448608f, 0.03137207f, -0.11697388f, 0.45574951f, 0.75192261f, -0.15435791f, | ||
| 767 | 0.04379272f, -0.00781250f, -0.00433350f, 0.03070068f, -0.11477661f, 0.44500732f, | ||
| 768 | 0.76095581f, -0.15435791f, 0.04385376f, -0.00787354f, -0.00421143f, 0.03002930f, | ||
| 769 | -0.11251831f, 0.43429565f, 0.76989746f, -0.15426636f, 0.04388428f, -0.00793457f, | ||
| 770 | -0.00405884f, 0.02935791f, -0.11026001f, 0.42358398f, 0.77871704f, -0.15408325f, | ||
| 771 | 0.04388428f, -0.00799561f, -0.00393677f, 0.02865601f, -0.10794067f, 0.41287231f, | ||
| 772 | 0.78741455f, -0.15377808f, 0.04388428f, -0.00805664f, -0.00381470f, 0.02795410f, | ||
| 773 | -0.10559082f, 0.40219116f, 0.79598999f, -0.15338135f, 0.04382324f, -0.00808716f, | ||
| 774 | -0.00366211f, 0.02725220f, -0.10321045f, 0.39154053f, 0.80441284f, -0.15283203f, | ||
| 775 | 0.04373169f, -0.00811768f, -0.00354004f, 0.02655029f, -0.10079956f, 0.38092041f, | ||
| 776 | 0.81271362f, -0.15219116f, 0.04357910f, -0.00814819f, -0.00341797f, 0.02584839f, | ||
| 777 | -0.09838867f, 0.37033081f, 0.82086182f, -0.15142822f, 0.04342651f, -0.00814819f, | ||
| 778 | -0.00326538f, 0.02514648f, -0.09594727f, 0.35977173f, 0.82888794f, -0.15054321f, | ||
| 779 | 0.04321289f, -0.00817871f, -0.00314331f, 0.02441406f, -0.09347534f, 0.34924316f, | ||
| 780 | 0.83673096f, -0.14953613f, 0.04299927f, -0.00817871f, -0.00302124f, 0.02371216f, | ||
| 781 | -0.09097290f, 0.33874512f, 0.84445190f, -0.14840698f, 0.04272461f, -0.00817871f, | ||
| 782 | -0.00289917f, 0.02297974f, -0.08847046f, 0.32833862f, 0.85202026f, -0.14715576f, | ||
| 783 | 0.04238892f, -0.00814819f, -0.00277710f, 0.02227783f, -0.08596802f, 0.31793213f, | ||
| 784 | 0.85940552f, -0.14578247f, 0.04205322f, -0.00814819f, -0.00265503f, 0.02154541f, | ||
| 785 | -0.08343506f, 0.30761719f, 0.86666870f, -0.14428711f, 0.04165649f, -0.00811768f, | ||
| 786 | -0.00253296f, 0.02084351f, -0.08090210f, 0.29733276f, 0.87374878f, -0.14263916f, | ||
| 787 | 0.04122925f, -0.00808716f, -0.00244141f, 0.02011108f, -0.07833862f, 0.28710938f, | ||
| 788 | 0.88064575f, -0.14089966f, 0.04077148f, -0.00802612f, -0.00231934f, 0.01940918f, | ||
| 789 | -0.07577515f, 0.27694702f, 0.88739014f, -0.13900757f, 0.04025269f, -0.00796509f, | ||
| 790 | -0.00219727f, 0.01870728f, -0.07321167f, 0.26687622f, 0.89395142f, -0.13696289f, | ||
| 791 | 0.03970337f, -0.00790405f, -0.00210571f, 0.01797485f, -0.07064819f, 0.25683594f, | ||
| 792 | 0.90036011f, -0.13479614f, 0.03909302f, -0.00784302f, -0.00198364f, 0.01727295f, | ||
| 793 | -0.06808472f, 0.24691772f, 0.90658569f, -0.13250732f, 0.03848267f, -0.00775146f, | ||
| 794 | -0.00189209f, 0.01657104f, -0.06555176f, 0.23703003f, 0.91262817f, -0.13006592f, | ||
| 795 | 0.03781128f, -0.00765991f, -0.00177002f, 0.01586914f, -0.06298828f, 0.22723389f, | ||
| 796 | 0.91848755f, -0.12750244f, 0.03707886f, -0.00753784f, -0.00167847f, 0.01519775f, | ||
| 797 | -0.06042480f, 0.21752930f, 0.92413330f, -0.12478638f, 0.03631592f, -0.00744629f, | ||
| 798 | -0.00158691f, 0.01449585f, -0.05786133f, 0.20791626f, 0.92962646f, -0.12194824f, | ||
| 799 | 0.03552246f, -0.00732422f, -0.00149536f, 0.01382446f, -0.05532837f, 0.19839478f, | ||
| 800 | 0.93490601f, -0.11895752f, 0.03466797f, -0.00717163f, -0.00140381f, 0.01315308f, | ||
| 801 | -0.05279541f, 0.18893433f, 0.94000244f, -0.11584473f, 0.03378296f, -0.00701904f, | ||
| 802 | -0.00131226f, 0.01248169f, -0.05029297f, 0.17959595f, 0.94491577f, -0.11254883f, | ||
| 803 | 0.03286743f, -0.00686646f, -0.00122070f, 0.01181030f, -0.04779053f, 0.17034912f, | ||
| 804 | 0.94961548f, -0.10916138f, 0.03189087f, -0.00671387f, -0.00112915f, 0.01113892f, | ||
| 805 | -0.04528809f, 0.16119385f, 0.95413208f, -0.10559082f, 0.03085327f, -0.00653076f, | ||
| 806 | -0.00106812f, 0.01049805f, -0.04281616f, 0.15213013f, 0.95843506f, -0.10189819f, | ||
| 807 | 0.02981567f, -0.00631714f, -0.00097656f, 0.00985718f, -0.04034424f, 0.14318848f, | ||
| 808 | 0.96252441f, -0.09805298f, 0.02868652f, -0.00613403f, -0.00091553f, 0.00924683f, | ||
| 809 | -0.03790283f, 0.13436890f, 0.96643066f, -0.09405518f, 0.02755737f, -0.00592041f, | ||
| 810 | -0.00082397f, 0.00860596f, -0.03549194f, 0.12561035f, 0.97012329f, -0.08993530f, | ||
| 811 | 0.02636719f, -0.00567627f, -0.00076294f, 0.00799561f, -0.03308105f, 0.11700439f, | ||
| 812 | 0.97360229f, -0.08566284f, 0.02511597f, -0.00543213f, -0.00070190f, 0.00738525f, | ||
| 813 | -0.03073120f, 0.10848999f, 0.97686768f, -0.08123779f, 0.02383423f, -0.00518799f, | ||
| 814 | -0.00061035f, 0.00680542f, -0.02835083f, 0.10012817f, 0.97991943f, -0.07666016f, | ||
| 815 | 0.02252197f, -0.00494385f, -0.00054932f, 0.00622559f, -0.02603149f, 0.09185791f, | ||
| 816 | 0.98278809f, -0.07192993f, 0.02114868f, -0.00463867f, -0.00048828f, 0.00564575f, | ||
| 817 | -0.02374268f, 0.08370972f, 0.98541260f, -0.06707764f, 0.01971436f, -0.00436401f, | ||
| 818 | -0.00042725f, 0.00506592f, -0.02145386f, 0.07568359f, 0.98782349f, -0.06207275f, | ||
| 819 | 0.01828003f, -0.00405884f, -0.00039673f, 0.00451660f, -0.01922607f, 0.06777954f, | ||
| 820 | 0.99002075f, -0.05691528f, 0.01678467f, -0.00375366f, -0.00033569f, 0.00396729f, | ||
| 821 | -0.01699829f, 0.05999756f, 0.99200439f, -0.05163574f, 0.01522827f, -0.00341797f, | ||
| 822 | -0.00027466f, 0.00344849f, -0.01483154f, 0.05233765f, 0.99377441f, -0.04620361f, | ||
| 823 | 0.01364136f, -0.00308228f, -0.00024414f, 0.00292969f, -0.01266479f, 0.04483032f, | ||
| 824 | 0.99533081f, -0.04061890f, 0.01202393f, -0.00274658f, -0.00018311f, 0.00241089f, | ||
| 825 | -0.01055908f, 0.03741455f, 0.99664307f, -0.03488159f, 0.01037598f, -0.00238037f, | ||
| 826 | -0.00015259f, 0.00192261f, -0.00845337f, 0.03018188f, 0.99774170f, -0.02899170f, | ||
| 827 | 0.00866699f, -0.00201416f, -0.00009155f, 0.00143433f, -0.00640869f, 0.02304077f, | ||
| 828 | 0.99862671f, -0.02297974f, 0.00689697f, -0.00161743f, -0.00006104f, 0.00097656f, | ||
| 829 | -0.00439453f, 0.01605225f, 0.99929810f, -0.01684570f, 0.00512695f, -0.00122070f, | ||
| 830 | -0.00003052f, 0.00051880f, -0.00241089f, 0.00918579f, 0.99975586f, -0.01052856f, | ||
| 831 | 0.00329590f, -0.00079346f, 0.00000000f, 0.00006104f, -0.00048828f, 0.00247192f, | ||
| 832 | 0.99996948f, -0.00408936f, 0.00143433f, -0.00036621f, | ||
| 833 | }; | ||
| 834 | |||
| 835 | const auto get_lut = [&]() -> std::span<const f32> { | ||
| 836 | if (sample_rate_ratio <= 1.0f) { | ||
| 837 | return std::span<const f32>(lut2.data(), lut2.size()); | ||
| 838 | } else if (sample_rate_ratio < 1.3f) { | ||
| 839 | return std::span<const f32>(lut1.data(), lut1.size()); | ||
| 840 | } else { | ||
| 841 | return std::span<const f32>(lut0.data(), lut0.size()); | ||
| 842 | } | ||
| 843 | }; | ||
| 844 | |||
| 845 | auto lut{get_lut()}; | ||
| 846 | u32 read_index{0}; | ||
| 847 | for (u32 i = 0; i < samples_to_write; i++) { | ||
| 848 | const auto lut_index{(fraction.get_frac() >> 8) * 8}; | ||
| 849 | const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]}; | ||
| 850 | const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]}; | ||
| 851 | const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]}; | ||
| 852 | const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]}; | ||
| 853 | const Common::FixedPoint<56, 8> sample4{input[read_index + 4] * lut[lut_index + 4]}; | ||
| 854 | const Common::FixedPoint<56, 8> sample5{input[read_index + 5] * lut[lut_index + 5]}; | ||
| 855 | const Common::FixedPoint<56, 8> sample6{input[read_index + 6] * lut[lut_index + 6]}; | ||
| 856 | const Common::FixedPoint<56, 8> sample7{input[read_index + 7] * lut[lut_index + 7]}; | ||
| 857 | output[i] = (sample0 + sample1 + sample2 + sample3 + sample4 + sample5 + sample6 + sample7) | ||
| 858 | .to_int_floor(); | ||
| 859 | fraction += sample_rate_ratio; | ||
| 860 | read_index += static_cast<u32>(fraction.to_int_floor()); | ||
| 861 | fraction.clear_int(); | ||
| 862 | } | ||
| 863 | } | ||
| 864 | |||
| 865 | void Resample(std::span<s32> output, std::span<const s16> input, | ||
| 866 | const Common::FixedPoint<49, 15>& sample_rate_ratio, | ||
| 867 | Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write, | ||
| 868 | const SrcQuality src_quality) { | ||
| 869 | |||
| 870 | switch (src_quality) { | ||
| 871 | case SrcQuality::Low: | ||
| 872 | ResampleLowQuality(output, input, sample_rate_ratio, fraction, samples_to_write); | ||
| 873 | break; | ||
| 874 | case SrcQuality::Medium: | ||
| 875 | ResampleNormalQuality(output, input, sample_rate_ratio, fraction, samples_to_write); | ||
| 876 | break; | ||
| 877 | case SrcQuality::High: | ||
| 878 | ResampleHighQuality(output, input, sample_rate_ratio, fraction, samples_to_write); | ||
| 879 | break; | ||
| 880 | } | ||
| 881 | } | ||
| 882 | |||
| 883 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <span> | ||
| 7 | |||
| 8 | #include "audio_core/common/common.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | #include "common/fixed_point.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | /** | ||
| 14 | * Resample an input buffer into an output buffer, according to the sample_rate_ratio. | ||
| 15 | * | ||
| 16 | * @param output - Output buffer. | ||
| 17 | * @param input - Input buffer. | ||
| 18 | * @param sample_rate_ratio - Ratio for resampling. | ||
| 19 | e.g 32000/48000 = 0.666 input samples read per output. | ||
| 20 | * @param fraction - Current read fraction, written to and should be passed back in for | ||
| 21 | * multiple calls. | ||
| 22 | * @param samples_to_write - Number of samples to write. | ||
| 23 | * @param src_quality - Resampling quality. | ||
| 24 | */ | ||
| 25 | void Resample(std::span<s32> output, std::span<const s16> input, | ||
| 26 | const Common::FixedPoint<49, 15>& sample_rate_ratio, | ||
| 27 | Common::FixedPoint<49, 15>& fraction, u32 samples_to_write, SrcQuality src_quality); | ||
| 28 | |||
| 29 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <array> | ||
| 5 | |||
| 6 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 7 | #include "audio_core/renderer/command/resample/upsample.h" | ||
| 8 | #include "audio_core/renderer/upsampler/upsampler_info.h" | ||
| 9 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | /** | ||
| 12 | * Upsampling impl. Input must be 8K, 16K or 32K, output is 48K. | ||
| 13 | * | ||
| 14 | * @param output - Output buffer. | ||
| 15 | * @param input - Input buffer. | ||
| 16 | * @param target_sample_count - Number of samples for output. | ||
| 17 | * @param state - Upsampler state, updated each call. | ||
| 18 | */ | ||
| 19 | static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input, | ||
| 20 | const u32 target_sample_count, const u32 source_sample_count, | ||
| 21 | UpsamplerState* state) { | ||
| 22 | constexpr u32 WindowSize = 10; | ||
| 23 | constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow1{ | ||
| 24 | 51.93359375f, -18.80078125f, 9.73046875f, -5.33203125f, 2.84375f, | ||
| 25 | -1.41015625f, 0.62109375f, -0.2265625f, 0.0625f, -0.00390625f, | ||
| 26 | }; | ||
| 27 | constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow2{ | ||
| 28 | 105.35546875f, -24.52734375f, 11.9609375f, -6.515625f, 3.52734375f, | ||
| 29 | -1.796875f, 0.828125f, -0.32421875f, 0.1015625f, -0.015625f, | ||
| 30 | }; | ||
| 31 | constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow3{ | ||
| 32 | 122.08203125f, -16.47656250f, 7.68359375f, -4.15625000f, 2.26171875f, | ||
| 33 | -1.16796875f, 0.54687500f, -0.22265625f, 0.07421875f, -0.01171875f, | ||
| 34 | }; | ||
| 35 | constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow4{ | ||
| 36 | 23.73437500f, -9.62109375f, 5.07812500f, -2.78125000f, 1.46875000f, | ||
| 37 | -0.71484375f, 0.30859375f, -0.10546875f, 0.02734375f, 0.00000000f, | ||
| 38 | }; | ||
| 39 | constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow5{ | ||
| 40 | 80.62500000f, -24.67187500f, 12.44921875f, -6.80859375f, 3.66406250f, | ||
| 41 | -1.83984375f, 0.83203125f, -0.31640625f, 0.09375000f, -0.01171875f, | ||
| 42 | }; | ||
| 43 | |||
| 44 | if (!state->initialized) { | ||
| 45 | switch (source_sample_count) { | ||
| 46 | case 40: | ||
| 47 | state->window_size = WindowSize; | ||
| 48 | state->ratio = 6.0f; | ||
| 49 | state->history.fill(0); | ||
| 50 | break; | ||
| 51 | |||
| 52 | case 80: | ||
| 53 | state->window_size = WindowSize; | ||
| 54 | state->ratio = 3.0f; | ||
| 55 | state->history.fill(0); | ||
| 56 | break; | ||
| 57 | |||
| 58 | case 160: | ||
| 59 | state->window_size = WindowSize; | ||
| 60 | state->ratio = 1.5f; | ||
| 61 | state->history.fill(0); | ||
| 62 | break; | ||
| 63 | |||
| 64 | default: | ||
| 65 | LOG_ERROR(Service_Audio, "Invalid upsampling source count {}!", source_sample_count); | ||
| 66 | // This continues anyway, but let's assume 160 for sanity | ||
| 67 | state->window_size = WindowSize; | ||
| 68 | state->ratio = 1.5f; | ||
| 69 | state->history.fill(0); | ||
| 70 | break; | ||
| 71 | } | ||
| 72 | |||
| 73 | state->history_input_index = 0; | ||
| 74 | state->history_output_index = 9; | ||
| 75 | state->history_start_index = 0; | ||
| 76 | state->history_end_index = UpsamplerState::HistorySize - 1; | ||
| 77 | state->initialized = true; | ||
| 78 | } | ||
| 79 | |||
| 80 | if (target_sample_count == 0) { | ||
| 81 | return; | ||
| 82 | } | ||
| 83 | |||
| 84 | u32 read_index{0}; | ||
| 85 | |||
| 86 | auto increment = [&]() -> void { | ||
| 87 | state->history[state->history_input_index] = input[read_index++]; | ||
| 88 | state->history_input_index = | ||
| 89 | static_cast<u16>((state->history_input_index + 1) % UpsamplerState::HistorySize); | ||
| 90 | state->history_output_index = | ||
| 91 | static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize); | ||
| 92 | }; | ||
| 93 | |||
| 94 | auto calculate_sample = [&state](std::span<const Common::FixedPoint<24, 8>> coeffs1, | ||
| 95 | std::span<const Common::FixedPoint<24, 8>> coeffs2) -> s32 { | ||
| 96 | auto output_index{state->history_output_index}; | ||
| 97 | auto start_pos{output_index - state->history_start_index + 1U}; | ||
| 98 | auto end_pos{10U}; | ||
| 99 | |||
| 100 | if (start_pos < 10) { | ||
| 101 | end_pos = start_pos; | ||
| 102 | } | ||
| 103 | |||
| 104 | u64 prev_contrib{0}; | ||
| 105 | u32 coeff_index{0}; | ||
| 106 | for (; coeff_index < end_pos; coeff_index++, output_index--) { | ||
| 107 | prev_contrib += static_cast<u64>(state->history[output_index].to_raw()) * | ||
| 108 | coeffs1[coeff_index].to_raw(); | ||
| 109 | } | ||
| 110 | |||
| 111 | auto end_index{state->history_end_index}; | ||
| 112 | for (; start_pos < 9; start_pos++, coeff_index++, end_index--) { | ||
| 113 | prev_contrib += static_cast<u64>(state->history[end_index].to_raw()) * | ||
| 114 | coeffs1[coeff_index].to_raw(); | ||
| 115 | } | ||
| 116 | |||
| 117 | output_index = | ||
| 118 | static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize); | ||
| 119 | start_pos = state->history_end_index - output_index + 1U; | ||
| 120 | end_pos = 10U; | ||
| 121 | |||
| 122 | if (start_pos < 10) { | ||
| 123 | end_pos = start_pos; | ||
| 124 | } | ||
| 125 | |||
| 126 | u64 next_contrib{0}; | ||
| 127 | coeff_index = 0; | ||
| 128 | for (; coeff_index < end_pos; coeff_index++, output_index++) { | ||
| 129 | next_contrib += static_cast<u64>(state->history[output_index].to_raw()) * | ||
| 130 | coeffs2[coeff_index].to_raw(); | ||
| 131 | } | ||
| 132 | |||
| 133 | auto start_index{state->history_start_index}; | ||
| 134 | for (; start_pos < 9; start_pos++, start_index++, coeff_index++) { | ||
| 135 | next_contrib += static_cast<u64>(state->history[start_index].to_raw()) * | ||
| 136 | coeffs2[coeff_index].to_raw(); | ||
| 137 | } | ||
| 138 | |||
| 139 | return static_cast<s32>(((prev_contrib >> 15) + (next_contrib >> 15)) >> 8); | ||
| 140 | }; | ||
| 141 | |||
| 142 | switch (state->ratio.to_int_floor()) { | ||
| 143 | // 40 -> 240 | ||
| 144 | case 6: | ||
| 145 | for (u32 write_index = 0; write_index < target_sample_count; write_index++) { | ||
| 146 | switch (state->sample_index) { | ||
| 147 | case 0: | ||
| 148 | increment(); | ||
| 149 | output[write_index] = state->history[state->history_output_index].to_int_floor(); | ||
| 150 | break; | ||
| 151 | |||
| 152 | case 1: | ||
| 153 | output[write_index] = calculate_sample(SincWindow3, SincWindow4); | ||
| 154 | break; | ||
| 155 | |||
| 156 | case 2: | ||
| 157 | output[write_index] = calculate_sample(SincWindow2, SincWindow1); | ||
| 158 | break; | ||
| 159 | |||
| 160 | case 3: | ||
| 161 | output[write_index] = calculate_sample(SincWindow5, SincWindow5); | ||
| 162 | break; | ||
| 163 | |||
| 164 | case 4: | ||
| 165 | output[write_index] = calculate_sample(SincWindow1, SincWindow2); | ||
| 166 | break; | ||
| 167 | |||
| 168 | case 5: | ||
| 169 | output[write_index] = calculate_sample(SincWindow4, SincWindow3); | ||
| 170 | break; | ||
| 171 | } | ||
| 172 | state->sample_index = static_cast<u8>((state->sample_index + 1) % 6); | ||
| 173 | } | ||
| 174 | break; | ||
| 175 | |||
| 176 | // 80 -> 240 | ||
| 177 | case 3: | ||
| 178 | for (u32 write_index = 0; write_index < target_sample_count; write_index++) { | ||
| 179 | switch (state->sample_index) { | ||
| 180 | case 0: | ||
| 181 | increment(); | ||
| 182 | output[write_index] = state->history[state->history_output_index].to_int_floor(); | ||
| 183 | break; | ||
| 184 | |||
| 185 | case 1: | ||
| 186 | output[write_index] = calculate_sample(SincWindow2, SincWindow1); | ||
| 187 | break; | ||
| 188 | |||
| 189 | case 2: | ||
| 190 | output[write_index] = calculate_sample(SincWindow1, SincWindow2); | ||
| 191 | break; | ||
| 192 | } | ||
| 193 | state->sample_index = static_cast<u8>((state->sample_index + 1) % 3); | ||
| 194 | } | ||
| 195 | break; | ||
| 196 | |||
| 197 | // 160 -> 240 | ||
| 198 | default: | ||
| 199 | for (u32 write_index = 0; write_index < target_sample_count; write_index++) { | ||
| 200 | switch (state->sample_index) { | ||
| 201 | case 0: | ||
| 202 | increment(); | ||
| 203 | output[write_index] = state->history[state->history_output_index].to_int_floor(); | ||
| 204 | break; | ||
| 205 | |||
| 206 | case 1: | ||
| 207 | output[write_index] = calculate_sample(SincWindow1, SincWindow2); | ||
| 208 | break; | ||
| 209 | |||
| 210 | case 2: | ||
| 211 | increment(); | ||
| 212 | output[write_index] = calculate_sample(SincWindow2, SincWindow1); | ||
| 213 | break; | ||
| 214 | } | ||
| 215 | state->sample_index = static_cast<u8>((state->sample_index + 1) % 3); | ||
| 216 | } | ||
| 217 | |||
| 218 | break; | ||
| 219 | } | ||
| 220 | } | ||
| 221 | |||
| 222 | auto UpsampleCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 223 | std::string& string) -> void { | ||
| 224 | string += fmt::format("UpsampleCommand\n\tsource_sample_count {} source_sample_rate {}", | ||
| 225 | source_sample_count, source_sample_rate); | ||
| 226 | const auto upsampler{reinterpret_cast<UpsamplerInfo*>(upsampler_info)}; | ||
| 227 | if (upsampler != nullptr) { | ||
| 228 | string += fmt::format("\n\tUpsampler\n\t\tenabled {} sample count {}\n\tinputs: ", | ||
| 229 | upsampler->enabled, upsampler->sample_count); | ||
| 230 | for (u32 i = 0; i < upsampler->input_count; i++) { | ||
| 231 | string += fmt::format("{:02X}, ", upsampler->inputs[i]); | ||
| 232 | } | ||
| 233 | } | ||
| 234 | string += "\n"; | ||
| 235 | } | ||
| 236 | |||
| 237 | void UpsampleCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 238 | const auto info{reinterpret_cast<UpsamplerInfo*>(upsampler_info)}; | ||
| 239 | const auto input_count{std::min(info->input_count, buffer_count)}; | ||
| 240 | const std::span<const s16> inputs_{reinterpret_cast<const s16*>(inputs), input_count}; | ||
| 241 | |||
| 242 | for (u32 i = 0; i < input_count; i++) { | ||
| 243 | const auto channel{inputs_[i]}; | ||
| 244 | |||
| 245 | if (channel >= 0 && channel < static_cast<s16>(processor.buffer_count)) { | ||
| 246 | auto state{&info->states[i]}; | ||
| 247 | std::span<s32> output{ | ||
| 248 | reinterpret_cast<s32*>(samples_buffer + info->sample_count * channel * sizeof(s32)), | ||
| 249 | info->sample_count}; | ||
| 250 | auto input{processor.mix_buffers.subspan(channel * processor.sample_count, | ||
| 251 | processor.sample_count)}; | ||
| 252 | |||
| 253 | SrcProcessFrame(output, input, info->sample_count, source_sample_count, state); | ||
| 254 | } | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | bool UpsampleCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 259 | return true; | ||
| 260 | } | ||
| 261 | |||
| 262 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | ||
| 14 | } | ||
| 15 | |||
| 16 | /** | ||
| 17 | * AudioRenderer command for upsampling a mix buffer to 48Khz. | ||
| 18 | * Input must be 8Khz, 16Khz or 32Khz, and output will be 48Khz. | ||
| 19 | */ | ||
| 20 | struct UpsampleCommand : ICommand { | ||
| 21 | /** | ||
| 22 | * Print this command's information to a string. | ||
| 23 | * | ||
| 24 | * @param processor - The CommandListProcessor processing this command. | ||
| 25 | * @param string - The string to print into. | ||
| 26 | */ | ||
| 27 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Process this command. | ||
| 31 | * | ||
| 32 | * @param processor - The CommandListProcessor processing this command. | ||
| 33 | */ | ||
| 34 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Verify this command's data is valid. | ||
| 38 | * | ||
| 39 | * @param processor - The CommandListProcessor processing this command. | ||
| 40 | * @return True if the command is valid, otherwise false. | ||
| 41 | */ | ||
| 42 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 43 | |||
| 44 | /// Pointer to the output samples buffer. | ||
| 45 | CpuAddr samples_buffer; | ||
| 46 | /// Pointer to input mix buffer indexes. | ||
| 47 | CpuAddr inputs; | ||
| 48 | /// Number of input mix buffers. | ||
| 49 | u32 buffer_count; | ||
| 50 | /// Unknown, unused. | ||
| 51 | u32 unk_20; | ||
| 52 | /// Source data sample count. | ||
| 53 | u32 source_sample_count; | ||
| 54 | /// Source data sample rate. | ||
| 55 | u32 source_sample_rate; | ||
| 56 | /// Pointer to the upsampler info for this command. | ||
| 57 | CpuAddr upsampler_info; | ||
| 58 | }; | ||
| 59 | |||
| 60 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <vector> | ||
| 5 | |||
| 6 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 7 | #include "audio_core/renderer/command/sink/circular_buffer.h" | ||
| 8 | #include "core/memory.h" | ||
| 9 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | void CircularBufferSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 13 | std::string& string) { | ||
| 14 | string += fmt::format( | ||
| 15 | "CircularBufferSinkCommand\n\tinput_count {} ring size {:04X} ring pos {:04X}\n\tinputs: ", | ||
| 16 | input_count, size, pos); | ||
| 17 | for (u32 i = 0; i < input_count; i++) { | ||
| 18 | string += fmt::format("{:02X}, ", inputs[i]); | ||
| 19 | } | ||
| 20 | string += "\n"; | ||
| 21 | } | ||
| 22 | |||
| 23 | void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 24 | constexpr s32 min{std::numeric_limits<s16>::min()}; | ||
| 25 | constexpr s32 max{std::numeric_limits<s16>::max()}; | ||
| 26 | |||
| 27 | std::vector<s16> output(processor.sample_count); | ||
| 28 | for (u32 channel = 0; channel < input_count; channel++) { | ||
| 29 | auto input{processor.mix_buffers.subspan(inputs[channel] * processor.sample_count, | ||
| 30 | processor.sample_count)}; | ||
| 31 | for (u32 sample_index = 0; sample_index < processor.sample_count; sample_index++) { | ||
| 32 | output[sample_index] = static_cast<s16>(std::clamp(input[sample_index], min, max)); | ||
| 33 | } | ||
| 34 | |||
| 35 | processor.memory->WriteBlockUnsafe(address + pos, output.data(), | ||
| 36 | output.size() * sizeof(s16)); | ||
| 37 | pos += static_cast<u32>(processor.sample_count * sizeof(s16)); | ||
| 38 | if (pos >= size) { | ||
| 39 | pos = 0; | ||
| 40 | } | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | bool CircularBufferSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 45 | return true; | ||
| 46 | } | ||
| 47 | |||
| 48 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/command/icommand.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | ||
| 14 | } | ||
| 15 | |||
| 16 | /** | ||
| 17 | * AudioRenderer command for sinking samples to a circular buffer. | ||
| 18 | */ | ||
| 19 | struct CircularBufferSinkCommand : ICommand { | ||
| 20 | /** | ||
| 21 | * Print this command's information to a string. | ||
| 22 | * | ||
| 23 | * @param processor - The CommandListProcessor processing this command. | ||
| 24 | * @param string - The string to print into. | ||
| 25 | */ | ||
| 26 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Process this command. | ||
| 30 | * | ||
| 31 | * @param processor - The CommandListProcessor processing this command. | ||
| 32 | */ | ||
| 33 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Verify this command's data is valid. | ||
| 37 | * | ||
| 38 | * @param processor - The CommandListProcessor processing this command. | ||
| 39 | * @return True if the command is valid, otherwise false. | ||
| 40 | */ | ||
| 41 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 42 | |||
| 43 | /// Number of input mix buffers | ||
| 44 | u32 input_count; | ||
| 45 | /// Input mix buffer indexes | ||
| 46 | std::array<s16, MaxChannels> inputs; | ||
| 47 | /// Circular buffer address | ||
| 48 | CpuAddr address; | ||
| 49 | /// Circular buffer size | ||
| 50 | u32 size; | ||
| 51 | /// Current buffer offset | ||
| 52 | u32 pos; | ||
| 53 | }; | ||
| 54 | |||
| 55 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | |||
| 6 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 7 | #include "audio_core/renderer/command/sink/device.h" | ||
| 8 | #include "audio_core/sink/sink.h" | ||
| 9 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | void DeviceSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 13 | std::string& string) { | ||
| 14 | string += fmt::format("DeviceSinkCommand\n\t{} session {} input_count {}\n\tinputs: ", | ||
| 15 | std::string_view(name), session_id, input_count); | ||
| 16 | for (u32 i = 0; i < input_count; i++) { | ||
| 17 | string += fmt::format("{:02X}, ", inputs[i]); | ||
| 18 | } | ||
| 19 | string += "\n"; | ||
| 20 | } | ||
| 21 | |||
| 22 | void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 23 | constexpr s32 min = std::numeric_limits<s16>::min(); | ||
| 24 | constexpr s32 max = std::numeric_limits<s16>::max(); | ||
| 25 | |||
| 26 | auto stream{processor.GetOutputSinkStream()}; | ||
| 27 | stream->SetSystemChannels(input_count); | ||
| 28 | |||
| 29 | Sink::SinkBuffer out_buffer{ | ||
| 30 | .frames{TargetSampleCount}, | ||
| 31 | .frames_played{0}, | ||
| 32 | .tag{0}, | ||
| 33 | .consumed{false}, | ||
| 34 | }; | ||
| 35 | |||
| 36 | std::vector<s16> samples(out_buffer.frames * input_count); | ||
| 37 | |||
| 38 | for (u32 channel = 0; channel < input_count; channel++) { | ||
| 39 | const auto offset{inputs[channel] * out_buffer.frames}; | ||
| 40 | |||
| 41 | for (u32 index = 0; index < out_buffer.frames; index++) { | ||
| 42 | samples[index * input_count + channel] = | ||
| 43 | static_cast<s16>(std::clamp(sample_buffer[offset + index], min, max)); | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | out_buffer.tag = reinterpret_cast<u64>(samples.data()); | ||
| 48 | stream->AppendBuffer(out_buffer, samples); | ||
| 49 | } | ||
| 50 | |||
| 51 | bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 52 | return true; | ||
| 53 | } | ||
| 54 | |||
| 55 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <span> | ||
| 8 | #include <string> | ||
| 9 | |||
| 10 | #include "audio_core/renderer/command/icommand.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | ||
| 16 | } | ||
| 17 | |||
| 18 | /** | ||
| 19 | * AudioRenderer command for sinking samples to an output device. | ||
| 20 | */ | ||
| 21 | struct DeviceSinkCommand : ICommand { | ||
| 22 | /** | ||
| 23 | * Print this command's information to a string. | ||
| 24 | * | ||
| 25 | * @param processor - The CommandListProcessor processing this command. | ||
| 26 | * @param string - The string to print into. | ||
| 27 | */ | ||
| 28 | void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Process this command. | ||
| 32 | * | ||
| 33 | * @param processor - The CommandListProcessor processing this command. | ||
| 34 | */ | ||
| 35 | void Process(const ADSP::CommandListProcessor& processor) override; | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Verify this command's data is valid. | ||
| 39 | * | ||
| 40 | * @param processor - The CommandListProcessor processing this command. | ||
| 41 | * @return True if the command is valid, otherwise false. | ||
| 42 | */ | ||
| 43 | bool Verify(const ADSP::CommandListProcessor& processor) override; | ||
| 44 | |||
| 45 | /// Device name | ||
| 46 | char name[0x100]; | ||
| 47 | /// System session id (unused) | ||
| 48 | s32 session_id; | ||
| 49 | /// Sample buffer to sink | ||
| 50 | std::span<s32> sample_buffer; | ||
| 51 | /// Number of input channels | ||
| 52 | u32 input_count; | ||
| 53 | /// Mix buffer indexes for each channel | ||
| 54 | std::array<s16, MaxChannels> inputs; | ||
| 55 | }; | ||
| 56 | |||
| 57 | } // namespace AudioCore::AudioRenderer | ||