diff options
270 files changed, 33704 insertions, 8437 deletions
diff --git a/.gitmodules b/.gitmodules index dc92d0a4b..ed533f8d4 100644 --- a/.gitmodules +++ b/.gitmodules | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | url = https://github.com/benhoyt/inih.git | 3 | url = https://github.com/benhoyt/inih.git |
| 4 | [submodule "cubeb"] | 4 | [submodule "cubeb"] |
| 5 | path = externals/cubeb | 5 | path = externals/cubeb |
| 6 | url = https://github.com/kinetiknz/cubeb.git | 6 | url = https://github.com/mozilla/cubeb.git |
| 7 | [submodule "dynarmic"] | 7 | [submodule "dynarmic"] |
| 8 | path = externals/dynarmic | 8 | path = externals/dynarmic |
| 9 | url = https://github.com/MerryMage/dynarmic.git | 9 | url = https://github.com/MerryMage/dynarmic.git |
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 89575a53e..2971c42a2 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt | |||
| @@ -1,54 +1,218 @@ | |||
| 1 | add_library(audio_core STATIC | 1 | add_library(audio_core STATIC |
| 2 | algorithm/filter.cpp | 2 | audio_core.cpp |
| 3 | algorithm/filter.h | 3 | audio_core.h |
| 4 | algorithm/interpolate.cpp | 4 | audio_event.h |
| 5 | algorithm/interpolate.h | 5 | audio_event.cpp |
| 6 | audio_out.cpp | 6 | audio_render_manager.cpp |
| 7 | audio_out.h | 7 | audio_render_manager.h |
| 8 | audio_renderer.cpp | 8 | audio_in_manager.cpp |
| 9 | audio_renderer.h | 9 | audio_in_manager.h |
| 10 | behavior_info.cpp | 10 | audio_out_manager.cpp |
| 11 | behavior_info.h | 11 | audio_out_manager.h |
| 12 | buffer.h | 12 | audio_manager.cpp |
| 13 | codec.cpp | 13 | audio_manager.h |
| 14 | codec.h | 14 | common/audio_renderer_parameter.h |
| 15 | command_generator.cpp | 15 | common/common.h |
| 16 | command_generator.h | 16 | common/feature_support.h |
| 17 | common.h | 17 | common/wave_buffer.h |
| 18 | delay_line.cpp | 18 | common/workbuffer_allocator.h |
| 19 | delay_line.h | 19 | device/audio_buffer.h |
| 20 | effect_context.cpp | 20 | device/audio_buffers.h |
| 21 | effect_context.h | 21 | device/device_session.cpp |
| 22 | info_updater.cpp | 22 | device/device_session.h |
| 23 | info_updater.h | 23 | in/audio_in.cpp |
| 24 | memory_pool.cpp | 24 | in/audio_in.h |
| 25 | memory_pool.h | 25 | in/audio_in_system.cpp |
| 26 | mix_context.cpp | 26 | in/audio_in_system.h |
| 27 | mix_context.h | 27 | out/audio_out.cpp |
| 28 | null_sink.h | 28 | out/audio_out.h |
| 29 | sink.h | 29 | out/audio_out_system.cpp |
| 30 | sink_context.cpp | 30 | out/audio_out_system.h |
| 31 | sink_context.h | 31 | renderer/adsp/adsp.cpp |
| 32 | sink_details.cpp | 32 | renderer/adsp/adsp.h |
| 33 | sink_details.h | 33 | renderer/adsp/audio_renderer.cpp |
| 34 | sink_stream.h | 34 | renderer/adsp/audio_renderer.h |
| 35 | splitter_context.cpp | 35 | renderer/adsp/command_buffer.h |
| 36 | splitter_context.h | 36 | renderer/adsp/command_list_processor.cpp |
| 37 | stream.cpp | 37 | renderer/adsp/command_list_processor.h |
| 38 | stream.h | 38 | renderer/audio_device.cpp |
| 39 | voice_context.cpp | 39 | renderer/audio_device.h |
| 40 | voice_context.h | 40 | renderer/audio_renderer.h |
| 41 | 41 | renderer/audio_renderer.cpp | |
| 42 | $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h> | 42 | renderer/behavior/behavior_info.cpp |
| 43 | $<$<BOOL:${ENABLE_SDL2}>:sdl2_sink.cpp sdl2_sink.h> | 43 | renderer/behavior/behavior_info.h |
| 44 | renderer/behavior/info_updater.cpp | ||
| 45 | renderer/behavior/info_updater.h | ||
| 46 | renderer/command/data_source/adpcm.cpp | ||
| 47 | renderer/command/data_source/adpcm.h | ||
| 48 | renderer/command/data_source/decode.cpp | ||
| 49 | renderer/command/data_source/decode.h | ||
| 50 | renderer/command/data_source/pcm_float.cpp | ||
| 51 | renderer/command/data_source/pcm_float.h | ||
| 52 | renderer/command/data_source/pcm_int16.cpp | ||
| 53 | renderer/command/data_source/pcm_int16.h | ||
| 54 | renderer/command/effect/aux_.cpp | ||
| 55 | renderer/command/effect/aux_.h | ||
| 56 | renderer/command/effect/biquad_filter.cpp | ||
| 57 | renderer/command/effect/biquad_filter.h | ||
| 58 | renderer/command/effect/capture.cpp | ||
| 59 | renderer/command/effect/capture.h | ||
| 60 | renderer/command/effect/compressor.cpp | ||
| 61 | renderer/command/effect/compressor.h | ||
| 62 | renderer/command/effect/delay.cpp | ||
| 63 | renderer/command/effect/delay.h | ||
| 64 | renderer/command/effect/i3dl2_reverb.cpp | ||
| 65 | renderer/command/effect/i3dl2_reverb.h | ||
| 66 | renderer/command/effect/light_limiter.cpp | ||
| 67 | renderer/command/effect/light_limiter.h | ||
| 68 | renderer/command/effect/multi_tap_biquad_filter.cpp | ||
| 69 | renderer/command/effect/multi_tap_biquad_filter.h | ||
| 70 | renderer/command/effect/reverb.cpp | ||
| 71 | renderer/command/effect/reverb.h | ||
| 72 | renderer/command/mix/clear_mix.cpp | ||
| 73 | renderer/command/mix/clear_mix.h | ||
| 74 | renderer/command/mix/copy_mix.cpp | ||
| 75 | renderer/command/mix/copy_mix.h | ||
| 76 | renderer/command/mix/depop_for_mix_buffers.cpp | ||
| 77 | renderer/command/mix/depop_for_mix_buffers.h | ||
| 78 | renderer/command/mix/depop_prepare.cpp | ||
| 79 | renderer/command/mix/depop_prepare.h | ||
| 80 | renderer/command/mix/mix.cpp | ||
| 81 | renderer/command/mix/mix.h | ||
| 82 | renderer/command/mix/mix_ramp.cpp | ||
| 83 | renderer/command/mix/mix_ramp.h | ||
| 84 | renderer/command/mix/mix_ramp_grouped.cpp | ||
| 85 | renderer/command/mix/mix_ramp_grouped.h | ||
| 86 | renderer/command/mix/volume.cpp | ||
| 87 | renderer/command/mix/volume.h | ||
| 88 | renderer/command/mix/volume_ramp.cpp | ||
| 89 | renderer/command/mix/volume_ramp.h | ||
| 90 | renderer/command/performance/performance.cpp | ||
| 91 | renderer/command/performance/performance.h | ||
| 92 | renderer/command/resample/downmix_6ch_to_2ch.cpp | ||
| 93 | renderer/command/resample/downmix_6ch_to_2ch.h | ||
| 94 | renderer/command/resample/resample.h | ||
| 95 | renderer/command/resample/resample.cpp | ||
| 96 | renderer/command/resample/upsample.cpp | ||
| 97 | renderer/command/resample/upsample.h | ||
| 98 | renderer/command/sink/device.cpp | ||
| 99 | renderer/command/sink/device.h | ||
| 100 | renderer/command/sink/circular_buffer.cpp | ||
| 101 | renderer/command/sink/circular_buffer.h | ||
| 102 | renderer/command/command_buffer.cpp | ||
| 103 | renderer/command/command_buffer.h | ||
| 104 | renderer/command/command_generator.cpp | ||
| 105 | renderer/command/command_generator.h | ||
| 106 | renderer/command/command_list_header.h | ||
| 107 | renderer/command/command_processing_time_estimator.cpp | ||
| 108 | renderer/command/command_processing_time_estimator.h | ||
| 109 | renderer/command/commands.h | ||
| 110 | renderer/command/icommand.h | ||
| 111 | renderer/effect/aux_.cpp | ||
| 112 | renderer/effect/aux_.h | ||
| 113 | renderer/effect/biquad_filter.cpp | ||
| 114 | renderer/effect/biquad_filter.h | ||
| 115 | renderer/effect/buffer_mixer.cpp | ||
| 116 | renderer/effect/buffer_mixer.h | ||
| 117 | renderer/effect/capture.cpp | ||
| 118 | renderer/effect/capture.h | ||
| 119 | renderer/effect/compressor.cpp | ||
| 120 | renderer/effect/compressor.h | ||
| 121 | renderer/effect/delay.cpp | ||
| 122 | renderer/effect/delay.h | ||
| 123 | renderer/effect/effect_context.cpp | ||
| 124 | renderer/effect/effect_context.h | ||
| 125 | renderer/effect/effect_info_base.h | ||
| 126 | renderer/effect/effect_reset.h | ||
| 127 | renderer/effect/effect_result_state.h | ||
| 128 | renderer/effect/i3dl2.cpp | ||
| 129 | renderer/effect/i3dl2.h | ||
| 130 | renderer/effect/light_limiter.cpp | ||
| 131 | renderer/effect/light_limiter.h | ||
| 132 | renderer/effect/reverb.h | ||
| 133 | renderer/effect/reverb.cpp | ||
| 134 | renderer/mix/mix_context.cpp | ||
| 135 | renderer/mix/mix_context.h | ||
| 136 | renderer/mix/mix_info.cpp | ||
| 137 | renderer/mix/mix_info.h | ||
| 138 | renderer/memory/address_info.h | ||
| 139 | renderer/memory/memory_pool_info.cpp | ||
| 140 | renderer/memory/memory_pool_info.h | ||
| 141 | renderer/memory/pool_mapper.cpp | ||
| 142 | renderer/memory/pool_mapper.h | ||
| 143 | renderer/nodes/bit_array.h | ||
| 144 | renderer/nodes/edge_matrix.cpp | ||
| 145 | renderer/nodes/edge_matrix.h | ||
| 146 | renderer/nodes/node_states.cpp | ||
| 147 | renderer/nodes/node_states.h | ||
| 148 | renderer/performance/detail_aspect.cpp | ||
| 149 | renderer/performance/detail_aspect.h | ||
| 150 | renderer/performance/entry_aspect.cpp | ||
| 151 | renderer/performance/entry_aspect.h | ||
| 152 | renderer/performance/performance_detail.h | ||
| 153 | renderer/performance/performance_entry.h | ||
| 154 | renderer/performance/performance_entry_addresses.h | ||
| 155 | renderer/performance/performance_frame_header.h | ||
| 156 | renderer/performance/performance_manager.cpp | ||
| 157 | renderer/performance/performance_manager.h | ||
| 158 | renderer/sink/circular_buffer_sink_info.cpp | ||
| 159 | renderer/sink/circular_buffer_sink_info.h | ||
| 160 | renderer/sink/device_sink_info.cpp | ||
| 161 | renderer/sink/device_sink_info.h | ||
| 162 | renderer/sink/sink_context.cpp | ||
| 163 | renderer/sink/sink_context.h | ||
| 164 | renderer/sink/sink_info_base.cpp | ||
| 165 | renderer/sink/sink_info_base.h | ||
| 166 | renderer/splitter/splitter_context.cpp | ||
| 167 | renderer/splitter/splitter_context.h | ||
| 168 | renderer/splitter/splitter_destinations_data.cpp | ||
| 169 | renderer/splitter/splitter_destinations_data.h | ||
| 170 | renderer/splitter/splitter_info.cpp | ||
| 171 | renderer/splitter/splitter_info.h | ||
| 172 | renderer/system.cpp | ||
| 173 | renderer/system.h | ||
| 174 | renderer/system_manager.cpp | ||
| 175 | renderer/system_manager.h | ||
| 176 | renderer/upsampler/upsampler_info.h | ||
| 177 | renderer/upsampler/upsampler_manager.cpp | ||
| 178 | renderer/upsampler/upsampler_manager.h | ||
| 179 | renderer/upsampler/upsampler_state.h | ||
| 180 | renderer/voice/voice_channel_resource.h | ||
| 181 | renderer/voice/voice_context.cpp | ||
| 182 | renderer/voice/voice_context.h | ||
| 183 | renderer/voice/voice_info.cpp | ||
| 184 | renderer/voice/voice_info.h | ||
| 185 | renderer/voice/voice_state.h | ||
| 186 | sink/cubeb_sink.cpp | ||
| 187 | sink/cubeb_sink.h | ||
| 188 | sink/null_sink.h | ||
| 189 | sink/sdl2_sink.cpp | ||
| 190 | sink/sdl2_sink.h | ||
| 191 | sink/sink.h | ||
| 192 | sink/sink_details.cpp | ||
| 193 | sink/sink_details.h | ||
| 194 | sink/sink_stream.h | ||
| 44 | ) | 195 | ) |
| 45 | 196 | ||
| 46 | create_target_directory_groups(audio_core) | 197 | create_target_directory_groups(audio_core) |
| 47 | 198 | ||
| 48 | if (NOT MSVC) | 199 | if (MSVC) |
| 200 | target_compile_options(audio_core PRIVATE | ||
| 201 | /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data | ||
| 202 | /we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data | ||
| 203 | /we4245 # 'conversion': conversion from 'type1' to 'type2', signed/unsigned mismatch | ||
| 204 | /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data | ||
| 205 | /we4456 # Declaration of 'identifier' hides previous local declaration | ||
| 206 | /we4457 # Declaration of 'identifier' hides function parameter | ||
| 207 | /we4458 # Declaration of 'identifier' hides class member | ||
| 208 | /we4459 # Declaration of 'identifier' hides global declaration | ||
| 209 | ) | ||
| 210 | else() | ||
| 49 | target_compile_options(audio_core PRIVATE | 211 | target_compile_options(audio_core PRIVATE |
| 50 | -Werror=conversion | 212 | -Werror=conversion |
| 51 | -Werror=ignored-qualifiers | 213 | -Werror=ignored-qualifiers |
| 214 | -Werror=shadow | ||
| 215 | -Werror=unused-variable | ||
| 52 | 216 | ||
| 53 | $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter> | 217 | $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter> |
| 54 | $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable> | 218 | $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable> |
| @@ -58,6 +222,9 @@ if (NOT MSVC) | |||
| 58 | endif() | 222 | endif() |
| 59 | 223 | ||
| 60 | target_link_libraries(audio_core PUBLIC common core) | 224 | target_link_libraries(audio_core PUBLIC common core) |
| 225 | if (ARCHITECTURE_x86_64) | ||
| 226 | target_link_libraries(audio_core PRIVATE dynarmic) | ||
| 227 | endif() | ||
| 61 | 228 | ||
| 62 | if(ENABLE_CUBEB) | 229 | if(ENABLE_CUBEB) |
| 63 | target_link_libraries(audio_core PRIVATE cubeb) | 230 | target_link_libraries(audio_core PRIVATE cubeb) |
diff --git a/src/audio_core/algorithm/filter.cpp b/src/audio_core/algorithm/filter.cpp deleted file mode 100644 index 96e37991f..000000000 --- a/src/audio_core/algorithm/filter.cpp +++ /dev/null | |||
| @@ -1,79 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #define _USE_MATH_DEFINES | ||
| 5 | |||
| 6 | #include <algorithm> | ||
| 7 | #include <array> | ||
| 8 | #include <cmath> | ||
| 9 | #include <vector> | ||
| 10 | #include "audio_core/algorithm/filter.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace AudioCore { | ||
| 14 | |||
| 15 | Filter Filter::LowPass(double cutoff, double Q) { | ||
| 16 | const double w0 = 2.0 * M_PI * cutoff; | ||
| 17 | const double sin_w0 = std::sin(w0); | ||
| 18 | const double cos_w0 = std::cos(w0); | ||
| 19 | const double alpha = sin_w0 / (2 * Q); | ||
| 20 | |||
| 21 | const double a0 = 1 + alpha; | ||
| 22 | const double a1 = -2.0 * cos_w0; | ||
| 23 | const double a2 = 1 - alpha; | ||
| 24 | const double b0 = 0.5 * (1 - cos_w0); | ||
| 25 | const double b1 = 1.0 * (1 - cos_w0); | ||
| 26 | const double b2 = 0.5 * (1 - cos_w0); | ||
| 27 | |||
| 28 | return {a0, a1, a2, b0, b1, b2}; | ||
| 29 | } | ||
| 30 | |||
| 31 | Filter::Filter() : Filter(1.0, 0.0, 0.0, 1.0, 0.0, 0.0) {} | ||
| 32 | |||
| 33 | Filter::Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_) | ||
| 34 | : a1(a1_ / a0_), a2(a2_ / a0_), b0(b0_ / a0_), b1(b1_ / a0_), b2(b2_ / a0_) {} | ||
| 35 | |||
| 36 | void Filter::Process(std::vector<s16>& signal) { | ||
| 37 | const std::size_t num_frames = signal.size() / 2; | ||
| 38 | for (std::size_t i = 0; i < num_frames; i++) { | ||
| 39 | std::rotate(in.begin(), in.end() - 1, in.end()); | ||
| 40 | std::rotate(out.begin(), out.end() - 1, out.end()); | ||
| 41 | |||
| 42 | for (std::size_t ch = 0; ch < channel_count; ch++) { | ||
| 43 | in[0][ch] = signal[i * channel_count + ch]; | ||
| 44 | |||
| 45 | out[0][ch] = b0 * in[0][ch] + b1 * in[1][ch] + b2 * in[2][ch] - a1 * out[1][ch] - | ||
| 46 | a2 * out[2][ch]; | ||
| 47 | |||
| 48 | signal[i * 2 + ch] = static_cast<s16>(std::clamp(out[0][ch], -32768.0, 32767.0)); | ||
| 49 | } | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | /// Calculates the appropriate Q for each biquad in a cascading filter. | ||
| 54 | /// @param total_count The total number of biquads to be cascaded. | ||
| 55 | /// @param index 0-index of the biquad to calculate the Q value for. | ||
| 56 | static double CascadingBiquadQ(std::size_t total_count, std::size_t index) { | ||
| 57 | const auto pole = | ||
| 58 | M_PI * static_cast<double>(2 * index + 1) / (4.0 * static_cast<double>(total_count)); | ||
| 59 | return 1.0 / (2.0 * std::cos(pole)); | ||
| 60 | } | ||
| 61 | |||
| 62 | CascadingFilter CascadingFilter::LowPass(double cutoff, std::size_t cascade_size) { | ||
| 63 | std::vector<Filter> cascade(cascade_size); | ||
| 64 | for (std::size_t i = 0; i < cascade_size; i++) { | ||
| 65 | cascade[i] = Filter::LowPass(cutoff, CascadingBiquadQ(cascade_size, i)); | ||
| 66 | } | ||
| 67 | return CascadingFilter{std::move(cascade)}; | ||
| 68 | } | ||
| 69 | |||
| 70 | CascadingFilter::CascadingFilter() = default; | ||
| 71 | CascadingFilter::CascadingFilter(std::vector<Filter> filters_) : filters(std::move(filters_)) {} | ||
| 72 | |||
| 73 | void CascadingFilter::Process(std::vector<s16>& signal) { | ||
| 74 | for (auto& filter : filters) { | ||
| 75 | filter.Process(signal); | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | } // namespace AudioCore | ||
diff --git a/src/audio_core/algorithm/filter.h b/src/audio_core/algorithm/filter.h deleted file mode 100644 index 2586f0079..000000000 --- a/src/audio_core/algorithm/filter.h +++ /dev/null | |||
| @@ -1,61 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <vector> | ||
| 8 | #include "common/common_types.h" | ||
| 9 | |||
| 10 | namespace AudioCore { | ||
| 11 | |||
| 12 | /// Digital biquad filter: | ||
| 13 | /// | ||
| 14 | /// b0 + b1 z^-1 + b2 z^-2 | ||
| 15 | /// H(z) = ------------------------ | ||
| 16 | /// a0 + a1 z^-1 + b2 z^-2 | ||
| 17 | class Filter { | ||
| 18 | public: | ||
| 19 | /// Creates a low-pass filter. | ||
| 20 | /// @param cutoff Determines the cutoff frequency. A value from 0.0 to 1.0. | ||
| 21 | /// @param Q Determines the quality factor of this filter. | ||
| 22 | static Filter LowPass(double cutoff, double Q = 0.7071); | ||
| 23 | |||
| 24 | /// Passthrough filter. | ||
| 25 | Filter(); | ||
| 26 | |||
| 27 | Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_); | ||
| 28 | |||
| 29 | void Process(std::vector<s16>& signal); | ||
| 30 | |||
| 31 | private: | ||
| 32 | static constexpr std::size_t channel_count = 2; | ||
| 33 | |||
| 34 | /// Coefficients are in normalized form (a0 = 1.0). | ||
| 35 | double a1, a2, b0, b1, b2; | ||
| 36 | /// Input History | ||
| 37 | std::array<std::array<double, channel_count>, 3> in; | ||
| 38 | /// Output History | ||
| 39 | std::array<std::array<double, channel_count>, 3> out; | ||
| 40 | }; | ||
| 41 | |||
| 42 | /// Cascade filters to build up higher-order filters from lower-order ones. | ||
| 43 | class CascadingFilter { | ||
| 44 | public: | ||
| 45 | /// Creates a cascading low-pass filter. | ||
| 46 | /// @param cutoff Determines the cutoff frequency. A value from 0.0 to 1.0. | ||
| 47 | /// @param cascade_size Number of biquads in cascade. | ||
| 48 | static CascadingFilter LowPass(double cutoff, std::size_t cascade_size); | ||
| 49 | |||
| 50 | /// Passthrough. | ||
| 51 | CascadingFilter(); | ||
| 52 | |||
| 53 | explicit CascadingFilter(std::vector<Filter> filters_); | ||
| 54 | |||
| 55 | void Process(std::vector<s16>& signal); | ||
| 56 | |||
| 57 | private: | ||
| 58 | std::vector<Filter> filters; | ||
| 59 | }; | ||
| 60 | |||
| 61 | } // namespace AudioCore | ||
diff --git a/src/audio_core/algorithm/interpolate.cpp b/src/audio_core/algorithm/interpolate.cpp deleted file mode 100644 index d2a4cd53f..000000000 --- a/src/audio_core/algorithm/interpolate.cpp +++ /dev/null | |||
| @@ -1,232 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #define _USE_MATH_DEFINES | ||
| 5 | |||
| 6 | #include <algorithm> | ||
| 7 | #include <climits> | ||
| 8 | #include <cmath> | ||
| 9 | #include <vector> | ||
| 10 | |||
| 11 | #include "audio_core/algorithm/interpolate.h" | ||
| 12 | #include "common/common_types.h" | ||
| 13 | #include "common/logging/log.h" | ||
| 14 | |||
| 15 | namespace AudioCore { | ||
| 16 | |||
| 17 | constexpr std::array<s16, 512> curve_lut0{ | ||
| 18 | 6600, 19426, 6722, 3, 6479, 19424, 6845, 9, 6359, 19419, 6968, 15, 6239, | ||
| 19 | 19412, 7093, 22, 6121, 19403, 7219, 28, 6004, 19391, 7345, 34, 5888, 19377, | ||
| 20 | 7472, 41, 5773, 19361, 7600, 48, 5659, 19342, 7728, 55, 5546, 19321, 7857, | ||
| 21 | 62, 5434, 19298, 7987, 69, 5323, 19273, 8118, 77, 5213, 19245, 8249, 84, | ||
| 22 | 5104, 19215, 8381, 92, 4997, 19183, 8513, 101, 4890, 19148, 8646, 109, 4785, | ||
| 23 | 19112, 8780, 118, 4681, 19073, 8914, 127, 4579, 19031, 9048, 137, 4477, 18988, | ||
| 24 | 9183, 147, 4377, 18942, 9318, 157, 4277, 18895, 9454, 168, 4179, 18845, 9590, | ||
| 25 | 179, 4083, 18793, 9726, 190, 3987, 18738, 9863, 202, 3893, 18682, 10000, 215, | ||
| 26 | 3800, 18624, 10137, 228, 3709, 18563, 10274, 241, 3618, 18500, 10411, 255, 3529, | ||
| 27 | 18436, 10549, 270, 3441, 18369, 10687, 285, 3355, 18300, 10824, 300, 3269, 18230, | ||
| 28 | 10962, 317, 3186, 18157, 11100, 334, 3103, 18082, 11238, 351, 3022, 18006, 11375, | ||
| 29 | 369, 2942, 17927, 11513, 388, 2863, 17847, 11650, 408, 2785, 17765, 11788, 428, | ||
| 30 | 2709, 17681, 11925, 449, 2635, 17595, 12062, 471, 2561, 17507, 12198, 494, 2489, | ||
| 31 | 17418, 12334, 517, 2418, 17327, 12470, 541, 2348, 17234, 12606, 566, 2280, 17140, | ||
| 32 | 12741, 592, 2213, 17044, 12876, 619, 2147, 16946, 13010, 647, 2083, 16846, 13144, | ||
| 33 | 675, 2020, 16745, 13277, 704, 1958, 16643, 13409, 735, 1897, 16539, 13541, 766, | ||
| 34 | 1838, 16434, 13673, 798, 1780, 16327, 13803, 832, 1723, 16218, 13933, 866, 1667, | ||
| 35 | 16109, 14062, 901, 1613, 15998, 14191, 937, 1560, 15885, 14318, 975, 1508, 15772, | ||
| 36 | 14445, 1013, 1457, 15657, 14571, 1052, 1407, 15540, 14695, 1093, 1359, 15423, 14819, | ||
| 37 | 1134, 1312, 15304, 14942, 1177, 1266, 15185, 15064, 1221, 1221, 15064, 15185, 1266, | ||
| 38 | 1177, 14942, 15304, 1312, 1134, 14819, 15423, 1359, 1093, 14695, 15540, 1407, 1052, | ||
| 39 | 14571, 15657, 1457, 1013, 14445, 15772, 1508, 975, 14318, 15885, 1560, 937, 14191, | ||
| 40 | 15998, 1613, 901, 14062, 16109, 1667, 866, 13933, 16218, 1723, 832, 13803, 16327, | ||
| 41 | 1780, 798, 13673, 16434, 1838, 766, 13541, 16539, 1897, 735, 13409, 16643, 1958, | ||
| 42 | 704, 13277, 16745, 2020, 675, 13144, 16846, 2083, 647, 13010, 16946, 2147, 619, | ||
| 43 | 12876, 17044, 2213, 592, 12741, 17140, 2280, 566, 12606, 17234, 2348, 541, 12470, | ||
| 44 | 17327, 2418, 517, 12334, 17418, 2489, 494, 12198, 17507, 2561, 471, 12062, 17595, | ||
| 45 | 2635, 449, 11925, 17681, 2709, 428, 11788, 17765, 2785, 408, 11650, 17847, 2863, | ||
| 46 | 388, 11513, 17927, 2942, 369, 11375, 18006, 3022, 351, 11238, 18082, 3103, 334, | ||
| 47 | 11100, 18157, 3186, 317, 10962, 18230, 3269, 300, 10824, 18300, 3355, 285, 10687, | ||
| 48 | 18369, 3441, 270, 10549, 18436, 3529, 255, 10411, 18500, 3618, 241, 10274, 18563, | ||
| 49 | 3709, 228, 10137, 18624, 3800, 215, 10000, 18682, 3893, 202, 9863, 18738, 3987, | ||
| 50 | 190, 9726, 18793, 4083, 179, 9590, 18845, 4179, 168, 9454, 18895, 4277, 157, | ||
| 51 | 9318, 18942, 4377, 147, 9183, 18988, 4477, 137, 9048, 19031, 4579, 127, 8914, | ||
| 52 | 19073, 4681, 118, 8780, 19112, 4785, 109, 8646, 19148, 4890, 101, 8513, 19183, | ||
| 53 | 4997, 92, 8381, 19215, 5104, 84, 8249, 19245, 5213, 77, 8118, 19273, 5323, | ||
| 54 | 69, 7987, 19298, 5434, 62, 7857, 19321, 5546, 55, 7728, 19342, 5659, 48, | ||
| 55 | 7600, 19361, 5773, 41, 7472, 19377, 5888, 34, 7345, 19391, 6004, 28, 7219, | ||
| 56 | 19403, 6121, 22, 7093, 19412, 6239, 15, 6968, 19419, 6359, 9, 6845, 19424, | ||
| 57 | 6479, 3, 6722, 19426, 6600}; | ||
| 58 | |||
| 59 | constexpr std::array<s16, 512> curve_lut1{ | ||
| 60 | -68, 32639, 69, -5, -200, 32630, 212, -15, -328, 32613, 359, -26, -450, | ||
| 61 | 32586, 512, -36, -568, 32551, 669, -47, -680, 32507, 832, -58, -788, 32454, | ||
| 62 | 1000, -69, -891, 32393, 1174, -80, -990, 32323, 1352, -92, -1084, 32244, 1536, | ||
| 63 | -103, -1173, 32157, 1724, -115, -1258, 32061, 1919, -128, -1338, 31956, 2118, -140, | ||
| 64 | -1414, 31844, 2322, -153, -1486, 31723, 2532, -167, -1554, 31593, 2747, -180, -1617, | ||
| 65 | 31456, 2967, -194, -1676, 31310, 3192, -209, -1732, 31157, 3422, -224, -1783, 30995, | ||
| 66 | 3657, -240, -1830, 30826, 3897, -256, -1874, 30649, 4143, -272, -1914, 30464, 4393, | ||
| 67 | -289, -1951, 30272, 4648, -307, -1984, 30072, 4908, -325, -2014, 29866, 5172, -343, | ||
| 68 | -2040, 29652, 5442, -362, -2063, 29431, 5716, -382, -2083, 29203, 5994, -403, -2100, | ||
| 69 | 28968, 6277, -424, -2114, 28727, 6565, -445, -2125, 28480, 6857, -468, -2133, 28226, | ||
| 70 | 7153, -490, -2139, 27966, 7453, -514, -2142, 27700, 7758, -538, -2142, 27428, 8066, | ||
| 71 | -563, -2141, 27151, 8378, -588, -2136, 26867, 8694, -614, -2130, 26579, 9013, -641, | ||
| 72 | -2121, 26285, 9336, -668, -2111, 25987, 9663, -696, -2098, 25683, 9993, -724, -2084, | ||
| 73 | 25375, 10326, -753, -2067, 25063, 10662, -783, -2049, 24746, 11000, -813, -2030, 24425, | ||
| 74 | 11342, -844, -2009, 24100, 11686, -875, -1986, 23771, 12033, -907, -1962, 23438, 12382, | ||
| 75 | -939, -1937, 23103, 12733, -972, -1911, 22764, 13086, -1005, -1883, 22422, 13441, -1039, | ||
| 76 | -1855, 22077, 13798, -1072, -1825, 21729, 14156, -1107, -1795, 21380, 14516, -1141, -1764, | ||
| 77 | 21027, 14877, -1176, -1732, 20673, 15239, -1211, -1700, 20317, 15602, -1246, -1667, 19959, | ||
| 78 | 15965, -1282, -1633, 19600, 16329, -1317, -1599, 19239, 16694, -1353, -1564, 18878, 17058, | ||
| 79 | -1388, -1530, 18515, 17423, -1424, -1495, 18151, 17787, -1459, -1459, 17787, 18151, -1495, | ||
| 80 | -1424, 17423, 18515, -1530, -1388, 17058, 18878, -1564, -1353, 16694, 19239, -1599, -1317, | ||
| 81 | 16329, 19600, -1633, -1282, 15965, 19959, -1667, -1246, 15602, 20317, -1700, -1211, 15239, | ||
| 82 | 20673, -1732, -1176, 14877, 21027, -1764, -1141, 14516, 21380, -1795, -1107, 14156, 21729, | ||
| 83 | -1825, -1072, 13798, 22077, -1855, -1039, 13441, 22422, -1883, -1005, 13086, 22764, -1911, | ||
| 84 | -972, 12733, 23103, -1937, -939, 12382, 23438, -1962, -907, 12033, 23771, -1986, -875, | ||
| 85 | 11686, 24100, -2009, -844, 11342, 24425, -2030, -813, 11000, 24746, -2049, -783, 10662, | ||
| 86 | 25063, -2067, -753, 10326, 25375, -2084, -724, 9993, 25683, -2098, -696, 9663, 25987, | ||
| 87 | -2111, -668, 9336, 26285, -2121, -641, 9013, 26579, -2130, -614, 8694, 26867, -2136, | ||
| 88 | -588, 8378, 27151, -2141, -563, 8066, 27428, -2142, -538, 7758, 27700, -2142, -514, | ||
| 89 | 7453, 27966, -2139, -490, 7153, 28226, -2133, -468, 6857, 28480, -2125, -445, 6565, | ||
| 90 | 28727, -2114, -424, 6277, 28968, -2100, -403, 5994, 29203, -2083, -382, 5716, 29431, | ||
| 91 | -2063, -362, 5442, 29652, -2040, -343, 5172, 29866, -2014, -325, 4908, 30072, -1984, | ||
| 92 | -307, 4648, 30272, -1951, -289, 4393, 30464, -1914, -272, 4143, 30649, -1874, -256, | ||
| 93 | 3897, 30826, -1830, -240, 3657, 30995, -1783, -224, 3422, 31157, -1732, -209, 3192, | ||
| 94 | 31310, -1676, -194, 2967, 31456, -1617, -180, 2747, 31593, -1554, -167, 2532, 31723, | ||
| 95 | -1486, -153, 2322, 31844, -1414, -140, 2118, 31956, -1338, -128, 1919, 32061, -1258, | ||
| 96 | -115, 1724, 32157, -1173, -103, 1536, 32244, -1084, -92, 1352, 32323, -990, -80, | ||
| 97 | 1174, 32393, -891, -69, 1000, 32454, -788, -58, 832, 32507, -680, -47, 669, | ||
| 98 | 32551, -568, -36, 512, 32586, -450, -26, 359, 32613, -328, -15, 212, 32630, | ||
| 99 | -200, -5, 69, 32639, -68}; | ||
| 100 | |||
| 101 | constexpr std::array<s16, 512> curve_lut2{ | ||
| 102 | 3195, 26287, 3329, -32, 3064, 26281, 3467, -34, 2936, 26270, 3608, -38, 2811, | ||
| 103 | 26253, 3751, -42, 2688, 26230, 3897, -46, 2568, 26202, 4046, -50, 2451, 26169, | ||
| 104 | 4199, -54, 2338, 26130, 4354, -58, 2227, 26085, 4512, -63, 2120, 26035, 4673, | ||
| 105 | -67, 2015, 25980, 4837, -72, 1912, 25919, 5004, -76, 1813, 25852, 5174, -81, | ||
| 106 | 1716, 25780, 5347, -87, 1622, 25704, 5522, -92, 1531, 25621, 5701, -98, 1442, | ||
| 107 | 25533, 5882, -103, 1357, 25440, 6066, -109, 1274, 25342, 6253, -115, 1193, 25239, | ||
| 108 | 6442, -121, 1115, 25131, 6635, -127, 1040, 25018, 6830, -133, 967, 24899, 7027, | ||
| 109 | -140, 897, 24776, 7227, -146, 829, 24648, 7430, -153, 764, 24516, 7635, -159, | ||
| 110 | 701, 24379, 7842, -166, 641, 24237, 8052, -174, 583, 24091, 8264, -181, 526, | ||
| 111 | 23940, 8478, -187, 472, 23785, 8695, -194, 420, 23626, 8914, -202, 371, 23462, | ||
| 112 | 9135, -209, 324, 23295, 9358, -215, 279, 23123, 9583, -222, 236, 22948, 9809, | ||
| 113 | -230, 194, 22769, 10038, -237, 154, 22586, 10269, -243, 117, 22399, 10501, -250, | ||
| 114 | 81, 22208, 10735, -258, 47, 22015, 10970, -265, 15, 21818, 11206, -271, -16, | ||
| 115 | 21618, 11444, -277, -44, 21415, 11684, -283, -71, 21208, 11924, -290, -97, 20999, | ||
| 116 | 12166, -296, -121, 20786, 12409, -302, -143, 20571, 12653, -306, -163, 20354, 12898, | ||
| 117 | -311, -183, 20134, 13143, -316, -201, 19911, 13389, -321, -218, 19686, 13635, -325, | ||
| 118 | -234, 19459, 13882, -328, -248, 19230, 14130, -332, -261, 18998, 14377, -335, -273, | ||
| 119 | 18765, 14625, -337, -284, 18531, 14873, -339, -294, 18295, 15121, -341, -302, 18057, | ||
| 120 | 15369, -341, -310, 17817, 15617, -341, -317, 17577, 15864, -340, -323, 17335, 16111, | ||
| 121 | -340, -328, 17092, 16357, -338, -332, 16848, 16603, -336, -336, 16603, 16848, -332, | ||
| 122 | -338, 16357, 17092, -328, -340, 16111, 17335, -323, -340, 15864, 17577, -317, -341, | ||
| 123 | 15617, 17817, -310, -341, 15369, 18057, -302, -341, 15121, 18295, -294, -339, 14873, | ||
| 124 | 18531, -284, -337, 14625, 18765, -273, -335, 14377, 18998, -261, -332, 14130, 19230, | ||
| 125 | -248, -328, 13882, 19459, -234, -325, 13635, 19686, -218, -321, 13389, 19911, -201, | ||
| 126 | -316, 13143, 20134, -183, -311, 12898, 20354, -163, -306, 12653, 20571, -143, -302, | ||
| 127 | 12409, 20786, -121, -296, 12166, 20999, -97, -290, 11924, 21208, -71, -283, 11684, | ||
| 128 | 21415, -44, -277, 11444, 21618, -16, -271, 11206, 21818, 15, -265, 10970, 22015, | ||
| 129 | 47, -258, 10735, 22208, 81, -250, 10501, 22399, 117, -243, 10269, 22586, 154, | ||
| 130 | -237, 10038, 22769, 194, -230, 9809, 22948, 236, -222, 9583, 23123, 279, -215, | ||
| 131 | 9358, 23295, 324, -209, 9135, 23462, 371, -202, 8914, 23626, 420, -194, 8695, | ||
| 132 | 23785, 472, -187, 8478, 23940, 526, -181, 8264, 24091, 583, -174, 8052, 24237, | ||
| 133 | 641, -166, 7842, 24379, 701, -159, 7635, 24516, 764, -153, 7430, 24648, 829, | ||
| 134 | -146, 7227, 24776, 897, -140, 7027, 24899, 967, -133, 6830, 25018, 1040, -127, | ||
| 135 | 6635, 25131, 1115, -121, 6442, 25239, 1193, -115, 6253, 25342, 1274, -109, 6066, | ||
| 136 | 25440, 1357, -103, 5882, 25533, 1442, -98, 5701, 25621, 1531, -92, 5522, 25704, | ||
| 137 | 1622, -87, 5347, 25780, 1716, -81, 5174, 25852, 1813, -76, 5004, 25919, 1912, | ||
| 138 | -72, 4837, 25980, 2015, -67, 4673, 26035, 2120, -63, 4512, 26085, 2227, -58, | ||
| 139 | 4354, 26130, 2338, -54, 4199, 26169, 2451, -50, 4046, 26202, 2568, -46, 3897, | ||
| 140 | 26230, 2688, -42, 3751, 26253, 2811, -38, 3608, 26270, 2936, -34, 3467, 26281, | ||
| 141 | 3064, -32, 3329, 26287, 3195}; | ||
| 142 | |||
| 143 | std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, double ratio) { | ||
| 144 | if (input.size() < 2) | ||
| 145 | return {}; | ||
| 146 | |||
| 147 | if (ratio <= 0) { | ||
| 148 | LOG_ERROR(Audio, "Nonsensical interpolation ratio {}", ratio); | ||
| 149 | return input; | ||
| 150 | } | ||
| 151 | |||
| 152 | const s32 step{static_cast<s32>(ratio * 0x8000)}; | ||
| 153 | const std::array<s16, 512>& lut = [step] { | ||
| 154 | if (step > 0xaaaa) { | ||
| 155 | return curve_lut0; | ||
| 156 | } | ||
| 157 | if (step <= 0x8000) { | ||
| 158 | return curve_lut1; | ||
| 159 | } | ||
| 160 | return curve_lut2; | ||
| 161 | }(); | ||
| 162 | |||
| 163 | const std::size_t num_frames{input.size() / 2}; | ||
| 164 | |||
| 165 | std::vector<s16> output; | ||
| 166 | output.reserve(static_cast<std::size_t>(static_cast<double>(input.size()) / ratio + | ||
| 167 | InterpolationState::taps)); | ||
| 168 | |||
| 169 | for (std::size_t frame{}; frame < num_frames; ++frame) { | ||
| 170 | const std::size_t lut_index{(state.fraction >> 8) * InterpolationState::taps}; | ||
| 171 | |||
| 172 | std::rotate(state.history.begin(), state.history.end() - 1, state.history.end()); | ||
| 173 | state.history[0][0] = input[frame * 2 + 0]; | ||
| 174 | state.history[0][1] = input[frame * 2 + 1]; | ||
| 175 | |||
| 176 | while (state.position <= 1.0) { | ||
| 177 | const s32 left{state.history[0][0] * lut[lut_index + 0] + | ||
| 178 | state.history[1][0] * lut[lut_index + 1] + | ||
| 179 | state.history[2][0] * lut[lut_index + 2] + | ||
| 180 | state.history[3][0] * lut[lut_index + 3]}; | ||
| 181 | const s32 right{state.history[0][1] * lut[lut_index + 0] + | ||
| 182 | state.history[1][1] * lut[lut_index + 1] + | ||
| 183 | state.history[2][1] * lut[lut_index + 2] + | ||
| 184 | state.history[3][1] * lut[lut_index + 3]}; | ||
| 185 | const s32 new_offset{state.fraction + step}; | ||
| 186 | |||
| 187 | state.fraction = new_offset & 0x7fff; | ||
| 188 | |||
| 189 | output.emplace_back(static_cast<s16>(std::clamp(left >> 15, SHRT_MIN, SHRT_MAX))); | ||
| 190 | output.emplace_back(static_cast<s16>(std::clamp(right >> 15, SHRT_MIN, SHRT_MAX))); | ||
| 191 | |||
| 192 | state.position += ratio; | ||
| 193 | } | ||
| 194 | state.position -= 1.0; | ||
| 195 | } | ||
| 196 | |||
| 197 | return output; | ||
| 198 | } | ||
| 199 | |||
| 200 | void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count) { | ||
| 201 | const std::array<s16, 512>& lut = [pitch] { | ||
| 202 | if (pitch > 0xaaaa) { | ||
| 203 | return curve_lut0; | ||
| 204 | } | ||
| 205 | if (pitch <= 0x8000) { | ||
| 206 | return curve_lut1; | ||
| 207 | } | ||
| 208 | return curve_lut2; | ||
| 209 | }(); | ||
| 210 | |||
| 211 | std::size_t index{}; | ||
| 212 | |||
| 213 | for (std::size_t i = 0; i < sample_count; i++) { | ||
| 214 | const std::size_t lut_index{(static_cast<std::size_t>(fraction) >> 8) * 4}; | ||
| 215 | const auto l0 = lut[lut_index + 0]; | ||
| 216 | const auto l1 = lut[lut_index + 1]; | ||
| 217 | const auto l2 = lut[lut_index + 2]; | ||
| 218 | const auto l3 = lut[lut_index + 3]; | ||
| 219 | |||
| 220 | const auto s0 = static_cast<s32>(input[index + 0]); | ||
| 221 | const auto s1 = static_cast<s32>(input[index + 1]); | ||
| 222 | const auto s2 = static_cast<s32>(input[index + 2]); | ||
| 223 | const auto s3 = static_cast<s32>(input[index + 3]); | ||
| 224 | |||
| 225 | output[i] = (l0 * s0 + l1 * s1 + l2 * s2 + l3 * s3) >> 15; | ||
| 226 | fraction += pitch; | ||
| 227 | index += (fraction >> 15); | ||
| 228 | fraction &= 0x7fff; | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | } // namespace AudioCore | ||
diff --git a/src/audio_core/algorithm/interpolate.h b/src/audio_core/algorithm/interpolate.h deleted file mode 100644 index 5e59f4d70..000000000 --- a/src/audio_core/algorithm/interpolate.h +++ /dev/null | |||
| @@ -1,43 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <vector> | ||
| 8 | |||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore { | ||
| 12 | |||
| 13 | struct InterpolationState { | ||
| 14 | static constexpr std::size_t taps{4}; | ||
| 15 | static constexpr std::size_t history_size{taps * 2 - 1}; | ||
| 16 | std::array<std::array<s16, 2>, history_size> history{}; | ||
| 17 | double position{}; | ||
| 18 | s32 fraction{}; | ||
| 19 | }; | ||
| 20 | |||
| 21 | /// Interpolates input signal to produce output signal. | ||
| 22 | /// @param input The signal to interpolate. | ||
| 23 | /// @param ratio Interpolation ratio. | ||
| 24 | /// ratio > 1.0 results in fewer output samples. | ||
| 25 | /// ratio < 1.0 results in more output samples. | ||
| 26 | /// @returns Output signal. | ||
| 27 | std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, double ratio); | ||
| 28 | |||
| 29 | /// Interpolates input signal to produce output signal. | ||
| 30 | /// @param input The signal to interpolate. | ||
| 31 | /// @param input_rate The sample rate of input. | ||
| 32 | /// @param output_rate The desired sample rate of the output. | ||
| 33 | /// @returns Output signal. | ||
| 34 | inline std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, | ||
| 35 | u32 input_rate, u32 output_rate) { | ||
| 36 | const double ratio = static_cast<double>(input_rate) / static_cast<double>(output_rate); | ||
| 37 | return Interpolate(state, std::move(input), ratio); | ||
| 38 | } | ||
| 39 | |||
| 40 | /// Nintendo Switchs DSP resampling algorithm. Based on a single channel | ||
| 41 | void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count); | ||
| 42 | |||
| 43 | } // namespace AudioCore | ||
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp new file mode 100644 index 000000000..78e615a10 --- /dev/null +++ b/src/audio_core/audio_core.cpp | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/audio_core.h" | ||
| 5 | #include "audio_core/sink/sink_details.h" | ||
| 6 | #include "common/settings.h" | ||
| 7 | #include "core/core.h" | ||
| 8 | |||
| 9 | namespace AudioCore { | ||
| 10 | |||
| 11 | AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>(system)} { | ||
| 12 | CreateSinks(); | ||
| 13 | // Must be created after the sinks | ||
| 14 | adsp = std::make_unique<AudioRenderer::ADSP::ADSP>(system, *output_sink); | ||
| 15 | } | ||
| 16 | |||
| 17 | AudioCore ::~AudioCore() { | ||
| 18 | Shutdown(); | ||
| 19 | } | ||
| 20 | |||
| 21 | void AudioCore::CreateSinks() { | ||
| 22 | const auto& sink_id{Settings::values.sink_id}; | ||
| 23 | const auto& audio_output_device_id{Settings::values.audio_output_device_id}; | ||
| 24 | const auto& audio_input_device_id{Settings::values.audio_input_device_id}; | ||
| 25 | |||
| 26 | output_sink = Sink::CreateSinkFromID(sink_id.GetValue(), audio_output_device_id.GetValue()); | ||
| 27 | input_sink = Sink::CreateSinkFromID(sink_id.GetValue(), audio_input_device_id.GetValue()); | ||
| 28 | } | ||
| 29 | |||
| 30 | void AudioCore::Shutdown() { | ||
| 31 | audio_manager->Shutdown(); | ||
| 32 | } | ||
| 33 | |||
| 34 | AudioManager& AudioCore::GetAudioManager() { | ||
| 35 | return *audio_manager; | ||
| 36 | } | ||
| 37 | |||
| 38 | Sink::Sink& AudioCore::GetOutputSink() { | ||
| 39 | return *output_sink; | ||
| 40 | } | ||
| 41 | |||
| 42 | Sink::Sink& AudioCore::GetInputSink() { | ||
| 43 | return *input_sink; | ||
| 44 | } | ||
| 45 | |||
| 46 | AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() { | ||
| 47 | return *adsp; | ||
| 48 | } | ||
| 49 | |||
| 50 | void AudioCore::PauseSinks(const bool pausing) const { | ||
| 51 | if (pausing) { | ||
| 52 | output_sink->PauseStreams(); | ||
| 53 | input_sink->PauseStreams(); | ||
| 54 | } else { | ||
| 55 | output_sink->UnpauseStreams(); | ||
| 56 | input_sink->UnpauseStreams(); | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | u32 AudioCore::GetStreamQueue() const { | ||
| 61 | return estimated_queue.load(); | ||
| 62 | } | ||
| 63 | |||
| 64 | void AudioCore::SetStreamQueue(u32 size) { | ||
| 65 | estimated_queue.store(size); | ||
| 66 | } | ||
| 67 | |||
| 68 | } // namespace AudioCore | ||
diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h new file mode 100644 index 000000000..0f7d61ee4 --- /dev/null +++ b/src/audio_core/audio_core.h | |||
| @@ -0,0 +1,100 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <memory> | ||
| 7 | |||
| 8 | #include "audio_core/audio_manager.h" | ||
| 9 | #include "audio_core/renderer/adsp/adsp.h" | ||
| 10 | #include "audio_core/sink/sink.h" | ||
| 11 | |||
| 12 | namespace Core { | ||
| 13 | class System; | ||
| 14 | } | ||
| 15 | |||
| 16 | namespace AudioCore { | ||
| 17 | |||
| 18 | class AudioManager; | ||
| 19 | /** | ||
| 20 | * Main audio class, sotred inside the core, and holding the audio manager, all sinks, and the ADSP. | ||
| 21 | */ | ||
| 22 | class AudioCore { | ||
| 23 | public: | ||
| 24 | explicit AudioCore(Core::System& system); | ||
| 25 | ~AudioCore(); | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Shutdown the audio core. | ||
| 29 | */ | ||
| 30 | void Shutdown(); | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Get a reference to the audio manager. | ||
| 34 | * | ||
| 35 | * @return Ref to the audio manager. | ||
| 36 | */ | ||
| 37 | AudioManager& GetAudioManager(); | ||
| 38 | |||
| 39 | /** | ||
| 40 | * Get the audio output sink currently in use. | ||
| 41 | * | ||
| 42 | * @return Ref to the sink. | ||
| 43 | */ | ||
| 44 | Sink::Sink& GetOutputSink(); | ||
| 45 | |||
| 46 | /** | ||
| 47 | * Get the audio input sink currently in use. | ||
| 48 | * | ||
| 49 | * @return Ref to the sink. | ||
| 50 | */ | ||
| 51 | Sink::Sink& GetInputSink(); | ||
| 52 | |||
| 53 | /** | ||
| 54 | * Get the ADSP. | ||
| 55 | * | ||
| 56 | * @return Ref to the ADSP. | ||
| 57 | */ | ||
| 58 | AudioRenderer::ADSP::ADSP& GetADSP(); | ||
| 59 | |||
| 60 | /** | ||
| 61 | * Pause the sink. Called from the core. | ||
| 62 | * | ||
| 63 | * @param pausing - Is this pause due to an actual pause, or shutdown? | ||
| 64 | * Unfortunately, shutdown also pauses streams, which can cause issues. | ||
| 65 | */ | ||
| 66 | void PauseSinks(bool pausing) const; | ||
| 67 | |||
| 68 | /** | ||
| 69 | * Get the size of the current stream queue. | ||
| 70 | * | ||
| 71 | * @return Current stream queue size. | ||
| 72 | */ | ||
| 73 | u32 GetStreamQueue() const; | ||
| 74 | |||
| 75 | /** | ||
| 76 | * Get the size of the current stream queue. | ||
| 77 | * | ||
| 78 | * @param size - New stream size. | ||
| 79 | */ | ||
| 80 | void SetStreamQueue(u32 size); | ||
| 81 | |||
| 82 | private: | ||
| 83 | /** | ||
| 84 | * Create the sinks on startup. | ||
| 85 | */ | ||
| 86 | void CreateSinks(); | ||
| 87 | |||
| 88 | /// Main audio manager for audio in/out | ||
| 89 | std::unique_ptr<AudioManager> audio_manager; | ||
| 90 | /// Sink used for audio renderer and audio out | ||
| 91 | std::unique_ptr<Sink::Sink> output_sink; | ||
| 92 | /// Sink used for audio input | ||
| 93 | std::unique_ptr<Sink::Sink> input_sink; | ||
| 94 | /// The ADSP in the sysmodule | ||
| 95 | std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp; | ||
| 96 | /// Current size of the stream queue | ||
| 97 | std::atomic<u32> estimated_queue{0}; | ||
| 98 | }; | ||
| 99 | |||
| 100 | } // namespace AudioCore | ||
diff --git a/src/audio_core/audio_event.cpp b/src/audio_core/audio_event.cpp new file mode 100644 index 000000000..424049c7a --- /dev/null +++ b/src/audio_core/audio_event.cpp | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/audio_event.h" | ||
| 5 | #include "common/assert.h" | ||
| 6 | |||
| 7 | namespace AudioCore { | ||
| 8 | |||
| 9 | size_t Event::GetManagerIndex(const Type type) const { | ||
| 10 | switch (type) { | ||
| 11 | case Type::AudioInManager: | ||
| 12 | return 0; | ||
| 13 | case Type::AudioOutManager: | ||
| 14 | return 1; | ||
| 15 | case Type::FinalOutputRecorderManager: | ||
| 16 | return 2; | ||
| 17 | case Type::Max: | ||
| 18 | return 3; | ||
| 19 | default: | ||
| 20 | UNREACHABLE(); | ||
| 21 | } | ||
| 22 | return 3; | ||
| 23 | } | ||
| 24 | |||
| 25 | void Event::SetAudioEvent(const Type type, const bool signalled) { | ||
| 26 | events_signalled[GetManagerIndex(type)] = signalled; | ||
| 27 | if (signalled) { | ||
| 28 | manager_event.notify_one(); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | bool Event::CheckAudioEventSet(const Type type) const { | ||
| 33 | return events_signalled[GetManagerIndex(type)]; | ||
| 34 | } | ||
| 35 | |||
| 36 | std::mutex& Event::GetAudioEventLock() { | ||
| 37 | return event_lock; | ||
| 38 | } | ||
| 39 | |||
| 40 | std::condition_variable_any& Event::GetAudioEvent() { | ||
| 41 | return manager_event; | ||
| 42 | } | ||
| 43 | |||
| 44 | bool Event::Wait(std::unique_lock<std::mutex>& l, const std::chrono::seconds timeout) { | ||
| 45 | bool timed_out{false}; | ||
| 46 | if (!manager_event.wait_for(l, timeout, [&]() { | ||
| 47 | return std::ranges::any_of(events_signalled, [](bool x) { return x; }); | ||
| 48 | })) { | ||
| 49 | timed_out = true; | ||
| 50 | } | ||
| 51 | return timed_out; | ||
| 52 | } | ||
| 53 | |||
| 54 | void Event::ClearEvents() { | ||
| 55 | events_signalled[0] = false; | ||
| 56 | events_signalled[1] = false; | ||
| 57 | events_signalled[2] = false; | ||
| 58 | events_signalled[3] = false; | ||
| 59 | } | ||
| 60 | |||
| 61 | } // namespace AudioCore | ||
diff --git a/src/audio_core/audio_event.h b/src/audio_core/audio_event.h new file mode 100644 index 000000000..82dd32dca --- /dev/null +++ b/src/audio_core/audio_event.h | |||
| @@ -0,0 +1,92 @@ | |||
| 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 <atomic> | ||
| 8 | #include <chrono> | ||
| 9 | #include <condition_variable> | ||
| 10 | #include <mutex> | ||
| 11 | |||
| 12 | namespace AudioCore { | ||
| 13 | /** | ||
| 14 | * Responsible for the input/output events, set by the stream backend when buffers are consumed, and | ||
| 15 | * waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer | ||
| 16 | * recycling going. | ||
| 17 | * In a real Switch this is not a seprate class, and exists entirely within the audio manager. | ||
| 18 | * On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can | ||
| 19 | * wait on multiple events at once, and the events are not needed by the backend. | ||
| 20 | */ | ||
| 21 | class Event { | ||
| 22 | public: | ||
| 23 | enum class Type { | ||
| 24 | AudioInManager, | ||
| 25 | AudioOutManager, | ||
| 26 | FinalOutputRecorderManager, | ||
| 27 | Max, | ||
| 28 | }; | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Convert a manager type to an index. | ||
| 32 | * | ||
| 33 | * @param type - The manager type to convert | ||
| 34 | * @return The index of the type. | ||
| 35 | */ | ||
| 36 | size_t GetManagerIndex(Type type) const; | ||
| 37 | |||
| 38 | /** | ||
| 39 | * Set an audio event to true or false. | ||
| 40 | * | ||
| 41 | * @param type - The manager type to signal. | ||
| 42 | * @param signalled - Its signal state. | ||
| 43 | */ | ||
| 44 | void SetAudioEvent(Type type, bool signalled); | ||
| 45 | |||
| 46 | /** | ||
| 47 | * Check if the given manager type is signalled. | ||
| 48 | * | ||
| 49 | * @param type - The manager type to check. | ||
| 50 | * @return True if the event is signalled, otherwise false. | ||
| 51 | */ | ||
| 52 | bool CheckAudioEventSet(Type type) const; | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Get the lock for audio events. | ||
| 56 | * | ||
| 57 | * @return Reference to the lock. | ||
| 58 | */ | ||
| 59 | std::mutex& GetAudioEventLock(); | ||
| 60 | |||
| 61 | /** | ||
| 62 | * Get the manager event, this signals the audio manager to release buffers and signal the game | ||
| 63 | * for more. | ||
| 64 | * | ||
| 65 | * @return Reference to the condition variable. | ||
| 66 | */ | ||
| 67 | std::condition_variable_any& GetAudioEvent(); | ||
| 68 | |||
| 69 | /** | ||
| 70 | * Wait on the manager_event. | ||
| 71 | * | ||
| 72 | * @param l - Lock held by the wait. | ||
| 73 | * @param timeout - Timeout for the wait. This is 2 seconds by default. | ||
| 74 | * @return True if the wait timed out, otherwise false if signalled. | ||
| 75 | */ | ||
| 76 | bool Wait(std::unique_lock<std::mutex>& l, std::chrono::seconds timeout); | ||
| 77 | |||
| 78 | /** | ||
| 79 | * Reset all manager events. | ||
| 80 | */ | ||
| 81 | void ClearEvents(); | ||
| 82 | |||
| 83 | private: | ||
| 84 | /// Lock, used bythe audio manager | ||
| 85 | std::mutex event_lock; | ||
| 86 | /// Array of events, one per system type (see Type), last event is used to terminate | ||
| 87 | std::array<std::atomic<bool>, 4> events_signalled; | ||
| 88 | /// Event to signal the audio manager | ||
| 89 | std::condition_variable_any manager_event; | ||
| 90 | }; | ||
| 91 | |||
| 92 | } // namespace AudioCore | ||
diff --git a/src/audio_core/audio_in_manager.cpp b/src/audio_core/audio_in_manager.cpp new file mode 100644 index 000000000..4aadb7fd6 --- /dev/null +++ b/src/audio_core/audio_in_manager.cpp | |||
| @@ -0,0 +1,91 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/audio_core.h" | ||
| 5 | #include "audio_core/audio_in_manager.h" | ||
| 6 | #include "audio_core/audio_manager.h" | ||
| 7 | #include "audio_core/in/audio_in.h" | ||
| 8 | #include "audio_core/sink/sink_details.h" | ||
| 9 | #include "common/settings.h" | ||
| 10 | #include "core/core.h" | ||
| 11 | #include "core/hle/service/audio/errors.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioIn { | ||
| 14 | |||
| 15 | Manager::Manager(Core::System& system_) : system{system_} { | ||
| 16 | std::iota(session_ids.begin(), session_ids.end(), 0); | ||
| 17 | num_free_sessions = MaxInSessions; | ||
| 18 | } | ||
| 19 | |||
| 20 | Result Manager::AcquireSessionId(size_t& session_id) { | ||
| 21 | if (num_free_sessions == 0) { | ||
| 22 | LOG_ERROR(Service_Audio, "All 4 AudioIn sessions are in use, cannot create any more"); | ||
| 23 | return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED; | ||
| 24 | } | ||
| 25 | session_id = session_ids[next_session_id]; | ||
| 26 | next_session_id = (next_session_id + 1) % MaxInSessions; | ||
| 27 | num_free_sessions--; | ||
| 28 | return ResultSuccess; | ||
| 29 | } | ||
| 30 | |||
| 31 | void Manager::ReleaseSessionId(const size_t session_id) { | ||
| 32 | std::scoped_lock l{mutex}; | ||
| 33 | LOG_DEBUG(Service_Audio, "Freeing AudioIn session {}", session_id); | ||
| 34 | session_ids[free_session_id] = session_id; | ||
| 35 | num_free_sessions++; | ||
| 36 | free_session_id = (free_session_id + 1) % MaxInSessions; | ||
| 37 | sessions[session_id].reset(); | ||
| 38 | applet_resource_user_ids[session_id] = 0; | ||
| 39 | } | ||
| 40 | |||
| 41 | Result Manager::LinkToManager() { | ||
| 42 | std::scoped_lock l{mutex}; | ||
| 43 | if (!linked_to_manager) { | ||
| 44 | AudioManager& manager{system.AudioCore().GetAudioManager()}; | ||
| 45 | manager.SetInManager(std::bind(&Manager::BufferReleaseAndRegister, this)); | ||
| 46 | linked_to_manager = true; | ||
| 47 | } | ||
| 48 | |||
| 49 | return ResultSuccess; | ||
| 50 | } | ||
| 51 | |||
| 52 | void Manager::Start() { | ||
| 53 | if (sessions_started) { | ||
| 54 | return; | ||
| 55 | } | ||
| 56 | |||
| 57 | std::scoped_lock l{mutex}; | ||
| 58 | for (auto& session : sessions) { | ||
| 59 | if (session) { | ||
| 60 | session->StartSession(); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | sessions_started = true; | ||
| 65 | } | ||
| 66 | |||
| 67 | void Manager::BufferReleaseAndRegister() { | ||
| 68 | std::scoped_lock l{mutex}; | ||
| 69 | for (auto& session : sessions) { | ||
| 70 | if (session != nullptr) { | ||
| 71 | session->ReleaseAndRegisterBuffers(); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names, | ||
| 77 | [[maybe_unused]] const u32 max_count, | ||
| 78 | [[maybe_unused]] const bool filter) { | ||
| 79 | std::scoped_lock l{mutex}; | ||
| 80 | |||
| 81 | LinkToManager(); | ||
| 82 | |||
| 83 | auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)}; | ||
| 84 | if (input_devices.size() > 1) { | ||
| 85 | names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac")); | ||
| 86 | return 1; | ||
| 87 | } | ||
| 88 | return 0; | ||
| 89 | } | ||
| 90 | |||
| 91 | } // namespace AudioCore::AudioIn | ||
diff --git a/src/audio_core/audio_in_manager.h b/src/audio_core/audio_in_manager.h new file mode 100644 index 000000000..75b73a0b6 --- /dev/null +++ b/src/audio_core/audio_in_manager.h | |||
| @@ -0,0 +1,92 @@ | |||
| 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 <mutex> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include "audio_core/renderer/audio_device.h" | ||
| 11 | |||
| 12 | namespace Core { | ||
| 13 | class System; | ||
| 14 | } | ||
| 15 | |||
| 16 | namespace AudioCore::AudioIn { | ||
| 17 | class In; | ||
| 18 | |||
| 19 | constexpr size_t MaxInSessions = 4; | ||
| 20 | /** | ||
| 21 | * Manages all audio in sessions. | ||
| 22 | */ | ||
| 23 | class Manager { | ||
| 24 | public: | ||
| 25 | explicit Manager(Core::System& system); | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Acquire a free session id for opening a new audio in. | ||
| 29 | * | ||
| 30 | * @param session_id - Output session_id. | ||
| 31 | * @return Result code. | ||
| 32 | */ | ||
| 33 | Result AcquireSessionId(size_t& session_id); | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Release a session id on close. | ||
| 37 | * | ||
| 38 | * @param session_id - Session id to free. | ||
| 39 | */ | ||
| 40 | void ReleaseSessionId(size_t session_id); | ||
| 41 | |||
| 42 | /** | ||
| 43 | * Link the audio in manager to the main audio manager. | ||
| 44 | * | ||
| 45 | * @return Result code. | ||
| 46 | */ | ||
| 47 | Result LinkToManager(); | ||
| 48 | |||
| 49 | /** | ||
| 50 | * Start the audio in manager. | ||
| 51 | */ | ||
| 52 | void Start(); | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Callback function, called by the audio manager when the audio in event is signalled. | ||
| 56 | */ | ||
| 57 | void BufferReleaseAndRegister(); | ||
| 58 | |||
| 59 | /** | ||
| 60 | * Get a list of audio in device names. | ||
| 61 | * | ||
| 62 | * @oaram names - Output container to write names to. | ||
| 63 | * @param max_count - Maximum numebr of deivce names to write. Unused | ||
| 64 | * @param filter - Should the list be filtered? Unused. | ||
| 65 | * @return Number of names written. | ||
| 66 | */ | ||
| 67 | u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names, | ||
| 68 | u32 max_count, bool filter); | ||
| 69 | |||
| 70 | /// Core system | ||
| 71 | Core::System& system; | ||
| 72 | /// Array of session ids | ||
| 73 | std::array<size_t, MaxInSessions> session_ids{}; | ||
| 74 | /// Array of resource user ids | ||
| 75 | std::array<size_t, MaxInSessions> applet_resource_user_ids{}; | ||
| 76 | /// Pointer to each open session | ||
| 77 | std::array<std::shared_ptr<In>, MaxInSessions> sessions{}; | ||
| 78 | /// The number of free sessions | ||
| 79 | size_t num_free_sessions{}; | ||
| 80 | /// The next session id to be taken | ||
| 81 | size_t next_session_id{}; | ||
| 82 | /// The next session id to be freed | ||
| 83 | size_t free_session_id{}; | ||
| 84 | /// Whether this is linked to the audio manager | ||
| 85 | bool linked_to_manager{}; | ||
| 86 | /// Whether the sessions have been started | ||
| 87 | bool sessions_started{}; | ||
| 88 | /// Protect state due to audio manager callback | ||
| 89 | std::recursive_mutex mutex{}; | ||
| 90 | }; | ||
| 91 | |||
| 92 | } // namespace AudioCore::AudioIn | ||
diff --git a/src/audio_core/audio_manager.cpp b/src/audio_core/audio_manager.cpp new file mode 100644 index 000000000..2f1bba9c3 --- /dev/null +++ b/src/audio_core/audio_manager.cpp | |||
| @@ -0,0 +1,80 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/audio_in_manager.h" | ||
| 5 | #include "audio_core/audio_manager.h" | ||
| 6 | #include "audio_core/audio_out_manager.h" | ||
| 7 | #include "core/core.h" | ||
| 8 | |||
| 9 | namespace AudioCore { | ||
| 10 | |||
| 11 | AudioManager::AudioManager(Core::System& system_) : system{system_} { | ||
| 12 | thread = std::jthread([this]() { ThreadFunc(); }); | ||
| 13 | } | ||
| 14 | |||
| 15 | void AudioManager::Shutdown() { | ||
| 16 | running = false; | ||
| 17 | events.SetAudioEvent(Event::Type::Max, true); | ||
| 18 | thread.join(); | ||
| 19 | } | ||
| 20 | |||
| 21 | Result AudioManager::SetOutManager(BufferEventFunc buffer_func) { | ||
| 22 | if (!running) { | ||
| 23 | return Service::Audio::ERR_OPERATION_FAILED; | ||
| 24 | } | ||
| 25 | |||
| 26 | std::scoped_lock l{lock}; | ||
| 27 | |||
| 28 | const auto index{events.GetManagerIndex(Event::Type::AudioOutManager)}; | ||
| 29 | if (buffer_events[index] == nullptr) { | ||
| 30 | buffer_events[index] = buffer_func; | ||
| 31 | needs_update = true; | ||
| 32 | events.SetAudioEvent(Event::Type::AudioOutManager, true); | ||
| 33 | } | ||
| 34 | return ResultSuccess; | ||
| 35 | } | ||
| 36 | |||
| 37 | Result AudioManager::SetInManager(BufferEventFunc buffer_func) { | ||
| 38 | if (!running) { | ||
| 39 | return Service::Audio::ERR_OPERATION_FAILED; | ||
| 40 | } | ||
| 41 | |||
| 42 | std::scoped_lock l{lock}; | ||
| 43 | |||
| 44 | const auto index{events.GetManagerIndex(Event::Type::AudioInManager)}; | ||
| 45 | if (buffer_events[index] == nullptr) { | ||
| 46 | buffer_events[index] = buffer_func; | ||
| 47 | needs_update = true; | ||
| 48 | events.SetAudioEvent(Event::Type::AudioInManager, true); | ||
| 49 | } | ||
| 50 | return ResultSuccess; | ||
| 51 | } | ||
| 52 | |||
| 53 | void AudioManager::SetEvent(const Event::Type type, const bool signalled) { | ||
| 54 | events.SetAudioEvent(type, signalled); | ||
| 55 | } | ||
| 56 | |||
| 57 | void AudioManager::ThreadFunc() { | ||
| 58 | std::unique_lock l{events.GetAudioEventLock()}; | ||
| 59 | events.ClearEvents(); | ||
| 60 | running = true; | ||
| 61 | |||
| 62 | while (running) { | ||
| 63 | auto timed_out{events.Wait(l, std::chrono::seconds(2))}; | ||
| 64 | |||
| 65 | if (events.CheckAudioEventSet(Event::Type::Max)) { | ||
| 66 | break; | ||
| 67 | } | ||
| 68 | |||
| 69 | for (size_t i = 0; i < buffer_events.size(); i++) { | ||
| 70 | if (events.CheckAudioEventSet(Event::Type(i)) || timed_out) { | ||
| 71 | if (buffer_events[i]) { | ||
| 72 | buffer_events[i](); | ||
| 73 | } | ||
| 74 | } | ||
| 75 | events.SetAudioEvent(Event::Type(i), false); | ||
| 76 | } | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | } // namespace AudioCore | ||
diff --git a/src/audio_core/audio_manager.h b/src/audio_core/audio_manager.h new file mode 100644 index 000000000..70316e9cb --- /dev/null +++ b/src/audio_core/audio_manager.h | |||
| @@ -0,0 +1,101 @@ | |||
| 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 <atomic> | ||
| 8 | #include <functional> | ||
| 9 | #include <mutex> | ||
| 10 | #include <thread> | ||
| 11 | |||
| 12 | #include "audio_core/audio_event.h" | ||
| 13 | #include "core/hle/service/audio/errors.h" | ||
| 14 | |||
| 15 | namespace Core { | ||
| 16 | class System; | ||
| 17 | } | ||
| 18 | |||
| 19 | namespace AudioCore { | ||
| 20 | |||
| 21 | namespace AudioOut { | ||
| 22 | class Manager; | ||
| 23 | } | ||
| 24 | |||
| 25 | namespace AudioIn { | ||
| 26 | class Manager; | ||
| 27 | } | ||
| 28 | |||
| 29 | /** | ||
| 30 | * The AudioManager's main purpose is to wait for buffer events for the audio in and out managers, | ||
| 31 | * and call an associated callback to release buffers. | ||
| 32 | * | ||
| 33 | * Execution pattern is: | ||
| 34 | * Buffers appended -> | ||
| 35 | * Buffers queued and played by the backend stream -> | ||
| 36 | * When consumed, set the corresponding manager event and signal the audio manager -> | ||
| 37 | * Consumed buffers are released, game is signalled -> | ||
| 38 | * Game appends more buffers. | ||
| 39 | * | ||
| 40 | * This is only used by audio in and audio out. | ||
| 41 | */ | ||
| 42 | class AudioManager { | ||
| 43 | using BufferEventFunc = std::function<void()>; | ||
| 44 | |||
| 45 | public: | ||
| 46 | explicit AudioManager(Core::System& system); | ||
| 47 | |||
| 48 | /** | ||
| 49 | * Shutdown the audio manager. | ||
| 50 | */ | ||
| 51 | void Shutdown(); | ||
| 52 | |||
| 53 | /** | ||
| 54 | * Register the out manager, keeping a function to be called when the out event is signalled. | ||
| 55 | * | ||
| 56 | * @param buffer_func - Function to be called on signal. | ||
| 57 | * @return Result code. | ||
| 58 | */ | ||
| 59 | Result SetOutManager(BufferEventFunc buffer_func); | ||
| 60 | |||
| 61 | /** | ||
| 62 | * Register the in manager, keeping a function to be called when the in event is signalled. | ||
| 63 | * | ||
| 64 | * @param buffer_func - Function to be called on signal. | ||
| 65 | * @return Result code. | ||
| 66 | */ | ||
| 67 | Result SetInManager(BufferEventFunc buffer_func); | ||
| 68 | |||
| 69 | /** | ||
| 70 | * Set an event to signalled, and signal the thread. | ||
| 71 | * | ||
| 72 | * @param type - Manager type to set. | ||
| 73 | * @param signalled - Set the event to true or false? | ||
| 74 | */ | ||
| 75 | void SetEvent(Event::Type type, bool signalled); | ||
| 76 | |||
| 77 | private: | ||
| 78 | /** | ||
| 79 | * Main thread, waiting on a manager signal and calling the registered fucntion. | ||
| 80 | */ | ||
| 81 | void ThreadFunc(); | ||
| 82 | |||
| 83 | /// Core system | ||
| 84 | Core::System& system; | ||
| 85 | /// Have sessions started palying? | ||
| 86 | bool sessions_started{}; | ||
| 87 | /// Is the main thread running? | ||
| 88 | std::atomic<bool> running{}; | ||
| 89 | /// Unused | ||
| 90 | bool needs_update{}; | ||
| 91 | /// Events to be set and signalled | ||
| 92 | Event events{}; | ||
| 93 | /// Callbacks for each manager | ||
| 94 | std::array<BufferEventFunc, 3> buffer_events{}; | ||
| 95 | /// General lock | ||
| 96 | std::mutex lock{}; | ||
| 97 | /// Main thread for waiting and callbacks | ||
| 98 | std::jthread thread; | ||
| 99 | }; | ||
| 100 | |||
| 101 | } // namespace AudioCore | ||
diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp deleted file mode 100644 index 83ec0221f..000000000 --- a/src/audio_core/audio_out.cpp +++ /dev/null | |||
| @@ -1,62 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/audio_out.h" | ||
| 5 | #include "audio_core/sink.h" | ||
| 6 | #include "audio_core/sink_details.h" | ||
| 7 | #include "common/assert.h" | ||
| 8 | #include "common/logging/log.h" | ||
| 9 | #include "common/settings.h" | ||
| 10 | |||
| 11 | namespace AudioCore { | ||
| 12 | |||
| 13 | /// Returns the stream format from the specified number of channels | ||
| 14 | static Stream::Format ChannelsToStreamFormat(u32 num_channels) { | ||
| 15 | switch (num_channels) { | ||
| 16 | case 1: | ||
| 17 | return Stream::Format::Mono16; | ||
| 18 | case 2: | ||
| 19 | return Stream::Format::Stereo16; | ||
| 20 | case 6: | ||
| 21 | return Stream::Format::Multi51Channel16; | ||
| 22 | } | ||
| 23 | |||
| 24 | UNIMPLEMENTED_MSG("Unimplemented num_channels={}", num_channels); | ||
| 25 | return {}; | ||
| 26 | } | ||
| 27 | |||
| 28 | StreamPtr AudioOut::OpenStream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, | ||
| 29 | u32 num_channels, std::string&& name, | ||
| 30 | Stream::ReleaseCallback&& release_callback) { | ||
| 31 | if (!sink) { | ||
| 32 | sink = CreateSinkFromID(Settings::values.sink_id.GetValue(), | ||
| 33 | Settings::values.audio_device_id.GetValue()); | ||
| 34 | } | ||
| 35 | |||
| 36 | return std::make_shared<Stream>( | ||
| 37 | core_timing, sample_rate, ChannelsToStreamFormat(num_channels), std::move(release_callback), | ||
| 38 | sink->AcquireSinkStream(sample_rate, num_channels, name), std::move(name)); | ||
| 39 | } | ||
| 40 | |||
| 41 | std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, | ||
| 42 | std::size_t max_count) { | ||
| 43 | return stream->GetTagsAndReleaseBuffers(max_count); | ||
| 44 | } | ||
| 45 | |||
| 46 | std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream) { | ||
| 47 | return stream->GetTagsAndReleaseBuffers(); | ||
| 48 | } | ||
| 49 | |||
| 50 | void AudioOut::StartStream(StreamPtr stream) { | ||
| 51 | stream->Play(); | ||
| 52 | } | ||
| 53 | |||
| 54 | void AudioOut::StopStream(StreamPtr stream) { | ||
| 55 | stream->Stop(); | ||
| 56 | } | ||
| 57 | |||
| 58 | bool AudioOut::QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<s16>&& data) { | ||
| 59 | return stream->QueueBuffer(std::make_shared<Buffer>(tag, std::move(data))); | ||
| 60 | } | ||
| 61 | |||
| 62 | } // namespace AudioCore | ||
diff --git a/src/audio_core/audio_out.h b/src/audio_core/audio_out.h deleted file mode 100644 index 6856373f1..000000000 --- a/src/audio_core/audio_out.h +++ /dev/null | |||
| @@ -1,49 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <memory> | ||
| 7 | #include <string> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include "audio_core/buffer.h" | ||
| 11 | #include "audio_core/sink.h" | ||
| 12 | #include "audio_core/stream.h" | ||
| 13 | #include "common/common_types.h" | ||
| 14 | |||
| 15 | namespace Core::Timing { | ||
| 16 | class CoreTiming; | ||
| 17 | } | ||
| 18 | |||
| 19 | namespace AudioCore { | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Represents an audio playback interface, used to open and play audio streams | ||
| 23 | */ | ||
| 24 | class AudioOut { | ||
| 25 | public: | ||
| 26 | /// Opens a new audio stream | ||
| 27 | StreamPtr OpenStream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, u32 num_channels, | ||
| 28 | std::string&& name, Stream::ReleaseCallback&& release_callback); | ||
| 29 | |||
| 30 | /// Returns a vector of recently released buffers specified by tag for the specified stream | ||
| 31 | std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream, std::size_t max_count); | ||
| 32 | |||
| 33 | /// Returns a vector of all recently released buffers specified by tag for the specified stream | ||
| 34 | std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream); | ||
| 35 | |||
| 36 | /// Starts an audio stream for playback | ||
| 37 | void StartStream(StreamPtr stream); | ||
| 38 | |||
| 39 | /// Stops an audio stream that is currently playing | ||
| 40 | void StopStream(StreamPtr stream); | ||
| 41 | |||
| 42 | /// Queues a buffer into the specified audio stream, returns true on success | ||
| 43 | bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<s16>&& data); | ||
| 44 | |||
| 45 | private: | ||
| 46 | SinkPtr sink; | ||
| 47 | }; | ||
| 48 | |||
| 49 | } // namespace AudioCore | ||
diff --git a/src/audio_core/audio_out_manager.cpp b/src/audio_core/audio_out_manager.cpp new file mode 100644 index 000000000..71d67de64 --- /dev/null +++ b/src/audio_core/audio_out_manager.cpp | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/audio_core.h" | ||
| 5 | #include "audio_core/audio_manager.h" | ||
| 6 | #include "audio_core/audio_out_manager.h" | ||
| 7 | #include "audio_core/out/audio_out.h" | ||
| 8 | #include "core/core.h" | ||
| 9 | #include "core/hle/kernel/k_event.h" | ||
| 10 | #include "core/hle/service/audio/errors.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioOut { | ||
| 13 | |||
| 14 | Manager::Manager(Core::System& system_) : system{system_} { | ||
| 15 | std::iota(session_ids.begin(), session_ids.end(), 0); | ||
| 16 | num_free_sessions = MaxOutSessions; | ||
| 17 | } | ||
| 18 | |||
| 19 | Result Manager::AcquireSessionId(size_t& session_id) { | ||
| 20 | if (num_free_sessions == 0) { | ||
| 21 | LOG_ERROR(Service_Audio, "All 12 Audio Out sessions are in use, cannot create any more"); | ||
| 22 | return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED; | ||
| 23 | } | ||
| 24 | session_id = session_ids[next_session_id]; | ||
| 25 | next_session_id = (next_session_id + 1) % MaxOutSessions; | ||
| 26 | num_free_sessions--; | ||
| 27 | return ResultSuccess; | ||
| 28 | } | ||
| 29 | |||
| 30 | void Manager::ReleaseSessionId(const size_t session_id) { | ||
| 31 | std::scoped_lock l{mutex}; | ||
| 32 | LOG_DEBUG(Service_Audio, "Freeing AudioOut session {}", session_id); | ||
| 33 | session_ids[free_session_id] = session_id; | ||
| 34 | num_free_sessions++; | ||
| 35 | free_session_id = (free_session_id + 1) % MaxOutSessions; | ||
| 36 | sessions[session_id].reset(); | ||
| 37 | applet_resource_user_ids[session_id] = 0; | ||
| 38 | } | ||
| 39 | |||
| 40 | Result Manager::LinkToManager() { | ||
| 41 | std::scoped_lock l{mutex}; | ||
| 42 | if (!linked_to_manager) { | ||
| 43 | AudioManager& manager{system.AudioCore().GetAudioManager()}; | ||
| 44 | manager.SetOutManager(std::bind(&Manager::BufferReleaseAndRegister, this)); | ||
| 45 | linked_to_manager = true; | ||
| 46 | } | ||
| 47 | |||
| 48 | return ResultSuccess; | ||
| 49 | } | ||
| 50 | |||
| 51 | void Manager::Start() { | ||
| 52 | if (sessions_started) { | ||
| 53 | return; | ||
| 54 | } | ||
| 55 | |||
| 56 | std::scoped_lock l{mutex}; | ||
| 57 | for (auto& session : sessions) { | ||
| 58 | if (session) { | ||
| 59 | session->StartSession(); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | sessions_started = true; | ||
| 64 | } | ||
| 65 | |||
| 66 | void Manager::BufferReleaseAndRegister() { | ||
| 67 | std::scoped_lock l{mutex}; | ||
| 68 | for (auto& session : sessions) { | ||
| 69 | if (session != nullptr) { | ||
| 70 | session->ReleaseAndRegisterBuffers(); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | u32 Manager::GetAudioOutDeviceNames( | ||
| 76 | std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const { | ||
| 77 | names.push_back({"DeviceOut"}); | ||
| 78 | return 1; | ||
| 79 | } | ||
| 80 | |||
| 81 | } // namespace AudioCore::AudioOut | ||
diff --git a/src/audio_core/audio_out_manager.h b/src/audio_core/audio_out_manager.h new file mode 100644 index 000000000..24981e08f --- /dev/null +++ b/src/audio_core/audio_out_manager.h | |||
| @@ -0,0 +1,89 @@ | |||
| 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 <mutex> | ||
| 8 | |||
| 9 | #include "audio_core/renderer/audio_device.h" | ||
| 10 | |||
| 11 | namespace Core { | ||
| 12 | class System; | ||
| 13 | } | ||
| 14 | |||
| 15 | namespace AudioCore::AudioOut { | ||
| 16 | class Out; | ||
| 17 | |||
| 18 | constexpr size_t MaxOutSessions = 12; | ||
| 19 | /** | ||
| 20 | * Manages all audio out sessions. | ||
| 21 | */ | ||
| 22 | class Manager { | ||
| 23 | public: | ||
| 24 | explicit Manager(Core::System& system); | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Acquire a free session id for opening a new audio out. | ||
| 28 | * | ||
| 29 | * @param session_id - Output session_id. | ||
| 30 | * @return Result code. | ||
| 31 | */ | ||
| 32 | Result AcquireSessionId(size_t& session_id); | ||
| 33 | |||
| 34 | /** | ||
| 35 | * Release a session id on close. | ||
| 36 | * | ||
| 37 | * @param session_id - Session id to free. | ||
| 38 | */ | ||
| 39 | void ReleaseSessionId(size_t session_id); | ||
| 40 | |||
| 41 | /** | ||
| 42 | * Link this manager to the main audio manager. | ||
| 43 | * | ||
| 44 | * @return Result code. | ||
| 45 | */ | ||
| 46 | Result LinkToManager(); | ||
| 47 | |||
| 48 | /** | ||
| 49 | * Start the audio out manager. | ||
| 50 | */ | ||
| 51 | void Start(); | ||
| 52 | |||
| 53 | /** | ||
| 54 | * Callback function, called by the audio manager when the audio out event is signalled. | ||
| 55 | */ | ||
| 56 | void BufferReleaseAndRegister(); | ||
| 57 | |||
| 58 | /** | ||
| 59 | * Get a list of audio out device names. | ||
| 60 | * | ||
| 61 | * @oaram names - Output container to write names to. | ||
| 62 | * @return Number of names written. | ||
| 63 | */ | ||
| 64 | u32 GetAudioOutDeviceNames( | ||
| 65 | std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const; | ||
| 66 | |||
| 67 | /// Core system | ||
| 68 | Core::System& system; | ||
| 69 | /// Array of session ids | ||
| 70 | std::array<size_t, MaxOutSessions> session_ids{}; | ||
| 71 | /// Array of resource user ids | ||
| 72 | std::array<size_t, MaxOutSessions> applet_resource_user_ids{}; | ||
| 73 | /// Pointer to each open session | ||
| 74 | std::array<std::shared_ptr<Out>, MaxOutSessions> sessions{}; | ||
| 75 | /// The number of free sessions | ||
| 76 | size_t num_free_sessions{}; | ||
| 77 | /// The next session id to be taken | ||
| 78 | size_t next_session_id{}; | ||
| 79 | /// The next session id to be freed | ||
| 80 | size_t free_session_id{}; | ||
| 81 | /// Whether this is linked to the audio manager | ||
| 82 | bool linked_to_manager{}; | ||
| 83 | /// Whether the sessions have been started | ||
| 84 | bool sessions_started{}; | ||
| 85 | /// Protect state due to audio manager callback | ||
| 86 | std::recursive_mutex mutex{}; | ||
| 87 | }; | ||
| 88 | |||
| 89 | } // namespace AudioCore::AudioOut | ||
diff --git a/src/audio_core/audio_render_manager.cpp b/src/audio_core/audio_render_manager.cpp new file mode 100644 index 000000000..7a846835b --- /dev/null +++ b/src/audio_core/audio_render_manager.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 "audio_core/audio_render_manager.h" | ||
| 5 | #include "audio_core/common/audio_renderer_parameter.h" | ||
| 6 | #include "audio_core/common/feature_support.h" | ||
| 7 | #include "core/core.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | Manager::Manager(Core::System& system_) | ||
| 12 | : system{system_}, system_manager{std::make_unique<SystemManager>(system)} { | ||
| 13 | std::iota(session_ids.begin(), session_ids.end(), 0); | ||
| 14 | } | ||
| 15 | |||
| 16 | Manager::~Manager() { | ||
| 17 | Stop(); | ||
| 18 | } | ||
| 19 | |||
| 20 | void Manager::Stop() { | ||
| 21 | system_manager->Stop(); | ||
| 22 | } | ||
| 23 | |||
| 24 | SystemManager& Manager::GetSystemManager() { | ||
| 25 | return *system_manager; | ||
| 26 | } | ||
| 27 | |||
| 28 | auto Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) | ||
| 29 | -> Result { | ||
| 30 | if (!CheckValidRevision(params.revision)) { | ||
| 31 | return Service::Audio::ERR_INVALID_REVISION; | ||
| 32 | } | ||
| 33 | |||
| 34 | out_count = System::GetWorkBufferSize(params); | ||
| 35 | |||
| 36 | return ResultSuccess; | ||
| 37 | } | ||
| 38 | |||
| 39 | s32 Manager::GetSessionId() { | ||
| 40 | std::scoped_lock l{session_lock}; | ||
| 41 | auto session_id{session_ids[session_count]}; | ||
| 42 | |||
| 43 | if (session_id == -1) { | ||
| 44 | return -1; | ||
| 45 | } | ||
| 46 | |||
| 47 | session_ids[session_count] = -1; | ||
| 48 | session_count++; | ||
| 49 | return session_id; | ||
| 50 | } | ||
| 51 | |||
| 52 | void Manager::ReleaseSessionId(const s32 session_id) { | ||
| 53 | std::scoped_lock l{session_lock}; | ||
| 54 | session_ids[--session_count] = session_id; | ||
| 55 | } | ||
| 56 | |||
| 57 | u32 Manager::GetSessionCount() { | ||
| 58 | std::scoped_lock l{session_lock}; | ||
| 59 | return session_count; | ||
| 60 | } | ||
| 61 | |||
| 62 | bool Manager::AddSystem(System& system_) { | ||
| 63 | return system_manager->Add(system_); | ||
| 64 | } | ||
| 65 | |||
| 66 | bool Manager::RemoveSystem(System& system_) { | ||
| 67 | return system_manager->Remove(system_); | ||
| 68 | } | ||
| 69 | |||
| 70 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/audio_render_manager.h b/src/audio_core/audio_render_manager.h new file mode 100644 index 000000000..6a508ec56 --- /dev/null +++ b/src/audio_core/audio_render_manager.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 <memory> | ||
| 8 | #include <mutex> | ||
| 9 | |||
| 10 | #include "audio_core/common/common.h" | ||
| 11 | #include "audio_core/renderer/system_manager.h" | ||
| 12 | #include "core/hle/service/audio/errors.h" | ||
| 13 | |||
| 14 | namespace Core { | ||
| 15 | class System; | ||
| 16 | } | ||
| 17 | |||
| 18 | namespace AudioCore { | ||
| 19 | struct AudioRendererParameterInternal; | ||
| 20 | |||
| 21 | namespace AudioRenderer { | ||
| 22 | /** | ||
| 23 | * Wrapper for the audio system manager, handles service calls. | ||
| 24 | */ | ||
| 25 | class Manager { | ||
| 26 | public: | ||
| 27 | explicit Manager(Core::System& system); | ||
| 28 | ~Manager(); | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Stop the manager. | ||
| 32 | */ | ||
| 33 | void Stop(); | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Get the system manager. | ||
| 37 | * | ||
| 38 | * @return The system manager. | ||
| 39 | */ | ||
| 40 | SystemManager& GetSystemManager(); | ||
| 41 | |||
| 42 | /** | ||
| 43 | * Get required size for the audio renderer workbuffer. | ||
| 44 | * | ||
| 45 | * @param params - Input parameters with the numbers of voices/mixes/sinks etc. | ||
| 46 | * @param out_count - Output size of the required workbuffer. | ||
| 47 | * @return Result code. | ||
| 48 | */ | ||
| 49 | Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count); | ||
| 50 | |||
| 51 | /** | ||
| 52 | * Get a new session id. | ||
| 53 | * | ||
| 54 | * @return The new session id. -1 if invalid, otherwise 0-MaxRendererSessions. | ||
| 55 | */ | ||
| 56 | s32 GetSessionId(); | ||
| 57 | |||
| 58 | /** | ||
| 59 | * Get the number of currently active sessions. | ||
| 60 | * | ||
| 61 | * @return The number of active sessions. | ||
| 62 | */ | ||
| 63 | u32 GetSessionCount(); | ||
| 64 | |||
| 65 | /** | ||
| 66 | * Add a renderer system to the manager. | ||
| 67 | * The system will be reguarly called to generate commands for the AudioRenderer. | ||
| 68 | * | ||
| 69 | * @param system - The system to add. | ||
| 70 | * @return True if the system was sucessfully added, otherwise false. | ||
| 71 | */ | ||
| 72 | bool AddSystem(System& system); | ||
| 73 | |||
| 74 | /** | ||
| 75 | * Remove a renderer system from the manager. | ||
| 76 | * | ||
| 77 | * @param system - The system to remove. | ||
| 78 | * @return True if the system was sucessfully removed, otherwise false. | ||
| 79 | */ | ||
| 80 | bool RemoveSystem(System& system); | ||
| 81 | |||
| 82 | /** | ||
| 83 | * Free a session id when the system wants to shut down. | ||
| 84 | * | ||
| 85 | * @param session_id - The session id to free. | ||
| 86 | */ | ||
| 87 | void ReleaseSessionId(s32 session_id); | ||
| 88 | |||
| 89 | private: | ||
| 90 | /// Core system | ||
| 91 | Core::System& system; | ||
| 92 | /// Session ids, -1 when in use | ||
| 93 | std::array<s32, MaxRendererSessions> session_ids{}; | ||
| 94 | /// Number of active renderers | ||
| 95 | u32 session_count{}; | ||
| 96 | /// Lock for interacting with the sessions | ||
| 97 | std::mutex session_lock{}; | ||
| 98 | /// Regularly generates commands from the registered systems for the AudioRenderer | ||
| 99 | std::unique_ptr<SystemManager> system_manager{}; | ||
| 100 | }; | ||
| 101 | |||
| 102 | } // namespace AudioRenderer | ||
| 103 | } // namespace AudioCore | ||
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp deleted file mode 100644 index 9191ca093..000000000 --- a/src/audio_core/audio_renderer.cpp +++ /dev/null | |||
| @@ -1,343 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <limits> | ||
| 5 | #include <optional> | ||
| 6 | #include <vector> | ||
| 7 | |||
| 8 | #include "audio_core/audio_out.h" | ||
| 9 | #include "audio_core/audio_renderer.h" | ||
| 10 | #include "audio_core/common.h" | ||
| 11 | #include "audio_core/info_updater.h" | ||
| 12 | #include "audio_core/voice_context.h" | ||
| 13 | #include "common/logging/log.h" | ||
| 14 | #include "common/settings.h" | ||
| 15 | #include "core/core_timing.h" | ||
| 16 | #include "core/memory.h" | ||
| 17 | |||
| 18 | namespace { | ||
| 19 | [[nodiscard]] static constexpr s16 ClampToS16(s32 value) { | ||
| 20 | return static_cast<s16>(std::clamp(value, s32{std::numeric_limits<s16>::min()}, | ||
| 21 | s32{std::numeric_limits<s16>::max()})); | ||
| 22 | } | ||
| 23 | |||
| 24 | [[nodiscard]] static constexpr s16 Mix2To1(s16 l_channel, s16 r_channel) { | ||
| 25 | // Mix 50% from left and 50% from right channel | ||
| 26 | constexpr float l_mix_amount = 50.0f / 100.0f; | ||
| 27 | constexpr float r_mix_amount = 50.0f / 100.0f; | ||
| 28 | return ClampToS16(static_cast<s32>((static_cast<float>(l_channel) * l_mix_amount) + | ||
| 29 | (static_cast<float>(r_channel) * r_mix_amount))); | ||
| 30 | } | ||
| 31 | |||
| 32 | [[maybe_unused, nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2( | ||
| 33 | s16 fl_channel, s16 fr_channel, s16 fc_channel, [[maybe_unused]] s16 lf_channel, s16 bl_channel, | ||
| 34 | s16 br_channel) { | ||
| 35 | // Front channels are mixed 36.94%, Center channels are mixed to be 26.12% & the back channels | ||
| 36 | // are mixed to be 36.94% | ||
| 37 | |||
| 38 | constexpr float front_mix_amount = 36.94f / 100.0f; | ||
| 39 | constexpr float center_mix_amount = 26.12f / 100.0f; | ||
| 40 | constexpr float back_mix_amount = 36.94f / 100.0f; | ||
| 41 | |||
| 42 | // Mix 50% from left and 50% from right channel | ||
| 43 | const auto left = front_mix_amount * static_cast<float>(fl_channel) + | ||
| 44 | center_mix_amount * static_cast<float>(fc_channel) + | ||
| 45 | back_mix_amount * static_cast<float>(bl_channel); | ||
| 46 | |||
| 47 | const auto right = front_mix_amount * static_cast<float>(fr_channel) + | ||
| 48 | center_mix_amount * static_cast<float>(fc_channel) + | ||
| 49 | back_mix_amount * static_cast<float>(br_channel); | ||
| 50 | |||
| 51 | return {ClampToS16(static_cast<s32>(left)), ClampToS16(static_cast<s32>(right))}; | ||
| 52 | } | ||
| 53 | |||
| 54 | [[nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2WithCoefficients( | ||
| 55 | s16 fl_channel, s16 fr_channel, s16 fc_channel, s16 lf_channel, s16 bl_channel, s16 br_channel, | ||
| 56 | const std::array<float_le, 4>& coeff) { | ||
| 57 | const auto left = | ||
| 58 | static_cast<float>(fl_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] + | ||
| 59 | static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(bl_channel) * coeff[3]; | ||
| 60 | |||
| 61 | const auto right = | ||
| 62 | static_cast<float>(fr_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] + | ||
| 63 | static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(br_channel) * coeff[3]; | ||
| 64 | |||
| 65 | return {ClampToS16(static_cast<s32>(left)), ClampToS16(static_cast<s32>(right))}; | ||
| 66 | } | ||
| 67 | |||
| 68 | } // namespace | ||
| 69 | |||
| 70 | namespace AudioCore { | ||
| 71 | constexpr s32 NUM_BUFFERS = 2; | ||
| 72 | |||
| 73 | AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_, | ||
| 74 | AudioCommon::AudioRendererParameter params, | ||
| 75 | Stream::ReleaseCallback&& release_callback, | ||
| 76 | std::size_t instance_number) | ||
| 77 | : worker_params{params}, memory_pool_info(params.effect_count + params.voice_count * 4), | ||
| 78 | voice_context(params.voice_count), effect_context(params.effect_count), mix_context(), | ||
| 79 | sink_context(params.sink_count), splitter_context(), | ||
| 80 | voices(params.voice_count), memory{memory_}, | ||
| 81 | command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context, | ||
| 82 | memory), | ||
| 83 | core_timing{core_timing_} { | ||
| 84 | behavior_info.SetUserRevision(params.revision); | ||
| 85 | splitter_context.Initialize(behavior_info, params.splitter_count, | ||
| 86 | params.num_splitter_send_channels); | ||
| 87 | mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count); | ||
| 88 | audio_out = std::make_unique<AudioCore::AudioOut>(); | ||
| 89 | stream = audio_out->OpenStream( | ||
| 90 | core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS, | ||
| 91 | fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback)); | ||
| 92 | process_event = | ||
| 93 | Core::Timing::CreateEvent(fmt::format("AudioRenderer-Instance{}-Process", instance_number), | ||
| 94 | [this](std::uintptr_t, s64, std::chrono::nanoseconds) { | ||
| 95 | ReleaseAndQueueBuffers(); | ||
| 96 | return std::nullopt; | ||
| 97 | }); | ||
| 98 | for (s32 i = 0; i < NUM_BUFFERS; ++i) { | ||
| 99 | QueueMixedBuffer(i); | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | AudioRenderer::~AudioRenderer() = default; | ||
| 104 | |||
| 105 | Result AudioRenderer::Start() { | ||
| 106 | audio_out->StartStream(stream); | ||
| 107 | ReleaseAndQueueBuffers(); | ||
| 108 | return ResultSuccess; | ||
| 109 | } | ||
| 110 | |||
| 111 | Result AudioRenderer::Stop() { | ||
| 112 | audio_out->StopStream(stream); | ||
| 113 | return ResultSuccess; | ||
| 114 | } | ||
| 115 | |||
| 116 | u32 AudioRenderer::GetSampleRate() const { | ||
| 117 | return worker_params.sample_rate; | ||
| 118 | } | ||
| 119 | |||
| 120 | u32 AudioRenderer::GetSampleCount() const { | ||
| 121 | return worker_params.sample_count; | ||
| 122 | } | ||
| 123 | |||
| 124 | u32 AudioRenderer::GetMixBufferCount() const { | ||
| 125 | return worker_params.mix_buffer_count; | ||
| 126 | } | ||
| 127 | |||
| 128 | Stream::State AudioRenderer::GetStreamState() const { | ||
| 129 | return stream->GetState(); | ||
| 130 | } | ||
| 131 | |||
| 132 | Result AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params, | ||
| 133 | std::vector<u8>& output_params) { | ||
| 134 | std::scoped_lock lock{mutex}; | ||
| 135 | InfoUpdater info_updater{input_params, output_params, behavior_info}; | ||
| 136 | |||
| 137 | if (!info_updater.UpdateBehaviorInfo(behavior_info)) { | ||
| 138 | LOG_ERROR(Audio, "Failed to update behavior info input parameters"); | ||
| 139 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 140 | } | ||
| 141 | |||
| 142 | if (!info_updater.UpdateMemoryPools(memory_pool_info)) { | ||
| 143 | LOG_ERROR(Audio, "Failed to update memory pool parameters"); | ||
| 144 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 145 | } | ||
| 146 | |||
| 147 | if (!info_updater.UpdateVoiceChannelResources(voice_context)) { | ||
| 148 | LOG_ERROR(Audio, "Failed to update voice channel resource parameters"); | ||
| 149 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 150 | } | ||
| 151 | |||
| 152 | if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) { | ||
| 153 | LOG_ERROR(Audio, "Failed to update voice parameters"); | ||
| 154 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 155 | } | ||
| 156 | |||
| 157 | // TODO(ogniK): Deal with stopped audio renderer but updates still taking place | ||
| 158 | if (!info_updater.UpdateEffects(effect_context, true)) { | ||
| 159 | LOG_ERROR(Audio, "Failed to update effect parameters"); | ||
| 160 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 161 | } | ||
| 162 | |||
| 163 | if (behavior_info.IsSplitterSupported()) { | ||
| 164 | if (!info_updater.UpdateSplitterInfo(splitter_context)) { | ||
| 165 | LOG_ERROR(Audio, "Failed to update splitter parameters"); | ||
| 166 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | const auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count, | ||
| 171 | splitter_context, effect_context); | ||
| 172 | |||
| 173 | if (mix_result.IsError()) { | ||
| 174 | LOG_ERROR(Audio, "Failed to update mix parameters"); | ||
| 175 | return mix_result; | ||
| 176 | } | ||
| 177 | |||
| 178 | // TODO(ogniK): Sinks | ||
| 179 | if (!info_updater.UpdateSinks(sink_context)) { | ||
| 180 | LOG_ERROR(Audio, "Failed to update sink parameters"); | ||
| 181 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 182 | } | ||
| 183 | |||
| 184 | // TODO(ogniK): Performance buffer | ||
| 185 | if (!info_updater.UpdatePerformanceBuffer()) { | ||
| 186 | LOG_ERROR(Audio, "Failed to update performance buffer parameters"); | ||
| 187 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 188 | } | ||
| 189 | |||
| 190 | if (!info_updater.UpdateErrorInfo(behavior_info)) { | ||
| 191 | LOG_ERROR(Audio, "Failed to update error info"); | ||
| 192 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 193 | } | ||
| 194 | |||
| 195 | if (behavior_info.IsElapsedFrameCountSupported()) { | ||
| 196 | if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) { | ||
| 197 | LOG_ERROR(Audio, "Failed to update renderer info"); | ||
| 198 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 199 | } | ||
| 200 | } | ||
| 201 | // TODO(ogniK): Statistics | ||
| 202 | |||
| 203 | if (!info_updater.WriteOutputHeader()) { | ||
| 204 | LOG_ERROR(Audio, "Failed to write output header"); | ||
| 205 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 206 | } | ||
| 207 | |||
| 208 | // TODO(ogniK): Check when all sections are implemented | ||
| 209 | |||
| 210 | if (!info_updater.CheckConsumedSize()) { | ||
| 211 | LOG_ERROR(Audio, "Audio buffers were not consumed!"); | ||
| 212 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 213 | } | ||
| 214 | return ResultSuccess; | ||
| 215 | } | ||
| 216 | |||
| 217 | void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { | ||
| 218 | command_generator.PreCommand(); | ||
| 219 | // Clear mix buffers before our next operation | ||
| 220 | command_generator.ClearMixBuffers(); | ||
| 221 | |||
| 222 | // If the splitter is not in use, sort our mixes | ||
| 223 | if (!splitter_context.UsingSplitter()) { | ||
| 224 | mix_context.SortInfo(); | ||
| 225 | } | ||
| 226 | // Sort our voices | ||
| 227 | voice_context.SortInfo(); | ||
| 228 | |||
| 229 | // Handle samples | ||
| 230 | command_generator.GenerateVoiceCommands(); | ||
| 231 | command_generator.GenerateSubMixCommands(); | ||
| 232 | command_generator.GenerateFinalMixCommands(); | ||
| 233 | |||
| 234 | command_generator.PostCommand(); | ||
| 235 | // Base sample size | ||
| 236 | std::size_t BUFFER_SIZE{worker_params.sample_count}; | ||
| 237 | // Samples, making sure to clear | ||
| 238 | std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels(), 0); | ||
| 239 | |||
| 240 | if (sink_context.InUse()) { | ||
| 241 | const auto stream_channel_count = stream->GetNumChannels(); | ||
| 242 | const auto buffer_offsets = sink_context.OutputBuffers(); | ||
| 243 | const auto channel_count = buffer_offsets.size(); | ||
| 244 | const auto& final_mix = mix_context.GetFinalMixInfo(); | ||
| 245 | const auto& in_params = final_mix.GetInParams(); | ||
| 246 | std::vector<std::span<s32>> mix_buffers(channel_count); | ||
| 247 | for (std::size_t i = 0; i < channel_count; i++) { | ||
| 248 | mix_buffers[i] = | ||
| 249 | command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]); | ||
| 250 | } | ||
| 251 | |||
| 252 | for (std::size_t i = 0; i < BUFFER_SIZE; i++) { | ||
| 253 | if (channel_count == 1) { | ||
| 254 | const auto sample = ClampToS16(mix_buffers[0][i]); | ||
| 255 | |||
| 256 | // Place sample in all channels | ||
| 257 | for (u32 channel = 0; channel < stream_channel_count; channel++) { | ||
| 258 | buffer[i * stream_channel_count + channel] = sample; | ||
| 259 | } | ||
| 260 | |||
| 261 | if (stream_channel_count == 6) { | ||
| 262 | // Output stream has a LF channel, mute it! | ||
| 263 | buffer[i * stream_channel_count + 3] = 0; | ||
| 264 | } | ||
| 265 | |||
| 266 | } else if (channel_count == 2) { | ||
| 267 | const auto l_sample = ClampToS16(mix_buffers[0][i]); | ||
| 268 | const auto r_sample = ClampToS16(mix_buffers[1][i]); | ||
| 269 | if (stream_channel_count == 1) { | ||
| 270 | buffer[i * stream_channel_count + 0] = Mix2To1(l_sample, r_sample); | ||
| 271 | } else if (stream_channel_count == 2) { | ||
| 272 | buffer[i * stream_channel_count + 0] = l_sample; | ||
| 273 | buffer[i * stream_channel_count + 1] = r_sample; | ||
| 274 | } else if (stream_channel_count == 6) { | ||
| 275 | buffer[i * stream_channel_count + 0] = l_sample; | ||
| 276 | buffer[i * stream_channel_count + 1] = r_sample; | ||
| 277 | |||
| 278 | // Combine both left and right channels to the center channel | ||
| 279 | buffer[i * stream_channel_count + 2] = Mix2To1(l_sample, r_sample); | ||
| 280 | |||
| 281 | buffer[i * stream_channel_count + 4] = l_sample; | ||
| 282 | buffer[i * stream_channel_count + 5] = r_sample; | ||
| 283 | } | ||
| 284 | |||
| 285 | } else if (channel_count == 6) { | ||
| 286 | const auto fl_sample = ClampToS16(mix_buffers[0][i]); | ||
| 287 | const auto fr_sample = ClampToS16(mix_buffers[1][i]); | ||
| 288 | const auto fc_sample = ClampToS16(mix_buffers[2][i]); | ||
| 289 | const auto lf_sample = ClampToS16(mix_buffers[3][i]); | ||
| 290 | const auto bl_sample = ClampToS16(mix_buffers[4][i]); | ||
| 291 | const auto br_sample = ClampToS16(mix_buffers[5][i]); | ||
| 292 | |||
| 293 | if (stream_channel_count == 1) { | ||
| 294 | // Games seem to ignore the center channel half the time, we use the front left | ||
| 295 | // and right channel for mixing as that's where majority of the audio goes | ||
| 296 | buffer[i * stream_channel_count + 0] = Mix2To1(fl_sample, fr_sample); | ||
| 297 | } else if (stream_channel_count == 2) { | ||
| 298 | // Mix all channels into 2 channels | ||
| 299 | const auto [left, right] = Mix6To2WithCoefficients( | ||
| 300 | fl_sample, fr_sample, fc_sample, lf_sample, bl_sample, br_sample, | ||
| 301 | sink_context.GetDownmixCoefficients()); | ||
| 302 | buffer[i * stream_channel_count + 0] = left; | ||
| 303 | buffer[i * stream_channel_count + 1] = right; | ||
| 304 | } else if (stream_channel_count == 6) { | ||
| 305 | // Pass through | ||
| 306 | buffer[i * stream_channel_count + 0] = fl_sample; | ||
| 307 | buffer[i * stream_channel_count + 1] = fr_sample; | ||
| 308 | buffer[i * stream_channel_count + 2] = fc_sample; | ||
| 309 | buffer[i * stream_channel_count + 3] = lf_sample; | ||
| 310 | buffer[i * stream_channel_count + 4] = bl_sample; | ||
| 311 | buffer[i * stream_channel_count + 5] = br_sample; | ||
| 312 | } | ||
| 313 | } | ||
| 314 | } | ||
| 315 | } | ||
| 316 | |||
| 317 | audio_out->QueueBuffer(stream, tag, std::move(buffer)); | ||
| 318 | elapsed_frame_count++; | ||
| 319 | voice_context.UpdateStateByDspShared(); | ||
| 320 | } | ||
| 321 | |||
| 322 | void AudioRenderer::ReleaseAndQueueBuffers() { | ||
| 323 | if (!stream->IsPlaying()) { | ||
| 324 | return; | ||
| 325 | } | ||
| 326 | |||
| 327 | { | ||
| 328 | std::scoped_lock lock{mutex}; | ||
| 329 | const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)}; | ||
| 330 | for (const auto& tag : released_buffers) { | ||
| 331 | QueueMixedBuffer(tag); | ||
| 332 | } | ||
| 333 | } | ||
| 334 | |||
| 335 | const f32 sample_rate = static_cast<f32>(GetSampleRate()); | ||
| 336 | const f32 sample_count = static_cast<f32>(GetSampleCount()); | ||
| 337 | const f32 consume_rate = sample_rate / (sample_count * (sample_count / 240)); | ||
| 338 | const s32 ms = (1000 / static_cast<s32>(consume_rate)) - 1; | ||
| 339 | const std::chrono::milliseconds next_event_time(std::max(ms / NUM_BUFFERS, 1)); | ||
| 340 | core_timing.ScheduleEvent(next_event_time, process_event, {}); | ||
| 341 | } | ||
| 342 | |||
| 343 | } // namespace AudioCore | ||
diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h deleted file mode 100644 index a67ffd592..000000000 --- a/src/audio_core/audio_renderer.h +++ /dev/null | |||
| @@ -1,78 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <memory> | ||
| 8 | #include <mutex> | ||
| 9 | #include <vector> | ||
| 10 | |||
| 11 | #include "audio_core/behavior_info.h" | ||
| 12 | #include "audio_core/command_generator.h" | ||
| 13 | #include "audio_core/common.h" | ||
| 14 | #include "audio_core/effect_context.h" | ||
| 15 | #include "audio_core/memory_pool.h" | ||
| 16 | #include "audio_core/mix_context.h" | ||
| 17 | #include "audio_core/sink_context.h" | ||
| 18 | #include "audio_core/splitter_context.h" | ||
| 19 | #include "audio_core/stream.h" | ||
| 20 | #include "audio_core/voice_context.h" | ||
| 21 | #include "common/common_funcs.h" | ||
| 22 | #include "common/common_types.h" | ||
| 23 | #include "common/swap.h" | ||
| 24 | #include "core/hle/result.h" | ||
| 25 | |||
| 26 | namespace Core::Timing { | ||
| 27 | class CoreTiming; | ||
| 28 | } | ||
| 29 | |||
| 30 | namespace Core::Memory { | ||
| 31 | class Memory; | ||
| 32 | } | ||
| 33 | |||
| 34 | namespace AudioCore { | ||
| 35 | using DSPStateHolder = std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>; | ||
| 36 | |||
| 37 | class AudioOut; | ||
| 38 | |||
| 39 | class AudioRenderer { | ||
| 40 | public: | ||
| 41 | AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, | ||
| 42 | AudioCommon::AudioRendererParameter params, | ||
| 43 | Stream::ReleaseCallback&& release_callback, std::size_t instance_number); | ||
| 44 | ~AudioRenderer(); | ||
| 45 | |||
| 46 | [[nodiscard]] Result UpdateAudioRenderer(const std::vector<u8>& input_params, | ||
| 47 | std::vector<u8>& output_params); | ||
| 48 | [[nodiscard]] Result Start(); | ||
| 49 | [[nodiscard]] Result Stop(); | ||
| 50 | void QueueMixedBuffer(Buffer::Tag tag); | ||
| 51 | void ReleaseAndQueueBuffers(); | ||
| 52 | [[nodiscard]] u32 GetSampleRate() const; | ||
| 53 | [[nodiscard]] u32 GetSampleCount() const; | ||
| 54 | [[nodiscard]] u32 GetMixBufferCount() const; | ||
| 55 | [[nodiscard]] Stream::State GetStreamState() const; | ||
| 56 | |||
| 57 | private: | ||
| 58 | BehaviorInfo behavior_info{}; | ||
| 59 | |||
| 60 | AudioCommon::AudioRendererParameter worker_params; | ||
| 61 | std::vector<ServerMemoryPoolInfo> memory_pool_info; | ||
| 62 | VoiceContext voice_context; | ||
| 63 | EffectContext effect_context; | ||
| 64 | MixContext mix_context; | ||
| 65 | SinkContext sink_context; | ||
| 66 | SplitterContext splitter_context; | ||
| 67 | std::vector<VoiceState> voices; | ||
| 68 | std::unique_ptr<AudioOut> audio_out; | ||
| 69 | StreamPtr stream; | ||
| 70 | Core::Memory::Memory& memory; | ||
| 71 | CommandGenerator command_generator; | ||
| 72 | std::size_t elapsed_frame_count{}; | ||
| 73 | Core::Timing::CoreTiming& core_timing; | ||
| 74 | std::shared_ptr<Core::Timing::EventType> process_event; | ||
| 75 | std::mutex mutex; | ||
| 76 | }; | ||
| 77 | |||
| 78 | } // namespace AudioCore | ||
diff --git a/src/audio_core/behavior_info.cpp b/src/audio_core/behavior_info.cpp deleted file mode 100644 index ea7e45617..000000000 --- a/src/audio_core/behavior_info.cpp +++ /dev/null | |||
| @@ -1,104 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <cstring> | ||
| 5 | #include "audio_core/behavior_info.h" | ||
| 6 | #include "audio_core/common.h" | ||
| 7 | #include "common/logging/log.h" | ||
| 8 | |||
| 9 | namespace AudioCore { | ||
| 10 | |||
| 11 | BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {} | ||
| 12 | BehaviorInfo::~BehaviorInfo() = default; | ||
| 13 | |||
| 14 | bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) { | ||
| 15 | if (!AudioCommon::CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) { | ||
| 16 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 17 | return false; | ||
| 18 | } | ||
| 19 | |||
| 20 | OutParams params{}; | ||
| 21 | std::memcpy(params.errors.data(), errors.data(), sizeof(ErrorInfo) * errors.size()); | ||
| 22 | params.error_count = static_cast<u32_le>(error_count); | ||
| 23 | std::memcpy(buffer.data() + offset, ¶ms, sizeof(OutParams)); | ||
| 24 | return true; | ||
| 25 | } | ||
| 26 | |||
| 27 | void BehaviorInfo::ClearError() { | ||
| 28 | error_count = 0; | ||
| 29 | } | ||
| 30 | |||
| 31 | void BehaviorInfo::UpdateFlags(u64_le dest_flags) { | ||
| 32 | flags = dest_flags; | ||
| 33 | } | ||
| 34 | |||
| 35 | void BehaviorInfo::SetUserRevision(u32_le revision) { | ||
| 36 | user_revision = revision; | ||
| 37 | } | ||
| 38 | |||
| 39 | u32_le BehaviorInfo::GetUserRevision() const { | ||
| 40 | return user_revision; | ||
| 41 | } | ||
| 42 | |||
| 43 | u32_le BehaviorInfo::GetProcessRevision() const { | ||
| 44 | return process_revision; | ||
| 45 | } | ||
| 46 | |||
| 47 | bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const { | ||
| 48 | return AudioCommon::IsRevisionSupported(2, user_revision); | ||
| 49 | } | ||
| 50 | |||
| 51 | bool BehaviorInfo::IsSplitterSupported() const { | ||
| 52 | return AudioCommon::IsRevisionSupported(2, user_revision); | ||
| 53 | } | ||
| 54 | |||
| 55 | bool BehaviorInfo::IsLongSizePreDelaySupported() const { | ||
| 56 | return AudioCommon::IsRevisionSupported(3, user_revision); | ||
| 57 | } | ||
| 58 | |||
| 59 | bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const { | ||
| 60 | return AudioCommon::IsRevisionSupported(5, user_revision); | ||
| 61 | } | ||
| 62 | |||
| 63 | bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const { | ||
| 64 | return AudioCommon::IsRevisionSupported(4, user_revision); | ||
| 65 | } | ||
| 66 | |||
| 67 | bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const { | ||
| 68 | return AudioCommon::IsRevisionSupported(1, user_revision); | ||
| 69 | } | ||
| 70 | |||
| 71 | bool BehaviorInfo::IsElapsedFrameCountSupported() const { | ||
| 72 | return AudioCommon::IsRevisionSupported(5, user_revision); | ||
| 73 | } | ||
| 74 | |||
| 75 | bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const { | ||
| 76 | return (flags & 1) != 0; | ||
| 77 | } | ||
| 78 | |||
| 79 | bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const { | ||
| 80 | return AudioCommon::IsRevisionSupported(5, user_revision); | ||
| 81 | } | ||
| 82 | |||
| 83 | bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const { | ||
| 84 | return AudioCommon::IsRevisionSupported(5, user_revision); | ||
| 85 | } | ||
| 86 | |||
| 87 | bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const { | ||
| 88 | return AudioCommon::IsRevisionSupported(5, user_revision); | ||
| 89 | } | ||
| 90 | |||
| 91 | bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const { | ||
| 92 | return AudioCommon::IsRevisionSupported(7, user_revision); | ||
| 93 | } | ||
| 94 | |||
| 95 | bool BehaviorInfo::IsSplitterBugFixed() const { | ||
| 96 | return AudioCommon::IsRevisionSupported(5, user_revision); | ||
| 97 | } | ||
| 98 | |||
| 99 | void BehaviorInfo::CopyErrorInfo(BehaviorInfo::OutParams& dst) { | ||
| 100 | dst.error_count = static_cast<u32>(error_count); | ||
| 101 | std::copy(errors.begin(), errors.begin() + error_count, dst.errors.begin()); | ||
| 102 | } | ||
| 103 | |||
| 104 | } // namespace AudioCore | ||
diff --git a/src/audio_core/behavior_info.h b/src/audio_core/behavior_info.h deleted file mode 100644 index b8c3159b9..000000000 --- a/src/audio_core/behavior_info.h +++ /dev/null | |||
| @@ -1,71 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | |||
| 8 | #include <vector> | ||
| 9 | #include "common/common_funcs.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | #include "common/swap.h" | ||
| 12 | |||
| 13 | namespace AudioCore { | ||
| 14 | class BehaviorInfo { | ||
| 15 | public: | ||
| 16 | struct ErrorInfo { | ||
| 17 | u32_le result{}; | ||
| 18 | INSERT_PADDING_WORDS(1); | ||
| 19 | u64_le result_info{}; | ||
| 20 | }; | ||
| 21 | static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size"); | ||
| 22 | |||
| 23 | struct InParams { | ||
| 24 | u32_le revision{}; | ||
| 25 | u32_le padding{}; | ||
| 26 | u64_le flags{}; | ||
| 27 | }; | ||
| 28 | static_assert(sizeof(InParams) == 0x10, "InParams is an invalid size"); | ||
| 29 | |||
| 30 | struct OutParams { | ||
| 31 | std::array<ErrorInfo, 10> errors{}; | ||
| 32 | u32_le error_count{}; | ||
| 33 | INSERT_PADDING_BYTES(12); | ||
| 34 | }; | ||
| 35 | static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size"); | ||
| 36 | |||
| 37 | explicit BehaviorInfo(); | ||
| 38 | ~BehaviorInfo(); | ||
| 39 | |||
| 40 | bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset); | ||
| 41 | |||
| 42 | void ClearError(); | ||
| 43 | void UpdateFlags(u64_le dest_flags); | ||
| 44 | void SetUserRevision(u32_le revision); | ||
| 45 | [[nodiscard]] u32_le GetUserRevision() const; | ||
| 46 | [[nodiscard]] u32_le GetProcessRevision() const; | ||
| 47 | |||
| 48 | [[nodiscard]] bool IsAdpcmLoopContextBugFixed() const; | ||
| 49 | [[nodiscard]] bool IsSplitterSupported() const; | ||
| 50 | [[nodiscard]] bool IsLongSizePreDelaySupported() const; | ||
| 51 | [[nodiscard]] bool IsAudioRendererProcessingTimeLimit80PercentSupported() const; | ||
| 52 | [[nodiscard]] bool IsAudioRendererProcessingTimeLimit75PercentSupported() const; | ||
| 53 | [[nodiscard]] bool IsAudioRendererProcessingTimeLimit70PercentSupported() const; | ||
| 54 | [[nodiscard]] bool IsElapsedFrameCountSupported() const; | ||
| 55 | [[nodiscard]] bool IsMemoryPoolForceMappingEnabled() const; | ||
| 56 | [[nodiscard]] bool IsFlushVoiceWaveBuffersSupported() const; | ||
| 57 | [[nodiscard]] bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const; | ||
| 58 | [[nodiscard]] bool IsVoicePitchAndSrcSkippedSupported() const; | ||
| 59 | [[nodiscard]] bool IsMixInParameterDirtyOnlyUpdateSupported() const; | ||
| 60 | [[nodiscard]] bool IsSplitterBugFixed() const; | ||
| 61 | void CopyErrorInfo(OutParams& dst); | ||
| 62 | |||
| 63 | private: | ||
| 64 | u32_le process_revision{}; | ||
| 65 | u32_le user_revision{}; | ||
| 66 | u64_le flags{}; | ||
| 67 | std::array<ErrorInfo, 10> errors{}; | ||
| 68 | std::size_t error_count{}; | ||
| 69 | }; | ||
| 70 | |||
| 71 | } // namespace AudioCore | ||
diff --git a/src/audio_core/buffer.h b/src/audio_core/buffer.h deleted file mode 100644 index ac001629f..000000000 --- a/src/audio_core/buffer.h +++ /dev/null | |||
| @@ -1,44 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <memory> | ||
| 7 | #include <vector> | ||
| 8 | |||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore { | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Represents a buffer of audio samples to be played in an audio stream | ||
| 15 | */ | ||
| 16 | class Buffer { | ||
| 17 | public: | ||
| 18 | using Tag = u64; | ||
| 19 | |||
| 20 | Buffer(Tag tag_, std::vector<s16>&& samples_) : tag{tag_}, samples{std::move(samples_)} {} | ||
| 21 | |||
| 22 | /// Returns the raw audio data for the buffer | ||
| 23 | std::vector<s16>& GetSamples() { | ||
| 24 | return samples; | ||
| 25 | } | ||
| 26 | |||
| 27 | /// Returns the raw audio data for the buffer | ||
| 28 | const std::vector<s16>& GetSamples() const { | ||
| 29 | return samples; | ||
| 30 | } | ||
| 31 | |||
| 32 | /// Returns the buffer tag, this is provided by the game to the audout service | ||
| 33 | Tag GetTag() const { | ||
| 34 | return tag; | ||
| 35 | } | ||
| 36 | |||
| 37 | private: | ||
| 38 | Tag tag; | ||
| 39 | std::vector<s16> samples; | ||
| 40 | }; | ||
| 41 | |||
| 42 | using BufferPtr = std::shared_ptr<Buffer>; | ||
| 43 | |||
| 44 | } // namespace AudioCore | ||
diff --git a/src/audio_core/codec.cpp b/src/audio_core/codec.cpp deleted file mode 100644 index 868b7a173..000000000 --- a/src/audio_core/codec.cpp +++ /dev/null | |||
| @@ -1,77 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | |||
| 6 | #include "audio_core/codec.h" | ||
| 7 | |||
| 8 | namespace AudioCore::Codec { | ||
| 9 | |||
| 10 | std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM_Coeff& coeff, | ||
| 11 | ADPCMState& state) { | ||
| 12 | // GC-ADPCM with scale factor and variable coefficients. | ||
| 13 | // Frames are 8 bytes long containing 14 samples each. | ||
| 14 | // Samples are 4 bits (one nibble) long. | ||
| 15 | |||
| 16 | constexpr std::size_t FRAME_LEN = 8; | ||
| 17 | constexpr std::size_t SAMPLES_PER_FRAME = 14; | ||
| 18 | static constexpr std::array<int, 16> SIGNED_NIBBLES{ | ||
| 19 | 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1, | ||
| 20 | }; | ||
| 21 | |||
| 22 | const std::size_t sample_count = (size / FRAME_LEN) * SAMPLES_PER_FRAME; | ||
| 23 | const std::size_t ret_size = | ||
| 24 | sample_count % 2 == 0 ? sample_count : sample_count + 1; // Ensure multiple of two. | ||
| 25 | std::vector<s16> ret(ret_size); | ||
| 26 | |||
| 27 | int yn1 = state.yn1, yn2 = state.yn2; | ||
| 28 | |||
| 29 | const std::size_t NUM_FRAMES = | ||
| 30 | (sample_count + (SAMPLES_PER_FRAME - 1)) / SAMPLES_PER_FRAME; // Round up. | ||
| 31 | for (std::size_t framei = 0; framei < NUM_FRAMES; framei++) { | ||
| 32 | const int frame_header = data[framei * FRAME_LEN]; | ||
| 33 | const int scale = 1 << (frame_header & 0xF); | ||
| 34 | const int idx = (frame_header >> 4) & 0x7; | ||
| 35 | |||
| 36 | // Coefficients are fixed point with 11 bits fractional part. | ||
| 37 | const int coef1 = coeff[idx * 2 + 0]; | ||
| 38 | const int coef2 = coeff[idx * 2 + 1]; | ||
| 39 | |||
| 40 | // Decodes an audio sample. One nibble produces one sample. | ||
| 41 | const auto decode_sample = [&](const int nibble) -> s16 { | ||
| 42 | const int xn = nibble * scale; | ||
| 43 | // We first transform everything into 11 bit fixed point, perform the second order | ||
| 44 | // digital filter, then transform back. | ||
| 45 | // 0x400 == 0.5 in 11 bit fixed point. | ||
| 46 | // Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2] | ||
| 47 | int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11; | ||
| 48 | // Clamp to output range. | ||
| 49 | val = std::clamp<s32>(val, -32768, 32767); | ||
| 50 | // Advance output feedback. | ||
| 51 | yn2 = yn1; | ||
| 52 | yn1 = val; | ||
| 53 | return static_cast<s16>(val); | ||
| 54 | }; | ||
| 55 | |||
| 56 | std::size_t outputi = framei * SAMPLES_PER_FRAME; | ||
| 57 | std::size_t datai = framei * FRAME_LEN + 1; | ||
| 58 | for (std::size_t i = 0; i < SAMPLES_PER_FRAME && outputi < sample_count; i += 2) { | ||
| 59 | const s16 sample1 = decode_sample(SIGNED_NIBBLES[data[datai] >> 4]); | ||
| 60 | ret[outputi] = sample1; | ||
| 61 | outputi++; | ||
| 62 | |||
| 63 | const s16 sample2 = decode_sample(SIGNED_NIBBLES[data[datai] & 0xF]); | ||
| 64 | ret[outputi] = sample2; | ||
| 65 | outputi++; | ||
| 66 | |||
| 67 | datai++; | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | state.yn1 = static_cast<s16>(yn1); | ||
| 72 | state.yn2 = static_cast<s16>(yn2); | ||
| 73 | |||
| 74 | return ret; | ||
| 75 | } | ||
| 76 | |||
| 77 | } // namespace AudioCore::Codec | ||
diff --git a/src/audio_core/codec.h b/src/audio_core/codec.h deleted file mode 100644 index 5a058b368..000000000 --- a/src/audio_core/codec.h +++ /dev/null | |||
| @@ -1,43 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <vector> | ||
| 8 | |||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore::Codec { | ||
| 12 | |||
| 13 | enum class PcmFormat : u32 { | ||
| 14 | Invalid = 0, | ||
| 15 | Int8 = 1, | ||
| 16 | Int16 = 2, | ||
| 17 | Int24 = 3, | ||
| 18 | Int32 = 4, | ||
| 19 | PcmFloat = 5, | ||
| 20 | Adpcm = 6, | ||
| 21 | }; | ||
| 22 | |||
| 23 | /// See: Codec::DecodeADPCM | ||
| 24 | struct ADPCMState { | ||
| 25 | // Two historical samples from previous processed buffer, | ||
| 26 | // required for ADPCM decoding | ||
| 27 | s16 yn1; ///< y[n-1] | ||
| 28 | s16 yn2; ///< y[n-2] | ||
| 29 | }; | ||
| 30 | |||
| 31 | using ADPCM_Coeff = std::array<s16, 16>; | ||
| 32 | |||
| 33 | /** | ||
| 34 | * @param data Pointer to buffer that contains ADPCM data to decode | ||
| 35 | * @param size Size of buffer in bytes | ||
| 36 | * @param coeff ADPCM coefficients | ||
| 37 | * @param state ADPCM state, this is updated with new state | ||
| 38 | * @return Decoded stereo signed PCM16 data, sample_count in length | ||
| 39 | */ | ||
| 40 | std::vector<s16> DecodeADPCM(const u8* data, std::size_t size, const ADPCM_Coeff& coeff, | ||
| 41 | ADPCMState& state); | ||
| 42 | |||
| 43 | }; // namespace AudioCore::Codec | ||
diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp deleted file mode 100644 index f97520820..000000000 --- a/src/audio_core/command_generator.cpp +++ /dev/null | |||
| @@ -1,1369 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <cmath> | ||
| 6 | #include <numbers> | ||
| 7 | |||
| 8 | #include "audio_core/algorithm/interpolate.h" | ||
| 9 | #include "audio_core/command_generator.h" | ||
| 10 | #include "audio_core/effect_context.h" | ||
| 11 | #include "audio_core/mix_context.h" | ||
| 12 | #include "audio_core/voice_context.h" | ||
| 13 | #include "common/common_types.h" | ||
| 14 | #include "core/memory.h" | ||
| 15 | |||
| 16 | namespace AudioCore { | ||
| 17 | namespace { | ||
| 18 | constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00; | ||
| 19 | constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL; | ||
| 20 | using DelayLineTimes = std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>; | ||
| 21 | |||
| 22 | constexpr DelayLineTimes FDN_MIN_DELAY_LINE_TIMES{5.0f, 6.0f, 13.0f, 14.0f}; | ||
| 23 | constexpr DelayLineTimes FDN_MAX_DELAY_LINE_TIMES{45.704f, 82.782f, 149.94f, 271.58f}; | ||
| 24 | constexpr DelayLineTimes DECAY0_MAX_DELAY_LINE_TIMES{17.0f, 13.0f, 9.0f, 7.0f}; | ||
| 25 | constexpr DelayLineTimes DECAY1_MAX_DELAY_LINE_TIMES{19.0f, 11.0f, 10.0f, 6.0f}; | ||
| 26 | constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_TAP_TIMES{ | ||
| 27 | 0.017136f, 0.059154f, 0.161733f, 0.390186f, 0.425262f, 0.455411f, 0.689737f, | ||
| 28 | 0.745910f, 0.833844f, 0.859502f, 0.000000f, 0.075024f, 0.168788f, 0.299901f, | ||
| 29 | 0.337443f, 0.371903f, 0.599011f, 0.716741f, 0.817859f, 0.851664f}; | ||
| 30 | constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_GAIN{ | ||
| 31 | 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f, | ||
| 32 | 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f, | ||
| 33 | 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f}; | ||
| 34 | |||
| 35 | template <std::size_t N> | ||
| 36 | void ApplyMix(std::span<s32> output, std::span<const s32> input, s32 gain, s32 sample_count) { | ||
| 37 | for (std::size_t i = 0; i < static_cast<std::size_t>(sample_count); i += N) { | ||
| 38 | for (std::size_t j = 0; j < N; j++) { | ||
| 39 | output[i + j] += | ||
| 40 | static_cast<s32>((static_cast<s64>(input[i + j]) * gain + 0x4000) >> 15); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, float gain, float delta, | ||
| 46 | s32 sample_count) { | ||
| 47 | // XC2 passes in NaN mix volumes, causing further issues as we handle everything as s32 rather | ||
| 48 | // than float, so the NaN propogation is lost. As the samples get further modified for | ||
| 49 | // volume etc, they can get out of NaN range, so a later heuristic for catching this is | ||
| 50 | // more difficult. Handle it here by setting these samples to silence. | ||
| 51 | if (std::isnan(gain)) { | ||
| 52 | gain = 0.0f; | ||
| 53 | delta = 0.0f; | ||
| 54 | } | ||
| 55 | |||
| 56 | s32 x = 0; | ||
| 57 | for (s32 i = 0; i < sample_count; i++) { | ||
| 58 | x = static_cast<s32>(static_cast<float>(input[i]) * gain); | ||
| 59 | output[i] += x; | ||
| 60 | gain += delta; | ||
| 61 | } | ||
| 62 | return x; | ||
| 63 | } | ||
| 64 | |||
| 65 | void ApplyGain(std::span<s32> output, std::span<const s32> input, s32 gain, s32 delta, | ||
| 66 | s32 sample_count) { | ||
| 67 | for (s32 i = 0; i < sample_count; i++) { | ||
| 68 | output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15); | ||
| 69 | gain += delta; | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | void ApplyGainWithoutDelta(std::span<s32> output, std::span<const s32> input, s32 gain, | ||
| 74 | s32 sample_count) { | ||
| 75 | for (s32 i = 0; i < sample_count; i++) { | ||
| 76 | output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | s32 ApplyMixDepop(std::span<s32> output, s32 first_sample, s32 delta, s32 sample_count) { | ||
| 81 | const bool positive = first_sample > 0; | ||
| 82 | auto final_sample = std::abs(first_sample); | ||
| 83 | for (s32 i = 0; i < sample_count; i++) { | ||
| 84 | final_sample = static_cast<s32>((static_cast<s64>(final_sample) * delta) >> 15); | ||
| 85 | if (positive) { | ||
| 86 | output[i] += final_sample; | ||
| 87 | } else { | ||
| 88 | output[i] -= final_sample; | ||
| 89 | } | ||
| 90 | } | ||
| 91 | if (positive) { | ||
| 92 | return final_sample; | ||
| 93 | } else { | ||
| 94 | return -final_sample; | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | float Pow10(float x) { | ||
| 99 | if (x >= 0.0f) { | ||
| 100 | return 1.0f; | ||
| 101 | } else if (x <= -5.3f) { | ||
| 102 | return 0.0f; | ||
| 103 | } | ||
| 104 | return std::pow(10.0f, x); | ||
| 105 | } | ||
| 106 | |||
| 107 | float SinD(float degrees) { | ||
| 108 | return std::sin(degrees * std::numbers::pi_v<float> / 180.0f); | ||
| 109 | } | ||
| 110 | |||
| 111 | float CosD(float degrees) { | ||
| 112 | return std::cos(degrees * std::numbers::pi_v<float> / 180.0f); | ||
| 113 | } | ||
| 114 | |||
| 115 | float ToFloat(s32 sample) { | ||
| 116 | return static_cast<float>(sample) / 65536.f; | ||
| 117 | } | ||
| 118 | |||
| 119 | s32 ToS32(float sample) { | ||
| 120 | constexpr auto min = -8388608.0f; | ||
| 121 | constexpr auto max = 8388607.f; | ||
| 122 | float rescaled_sample = sample * 65536.0f; | ||
| 123 | if (rescaled_sample < min) { | ||
| 124 | rescaled_sample = min; | ||
| 125 | } | ||
| 126 | if (rescaled_sample > max) { | ||
| 127 | rescaled_sample = max; | ||
| 128 | } | ||
| 129 | return static_cast<s32>(rescaled_sample); | ||
| 130 | } | ||
| 131 | |||
| 132 | constexpr std::array<u8, 20> REVERB_TAP_INDEX_1CH{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 133 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; | ||
| 134 | |||
| 135 | constexpr std::array<u8, 20> REVERB_TAP_INDEX_2CH{0, 0, 0, 1, 1, 1, 1, 0, 0, 0, | ||
| 136 | 1, 1, 1, 0, 0, 0, 0, 1, 1, 1}; | ||
| 137 | |||
| 138 | constexpr std::array<u8, 20> REVERB_TAP_INDEX_4CH{0, 0, 0, 1, 1, 1, 1, 2, 2, 2, | ||
| 139 | 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; | ||
| 140 | |||
| 141 | constexpr std::array<u8, 20> REVERB_TAP_INDEX_6CH{4, 0, 0, 1, 1, 1, 1, 2, 2, 2, | ||
| 142 | 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; | ||
| 143 | |||
| 144 | template <std::size_t CHANNEL_COUNT> | ||
| 145 | void ApplyReverbGeneric( | ||
| 146 | I3dl2ReverbState& state, | ||
| 147 | const std::array<std::span<const s32>, AudioCommon::MAX_CHANNEL_COUNT>& input, | ||
| 148 | const std::array<std::span<s32>, AudioCommon::MAX_CHANNEL_COUNT>& output, s32 sample_count) { | ||
| 149 | |||
| 150 | auto GetTapLookup = []() { | ||
| 151 | if constexpr (CHANNEL_COUNT == 1) { | ||
| 152 | return REVERB_TAP_INDEX_1CH; | ||
| 153 | } else if constexpr (CHANNEL_COUNT == 2) { | ||
| 154 | return REVERB_TAP_INDEX_2CH; | ||
| 155 | } else if constexpr (CHANNEL_COUNT == 4) { | ||
| 156 | return REVERB_TAP_INDEX_4CH; | ||
| 157 | } else if constexpr (CHANNEL_COUNT == 6) { | ||
| 158 | return REVERB_TAP_INDEX_6CH; | ||
| 159 | } | ||
| 160 | }; | ||
| 161 | |||
| 162 | const auto& tap_index_lut = GetTapLookup(); | ||
| 163 | for (s32 sample = 0; sample < sample_count; sample++) { | ||
| 164 | std::array<f32, CHANNEL_COUNT> out_samples{}; | ||
| 165 | std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fsamp{}; | ||
| 166 | std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> mixed{}; | ||
| 167 | std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> osamp{}; | ||
| 168 | |||
| 169 | // Mix everything into a single sample | ||
| 170 | s32 temp_mixed_sample = 0; | ||
| 171 | for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { | ||
| 172 | temp_mixed_sample += input[i][sample]; | ||
| 173 | } | ||
| 174 | const auto current_sample = ToFloat(temp_mixed_sample); | ||
| 175 | const auto early_tap = state.early_delay_line.TapOut(state.early_to_late_taps); | ||
| 176 | |||
| 177 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_TAPS; i++) { | ||
| 178 | const auto tapped_samp = | ||
| 179 | state.early_delay_line.TapOut(state.early_tap_steps[i]) * EARLY_GAIN[i]; | ||
| 180 | out_samples[tap_index_lut[i]] += tapped_samp; | ||
| 181 | |||
| 182 | if constexpr (CHANNEL_COUNT == 6) { | ||
| 183 | // handle lfe | ||
| 184 | out_samples[5] += tapped_samp; | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | state.lowpass_0 = current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1; | ||
| 189 | state.early_delay_line.Tick(state.lowpass_0); | ||
| 190 | |||
| 191 | for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { | ||
| 192 | out_samples[i] *= state.early_gain; | ||
| 193 | } | ||
| 194 | |||
| 195 | // Two channel seems to apply a latet gain, we require to save this | ||
| 196 | f32 filter{}; | ||
| 197 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 198 | filter = state.fdn_delay_line[i].GetOutputSample(); | ||
| 199 | const auto computed = filter * state.lpf_coefficients[0][i] + state.shelf_filter[i]; | ||
| 200 | state.shelf_filter[i] = | ||
| 201 | filter * state.lpf_coefficients[1][i] + computed * state.lpf_coefficients[2][i]; | ||
| 202 | fsamp[i] = computed; | ||
| 203 | } | ||
| 204 | |||
| 205 | // Mixing matrix | ||
| 206 | mixed[0] = fsamp[1] + fsamp[2]; | ||
| 207 | mixed[1] = -fsamp[0] - fsamp[3]; | ||
| 208 | mixed[2] = fsamp[0] - fsamp[3]; | ||
| 209 | mixed[3] = fsamp[1] - fsamp[2]; | ||
| 210 | |||
| 211 | if constexpr (CHANNEL_COUNT == 2) { | ||
| 212 | for (auto& mix : mixed) { | ||
| 213 | mix *= (filter * state.late_gain); | ||
| 214 | } | ||
| 215 | } | ||
| 216 | |||
| 217 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 218 | const auto late = early_tap * state.late_gain; | ||
| 219 | osamp[i] = state.decay_delay_line0[i].Tick(late + mixed[i]); | ||
| 220 | osamp[i] = state.decay_delay_line1[i].Tick(osamp[i]); | ||
| 221 | state.fdn_delay_line[i].Tick(osamp[i]); | ||
| 222 | } | ||
| 223 | |||
| 224 | if constexpr (CHANNEL_COUNT == 1) { | ||
| 225 | output[0][sample] = ToS32(state.dry_gain * ToFloat(input[0][sample]) + | ||
| 226 | (out_samples[0] + osamp[0] + osamp[1])); | ||
| 227 | } else if constexpr (CHANNEL_COUNT == 2 || CHANNEL_COUNT == 4) { | ||
| 228 | for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { | ||
| 229 | output[i][sample] = | ||
| 230 | ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i])); | ||
| 231 | } | ||
| 232 | } else if constexpr (CHANNEL_COUNT == 6) { | ||
| 233 | const auto temp_center = state.center_delay_line.Tick(0.5f * (osamp[2] - osamp[3])); | ||
| 234 | for (std::size_t i = 0; i < 4; i++) { | ||
| 235 | output[i][sample] = | ||
| 236 | ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i])); | ||
| 237 | } | ||
| 238 | output[4][sample] = | ||
| 239 | ToS32(state.dry_gain * ToFloat(input[4][sample]) + (out_samples[4] + temp_center)); | ||
| 240 | output[5][sample] = | ||
| 241 | ToS32(state.dry_gain * ToFloat(input[5][sample]) + (out_samples[5] + osamp[3])); | ||
| 242 | } | ||
| 243 | } | ||
| 244 | } | ||
| 245 | |||
| 246 | } // namespace | ||
| 247 | |||
| 248 | CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_, | ||
| 249 | VoiceContext& voice_context_, MixContext& mix_context_, | ||
| 250 | SplitterContext& splitter_context_, | ||
| 251 | EffectContext& effect_context_, Core::Memory::Memory& memory_) | ||
| 252 | : worker_params(worker_params_), voice_context(voice_context_), mix_context(mix_context_), | ||
| 253 | splitter_context(splitter_context_), effect_context(effect_context_), memory(memory_), | ||
| 254 | mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) * | ||
| 255 | worker_params.sample_count), | ||
| 256 | sample_buffer(MIX_BUFFER_SIZE), | ||
| 257 | depop_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) * | ||
| 258 | worker_params.sample_count) {} | ||
| 259 | CommandGenerator::~CommandGenerator() = default; | ||
| 260 | |||
| 261 | void CommandGenerator::ClearMixBuffers() { | ||
| 262 | std::fill(mix_buffer.begin(), mix_buffer.end(), 0); | ||
| 263 | std::fill(sample_buffer.begin(), sample_buffer.end(), 0); | ||
| 264 | // std::fill(depop_buffer.begin(), depop_buffer.end(), 0); | ||
| 265 | } | ||
| 266 | |||
| 267 | void CommandGenerator::GenerateVoiceCommands() { | ||
| 268 | if (dumping_frame) { | ||
| 269 | LOG_DEBUG(Audio, "(DSP_TRACE) GenerateVoiceCommands"); | ||
| 270 | } | ||
| 271 | // Grab all our voices | ||
| 272 | const auto voice_count = voice_context.GetVoiceCount(); | ||
| 273 | for (std::size_t i = 0; i < voice_count; i++) { | ||
| 274 | auto& voice_info = voice_context.GetSortedInfo(i); | ||
| 275 | // Update voices and check if we should queue them | ||
| 276 | if (voice_info.ShouldSkip() || !voice_info.UpdateForCommandGeneration(voice_context)) { | ||
| 277 | continue; | ||
| 278 | } | ||
| 279 | |||
| 280 | // Queue our voice | ||
| 281 | GenerateVoiceCommand(voice_info); | ||
| 282 | } | ||
| 283 | // Update our splitters | ||
| 284 | splitter_context.UpdateInternalState(); | ||
| 285 | } | ||
| 286 | |||
| 287 | void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) { | ||
| 288 | auto& in_params = voice_info.GetInParams(); | ||
| 289 | const auto channel_count = in_params.channel_count; | ||
| 290 | |||
| 291 | for (s32 channel = 0; channel < channel_count; channel++) { | ||
| 292 | const auto resource_id = in_params.voice_channel_resource_id[channel]; | ||
| 293 | auto& dsp_state = voice_context.GetDspSharedState(resource_id); | ||
| 294 | auto& channel_resource = voice_context.GetChannelResource(resource_id); | ||
| 295 | |||
| 296 | // Decode our samples for our channel | ||
| 297 | GenerateDataSourceCommand(voice_info, dsp_state, channel); | ||
| 298 | |||
| 299 | if (in_params.should_depop) { | ||
| 300 | in_params.last_volume = 0.0f; | ||
| 301 | } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER || | ||
| 302 | in_params.mix_id != AudioCommon::NO_MIX) { | ||
| 303 | // Apply a biquad filter if needed | ||
| 304 | GenerateBiquadFilterCommandForVoice(voice_info, dsp_state, | ||
| 305 | worker_params.mix_buffer_count, channel); | ||
| 306 | // Base voice volume ramping | ||
| 307 | GenerateVolumeRampCommand(in_params.last_volume, in_params.volume, channel, | ||
| 308 | in_params.node_id); | ||
| 309 | in_params.last_volume = in_params.volume; | ||
| 310 | |||
| 311 | if (in_params.mix_id != AudioCommon::NO_MIX) { | ||
| 312 | // If we're using a mix id | ||
| 313 | auto& mix_info = mix_context.GetInfo(in_params.mix_id); | ||
| 314 | const auto& dest_mix_params = mix_info.GetInParams(); | ||
| 315 | |||
| 316 | // Voice Mixing | ||
| 317 | GenerateVoiceMixCommand( | ||
| 318 | channel_resource.GetCurrentMixVolume(), channel_resource.GetLastMixVolume(), | ||
| 319 | dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count, | ||
| 320 | worker_params.mix_buffer_count + channel, in_params.node_id); | ||
| 321 | |||
| 322 | // Update last mix volumes | ||
| 323 | channel_resource.UpdateLastMixVolumes(); | ||
| 324 | } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) { | ||
| 325 | s32 base = channel; | ||
| 326 | while (auto* destination_data = | ||
| 327 | GetDestinationData(in_params.splitter_info_id, base)) { | ||
| 328 | base += channel_count; | ||
| 329 | |||
| 330 | if (!destination_data->IsConfigured()) { | ||
| 331 | continue; | ||
| 332 | } | ||
| 333 | if (destination_data->GetMixId() >= static_cast<int>(mix_context.GetCount())) { | ||
| 334 | continue; | ||
| 335 | } | ||
| 336 | |||
| 337 | const auto& mix_info = mix_context.GetInfo(destination_data->GetMixId()); | ||
| 338 | const auto& dest_mix_params = mix_info.GetInParams(); | ||
| 339 | GenerateVoiceMixCommand( | ||
| 340 | destination_data->CurrentMixVolumes(), destination_data->LastMixVolumes(), | ||
| 341 | dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count, | ||
| 342 | worker_params.mix_buffer_count + channel, in_params.node_id); | ||
| 343 | destination_data->MarkDirty(); | ||
| 344 | } | ||
| 345 | } | ||
| 346 | // Update biquad filter enabled states | ||
| 347 | for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) { | ||
| 348 | in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled; | ||
| 349 | } | ||
| 350 | } | ||
| 351 | } | ||
| 352 | } | ||
| 353 | |||
| 354 | void CommandGenerator::GenerateSubMixCommands() { | ||
| 355 | const auto mix_count = mix_context.GetCount(); | ||
| 356 | for (std::size_t i = 0; i < mix_count; i++) { | ||
| 357 | auto& mix_info = mix_context.GetSortedInfo(i); | ||
| 358 | const auto& in_params = mix_info.GetInParams(); | ||
| 359 | if (!in_params.in_use || in_params.mix_id == AudioCommon::FINAL_MIX) { | ||
| 360 | continue; | ||
| 361 | } | ||
| 362 | GenerateSubMixCommand(mix_info); | ||
| 363 | } | ||
| 364 | } | ||
| 365 | |||
| 366 | void CommandGenerator::GenerateFinalMixCommands() { | ||
| 367 | GenerateFinalMixCommand(); | ||
| 368 | } | ||
| 369 | |||
| 370 | void CommandGenerator::PreCommand() { | ||
| 371 | if (!dumping_frame) { | ||
| 372 | return; | ||
| 373 | } | ||
| 374 | for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) { | ||
| 375 | const auto& base = splitter_context.GetInfo(i); | ||
| 376 | std::string graph = fmt::format("b[{}]", i); | ||
| 377 | const auto* head = base.GetHead(); | ||
| 378 | while (head != nullptr) { | ||
| 379 | graph += fmt::format("->{}", head->GetMixId()); | ||
| 380 | head = head->GetNextDestination(); | ||
| 381 | } | ||
| 382 | LOG_DEBUG(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph); | ||
| 383 | } | ||
| 384 | } | ||
| 385 | |||
| 386 | void CommandGenerator::PostCommand() { | ||
| 387 | if (!dumping_frame) { | ||
| 388 | return; | ||
| 389 | } | ||
| 390 | dumping_frame = false; | ||
| 391 | } | ||
| 392 | |||
| 393 | void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, | ||
| 394 | s32 channel) { | ||
| 395 | const auto& in_params = voice_info.GetInParams(); | ||
| 396 | const auto depop = in_params.should_depop; | ||
| 397 | |||
| 398 | if (depop) { | ||
| 399 | if (in_params.mix_id != AudioCommon::NO_MIX) { | ||
| 400 | auto& mix_info = mix_context.GetInfo(in_params.mix_id); | ||
| 401 | const auto& mix_in = mix_info.GetInParams(); | ||
| 402 | GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset); | ||
| 403 | } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) { | ||
| 404 | s32 index{}; | ||
| 405 | while (const auto* destination = | ||
| 406 | GetDestinationData(in_params.splitter_info_id, index++)) { | ||
| 407 | if (!destination->IsConfigured()) { | ||
| 408 | continue; | ||
| 409 | } | ||
| 410 | auto& mix_info = mix_context.GetInfo(destination->GetMixId()); | ||
| 411 | const auto& mix_in = mix_info.GetInParams(); | ||
| 412 | GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset); | ||
| 413 | } | ||
| 414 | } | ||
| 415 | } else { | ||
| 416 | switch (in_params.sample_format) { | ||
| 417 | case SampleFormat::Pcm8: | ||
| 418 | case SampleFormat::Pcm16: | ||
| 419 | case SampleFormat::Pcm32: | ||
| 420 | case SampleFormat::PcmFloat: | ||
| 421 | DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel, | ||
| 422 | worker_params.sample_rate, worker_params.sample_count, | ||
| 423 | in_params.node_id); | ||
| 424 | break; | ||
| 425 | case SampleFormat::Adpcm: | ||
| 426 | ASSERT(channel == 0 && in_params.channel_count == 1); | ||
| 427 | DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0, | ||
| 428 | worker_params.sample_rate, worker_params.sample_count, | ||
| 429 | in_params.node_id); | ||
| 430 | break; | ||
| 431 | default: | ||
| 432 | ASSERT_MSG(false, "Unimplemented sample format={}", in_params.sample_format); | ||
| 433 | } | ||
| 434 | } | ||
| 435 | } | ||
| 436 | |||
| 437 | void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, | ||
| 438 | VoiceState& dsp_state, | ||
| 439 | [[maybe_unused]] s32 mix_buffer_count, | ||
| 440 | [[maybe_unused]] s32 channel) { | ||
| 441 | for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) { | ||
| 442 | const auto& in_params = voice_info.GetInParams(); | ||
| 443 | auto& biquad_filter = in_params.biquad_filter[i]; | ||
| 444 | // Check if biquad filter is actually used | ||
| 445 | if (!biquad_filter.enabled) { | ||
| 446 | continue; | ||
| 447 | } | ||
| 448 | |||
| 449 | // Reinitialize our biquad filter state if it was enabled previously | ||
| 450 | if (!in_params.was_biquad_filter_enabled[i]) { | ||
| 451 | dsp_state.biquad_filter_state.fill(0); | ||
| 452 | } | ||
| 453 | |||
| 454 | // Generate biquad filter | ||
| 455 | // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter, | ||
| 456 | // dsp_state.biquad_filter_state, | ||
| 457 | // mix_buffer_count + channel, mix_buffer_count + channel, | ||
| 458 | // worker_params.sample_count, voice_info.GetInParams().node_id); | ||
| 459 | } | ||
| 460 | } | ||
| 461 | |||
| 462 | void CommandGenerator::GenerateBiquadFilterCommand([[maybe_unused]] s32 mix_buffer_id, | ||
| 463 | const BiquadFilterParameter& params, | ||
| 464 | std::array<s64, 2>& state, | ||
| 465 | std::size_t input_offset, | ||
| 466 | std::size_t output_offset, s32 sample_count, | ||
| 467 | s32 node_id) { | ||
| 468 | if (dumping_frame) { | ||
| 469 | LOG_DEBUG(Audio, | ||
| 470 | "(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, " | ||
| 471 | "input_mix_buffer={}, output_mix_buffer={}", | ||
| 472 | node_id, input_offset, output_offset); | ||
| 473 | } | ||
| 474 | std::span<const s32> input = GetMixBuffer(input_offset); | ||
| 475 | std::span<s32> output = GetMixBuffer(output_offset); | ||
| 476 | |||
| 477 | // Biquad filter parameters | ||
| 478 | const auto [n0, n1, n2] = params.numerator; | ||
| 479 | const auto [d0, d1] = params.denominator; | ||
| 480 | |||
| 481 | // Biquad filter states | ||
| 482 | auto [s0, s1] = state; | ||
| 483 | |||
| 484 | constexpr s64 int32_min = std::numeric_limits<s32>::min(); | ||
| 485 | constexpr s64 int32_max = std::numeric_limits<s32>::max(); | ||
| 486 | |||
| 487 | for (int i = 0; i < sample_count; ++i) { | ||
| 488 | const auto sample = static_cast<s64>(input[i]); | ||
| 489 | const auto f = (sample * n0 + s0 + 0x4000) >> 15; | ||
| 490 | const auto y = std::clamp(f, int32_min, int32_max); | ||
| 491 | s0 = sample * n1 + y * d0 + s1; | ||
| 492 | s1 = sample * n2 + y * d1; | ||
| 493 | output[i] = static_cast<s32>(y); | ||
| 494 | } | ||
| 495 | |||
| 496 | state = {s0, s1}; | ||
| 497 | } | ||
| 498 | |||
| 499 | void CommandGenerator::GenerateDepopPrepareCommand(VoiceState& dsp_state, | ||
| 500 | std::size_t mix_buffer_count, | ||
| 501 | std::size_t mix_buffer_offset) { | ||
| 502 | for (std::size_t i = 0; i < mix_buffer_count; i++) { | ||
| 503 | auto& sample = dsp_state.previous_samples[i]; | ||
| 504 | if (sample != 0) { | ||
| 505 | depop_buffer[mix_buffer_offset + i] += sample; | ||
| 506 | sample = 0; | ||
| 507 | } | ||
| 508 | } | ||
| 509 | } | ||
| 510 | |||
| 511 | void CommandGenerator::GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count, | ||
| 512 | std::size_t mix_buffer_offset, | ||
| 513 | s32 sample_rate) { | ||
| 514 | const std::size_t end_offset = | ||
| 515 | std::min(mix_buffer_offset + mix_buffer_count, GetTotalMixBufferCount()); | ||
| 516 | const s32 delta = sample_rate == 48000 ? 0x7B29 : 0x78CB; | ||
| 517 | for (std::size_t i = mix_buffer_offset; i < end_offset; i++) { | ||
| 518 | if (depop_buffer[i] == 0) { | ||
| 519 | continue; | ||
| 520 | } | ||
| 521 | |||
| 522 | depop_buffer[i] = | ||
| 523 | ApplyMixDepop(GetMixBuffer(i), depop_buffer[i], delta, worker_params.sample_count); | ||
| 524 | } | ||
| 525 | } | ||
| 526 | |||
| 527 | void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) { | ||
| 528 | const std::size_t effect_count = effect_context.GetCount(); | ||
| 529 | const auto buffer_offset = mix_info.GetInParams().buffer_offset; | ||
| 530 | for (std::size_t i = 0; i < effect_count; i++) { | ||
| 531 | const auto index = mix_info.GetEffectOrder(i); | ||
| 532 | if (index == AudioCommon::NO_EFFECT_ORDER) { | ||
| 533 | break; | ||
| 534 | } | ||
| 535 | auto* info = effect_context.GetInfo(index); | ||
| 536 | const auto type = info->GetType(); | ||
| 537 | |||
| 538 | // TODO(ogniK): Finish remaining effects | ||
| 539 | switch (type) { | ||
| 540 | case EffectType::Aux: | ||
| 541 | GenerateAuxCommand(buffer_offset, info, info->IsEnabled()); | ||
| 542 | break; | ||
| 543 | case EffectType::I3dl2Reverb: | ||
| 544 | GenerateI3dl2ReverbEffectCommand(buffer_offset, info, info->IsEnabled()); | ||
| 545 | break; | ||
| 546 | case EffectType::BiquadFilter: | ||
| 547 | GenerateBiquadFilterEffectCommand(buffer_offset, info, info->IsEnabled()); | ||
| 548 | break; | ||
| 549 | default: | ||
| 550 | break; | ||
| 551 | } | ||
| 552 | |||
| 553 | info->UpdateForCommandGeneration(); | ||
| 554 | } | ||
| 555 | } | ||
| 556 | |||
| 557 | void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, | ||
| 558 | bool enabled) { | ||
| 559 | auto* reverb = dynamic_cast<EffectI3dl2Reverb*>(info); | ||
| 560 | const auto& params = reverb->GetParams(); | ||
| 561 | auto& state = reverb->GetState(); | ||
| 562 | const auto channel_count = params.channel_count; | ||
| 563 | |||
| 564 | if (channel_count != 1 && channel_count != 2 && channel_count != 4 && channel_count != 6) { | ||
| 565 | return; | ||
| 566 | } | ||
| 567 | |||
| 568 | std::array<std::span<const s32>, AudioCommon::MAX_CHANNEL_COUNT> input{}; | ||
| 569 | std::array<std::span<s32>, AudioCommon::MAX_CHANNEL_COUNT> output{}; | ||
| 570 | |||
| 571 | const auto status = params.status; | ||
| 572 | for (s32 i = 0; i < channel_count; i++) { | ||
| 573 | input[i] = GetMixBuffer(mix_buffer_offset + params.input[i]); | ||
| 574 | output[i] = GetMixBuffer(mix_buffer_offset + params.output[i]); | ||
| 575 | } | ||
| 576 | |||
| 577 | if (enabled) { | ||
| 578 | if (status == ParameterStatus::Initialized) { | ||
| 579 | InitializeI3dl2Reverb(reverb->GetParams(), state, info->GetWorkBuffer()); | ||
| 580 | } else if (status == ParameterStatus::Updating) { | ||
| 581 | UpdateI3dl2Reverb(reverb->GetParams(), state, false); | ||
| 582 | } | ||
| 583 | } | ||
| 584 | |||
| 585 | if (enabled) { | ||
| 586 | switch (channel_count) { | ||
| 587 | case 1: | ||
| 588 | ApplyReverbGeneric<1>(state, input, output, worker_params.sample_count); | ||
| 589 | break; | ||
| 590 | case 2: | ||
| 591 | ApplyReverbGeneric<2>(state, input, output, worker_params.sample_count); | ||
| 592 | break; | ||
| 593 | case 4: | ||
| 594 | ApplyReverbGeneric<4>(state, input, output, worker_params.sample_count); | ||
| 595 | break; | ||
| 596 | case 6: | ||
| 597 | ApplyReverbGeneric<6>(state, input, output, worker_params.sample_count); | ||
| 598 | break; | ||
| 599 | } | ||
| 600 | } else { | ||
| 601 | for (s32 i = 0; i < channel_count; i++) { | ||
| 602 | // Only copy if the buffer input and output do not match! | ||
| 603 | if ((mix_buffer_offset + params.input[i]) != (mix_buffer_offset + params.output[i])) { | ||
| 604 | std::memcpy(output[i].data(), input[i].data(), | ||
| 605 | worker_params.sample_count * sizeof(s32)); | ||
| 606 | } | ||
| 607 | } | ||
| 608 | } | ||
| 609 | } | ||
| 610 | |||
| 611 | void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, | ||
| 612 | bool enabled) { | ||
| 613 | if (!enabled) { | ||
| 614 | return; | ||
| 615 | } | ||
| 616 | const auto& params = dynamic_cast<EffectBiquadFilter*>(info)->GetParams(); | ||
| 617 | const auto channel_count = params.channel_count; | ||
| 618 | for (s32 i = 0; i < channel_count; i++) { | ||
| 619 | // TODO(ogniK): Actually implement biquad filter | ||
| 620 | if (params.input[i] != params.output[i]) { | ||
| 621 | std::span<const s32> input = GetMixBuffer(mix_buffer_offset + params.input[i]); | ||
| 622 | std::span<s32> output = GetMixBuffer(mix_buffer_offset + params.output[i]); | ||
| 623 | ApplyMix<1>(output, input, 32768, worker_params.sample_count); | ||
| 624 | } | ||
| 625 | } | ||
| 626 | } | ||
| 627 | |||
| 628 | void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) { | ||
| 629 | auto* aux = dynamic_cast<EffectAuxInfo*>(info); | ||
| 630 | const auto& params = aux->GetParams(); | ||
| 631 | if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) { | ||
| 632 | const auto max_channels = params.count; | ||
| 633 | u32 offset{}; | ||
| 634 | for (u32 channel = 0; channel < max_channels; channel++) { | ||
| 635 | u32 write_count = 0; | ||
| 636 | if (channel == (max_channels - 1)) { | ||
| 637 | write_count = offset + worker_params.sample_count; | ||
| 638 | } | ||
| 639 | |||
| 640 | const auto input_index = params.input_mix_buffers[channel] + mix_buffer_offset; | ||
| 641 | const auto output_index = params.output_mix_buffers[channel] + mix_buffer_offset; | ||
| 642 | |||
| 643 | if (enabled) { | ||
| 644 | AuxInfoDSP send_info{}; | ||
| 645 | AuxInfoDSP recv_info{}; | ||
| 646 | memory.ReadBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP)); | ||
| 647 | memory.ReadBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP)); | ||
| 648 | |||
| 649 | WriteAuxBuffer(send_info, aux->GetSendBuffer(), params.sample_count, | ||
| 650 | GetMixBuffer(input_index), worker_params.sample_count, offset, | ||
| 651 | write_count); | ||
| 652 | memory.WriteBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP)); | ||
| 653 | |||
| 654 | const auto samples_read = ReadAuxBuffer( | ||
| 655 | recv_info, aux->GetRecvBuffer(), params.sample_count, | ||
| 656 | GetMixBuffer(output_index), worker_params.sample_count, offset, write_count); | ||
| 657 | memory.WriteBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP)); | ||
| 658 | |||
| 659 | if (samples_read != static_cast<int>(worker_params.sample_count) && | ||
| 660 | samples_read <= params.sample_count) { | ||
| 661 | std::memset(GetMixBuffer(output_index).data(), 0, | ||
| 662 | params.sample_count - samples_read); | ||
| 663 | } | ||
| 664 | } else { | ||
| 665 | AuxInfoDSP empty{}; | ||
| 666 | memory.WriteBlock(aux->GetSendInfo(), &empty, sizeof(AuxInfoDSP)); | ||
| 667 | memory.WriteBlock(aux->GetRecvInfo(), &empty, sizeof(AuxInfoDSP)); | ||
| 668 | if (output_index != input_index) { | ||
| 669 | std::memcpy(GetMixBuffer(output_index).data(), GetMixBuffer(input_index).data(), | ||
| 670 | worker_params.sample_count * sizeof(s32)); | ||
| 671 | } | ||
| 672 | } | ||
| 673 | |||
| 674 | offset += worker_params.sample_count; | ||
| 675 | } | ||
| 676 | } | ||
| 677 | } | ||
| 678 | |||
| 679 | ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) { | ||
| 680 | if (splitter_id == AudioCommon::NO_SPLITTER) { | ||
| 681 | return nullptr; | ||
| 682 | } | ||
| 683 | return splitter_context.GetDestinationData(splitter_id, index); | ||
| 684 | } | ||
| 685 | |||
| 686 | s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, | ||
| 687 | std::span<const s32> data, u32 sample_count, u32 write_offset, | ||
| 688 | u32 write_count) { | ||
| 689 | if (max_samples == 0) { | ||
| 690 | return 0; | ||
| 691 | } | ||
| 692 | u32 offset = dsp_info.write_offset + write_offset; | ||
| 693 | if (send_buffer == 0 || offset > max_samples) { | ||
| 694 | return 0; | ||
| 695 | } | ||
| 696 | |||
| 697 | s32 data_offset{}; | ||
| 698 | u32 remaining = sample_count; | ||
| 699 | while (remaining > 0) { | ||
| 700 | // Get position in buffer | ||
| 701 | const auto base = send_buffer + (offset * sizeof(u32)); | ||
| 702 | const auto samples_to_grab = std::min(max_samples - offset, remaining); | ||
| 703 | // Write to output | ||
| 704 | memory.WriteBlock(base, (data.data() + data_offset), samples_to_grab * sizeof(u32)); | ||
| 705 | offset = (offset + samples_to_grab) % max_samples; | ||
| 706 | remaining -= samples_to_grab; | ||
| 707 | data_offset += samples_to_grab; | ||
| 708 | } | ||
| 709 | |||
| 710 | if (write_count != 0) { | ||
| 711 | dsp_info.write_offset = (dsp_info.write_offset + write_count) % max_samples; | ||
| 712 | } | ||
| 713 | return sample_count; | ||
| 714 | } | ||
| 715 | |||
| 716 | s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, | ||
| 717 | std::span<s32> out_data, u32 sample_count, u32 read_offset, | ||
| 718 | u32 read_count) { | ||
| 719 | if (max_samples == 0) { | ||
| 720 | return 0; | ||
| 721 | } | ||
| 722 | |||
| 723 | u32 offset = recv_info.read_offset + read_offset; | ||
| 724 | if (recv_buffer == 0 || offset > max_samples) { | ||
| 725 | return 0; | ||
| 726 | } | ||
| 727 | |||
| 728 | u32 remaining = sample_count; | ||
| 729 | s32 data_offset{}; | ||
| 730 | while (remaining > 0) { | ||
| 731 | const auto base = recv_buffer + (offset * sizeof(u32)); | ||
| 732 | const auto samples_to_grab = std::min(max_samples - offset, remaining); | ||
| 733 | std::vector<s32> buffer(samples_to_grab); | ||
| 734 | memory.ReadBlock(base, buffer.data(), buffer.size() * sizeof(u32)); | ||
| 735 | std::memcpy(out_data.data() + data_offset, buffer.data(), buffer.size() * sizeof(u32)); | ||
| 736 | offset = (offset + samples_to_grab) % max_samples; | ||
| 737 | remaining -= samples_to_grab; | ||
| 738 | data_offset += samples_to_grab; | ||
| 739 | } | ||
| 740 | |||
| 741 | if (read_count != 0) { | ||
| 742 | recv_info.read_offset = (recv_info.read_offset + read_count) % max_samples; | ||
| 743 | } | ||
| 744 | return sample_count; | ||
| 745 | } | ||
| 746 | |||
| 747 | void CommandGenerator::InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, | ||
| 748 | std::vector<u8>& work_buffer) { | ||
| 749 | // Reset state | ||
| 750 | state.lowpass_0 = 0.0f; | ||
| 751 | state.lowpass_1 = 0.0f; | ||
| 752 | state.lowpass_2 = 0.0f; | ||
| 753 | |||
| 754 | state.early_delay_line.Reset(); | ||
| 755 | state.early_tap_steps.fill(0); | ||
| 756 | state.early_gain = 0.0f; | ||
| 757 | state.late_gain = 0.0f; | ||
| 758 | state.early_to_late_taps = 0; | ||
| 759 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 760 | state.fdn_delay_line[i].Reset(); | ||
| 761 | state.decay_delay_line0[i].Reset(); | ||
| 762 | state.decay_delay_line1[i].Reset(); | ||
| 763 | } | ||
| 764 | state.last_reverb_echo = 0.0f; | ||
| 765 | state.center_delay_line.Reset(); | ||
| 766 | for (auto& coef : state.lpf_coefficients) { | ||
| 767 | coef.fill(0.0f); | ||
| 768 | } | ||
| 769 | state.shelf_filter.fill(0.0f); | ||
| 770 | state.dry_gain = 0.0f; | ||
| 771 | |||
| 772 | const auto sample_rate = info.sample_rate / 1000; | ||
| 773 | f32* work_buffer_ptr = reinterpret_cast<f32*>(work_buffer.data()); | ||
| 774 | |||
| 775 | s32 delay_samples{}; | ||
| 776 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 777 | delay_samples = | ||
| 778 | AudioCommon::CalculateDelaySamples(sample_rate, FDN_MAX_DELAY_LINE_TIMES[i]); | ||
| 779 | state.fdn_delay_line[i].Initialize(delay_samples, work_buffer_ptr); | ||
| 780 | work_buffer_ptr += delay_samples + 1; | ||
| 781 | |||
| 782 | delay_samples = | ||
| 783 | AudioCommon::CalculateDelaySamples(sample_rate, DECAY0_MAX_DELAY_LINE_TIMES[i]); | ||
| 784 | state.decay_delay_line0[i].Initialize(delay_samples, 0.0f, work_buffer_ptr); | ||
| 785 | work_buffer_ptr += delay_samples + 1; | ||
| 786 | |||
| 787 | delay_samples = | ||
| 788 | AudioCommon::CalculateDelaySamples(sample_rate, DECAY1_MAX_DELAY_LINE_TIMES[i]); | ||
| 789 | state.decay_delay_line1[i].Initialize(delay_samples, 0.0f, work_buffer_ptr); | ||
| 790 | work_buffer_ptr += delay_samples + 1; | ||
| 791 | } | ||
| 792 | delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 5.0f); | ||
| 793 | state.center_delay_line.Initialize(delay_samples, work_buffer_ptr); | ||
| 794 | work_buffer_ptr += delay_samples + 1; | ||
| 795 | |||
| 796 | delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 400.0f); | ||
| 797 | state.early_delay_line.Initialize(delay_samples, work_buffer_ptr); | ||
| 798 | |||
| 799 | UpdateI3dl2Reverb(info, state, true); | ||
| 800 | } | ||
| 801 | |||
| 802 | void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, | ||
| 803 | bool should_clear) { | ||
| 804 | |||
| 805 | state.dry_gain = info.dry_gain; | ||
| 806 | state.shelf_filter.fill(0.0f); | ||
| 807 | state.lowpass_0 = 0.0f; | ||
| 808 | state.early_gain = Pow10(std::min(info.room + info.reflection, 5000.0f) / 2000.0f); | ||
| 809 | state.late_gain = Pow10(std::min(info.room + info.reverb, 5000.0f) / 2000.0f); | ||
| 810 | |||
| 811 | const auto sample_rate = info.sample_rate / 1000; | ||
| 812 | const f32 hf_gain = Pow10(info.room_hf / 2000.0f); | ||
| 813 | if (hf_gain >= 1.0f) { | ||
| 814 | state.lowpass_2 = 1.0f; | ||
| 815 | state.lowpass_1 = 0.0f; | ||
| 816 | } else { | ||
| 817 | const auto a = 1.0f - hf_gain; | ||
| 818 | const auto b = 2.0f * (2.0f - hf_gain * CosD(256.0f * info.hf_reference / | ||
| 819 | static_cast<f32>(info.sample_rate))); | ||
| 820 | const auto c = std::sqrt(b * b - 4.0f * a * a); | ||
| 821 | |||
| 822 | state.lowpass_1 = (b - c) / (2.0f * a); | ||
| 823 | state.lowpass_2 = 1.0f - state.lowpass_1; | ||
| 824 | } | ||
| 825 | state.early_to_late_taps = AudioCommon::CalculateDelaySamples( | ||
| 826 | sample_rate, 1000.0f * (info.reflection_delay + info.reverb_delay)); | ||
| 827 | |||
| 828 | state.last_reverb_echo = 0.6f * info.diffusion * 0.01f; | ||
| 829 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 830 | const auto length = | ||
| 831 | FDN_MIN_DELAY_LINE_TIMES[i] + | ||
| 832 | (info.density / 100.0f) * (FDN_MAX_DELAY_LINE_TIMES[i] - FDN_MIN_DELAY_LINE_TIMES[i]); | ||
| 833 | state.fdn_delay_line[i].SetDelay(AudioCommon::CalculateDelaySamples(sample_rate, length)); | ||
| 834 | |||
| 835 | const auto delay_sample_counts = state.fdn_delay_line[i].GetDelay() + | ||
| 836 | state.decay_delay_line0[i].GetDelay() + | ||
| 837 | state.decay_delay_line1[i].GetDelay(); | ||
| 838 | |||
| 839 | float a = (-60.0f * static_cast<f32>(delay_sample_counts)) / | ||
| 840 | (info.decay_time * static_cast<f32>(info.sample_rate)); | ||
| 841 | float b = a / info.hf_decay_ratio; | ||
| 842 | float c = CosD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate)) / | ||
| 843 | SinD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate)); | ||
| 844 | float d = Pow10((b - a) / 40.0f); | ||
| 845 | float e = Pow10((b + a) / 40.0f) * 0.7071f; | ||
| 846 | |||
| 847 | state.lpf_coefficients[0][i] = e * ((d * c) + 1.0f) / (c + d); | ||
| 848 | state.lpf_coefficients[1][i] = e * (1.0f - (d * c)) / (c + d); | ||
| 849 | state.lpf_coefficients[2][i] = (c - d) / (c + d); | ||
| 850 | |||
| 851 | state.decay_delay_line0[i].SetCoefficient(state.last_reverb_echo); | ||
| 852 | state.decay_delay_line1[i].SetCoefficient(-0.9f * state.last_reverb_echo); | ||
| 853 | } | ||
| 854 | |||
| 855 | if (should_clear) { | ||
| 856 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 857 | state.fdn_delay_line[i].Clear(); | ||
| 858 | state.decay_delay_line0[i].Clear(); | ||
| 859 | state.decay_delay_line1[i].Clear(); | ||
| 860 | } | ||
| 861 | state.early_delay_line.Clear(); | ||
| 862 | state.center_delay_line.Clear(); | ||
| 863 | } | ||
| 864 | |||
| 865 | const auto max_early_delay = state.early_delay_line.GetMaxDelay(); | ||
| 866 | const auto reflection_time = 1000.0f * (0.9998f * info.reverb_delay + 0.02f); | ||
| 867 | for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) { | ||
| 868 | const auto length = AudioCommon::CalculateDelaySamples( | ||
| 869 | sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]); | ||
| 870 | state.early_tap_steps[tap] = std::min(length, max_early_delay); | ||
| 871 | } | ||
| 872 | } | ||
| 873 | |||
| 874 | void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume, | ||
| 875 | s32 channel, s32 node_id) { | ||
| 876 | const auto last = static_cast<s32>(last_volume * 32768.0f); | ||
| 877 | const auto current = static_cast<s32>(current_volume * 32768.0f); | ||
| 878 | const auto delta = static_cast<s32>((static_cast<float>(current) - static_cast<float>(last)) / | ||
| 879 | static_cast<float>(worker_params.sample_count)); | ||
| 880 | |||
| 881 | if (dumping_frame) { | ||
| 882 | LOG_DEBUG(Audio, | ||
| 883 | "(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, " | ||
| 884 | "last_volume={}, current_volume={}", | ||
| 885 | node_id, GetMixChannelBufferOffset(channel), GetMixChannelBufferOffset(channel), | ||
| 886 | last_volume, current_volume); | ||
| 887 | } | ||
| 888 | // Apply generic gain on samples | ||
| 889 | ApplyGain(GetChannelMixBuffer(channel), GetChannelMixBuffer(channel), last, delta, | ||
| 890 | worker_params.sample_count); | ||
| 891 | } | ||
| 892 | |||
| 893 | void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes, | ||
| 894 | const MixVolumeBuffer& last_mix_volumes, | ||
| 895 | VoiceState& dsp_state, s32 mix_buffer_offset, | ||
| 896 | s32 mix_buffer_count, s32 voice_index, s32 node_id) { | ||
| 897 | // Loop all our mix buffers | ||
| 898 | for (s32 i = 0; i < mix_buffer_count; i++) { | ||
| 899 | if (last_mix_volumes[i] != 0.0f || mix_volumes[i] != 0.0f) { | ||
| 900 | const auto delta = static_cast<float>((mix_volumes[i] - last_mix_volumes[i])) / | ||
| 901 | static_cast<float>(worker_params.sample_count); | ||
| 902 | |||
| 903 | if (dumping_frame) { | ||
| 904 | LOG_DEBUG(Audio, | ||
| 905 | "(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, " | ||
| 906 | "output={}, last_volume={}, current_volume={}", | ||
| 907 | node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i], | ||
| 908 | mix_volumes[i]); | ||
| 909 | } | ||
| 910 | |||
| 911 | dsp_state.previous_samples[i] = | ||
| 912 | ApplyMixRamp(GetMixBuffer(mix_buffer_offset + i), GetMixBuffer(voice_index), | ||
| 913 | last_mix_volumes[i], delta, worker_params.sample_count); | ||
| 914 | } else { | ||
| 915 | dsp_state.previous_samples[i] = 0; | ||
| 916 | } | ||
| 917 | } | ||
| 918 | } | ||
| 919 | |||
| 920 | void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) { | ||
| 921 | if (dumping_frame) { | ||
| 922 | LOG_DEBUG(Audio, "(DSP_TRACE) GenerateSubMixCommand"); | ||
| 923 | } | ||
| 924 | const auto& in_params = mix_info.GetInParams(); | ||
| 925 | GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset, | ||
| 926 | in_params.sample_rate); | ||
| 927 | |||
| 928 | GenerateEffectCommand(mix_info); | ||
| 929 | |||
| 930 | GenerateMixCommands(mix_info); | ||
| 931 | } | ||
| 932 | |||
| 933 | void CommandGenerator::GenerateMixCommands(ServerMixInfo& mix_info) { | ||
| 934 | if (!mix_info.HasAnyConnection()) { | ||
| 935 | return; | ||
| 936 | } | ||
| 937 | const auto& in_params = mix_info.GetInParams(); | ||
| 938 | if (in_params.dest_mix_id != AudioCommon::NO_MIX) { | ||
| 939 | const auto& dest_mix = mix_context.GetInfo(in_params.dest_mix_id); | ||
| 940 | const auto& dest_in_params = dest_mix.GetInParams(); | ||
| 941 | |||
| 942 | const auto buffer_count = in_params.buffer_count; | ||
| 943 | |||
| 944 | for (s32 i = 0; i < buffer_count; i++) { | ||
| 945 | for (s32 j = 0; j < dest_in_params.buffer_count; j++) { | ||
| 946 | const auto mixed_volume = in_params.volume * in_params.mix_volume[i][j]; | ||
| 947 | if (mixed_volume != 0.0f) { | ||
| 948 | GenerateMixCommand(dest_in_params.buffer_offset + j, | ||
| 949 | in_params.buffer_offset + i, mixed_volume, | ||
| 950 | in_params.node_id); | ||
| 951 | } | ||
| 952 | } | ||
| 953 | } | ||
| 954 | } else if (in_params.splitter_id != AudioCommon::NO_SPLITTER) { | ||
| 955 | s32 base{}; | ||
| 956 | while (const auto* destination_data = GetDestinationData(in_params.splitter_id, base++)) { | ||
| 957 | if (!destination_data->IsConfigured()) { | ||
| 958 | continue; | ||
| 959 | } | ||
| 960 | |||
| 961 | const auto& dest_mix = mix_context.GetInfo(destination_data->GetMixId()); | ||
| 962 | const auto& dest_in_params = dest_mix.GetInParams(); | ||
| 963 | const auto mix_index = (base - 1) % in_params.buffer_count + in_params.buffer_offset; | ||
| 964 | for (std::size_t i = 0; i < static_cast<std::size_t>(dest_in_params.buffer_count); | ||
| 965 | i++) { | ||
| 966 | const auto mixed_volume = in_params.volume * destination_data->GetMixVolume(i); | ||
| 967 | if (mixed_volume != 0.0f) { | ||
| 968 | GenerateMixCommand(dest_in_params.buffer_offset + i, mix_index, mixed_volume, | ||
| 969 | in_params.node_id); | ||
| 970 | } | ||
| 971 | } | ||
| 972 | } | ||
| 973 | } | ||
| 974 | } | ||
| 975 | |||
| 976 | void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, | ||
| 977 | float volume, s32 node_id) { | ||
| 978 | |||
| 979 | if (dumping_frame) { | ||
| 980 | LOG_DEBUG(Audio, | ||
| 981 | "(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}", | ||
| 982 | node_id, input_offset, output_offset, volume); | ||
| 983 | } | ||
| 984 | |||
| 985 | std::span<s32> output = GetMixBuffer(output_offset); | ||
| 986 | std::span<const s32> input = GetMixBuffer(input_offset); | ||
| 987 | |||
| 988 | const s32 gain = static_cast<s32>(volume * 32768.0f); | ||
| 989 | // Mix with loop unrolling | ||
| 990 | if (worker_params.sample_count % 4 == 0) { | ||
| 991 | ApplyMix<4>(output, input, gain, worker_params.sample_count); | ||
| 992 | } else if (worker_params.sample_count % 2 == 0) { | ||
| 993 | ApplyMix<2>(output, input, gain, worker_params.sample_count); | ||
| 994 | } else { | ||
| 995 | ApplyMix<1>(output, input, gain, worker_params.sample_count); | ||
| 996 | } | ||
| 997 | } | ||
| 998 | |||
| 999 | void CommandGenerator::GenerateFinalMixCommand() { | ||
| 1000 | if (dumping_frame) { | ||
| 1001 | LOG_DEBUG(Audio, "(DSP_TRACE) GenerateFinalMixCommand"); | ||
| 1002 | } | ||
| 1003 | auto& mix_info = mix_context.GetFinalMixInfo(); | ||
| 1004 | const auto& in_params = mix_info.GetInParams(); | ||
| 1005 | |||
| 1006 | GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset, | ||
| 1007 | in_params.sample_rate); | ||
| 1008 | |||
| 1009 | GenerateEffectCommand(mix_info); | ||
| 1010 | |||
| 1011 | for (s32 i = 0; i < in_params.buffer_count; i++) { | ||
| 1012 | const s32 gain = static_cast<s32>(in_params.volume * 32768.0f); | ||
| 1013 | if (dumping_frame) { | ||
| 1014 | LOG_DEBUG( | ||
| 1015 | Audio, | ||
| 1016 | "(DSP_TRACE) ApplyGainWithoutDelta node_id={}, input={}, output={}, volume={}", | ||
| 1017 | in_params.node_id, in_params.buffer_offset + i, in_params.buffer_offset + i, | ||
| 1018 | in_params.volume); | ||
| 1019 | } | ||
| 1020 | ApplyGainWithoutDelta(GetMixBuffer(in_params.buffer_offset + i), | ||
| 1021 | GetMixBuffer(in_params.buffer_offset + i), gain, | ||
| 1022 | worker_params.sample_count); | ||
| 1023 | } | ||
| 1024 | } | ||
| 1025 | |||
| 1026 | template <typename T> | ||
| 1027 | s32 CommandGenerator::DecodePcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, | ||
| 1028 | s32 sample_start_offset, s32 sample_end_offset, s32 sample_count, | ||
| 1029 | s32 channel, std::size_t mix_offset) { | ||
| 1030 | const auto& in_params = voice_info.GetInParams(); | ||
| 1031 | const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; | ||
| 1032 | if (wave_buffer.buffer_address == 0) { | ||
| 1033 | return 0; | ||
| 1034 | } | ||
| 1035 | if (wave_buffer.buffer_size == 0) { | ||
| 1036 | return 0; | ||
| 1037 | } | ||
| 1038 | if (sample_end_offset < sample_start_offset) { | ||
| 1039 | return 0; | ||
| 1040 | } | ||
| 1041 | const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset; | ||
| 1042 | const auto start_offset = | ||
| 1043 | ((dsp_state.offset + sample_start_offset) * in_params.channel_count) * sizeof(T); | ||
| 1044 | const auto buffer_pos = wave_buffer.buffer_address + start_offset; | ||
| 1045 | const auto samples_processed = std::min(sample_count, samples_remaining); | ||
| 1046 | |||
| 1047 | const auto channel_count = in_params.channel_count; | ||
| 1048 | std::vector<T> buffer(samples_processed * channel_count); | ||
| 1049 | memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(T)); | ||
| 1050 | |||
| 1051 | if constexpr (std::is_floating_point_v<T>) { | ||
| 1052 | for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) { | ||
| 1053 | sample_buffer[mix_offset + i] = static_cast<s32>(buffer[i * channel_count + channel] * | ||
| 1054 | std::numeric_limits<s16>::max()); | ||
| 1055 | } | ||
| 1056 | } else if constexpr (sizeof(T) == 1) { | ||
| 1057 | for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) { | ||
| 1058 | sample_buffer[mix_offset + i] = | ||
| 1059 | static_cast<s32>(static_cast<f32>(buffer[i * channel_count + channel] / | ||
| 1060 | std::numeric_limits<s8>::max()) * | ||
| 1061 | std::numeric_limits<s16>::max()); | ||
| 1062 | } | ||
| 1063 | } else if constexpr (sizeof(T) == 2) { | ||
| 1064 | for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) { | ||
| 1065 | sample_buffer[mix_offset + i] = buffer[i * channel_count + channel]; | ||
| 1066 | } | ||
| 1067 | } else { | ||
| 1068 | for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) { | ||
| 1069 | sample_buffer[mix_offset + i] = | ||
| 1070 | static_cast<s32>(static_cast<f32>(buffer[i * channel_count + channel] / | ||
| 1071 | std::numeric_limits<s32>::max()) * | ||
| 1072 | std::numeric_limits<s16>::max()); | ||
| 1073 | } | ||
| 1074 | } | ||
| 1075 | |||
| 1076 | return samples_processed; | ||
| 1077 | } | ||
| 1078 | |||
| 1079 | s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, | ||
| 1080 | s32 sample_start_offset, s32 sample_end_offset, s32 sample_count, | ||
| 1081 | [[maybe_unused]] s32 channel, std::size_t mix_offset) { | ||
| 1082 | const auto& in_params = voice_info.GetInParams(); | ||
| 1083 | const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; | ||
| 1084 | if (wave_buffer.buffer_address == 0) { | ||
| 1085 | return 0; | ||
| 1086 | } | ||
| 1087 | if (wave_buffer.buffer_size == 0) { | ||
| 1088 | return 0; | ||
| 1089 | } | ||
| 1090 | if (sample_end_offset < sample_start_offset) { | ||
| 1091 | return 0; | ||
| 1092 | } | ||
| 1093 | |||
| 1094 | static constexpr std::array<int, 16> SIGNED_NIBBLES{ | ||
| 1095 | 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1, | ||
| 1096 | }; | ||
| 1097 | |||
| 1098 | constexpr std::size_t FRAME_LEN = 8; | ||
| 1099 | constexpr std::size_t NIBBLES_PER_SAMPLE = 16; | ||
| 1100 | constexpr std::size_t SAMPLES_PER_FRAME = 14; | ||
| 1101 | |||
| 1102 | auto frame_header = dsp_state.context.header; | ||
| 1103 | s32 idx = (frame_header >> 4) & 0xf; | ||
| 1104 | s32 scale = frame_header & 0xf; | ||
| 1105 | s16 yn1 = dsp_state.context.yn1; | ||
| 1106 | s16 yn2 = dsp_state.context.yn2; | ||
| 1107 | |||
| 1108 | Codec::ADPCM_Coeff coeffs; | ||
| 1109 | memory.ReadBlock(in_params.additional_params_address, coeffs.data(), | ||
| 1110 | sizeof(Codec::ADPCM_Coeff)); | ||
| 1111 | |||
| 1112 | s32 coef1 = coeffs[idx * 2]; | ||
| 1113 | s32 coef2 = coeffs[idx * 2 + 1]; | ||
| 1114 | |||
| 1115 | const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset; | ||
| 1116 | const auto samples_processed = std::min(sample_count, samples_remaining); | ||
| 1117 | const auto sample_pos = dsp_state.offset + sample_start_offset; | ||
| 1118 | |||
| 1119 | const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME; | ||
| 1120 | auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) + | ||
| 1121 | samples_remaining_in_frame + (samples_remaining_in_frame != 0 ? 2 : 0); | ||
| 1122 | |||
| 1123 | const auto decode_sample = [&](const int nibble) -> s16 { | ||
| 1124 | const int xn = nibble * (1 << scale); | ||
| 1125 | // We first transform everything into 11 bit fixed point, perform the second order | ||
| 1126 | // digital filter, then transform back. | ||
| 1127 | // 0x400 == 0.5 in 11 bit fixed point. | ||
| 1128 | // Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2] | ||
| 1129 | int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11; | ||
| 1130 | // Clamp to output range. | ||
| 1131 | val = std::clamp<s32>(val, -32768, 32767); | ||
| 1132 | // Advance output feedback. | ||
| 1133 | yn2 = yn1; | ||
| 1134 | yn1 = static_cast<s16>(val); | ||
| 1135 | return yn1; | ||
| 1136 | }; | ||
| 1137 | |||
| 1138 | std::size_t buffer_offset{}; | ||
| 1139 | std::vector<u8> buffer( | ||
| 1140 | std::max((samples_processed / FRAME_LEN) * SAMPLES_PER_FRAME, FRAME_LEN)); | ||
| 1141 | memory.ReadBlock(wave_buffer.buffer_address + (position_in_frame / 2), buffer.data(), | ||
| 1142 | buffer.size()); | ||
| 1143 | std::size_t cur_mix_offset = mix_offset; | ||
| 1144 | |||
| 1145 | auto remaining_samples = samples_processed; | ||
| 1146 | while (remaining_samples > 0) { | ||
| 1147 | if (position_in_frame % NIBBLES_PER_SAMPLE == 0) { | ||
| 1148 | // Read header | ||
| 1149 | frame_header = buffer[buffer_offset++]; | ||
| 1150 | idx = (frame_header >> 4) & 0xf; | ||
| 1151 | scale = frame_header & 0xf; | ||
| 1152 | coef1 = coeffs[idx * 2]; | ||
| 1153 | coef2 = coeffs[idx * 2 + 1]; | ||
| 1154 | position_in_frame += 2; | ||
| 1155 | |||
| 1156 | // Decode entire frame | ||
| 1157 | if (remaining_samples >= static_cast<int>(SAMPLES_PER_FRAME)) { | ||
| 1158 | for (std::size_t i = 0; i < SAMPLES_PER_FRAME / 2; i++) { | ||
| 1159 | // Sample 1 | ||
| 1160 | const s32 s0 = SIGNED_NIBBLES[buffer[buffer_offset] >> 4]; | ||
| 1161 | const s32 s1 = SIGNED_NIBBLES[buffer[buffer_offset++] & 0xf]; | ||
| 1162 | const s16 sample_1 = decode_sample(s0); | ||
| 1163 | const s16 sample_2 = decode_sample(s1); | ||
| 1164 | sample_buffer[cur_mix_offset++] = sample_1; | ||
| 1165 | sample_buffer[cur_mix_offset++] = sample_2; | ||
| 1166 | } | ||
| 1167 | remaining_samples -= static_cast<int>(SAMPLES_PER_FRAME); | ||
| 1168 | position_in_frame += SAMPLES_PER_FRAME; | ||
| 1169 | continue; | ||
| 1170 | } | ||
| 1171 | } | ||
| 1172 | // Decode mid frame | ||
| 1173 | s32 current_nibble = buffer[buffer_offset]; | ||
| 1174 | if (position_in_frame++ & 0x1) { | ||
| 1175 | current_nibble &= 0xf; | ||
| 1176 | buffer_offset++; | ||
| 1177 | } else { | ||
| 1178 | current_nibble >>= 4; | ||
| 1179 | } | ||
| 1180 | const s16 sample = decode_sample(SIGNED_NIBBLES[current_nibble]); | ||
| 1181 | sample_buffer[cur_mix_offset++] = sample; | ||
| 1182 | remaining_samples--; | ||
| 1183 | } | ||
| 1184 | |||
| 1185 | dsp_state.context.header = frame_header; | ||
| 1186 | dsp_state.context.yn1 = yn1; | ||
| 1187 | dsp_state.context.yn2 = yn2; | ||
| 1188 | |||
| 1189 | return samples_processed; | ||
| 1190 | } | ||
| 1191 | |||
| 1192 | std::span<s32> CommandGenerator::GetMixBuffer(std::size_t index) { | ||
| 1193 | return std::span<s32>(mix_buffer.data() + (index * worker_params.sample_count), | ||
| 1194 | worker_params.sample_count); | ||
| 1195 | } | ||
| 1196 | |||
| 1197 | std::span<const s32> CommandGenerator::GetMixBuffer(std::size_t index) const { | ||
| 1198 | return std::span<const s32>(mix_buffer.data() + (index * worker_params.sample_count), | ||
| 1199 | worker_params.sample_count); | ||
| 1200 | } | ||
| 1201 | |||
| 1202 | std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const { | ||
| 1203 | return worker_params.mix_buffer_count + channel; | ||
| 1204 | } | ||
| 1205 | |||
| 1206 | std::size_t CommandGenerator::GetTotalMixBufferCount() const { | ||
| 1207 | return worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT; | ||
| 1208 | } | ||
| 1209 | |||
| 1210 | std::span<s32> CommandGenerator::GetChannelMixBuffer(s32 channel) { | ||
| 1211 | return GetMixBuffer(worker_params.mix_buffer_count + channel); | ||
| 1212 | } | ||
| 1213 | |||
| 1214 | std::span<const s32> CommandGenerator::GetChannelMixBuffer(s32 channel) const { | ||
| 1215 | return GetMixBuffer(worker_params.mix_buffer_count + channel); | ||
| 1216 | } | ||
| 1217 | |||
| 1218 | void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, std::span<s32> output, | ||
| 1219 | VoiceState& dsp_state, s32 channel, | ||
| 1220 | s32 target_sample_rate, s32 sample_count, | ||
| 1221 | s32 node_id) { | ||
| 1222 | const auto& in_params = voice_info.GetInParams(); | ||
| 1223 | if (dumping_frame) { | ||
| 1224 | LOG_DEBUG(Audio, | ||
| 1225 | "(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, " | ||
| 1226 | "format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}", | ||
| 1227 | node_id, channel, in_params.sample_format, sample_count, in_params.sample_rate, | ||
| 1228 | in_params.mix_id, in_params.splitter_info_id); | ||
| 1229 | } | ||
| 1230 | ASSERT_OR_EXECUTE(output.data() != nullptr, { return; }); | ||
| 1231 | |||
| 1232 | const auto resample_rate = static_cast<s32>( | ||
| 1233 | static_cast<float>(in_params.sample_rate) / static_cast<float>(target_sample_rate) * | ||
| 1234 | static_cast<float>(static_cast<s32>(in_params.pitch * 32768.0f))); | ||
| 1235 | if (dsp_state.fraction + sample_count * resample_rate > | ||
| 1236 | static_cast<s32>(SCALED_MIX_BUFFER_SIZE - 4ULL)) { | ||
| 1237 | return; | ||
| 1238 | } | ||
| 1239 | |||
| 1240 | auto min_required_samples = | ||
| 1241 | std::min(static_cast<s32>(SCALED_MIX_BUFFER_SIZE) - dsp_state.fraction, resample_rate); | ||
| 1242 | if (min_required_samples >= sample_count) { | ||
| 1243 | min_required_samples = sample_count; | ||
| 1244 | } | ||
| 1245 | |||
| 1246 | std::size_t temp_mix_offset{}; | ||
| 1247 | s32 samples_output{}; | ||
| 1248 | auto samples_remaining = sample_count; | ||
| 1249 | while (samples_remaining > 0) { | ||
| 1250 | const auto samples_to_output = std::min(samples_remaining, min_required_samples); | ||
| 1251 | const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15; | ||
| 1252 | |||
| 1253 | if (!in_params.behavior_flags.is_pitch_and_src_skipped) { | ||
| 1254 | // Append sample histtory for resampler | ||
| 1255 | for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) { | ||
| 1256 | sample_buffer[temp_mix_offset + i] = dsp_state.sample_history[i]; | ||
| 1257 | } | ||
| 1258 | temp_mix_offset += 4; | ||
| 1259 | } | ||
| 1260 | |||
| 1261 | s32 samples_read{}; | ||
| 1262 | while (samples_read < samples_to_read) { | ||
| 1263 | const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; | ||
| 1264 | // No more data can be read | ||
| 1265 | if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) { | ||
| 1266 | break; | ||
| 1267 | } | ||
| 1268 | |||
| 1269 | if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 && | ||
| 1270 | wave_buffer.context_address != 0 && wave_buffer.context_size != 0) { | ||
| 1271 | memory.ReadBlock(wave_buffer.context_address, &dsp_state.context, | ||
| 1272 | sizeof(ADPCMContext)); | ||
| 1273 | } | ||
| 1274 | |||
| 1275 | s32 samples_offset_start; | ||
| 1276 | s32 samples_offset_end; | ||
| 1277 | if (dsp_state.loop_count > 0 && wave_buffer.loop_start_sample != 0 && | ||
| 1278 | wave_buffer.loop_end_sample != 0 && | ||
| 1279 | wave_buffer.loop_start_sample <= wave_buffer.loop_end_sample) { | ||
| 1280 | samples_offset_start = wave_buffer.loop_start_sample; | ||
| 1281 | samples_offset_end = wave_buffer.loop_end_sample; | ||
| 1282 | } else { | ||
| 1283 | samples_offset_start = wave_buffer.start_sample_offset; | ||
| 1284 | samples_offset_end = wave_buffer.end_sample_offset; | ||
| 1285 | } | ||
| 1286 | |||
| 1287 | s32 samples_decoded{0}; | ||
| 1288 | switch (in_params.sample_format) { | ||
| 1289 | case SampleFormat::Pcm8: | ||
| 1290 | samples_decoded = | ||
| 1291 | DecodePcm<s8>(voice_info, dsp_state, samples_offset_start, samples_offset_end, | ||
| 1292 | samples_to_read - samples_read, channel, temp_mix_offset); | ||
| 1293 | break; | ||
| 1294 | case SampleFormat::Pcm16: | ||
| 1295 | samples_decoded = | ||
| 1296 | DecodePcm<s16>(voice_info, dsp_state, samples_offset_start, samples_offset_end, | ||
| 1297 | samples_to_read - samples_read, channel, temp_mix_offset); | ||
| 1298 | break; | ||
| 1299 | case SampleFormat::Pcm32: | ||
| 1300 | samples_decoded = | ||
| 1301 | DecodePcm<s32>(voice_info, dsp_state, samples_offset_start, samples_offset_end, | ||
| 1302 | samples_to_read - samples_read, channel, temp_mix_offset); | ||
| 1303 | break; | ||
| 1304 | case SampleFormat::PcmFloat: | ||
| 1305 | samples_decoded = | ||
| 1306 | DecodePcm<f32>(voice_info, dsp_state, samples_offset_start, samples_offset_end, | ||
| 1307 | samples_to_read - samples_read, channel, temp_mix_offset); | ||
| 1308 | break; | ||
| 1309 | case SampleFormat::Adpcm: | ||
| 1310 | samples_decoded = | ||
| 1311 | DecodeAdpcm(voice_info, dsp_state, samples_offset_start, samples_offset_end, | ||
| 1312 | samples_to_read - samples_read, channel, temp_mix_offset); | ||
| 1313 | break; | ||
| 1314 | default: | ||
| 1315 | ASSERT_MSG(false, "Unimplemented sample format={}", in_params.sample_format); | ||
| 1316 | } | ||
| 1317 | |||
| 1318 | temp_mix_offset += samples_decoded; | ||
| 1319 | samples_read += samples_decoded; | ||
| 1320 | dsp_state.offset += samples_decoded; | ||
| 1321 | dsp_state.played_sample_count += samples_decoded; | ||
| 1322 | |||
| 1323 | if (dsp_state.offset >= (samples_offset_end - samples_offset_start) || | ||
| 1324 | samples_decoded == 0) { | ||
| 1325 | // Reset our sample offset | ||
| 1326 | dsp_state.offset = 0; | ||
| 1327 | if (wave_buffer.is_looping) { | ||
| 1328 | dsp_state.loop_count++; | ||
| 1329 | if (wave_buffer.loop_count > 0 && | ||
| 1330 | (dsp_state.loop_count > wave_buffer.loop_count || samples_decoded == 0)) { | ||
| 1331 | // End of our buffer | ||
| 1332 | voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer); | ||
| 1333 | } | ||
| 1334 | |||
| 1335 | if (samples_decoded == 0) { | ||
| 1336 | break; | ||
| 1337 | } | ||
| 1338 | |||
| 1339 | if (in_params.behavior_flags.is_played_samples_reset_at_loop_point.Value()) { | ||
| 1340 | dsp_state.played_sample_count = 0; | ||
| 1341 | } | ||
| 1342 | } else { | ||
| 1343 | // Update our wave buffer states | ||
| 1344 | voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer); | ||
| 1345 | } | ||
| 1346 | } | ||
| 1347 | } | ||
| 1348 | |||
| 1349 | if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) { | ||
| 1350 | // No need to resample | ||
| 1351 | std::memcpy(output.data() + samples_output, sample_buffer.data(), | ||
| 1352 | samples_read * sizeof(s32)); | ||
| 1353 | } else { | ||
| 1354 | std::fill(sample_buffer.begin() + temp_mix_offset, | ||
| 1355 | sample_buffer.begin() + temp_mix_offset + (samples_to_read - samples_read), | ||
| 1356 | 0); | ||
| 1357 | AudioCore::Resample(output.data() + samples_output, sample_buffer.data(), resample_rate, | ||
| 1358 | dsp_state.fraction, samples_to_output); | ||
| 1359 | // Resample | ||
| 1360 | for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) { | ||
| 1361 | dsp_state.sample_history[i] = sample_buffer[samples_to_read + i]; | ||
| 1362 | } | ||
| 1363 | } | ||
| 1364 | samples_remaining -= samples_to_output; | ||
| 1365 | samples_output += samples_to_output; | ||
| 1366 | } | ||
| 1367 | } | ||
| 1368 | |||
| 1369 | } // namespace AudioCore | ||
diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h deleted file mode 100644 index 8077e7768..000000000 --- a/src/audio_core/command_generator.h +++ /dev/null | |||
| @@ -1,110 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 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 "audio_core/common.h" | ||
| 9 | #include "audio_core/voice_context.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace Core::Memory { | ||
| 13 | class Memory; | ||
| 14 | } | ||
| 15 | |||
| 16 | namespace AudioCore { | ||
| 17 | class MixContext; | ||
| 18 | class SplitterContext; | ||
| 19 | class ServerSplitterDestinationData; | ||
| 20 | class ServerMixInfo; | ||
| 21 | class EffectContext; | ||
| 22 | class EffectBase; | ||
| 23 | struct AuxInfoDSP; | ||
| 24 | struct I3dl2ReverbParams; | ||
| 25 | struct I3dl2ReverbState; | ||
| 26 | using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>; | ||
| 27 | |||
| 28 | class CommandGenerator { | ||
| 29 | public: | ||
| 30 | explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_, | ||
| 31 | VoiceContext& voice_context_, MixContext& mix_context_, | ||
| 32 | SplitterContext& splitter_context_, EffectContext& effect_context_, | ||
| 33 | Core::Memory::Memory& memory_); | ||
| 34 | ~CommandGenerator(); | ||
| 35 | |||
| 36 | void ClearMixBuffers(); | ||
| 37 | void GenerateVoiceCommands(); | ||
| 38 | void GenerateVoiceCommand(ServerVoiceInfo& voice_info); | ||
| 39 | void GenerateSubMixCommands(); | ||
| 40 | void GenerateFinalMixCommands(); | ||
| 41 | void PreCommand(); | ||
| 42 | void PostCommand(); | ||
| 43 | |||
| 44 | [[nodiscard]] std::span<s32> GetChannelMixBuffer(s32 channel); | ||
| 45 | [[nodiscard]] std::span<const s32> GetChannelMixBuffer(s32 channel) const; | ||
| 46 | [[nodiscard]] std::span<s32> GetMixBuffer(std::size_t index); | ||
| 47 | [[nodiscard]] std::span<const s32> GetMixBuffer(std::size_t index) const; | ||
| 48 | [[nodiscard]] std::size_t GetMixChannelBufferOffset(s32 channel) const; | ||
| 49 | |||
| 50 | [[nodiscard]] std::size_t GetTotalMixBufferCount() const; | ||
| 51 | |||
| 52 | private: | ||
| 53 | void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel); | ||
| 54 | void GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state, | ||
| 55 | s32 mix_buffer_count, s32 channel); | ||
| 56 | void GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel, | ||
| 57 | s32 node_id); | ||
| 58 | void GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes, | ||
| 59 | const MixVolumeBuffer& last_mix_volumes, VoiceState& dsp_state, | ||
| 60 | s32 mix_buffer_offset, s32 mix_buffer_count, s32 voice_index, | ||
| 61 | s32 node_id); | ||
| 62 | void GenerateSubMixCommand(ServerMixInfo& mix_info); | ||
| 63 | void GenerateMixCommands(ServerMixInfo& mix_info); | ||
| 64 | void GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, float volume, | ||
| 65 | s32 node_id); | ||
| 66 | void GenerateFinalMixCommand(); | ||
| 67 | void GenerateBiquadFilterCommand(s32 mix_buffer, const BiquadFilterParameter& params, | ||
| 68 | std::array<s64, 2>& state, std::size_t input_offset, | ||
| 69 | std::size_t output_offset, s32 sample_count, s32 node_id); | ||
| 70 | void GenerateDepopPrepareCommand(VoiceState& dsp_state, std::size_t mix_buffer_count, | ||
| 71 | std::size_t mix_buffer_offset); | ||
| 72 | void GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count, | ||
| 73 | std::size_t mix_buffer_offset, s32 sample_rate); | ||
| 74 | void GenerateEffectCommand(ServerMixInfo& mix_info); | ||
| 75 | void GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); | ||
| 76 | void GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); | ||
| 77 | void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); | ||
| 78 | [[nodiscard]] ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index); | ||
| 79 | |||
| 80 | s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, | ||
| 81 | std::span<const s32> data, u32 sample_count, u32 write_offset, | ||
| 82 | u32 write_count); | ||
| 83 | s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, | ||
| 84 | std::span<s32> out_data, u32 sample_count, u32 read_offset, u32 read_count); | ||
| 85 | |||
| 86 | void InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, | ||
| 87 | std::vector<u8>& work_buffer); | ||
| 88 | void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear); | ||
| 89 | // DSP Code | ||
| 90 | template <typename T> | ||
| 91 | s32 DecodePcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset, | ||
| 92 | s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset); | ||
| 93 | s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset, | ||
| 94 | s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset); | ||
| 95 | void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, std::span<s32> output, | ||
| 96 | VoiceState& dsp_state, s32 channel, s32 target_sample_rate, | ||
| 97 | s32 sample_count, s32 node_id); | ||
| 98 | |||
| 99 | AudioCommon::AudioRendererParameter& worker_params; | ||
| 100 | VoiceContext& voice_context; | ||
| 101 | MixContext& mix_context; | ||
| 102 | SplitterContext& splitter_context; | ||
| 103 | EffectContext& effect_context; | ||
| 104 | Core::Memory::Memory& memory; | ||
| 105 | std::vector<s32> mix_buffer{}; | ||
| 106 | std::vector<s32> sample_buffer{}; | ||
| 107 | std::vector<s32> depop_buffer{}; | ||
| 108 | bool dumping_frame{false}; | ||
| 109 | }; | ||
| 110 | } // namespace AudioCore | ||
diff --git a/src/audio_core/common.h b/src/audio_core/common.h deleted file mode 100644 index 056a0ac70..000000000 --- a/src/audio_core/common.h +++ /dev/null | |||
| @@ -1,132 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | #include "common/swap.h" | ||
| 9 | #include "core/hle/result.h" | ||
| 10 | |||
| 11 | namespace AudioCommon { | ||
| 12 | namespace Audren { | ||
| 13 | constexpr Result ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41}; | ||
| 14 | constexpr Result ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43}; | ||
| 15 | } // namespace Audren | ||
| 16 | |||
| 17 | constexpr u8 BASE_REVISION = '0'; | ||
| 18 | constexpr u32_le CURRENT_PROCESS_REVISION = | ||
| 19 | Common::MakeMagic('R', 'E', 'V', static_cast<u8>(BASE_REVISION + 0xA)); | ||
| 20 | constexpr std::size_t MAX_MIX_BUFFERS = 24; | ||
| 21 | constexpr std::size_t MAX_BIQUAD_FILTERS = 2; | ||
| 22 | constexpr std::size_t MAX_CHANNEL_COUNT = 6; | ||
| 23 | constexpr std::size_t MAX_WAVE_BUFFERS = 4; | ||
| 24 | constexpr std::size_t MAX_SAMPLE_HISTORY = 4; | ||
| 25 | constexpr u32 STREAM_SAMPLE_RATE = 48000; | ||
| 26 | constexpr u32 STREAM_NUM_CHANNELS = 2; | ||
| 27 | constexpr s32 NO_SPLITTER = -1; | ||
| 28 | constexpr s32 NO_MIX = 0x7fffffff; | ||
| 29 | constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min(); | ||
| 30 | constexpr s32 FINAL_MIX = 0; | ||
| 31 | constexpr s32 NO_EFFECT_ORDER = -1; | ||
| 32 | constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant | ||
| 33 | // Any size checks seem to take the sample history into account | ||
| 34 | // and our const ends up being 0x3f04, the 4 bytes are most | ||
| 35 | // likely the sample history | ||
| 36 | constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY; | ||
| 37 | constexpr f32 I3DL2REVERB_MAX_LEVEL = 5000.0f; | ||
| 38 | constexpr f32 I3DL2REVERB_MIN_REFLECTION_DURATION = 0.02f; | ||
| 39 | constexpr std::size_t I3DL2REVERB_TAPS = 20; | ||
| 40 | constexpr std::size_t I3DL2REVERB_DELAY_LINE_COUNT = 4; | ||
| 41 | using Fractional = s32; | ||
| 42 | |||
| 43 | template <typename T> | ||
| 44 | constexpr Fractional ToFractional(T x) { | ||
| 45 | return static_cast<Fractional>(x * static_cast<T>(0x4000)); | ||
| 46 | } | ||
| 47 | |||
| 48 | constexpr Fractional MultiplyFractional(Fractional lhs, Fractional rhs) { | ||
| 49 | return static_cast<Fractional>(static_cast<s64>(lhs) * rhs >> 14); | ||
| 50 | } | ||
| 51 | |||
| 52 | constexpr s32 FractionalToFixed(Fractional x) { | ||
| 53 | const auto s = x & (1 << 13); | ||
| 54 | return static_cast<s32>(x >> 14) + s; | ||
| 55 | } | ||
| 56 | |||
| 57 | constexpr s32 CalculateDelaySamples(s32 sample_rate_khz, float time) { | ||
| 58 | return FractionalToFixed(MultiplyFractional(ToFractional(sample_rate_khz), ToFractional(time))); | ||
| 59 | } | ||
| 60 | |||
| 61 | static constexpr u32 VersionFromRevision(u32_le rev) { | ||
| 62 | // "REV7" -> 7 | ||
| 63 | return ((rev >> 24) & 0xff) - 0x30; | ||
| 64 | } | ||
| 65 | |||
| 66 | static constexpr bool IsRevisionSupported(u32 required, u32_le user_revision) { | ||
| 67 | const auto base = VersionFromRevision(user_revision); | ||
| 68 | return required <= base; | ||
| 69 | } | ||
| 70 | |||
| 71 | static constexpr bool IsValidRevision(u32_le revision) { | ||
| 72 | const auto base = VersionFromRevision(revision); | ||
| 73 | constexpr auto max_rev = VersionFromRevision(CURRENT_PROCESS_REVISION); | ||
| 74 | return base <= max_rev; | ||
| 75 | } | ||
| 76 | |||
| 77 | static constexpr bool CanConsumeBuffer(std::size_t size, std::size_t offset, std::size_t required) { | ||
| 78 | if (offset > size) { | ||
| 79 | return false; | ||
| 80 | } | ||
| 81 | if (size < required) { | ||
| 82 | return false; | ||
| 83 | } | ||
| 84 | if ((size - offset) < required) { | ||
| 85 | return false; | ||
| 86 | } | ||
| 87 | return true; | ||
| 88 | } | ||
| 89 | |||
| 90 | struct UpdateDataSizes { | ||
| 91 | u32_le behavior{}; | ||
| 92 | u32_le memory_pool{}; | ||
| 93 | u32_le voice{}; | ||
| 94 | u32_le voice_channel_resource{}; | ||
| 95 | u32_le effect{}; | ||
| 96 | u32_le mixer{}; | ||
| 97 | u32_le sink{}; | ||
| 98 | u32_le performance{}; | ||
| 99 | u32_le splitter{}; | ||
| 100 | u32_le render_info{}; | ||
| 101 | INSERT_PADDING_WORDS(4); | ||
| 102 | }; | ||
| 103 | static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size"); | ||
| 104 | |||
| 105 | struct UpdateDataHeader { | ||
| 106 | u32_le revision{}; | ||
| 107 | UpdateDataSizes size{}; | ||
| 108 | u32_le total_size{}; | ||
| 109 | }; | ||
| 110 | static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size"); | ||
| 111 | |||
| 112 | struct AudioRendererParameter { | ||
| 113 | u32_le sample_rate; | ||
| 114 | u32_le sample_count; | ||
| 115 | u32_le mix_buffer_count; | ||
| 116 | u32_le submix_count; | ||
| 117 | u32_le voice_count; | ||
| 118 | u32_le sink_count; | ||
| 119 | u32_le effect_count; | ||
| 120 | u32_le performance_frame_count; | ||
| 121 | u8 is_voice_drop_enabled; | ||
| 122 | u8 unknown_21; | ||
| 123 | u8 unknown_22; | ||
| 124 | u8 execution_mode; | ||
| 125 | u32_le splitter_count; | ||
| 126 | u32_le num_splitter_send_channels; | ||
| 127 | u32_le unknown_30; | ||
| 128 | u32_le revision; | ||
| 129 | }; | ||
| 130 | static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size"); | ||
| 131 | |||
| 132 | } // namespace AudioCommon | ||
diff --git a/src/audio_core/common/audio_renderer_parameter.h b/src/audio_core/common/audio_renderer_parameter.h new file mode 100644 index 000000000..2f62c383b --- /dev/null +++ b/src/audio_core/common/audio_renderer_parameter.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 <span> | ||
| 7 | |||
| 8 | #include "audio_core/renderer/behavior/behavior_info.h" | ||
| 9 | #include "audio_core/renderer/memory/memory_pool_info.h" | ||
| 10 | #include "audio_core/renderer/upsampler/upsampler_manager.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace AudioCore { | ||
| 14 | /** | ||
| 15 | * Execution mode of the audio renderer. | ||
| 16 | * Only Auto is currently supported. | ||
| 17 | */ | ||
| 18 | enum class ExecutionMode : u8 { | ||
| 19 | Auto, | ||
| 20 | Manual, | ||
| 21 | }; | ||
| 22 | |||
| 23 | /** | ||
| 24 | * Parameters from the game, passed to the audio renderer for initialisation. | ||
| 25 | */ | ||
| 26 | struct AudioRendererParameterInternal { | ||
| 27 | /* 0x00 */ u32 sample_rate; | ||
| 28 | /* 0x04 */ u32 sample_count; | ||
| 29 | /* 0x08 */ u32 mixes; | ||
| 30 | /* 0x0C */ u32 sub_mixes; | ||
| 31 | /* 0x10 */ u32 voices; | ||
| 32 | /* 0x14 */ u32 sinks; | ||
| 33 | /* 0x18 */ u32 effects; | ||
| 34 | /* 0x1C */ u32 perf_frames; | ||
| 35 | /* 0x20 */ u16 voice_drop_enabled; | ||
| 36 | /* 0x22 */ u8 rendering_device; | ||
| 37 | /* 0x23 */ ExecutionMode execution_mode; | ||
| 38 | /* 0x24 */ u32 splitter_infos; | ||
| 39 | /* 0x28 */ s32 splitter_destinations; | ||
| 40 | /* 0x2C */ u32 external_context_size; | ||
| 41 | /* 0x30 */ u32 revision; | ||
| 42 | /* 0x34 */ char unk34[0x4]; | ||
| 43 | }; | ||
| 44 | static_assert(sizeof(AudioRendererParameterInternal) == 0x38, | ||
| 45 | "AudioRendererParameterInternal has the wrong size!"); | ||
| 46 | |||
| 47 | /** | ||
| 48 | * Context for rendering, contains a bunch of useful fields for the command generator. | ||
| 49 | */ | ||
| 50 | struct AudioRendererSystemContext { | ||
| 51 | s32 session_id; | ||
| 52 | s8 channels; | ||
| 53 | s16 mix_buffer_count; | ||
| 54 | AudioRenderer::BehaviorInfo* behavior; | ||
| 55 | std::span<s32> depop_buffer; | ||
| 56 | AudioRenderer::UpsamplerManager* upsampler_manager; | ||
| 57 | AudioRenderer::MemoryPoolInfo* memory_pool_info; | ||
| 58 | }; | ||
| 59 | |||
| 60 | } // namespace AudioCore | ||
diff --git a/src/audio_core/common/common.h b/src/audio_core/common/common.h new file mode 100644 index 000000000..6abd9be45 --- /dev/null +++ b/src/audio_core/common/common.h | |||
| @@ -0,0 +1,138 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <numeric> | ||
| 7 | #include <span> | ||
| 8 | |||
| 9 | #include "common/assert.h" | ||
| 10 | #include "common/common_funcs.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace AudioCore { | ||
| 14 | using CpuAddr = std::uintptr_t; | ||
| 15 | |||
| 16 | enum class PlayState : u8 { | ||
| 17 | Started, | ||
| 18 | Stopped, | ||
| 19 | Paused, | ||
| 20 | }; | ||
| 21 | |||
| 22 | enum class SrcQuality : u8 { | ||
| 23 | Medium, | ||
| 24 | High, | ||
| 25 | Low, | ||
| 26 | }; | ||
| 27 | |||
| 28 | enum class SampleFormat : u8 { | ||
| 29 | Invalid, | ||
| 30 | PcmInt8, | ||
| 31 | PcmInt16, | ||
| 32 | PcmInt24, | ||
| 33 | PcmInt32, | ||
| 34 | PcmFloat, | ||
| 35 | Adpcm, | ||
| 36 | }; | ||
| 37 | |||
| 38 | enum class SessionTypes { | ||
| 39 | AudioIn, | ||
| 40 | AudioOut, | ||
| 41 | FinalOutputRecorder, | ||
| 42 | }; | ||
| 43 | |||
| 44 | enum class Channels : u32 { | ||
| 45 | FrontLeft, | ||
| 46 | FrontRight, | ||
| 47 | Center, | ||
| 48 | LFE, | ||
| 49 | BackLeft, | ||
| 50 | BackRight, | ||
| 51 | }; | ||
| 52 | |||
| 53 | // These are used by Delay, Reverb and I3dl2Reverb prior to Revision 11. | ||
| 54 | enum class OldChannels : u32 { | ||
| 55 | FrontLeft, | ||
| 56 | FrontRight, | ||
| 57 | BackLeft, | ||
| 58 | BackRight, | ||
| 59 | Center, | ||
| 60 | LFE, | ||
| 61 | }; | ||
| 62 | |||
| 63 | constexpr u32 BufferCount = 32; | ||
| 64 | |||
| 65 | constexpr u32 MaxRendererSessions = 2; | ||
| 66 | constexpr u32 TargetSampleCount = 240; | ||
| 67 | constexpr u32 TargetSampleRate = 48'000; | ||
| 68 | constexpr u32 MaxChannels = 6; | ||
| 69 | constexpr u32 MaxMixBuffers = 24; | ||
| 70 | constexpr u32 MaxWaveBuffers = 4; | ||
| 71 | constexpr s32 LowestVoicePriority = 0xFF; | ||
| 72 | constexpr s32 HighestVoicePriority = 0; | ||
| 73 | constexpr u32 BufferAlignment = 0x40; | ||
| 74 | constexpr u32 WorkbufferAlignment = 0x1000; | ||
| 75 | constexpr s32 FinalMixId = 0; | ||
| 76 | constexpr s32 InvalidDistanceFromFinalMix = std::numeric_limits<s32>::min(); | ||
| 77 | constexpr s32 UnusedSplitterId = -1; | ||
| 78 | constexpr s32 UnusedMixId = std::numeric_limits<s32>::max(); | ||
| 79 | constexpr u32 InvalidNodeId = 0xF0000000; | ||
| 80 | constexpr s32 InvalidProcessOrder = -1; | ||
| 81 | constexpr u32 MaxBiquadFilters = 2; | ||
| 82 | constexpr u32 MaxEffects = 256; | ||
| 83 | |||
| 84 | constexpr bool IsChannelCountValid(u16 channel_count) { | ||
| 85 | return channel_count <= 6 && | ||
| 86 | (channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6); | ||
| 87 | } | ||
| 88 | |||
| 89 | constexpr void UseOldChannelMapping(std::span<s16> inputs, std::span<s16> outputs) { | ||
| 90 | constexpr auto old_center{static_cast<u32>(OldChannels::Center)}; | ||
| 91 | constexpr auto new_center{static_cast<u32>(Channels::Center)}; | ||
| 92 | constexpr auto old_lfe{static_cast<u32>(OldChannels::LFE)}; | ||
| 93 | constexpr auto new_lfe{static_cast<u32>(Channels::LFE)}; | ||
| 94 | |||
| 95 | auto center{inputs[old_center]}; | ||
| 96 | auto lfe{inputs[old_lfe]}; | ||
| 97 | inputs[old_center] = inputs[new_center]; | ||
| 98 | inputs[old_lfe] = inputs[new_lfe]; | ||
| 99 | inputs[new_center] = center; | ||
| 100 | inputs[new_lfe] = lfe; | ||
| 101 | |||
| 102 | center = outputs[old_center]; | ||
| 103 | lfe = outputs[old_lfe]; | ||
| 104 | outputs[old_center] = outputs[new_center]; | ||
| 105 | outputs[old_lfe] = outputs[new_lfe]; | ||
| 106 | outputs[new_center] = center; | ||
| 107 | outputs[new_lfe] = lfe; | ||
| 108 | } | ||
| 109 | |||
| 110 | constexpr u32 GetSplitterInParamHeaderMagic() { | ||
| 111 | return Common::MakeMagic('S', 'N', 'D', 'H'); | ||
| 112 | } | ||
| 113 | |||
| 114 | constexpr u32 GetSplitterInfoMagic() { | ||
| 115 | return Common::MakeMagic('S', 'N', 'D', 'I'); | ||
| 116 | } | ||
| 117 | |||
| 118 | constexpr u32 GetSplitterSendDataMagic() { | ||
| 119 | return Common::MakeMagic('S', 'N', 'D', 'D'); | ||
| 120 | } | ||
| 121 | |||
| 122 | constexpr size_t GetSampleFormatByteSize(SampleFormat format) { | ||
| 123 | switch (format) { | ||
| 124 | case SampleFormat::PcmInt8: | ||
| 125 | return 1; | ||
| 126 | case SampleFormat::PcmInt16: | ||
| 127 | return 2; | ||
| 128 | case SampleFormat::PcmInt24: | ||
| 129 | return 3; | ||
| 130 | case SampleFormat::PcmInt32: | ||
| 131 | case SampleFormat::PcmFloat: | ||
| 132 | return 4; | ||
| 133 | default: | ||
| 134 | return 2; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | } // namespace AudioCore | ||
diff --git a/src/audio_core/common/feature_support.h b/src/audio_core/common/feature_support.h new file mode 100644 index 000000000..55c9e690d --- /dev/null +++ b/src/audio_core/common/feature_support.h | |||
| @@ -0,0 +1,105 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <map> | ||
| 7 | #include <ranges> | ||
| 8 | #include <tuple> | ||
| 9 | |||
| 10 | #include "common/assert.h" | ||
| 11 | #include "common/common_funcs.h" | ||
| 12 | #include "common/common_types.h" | ||
| 13 | |||
| 14 | namespace AudioCore { | ||
| 15 | constexpr u32 CurrentRevision = 11; | ||
| 16 | |||
| 17 | enum class SupportTags { | ||
| 18 | CommandProcessingTimeEstimatorVersion4, | ||
| 19 | CommandProcessingTimeEstimatorVersion3, | ||
| 20 | CommandProcessingTimeEstimatorVersion2, | ||
| 21 | MultiTapBiquadFilterProcessing, | ||
| 22 | EffectInfoVer2, | ||
| 23 | WaveBufferVer2, | ||
| 24 | BiquadFilterFloatProcessing, | ||
| 25 | VolumeMixParameterPrecisionQ23, | ||
| 26 | MixInParameterDirtyOnlyUpdate, | ||
| 27 | BiquadFilterEffectStateClearBugFix, | ||
| 28 | VoicePlayedSampleCountResetAtLoopPoint, | ||
| 29 | VoicePitchAndSrcSkipped, | ||
| 30 | SplitterBugFix, | ||
| 31 | FlushVoiceWaveBuffers, | ||
| 32 | ElapsedFrameCount, | ||
| 33 | AudioRendererVariadicCommandBufferSize, | ||
| 34 | PerformanceMetricsDataFormatVersion2, | ||
| 35 | AudioRendererProcessingTimeLimit80Percent, | ||
| 36 | AudioRendererProcessingTimeLimit75Percent, | ||
| 37 | AudioRendererProcessingTimeLimit70Percent, | ||
| 38 | AdpcmLoopContextBugFix, | ||
| 39 | Splitter, | ||
| 40 | LongSizePreDelay, | ||
| 41 | AudioUsbDeviceOutput, | ||
| 42 | DeviceApiVersion2, | ||
| 43 | DelayChannelMappingChange, | ||
| 44 | ReverbChannelMappingChange, | ||
| 45 | I3dl2ReverbChannelMappingChange, | ||
| 46 | |||
| 47 | // Not a real tag, just here to get the count. | ||
| 48 | Size | ||
| 49 | }; | ||
| 50 | |||
| 51 | constexpr u32 GetRevisionNum(u32 user_revision) { | ||
| 52 | if (user_revision >= 0x100) { | ||
| 53 | user_revision -= Common::MakeMagic('R', 'E', 'V', '0'); | ||
| 54 | user_revision >>= 24; | ||
| 55 | } | ||
| 56 | return user_revision; | ||
| 57 | }; | ||
| 58 | |||
| 59 | constexpr bool CheckFeatureSupported(SupportTags tag, u32 user_revision) { | ||
| 60 | constexpr std::array<std::pair<SupportTags, u32>, static_cast<u32>(SupportTags::Size)> features{ | ||
| 61 | { | ||
| 62 | {SupportTags::AudioRendererProcessingTimeLimit70Percent, 1}, | ||
| 63 | {SupportTags::Splitter, 2}, | ||
| 64 | {SupportTags::AdpcmLoopContextBugFix, 2}, | ||
| 65 | {SupportTags::LongSizePreDelay, 3}, | ||
| 66 | {SupportTags::AudioUsbDeviceOutput, 4}, | ||
| 67 | {SupportTags::AudioRendererProcessingTimeLimit75Percent, 4}, | ||
| 68 | {SupportTags::VoicePlayedSampleCountResetAtLoopPoint, 5}, | ||
| 69 | {SupportTags::VoicePitchAndSrcSkipped, 5}, | ||
| 70 | {SupportTags::SplitterBugFix, 5}, | ||
| 71 | {SupportTags::FlushVoiceWaveBuffers, 5}, | ||
| 72 | {SupportTags::ElapsedFrameCount, 5}, | ||
| 73 | {SupportTags::AudioRendererProcessingTimeLimit80Percent, 5}, | ||
| 74 | {SupportTags::AudioRendererVariadicCommandBufferSize, 5}, | ||
| 75 | {SupportTags::PerformanceMetricsDataFormatVersion2, 5}, | ||
| 76 | {SupportTags::CommandProcessingTimeEstimatorVersion2, 5}, | ||
| 77 | {SupportTags::BiquadFilterEffectStateClearBugFix, 6}, | ||
| 78 | {SupportTags::BiquadFilterFloatProcessing, 7}, | ||
| 79 | {SupportTags::VolumeMixParameterPrecisionQ23, 7}, | ||
| 80 | {SupportTags::MixInParameterDirtyOnlyUpdate, 7}, | ||
| 81 | {SupportTags::WaveBufferVer2, 8}, | ||
| 82 | {SupportTags::CommandProcessingTimeEstimatorVersion3, 8}, | ||
| 83 | {SupportTags::EffectInfoVer2, 9}, | ||
| 84 | {SupportTags::CommandProcessingTimeEstimatorVersion4, 10}, | ||
| 85 | {SupportTags::MultiTapBiquadFilterProcessing, 10}, | ||
| 86 | {SupportTags::DelayChannelMappingChange, 11}, | ||
| 87 | {SupportTags::ReverbChannelMappingChange, 11}, | ||
| 88 | {SupportTags::I3dl2ReverbChannelMappingChange, 11}, | ||
| 89 | }}; | ||
| 90 | |||
| 91 | const auto& feature = | ||
| 92 | std::ranges::find_if(features, [tag](const auto& entry) { return entry.first == tag; }); | ||
| 93 | if (feature == features.cend()) { | ||
| 94 | LOG_ERROR(Service_Audio, "Invalid SupportTag {}!", static_cast<u32>(tag)); | ||
| 95 | return false; | ||
| 96 | } | ||
| 97 | user_revision = GetRevisionNum(user_revision); | ||
| 98 | return (*feature).second <= user_revision; | ||
| 99 | } | ||
| 100 | |||
| 101 | constexpr bool CheckValidRevision(u32 user_revision) { | ||
| 102 | return GetRevisionNum(user_revision) <= CurrentRevision; | ||
| 103 | }; | ||
| 104 | |||
| 105 | } // namespace AudioCore | ||
diff --git a/src/audio_core/common/wave_buffer.h b/src/audio_core/common/wave_buffer.h new file mode 100644 index 000000000..fc478ef79 --- /dev/null +++ b/src/audio_core/common/wave_buffer.h | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_types.h" | ||
| 7 | |||
| 8 | namespace AudioCore { | ||
| 9 | |||
| 10 | struct WaveBufferVersion1 { | ||
| 11 | CpuAddr buffer; | ||
| 12 | u64 buffer_size; | ||
| 13 | u32 start_offset; | ||
| 14 | u32 end_offset; | ||
| 15 | bool loop; | ||
| 16 | bool stream_ended; | ||
| 17 | CpuAddr context; | ||
| 18 | u64 context_size; | ||
| 19 | }; | ||
| 20 | |||
| 21 | struct WaveBufferVersion2 { | ||
| 22 | CpuAddr buffer; | ||
| 23 | CpuAddr context; | ||
| 24 | u64 buffer_size; | ||
| 25 | u64 context_size; | ||
| 26 | u32 start_offset; | ||
| 27 | u32 end_offset; | ||
| 28 | u32 loop_start_offset; | ||
| 29 | u32 loop_end_offset; | ||
| 30 | s32 loop_count; | ||
| 31 | bool loop; | ||
| 32 | bool stream_ended; | ||
| 33 | }; | ||
| 34 | |||
| 35 | } // namespace AudioCore | ||
diff --git a/src/audio_core/common/workbuffer_allocator.h b/src/audio_core/common/workbuffer_allocator.h new file mode 100644 index 000000000..fb89f97fe --- /dev/null +++ b/src/audio_core/common/workbuffer_allocator.h | |||
| @@ -0,0 +1,100 @@ | |||
| 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 "common/alignment.h" | ||
| 9 | #include "common/assert.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore { | ||
| 13 | /** | ||
| 14 | * Responsible for allocating up a workbuffer into multiple pieces. | ||
| 15 | * Takes in a buffer and size (it does not own them), and allocates up the buffer via Allocate. | ||
| 16 | */ | ||
| 17 | class WorkbufferAllocator { | ||
| 18 | public: | ||
| 19 | explicit WorkbufferAllocator(std::span<u8> buffer_, u64 size_) | ||
| 20 | : buffer{reinterpret_cast<u64>(buffer_.data())}, size{size_} {} | ||
| 21 | |||
| 22 | /** | ||
| 23 | * Allocate the given count of T elements, aligned to alignment. | ||
| 24 | * | ||
| 25 | * @param count - The number of elements to allocate. | ||
| 26 | * @param alignment - The required starting alignment. | ||
| 27 | * @return Non-owning container of allocated elements. | ||
| 28 | */ | ||
| 29 | template <typename T> | ||
| 30 | std::span<T> Allocate(u64 count, u64 alignment) { | ||
| 31 | u64 out{0}; | ||
| 32 | u64 byte_size{count * sizeof(T)}; | ||
| 33 | |||
| 34 | if (byte_size > 0) { | ||
| 35 | auto current{buffer + offset}; | ||
| 36 | auto aligned_buffer{Common::AlignUp(current, alignment)}; | ||
| 37 | if (aligned_buffer + byte_size <= buffer + size) { | ||
| 38 | out = aligned_buffer; | ||
| 39 | offset = byte_size - buffer + aligned_buffer; | ||
| 40 | } else { | ||
| 41 | LOG_ERROR( | ||
| 42 | Service_Audio, | ||
| 43 | "Allocated buffer was too small to hold new alloc.\nAllocator size={:08X}, " | ||
| 44 | "offset={:08X}.\nAttempting to allocate {:08X} with alignment={:02X}", | ||
| 45 | size, offset, byte_size, alignment); | ||
| 46 | count = 0; | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | return std::span<T>(reinterpret_cast<T*>(out), count); | ||
| 51 | } | ||
| 52 | |||
| 53 | /** | ||
| 54 | * Align the current offset to the given alignment. | ||
| 55 | * | ||
| 56 | * @param alignment - The required starting alignment. | ||
| 57 | */ | ||
| 58 | void Align(u64 alignment) { | ||
| 59 | auto current{buffer + offset}; | ||
| 60 | auto aligned_buffer{Common::AlignUp(current, alignment)}; | ||
| 61 | offset = 0 - buffer + aligned_buffer; | ||
| 62 | } | ||
| 63 | |||
| 64 | /** | ||
| 65 | * Get the current buffer offset. | ||
| 66 | * | ||
| 67 | * @return The current allocating offset. | ||
| 68 | */ | ||
| 69 | u64 GetCurrentOffset() const { | ||
| 70 | return offset; | ||
| 71 | } | ||
| 72 | |||
| 73 | /** | ||
| 74 | * Get the current buffer size. | ||
| 75 | * | ||
| 76 | * @return The size of the current buffer. | ||
| 77 | */ | ||
| 78 | u64 GetSize() const { | ||
| 79 | return size; | ||
| 80 | } | ||
| 81 | |||
| 82 | /** | ||
| 83 | * Get the remaining size that can be allocated. | ||
| 84 | * | ||
| 85 | * @return The remaining size left in the buffer. | ||
| 86 | */ | ||
| 87 | u64 GetRemainingSize() const { | ||
| 88 | return size - offset; | ||
| 89 | } | ||
| 90 | |||
| 91 | private: | ||
| 92 | /// The buffer into which we are allocating. | ||
| 93 | u64 buffer; | ||
| 94 | /// Size of the buffer we're allocating to. | ||
| 95 | u64 size; | ||
| 96 | /// Current offset into the buffer, an error will be thrown if it exceeds size. | ||
| 97 | u64 offset{}; | ||
| 98 | }; | ||
| 99 | |||
| 100 | } // namespace AudioCore | ||
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp deleted file mode 100644 index e48c1ee8e..000000000 --- a/src/audio_core/cubeb_sink.cpp +++ /dev/null | |||
| @@ -1,249 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <atomic> | ||
| 6 | #include <cstring> | ||
| 7 | #include "audio_core/cubeb_sink.h" | ||
| 8 | #include "audio_core/stream.h" | ||
| 9 | #include "common/assert.h" | ||
| 10 | #include "common/logging/log.h" | ||
| 11 | #include "common/ring_buffer.h" | ||
| 12 | #include "common/settings.h" | ||
| 13 | |||
| 14 | #ifdef _WIN32 | ||
| 15 | #include <objbase.h> | ||
| 16 | #endif | ||
| 17 | |||
| 18 | namespace AudioCore { | ||
| 19 | |||
| 20 | class CubebSinkStream final : public SinkStream { | ||
| 21 | public: | ||
| 22 | CubebSinkStream(cubeb* ctx_, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, | ||
| 23 | const std::string& name) | ||
| 24 | : ctx{ctx_}, num_channels{std::min(num_channels_, 6u)} { | ||
| 25 | |||
| 26 | cubeb_stream_params params{}; | ||
| 27 | params.rate = sample_rate; | ||
| 28 | params.channels = num_channels; | ||
| 29 | params.format = CUBEB_SAMPLE_S16NE; | ||
| 30 | params.prefs = CUBEB_STREAM_PREF_PERSIST; | ||
| 31 | switch (num_channels) { | ||
| 32 | case 1: | ||
| 33 | params.layout = CUBEB_LAYOUT_MONO; | ||
| 34 | break; | ||
| 35 | case 2: | ||
| 36 | params.layout = CUBEB_LAYOUT_STEREO; | ||
| 37 | break; | ||
| 38 | case 6: | ||
| 39 | params.layout = CUBEB_LAYOUT_3F2_LFE; | ||
| 40 | break; | ||
| 41 | } | ||
| 42 | |||
| 43 | u32 minimum_latency{}; | ||
| 44 | if (cubeb_get_min_latency(ctx, ¶ms, &minimum_latency) != CUBEB_OK) { | ||
| 45 | LOG_CRITICAL(Audio_Sink, "Error getting minimum latency"); | ||
| 46 | } | ||
| 47 | |||
| 48 | if (cubeb_stream_init(ctx, &stream_backend, name.c_str(), nullptr, nullptr, output_device, | ||
| 49 | ¶ms, std::max(512u, minimum_latency), | ||
| 50 | &CubebSinkStream::DataCallback, &CubebSinkStream::StateCallback, | ||
| 51 | this) != CUBEB_OK) { | ||
| 52 | LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream"); | ||
| 53 | return; | ||
| 54 | } | ||
| 55 | |||
| 56 | if (cubeb_stream_start(stream_backend) != CUBEB_OK) { | ||
| 57 | LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); | ||
| 58 | return; | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | ~CubebSinkStream() override { | ||
| 63 | if (!ctx) { | ||
| 64 | return; | ||
| 65 | } | ||
| 66 | |||
| 67 | if (cubeb_stream_stop(stream_backend) != CUBEB_OK) { | ||
| 68 | LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); | ||
| 69 | } | ||
| 70 | |||
| 71 | cubeb_stream_destroy(stream_backend); | ||
| 72 | } | ||
| 73 | |||
| 74 | void EnqueueSamples(u32 source_num_channels, const std::vector<s16>& samples) override { | ||
| 75 | if (source_num_channels > num_channels) { | ||
| 76 | // Downsample 6 channels to 2 | ||
| 77 | ASSERT_MSG(source_num_channels == 6, "Channel count must be 6"); | ||
| 78 | |||
| 79 | std::vector<s16> buf; | ||
| 80 | buf.reserve(samples.size() * num_channels / source_num_channels); | ||
| 81 | for (std::size_t i = 0; i < samples.size(); i += source_num_channels) { | ||
| 82 | // Downmixing implementation taken from the ATSC standard | ||
| 83 | const s16 left{samples[i + 0]}; | ||
| 84 | const s16 right{samples[i + 1]}; | ||
| 85 | const s16 center{samples[i + 2]}; | ||
| 86 | const s16 surround_left{samples[i + 4]}; | ||
| 87 | const s16 surround_right{samples[i + 5]}; | ||
| 88 | // Not used in the ATSC reference implementation | ||
| 89 | [[maybe_unused]] const s16 low_frequency_effects{samples[i + 3]}; | ||
| 90 | |||
| 91 | constexpr s32 clev{707}; // center mixing level coefficient | ||
| 92 | constexpr s32 slev{707}; // surround mixing level coefficient | ||
| 93 | |||
| 94 | buf.push_back(static_cast<s16>(left + (clev * center / 1000) + | ||
| 95 | (slev * surround_left / 1000))); | ||
| 96 | buf.push_back(static_cast<s16>(right + (clev * center / 1000) + | ||
| 97 | (slev * surround_right / 1000))); | ||
| 98 | } | ||
| 99 | queue.Push(buf); | ||
| 100 | return; | ||
| 101 | } | ||
| 102 | |||
| 103 | queue.Push(samples); | ||
| 104 | } | ||
| 105 | |||
| 106 | std::size_t SamplesInQueue(u32 channel_count) const override { | ||
| 107 | if (!ctx) | ||
| 108 | return 0; | ||
| 109 | |||
| 110 | return queue.Size() / channel_count; | ||
| 111 | } | ||
| 112 | |||
| 113 | void Flush() override { | ||
| 114 | should_flush = true; | ||
| 115 | } | ||
| 116 | |||
| 117 | u32 GetNumChannels() const { | ||
| 118 | return num_channels; | ||
| 119 | } | ||
| 120 | |||
| 121 | private: | ||
| 122 | std::vector<std::string> device_list; | ||
| 123 | |||
| 124 | cubeb* ctx{}; | ||
| 125 | cubeb_stream* stream_backend{}; | ||
| 126 | u32 num_channels{}; | ||
| 127 | |||
| 128 | Common::RingBuffer<s16, 0x10000> queue; | ||
| 129 | std::array<s16, 2> last_frame{}; | ||
| 130 | std::atomic<bool> should_flush{}; | ||
| 131 | |||
| 132 | static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, | ||
| 133 | void* output_buffer, long num_frames); | ||
| 134 | static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state); | ||
| 135 | }; | ||
| 136 | |||
| 137 | CubebSink::CubebSink(std::string_view target_device_name) { | ||
| 138 | // Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows | ||
| 139 | #ifdef _WIN32 | ||
| 140 | com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); | ||
| 141 | #endif | ||
| 142 | |||
| 143 | if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) { | ||
| 144 | LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); | ||
| 145 | return; | ||
| 146 | } | ||
| 147 | |||
| 148 | if (target_device_name != auto_device_name && !target_device_name.empty()) { | ||
| 149 | cubeb_device_collection collection; | ||
| 150 | if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) { | ||
| 151 | LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported"); | ||
| 152 | } else { | ||
| 153 | const auto collection_end{collection.device + collection.count}; | ||
| 154 | const auto device{ | ||
| 155 | std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) { | ||
| 156 | return info.friendly_name != nullptr && | ||
| 157 | target_device_name == info.friendly_name; | ||
| 158 | })}; | ||
| 159 | if (device != collection_end) { | ||
| 160 | output_device = device->devid; | ||
| 161 | } | ||
| 162 | cubeb_device_collection_destroy(ctx, &collection); | ||
| 163 | } | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | CubebSink::~CubebSink() { | ||
| 168 | if (!ctx) { | ||
| 169 | return; | ||
| 170 | } | ||
| 171 | |||
| 172 | for (auto& sink_stream : sink_streams) { | ||
| 173 | sink_stream.reset(); | ||
| 174 | } | ||
| 175 | |||
| 176 | cubeb_destroy(ctx); | ||
| 177 | |||
| 178 | #ifdef _WIN32 | ||
| 179 | if (SUCCEEDED(com_init_result)) { | ||
| 180 | CoUninitialize(); | ||
| 181 | } | ||
| 182 | #endif | ||
| 183 | } | ||
| 184 | |||
| 185 | SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, | ||
| 186 | const std::string& name) { | ||
| 187 | sink_streams.push_back( | ||
| 188 | std::make_unique<CubebSinkStream>(ctx, sample_rate, num_channels, output_device, name)); | ||
| 189 | return *sink_streams.back(); | ||
| 190 | } | ||
| 191 | |||
| 192 | long CubebSinkStream::DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data, | ||
| 193 | [[maybe_unused]] const void* input_buffer, void* output_buffer, | ||
| 194 | long num_frames) { | ||
| 195 | auto* impl = static_cast<CubebSinkStream*>(user_data); | ||
| 196 | auto* buffer = static_cast<u8*>(output_buffer); | ||
| 197 | |||
| 198 | if (!impl) { | ||
| 199 | return {}; | ||
| 200 | } | ||
| 201 | |||
| 202 | const std::size_t num_channels = impl->GetNumChannels(); | ||
| 203 | const std::size_t samples_to_write = num_channels * num_frames; | ||
| 204 | const std::size_t samples_written = impl->queue.Pop(buffer, samples_to_write); | ||
| 205 | |||
| 206 | if (samples_written >= num_channels) { | ||
| 207 | std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16), | ||
| 208 | num_channels * sizeof(s16)); | ||
| 209 | } | ||
| 210 | |||
| 211 | // Fill the rest of the frames with last_frame | ||
| 212 | for (std::size_t i = samples_written; i < samples_to_write; i += num_channels) { | ||
| 213 | std::memcpy(buffer + i * sizeof(s16), &impl->last_frame[0], num_channels * sizeof(s16)); | ||
| 214 | } | ||
| 215 | |||
| 216 | return num_frames; | ||
| 217 | } | ||
| 218 | |||
| 219 | void CubebSinkStream::StateCallback([[maybe_unused]] cubeb_stream* stream, | ||
| 220 | [[maybe_unused]] void* user_data, | ||
| 221 | [[maybe_unused]] cubeb_state state) {} | ||
| 222 | |||
| 223 | std::vector<std::string> ListCubebSinkDevices() { | ||
| 224 | std::vector<std::string> device_list; | ||
| 225 | cubeb* ctx; | ||
| 226 | |||
| 227 | if (cubeb_init(&ctx, "yuzu Device Enumerator", nullptr) != CUBEB_OK) { | ||
| 228 | LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); | ||
| 229 | return {}; | ||
| 230 | } | ||
| 231 | |||
| 232 | cubeb_device_collection collection; | ||
| 233 | if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) { | ||
| 234 | LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported"); | ||
| 235 | } else { | ||
| 236 | for (std::size_t i = 0; i < collection.count; i++) { | ||
| 237 | const cubeb_device_info& device = collection.device[i]; | ||
| 238 | if (device.friendly_name) { | ||
| 239 | device_list.emplace_back(device.friendly_name); | ||
| 240 | } | ||
| 241 | } | ||
| 242 | cubeb_device_collection_destroy(ctx, &collection); | ||
| 243 | } | ||
| 244 | |||
| 245 | cubeb_destroy(ctx); | ||
| 246 | return device_list; | ||
| 247 | } | ||
| 248 | |||
| 249 | } // namespace AudioCore | ||
diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h deleted file mode 100644 index c124b7ee8..000000000 --- a/src/audio_core/cubeb_sink.h +++ /dev/null | |||
| @@ -1,35 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | #include <vector> | ||
| 8 | |||
| 9 | #include <cubeb/cubeb.h> | ||
| 10 | |||
| 11 | #include "audio_core/sink.h" | ||
| 12 | |||
| 13 | namespace AudioCore { | ||
| 14 | |||
| 15 | class CubebSink final : public Sink { | ||
| 16 | public: | ||
| 17 | explicit CubebSink(std::string_view device_id); | ||
| 18 | ~CubebSink() override; | ||
| 19 | |||
| 20 | SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels, | ||
| 21 | const std::string& name) override; | ||
| 22 | |||
| 23 | private: | ||
| 24 | cubeb* ctx{}; | ||
| 25 | cubeb_devid output_device{}; | ||
| 26 | std::vector<SinkStreamPtr> sink_streams; | ||
| 27 | |||
| 28 | #ifdef _WIN32 | ||
| 29 | u32 com_init_result = 0; | ||
| 30 | #endif | ||
| 31 | }; | ||
| 32 | |||
| 33 | std::vector<std::string> ListCubebSinkDevices(); | ||
| 34 | |||
| 35 | } // namespace AudioCore | ||
diff --git a/src/audio_core/delay_line.cpp b/src/audio_core/delay_line.cpp deleted file mode 100644 index b1626a71b..000000000 --- a/src/audio_core/delay_line.cpp +++ /dev/null | |||
| @@ -1,107 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <cstring> | ||
| 5 | #include "audio_core/delay_line.h" | ||
| 6 | |||
| 7 | namespace AudioCore { | ||
| 8 | DelayLineBase::DelayLineBase() = default; | ||
| 9 | DelayLineBase::~DelayLineBase() = default; | ||
| 10 | |||
| 11 | void DelayLineBase::Initialize(s32 max_delay_, float* src_buffer) { | ||
| 12 | buffer = src_buffer; | ||
| 13 | buffer_end = buffer + max_delay_; | ||
| 14 | max_delay = max_delay_; | ||
| 15 | output = buffer; | ||
| 16 | SetDelay(max_delay_); | ||
| 17 | Clear(); | ||
| 18 | } | ||
| 19 | |||
| 20 | void DelayLineBase::SetDelay(s32 new_delay) { | ||
| 21 | if (max_delay < new_delay) { | ||
| 22 | return; | ||
| 23 | } | ||
| 24 | delay = new_delay; | ||
| 25 | input = (buffer + ((output - buffer) + new_delay) % (max_delay + 1)); | ||
| 26 | } | ||
| 27 | |||
| 28 | s32 DelayLineBase::GetDelay() const { | ||
| 29 | return delay; | ||
| 30 | } | ||
| 31 | |||
| 32 | s32 DelayLineBase::GetMaxDelay() const { | ||
| 33 | return max_delay; | ||
| 34 | } | ||
| 35 | |||
| 36 | f32 DelayLineBase::TapOut(s32 last_sample) { | ||
| 37 | const float* ptr = input - (last_sample + 1); | ||
| 38 | if (ptr < buffer) { | ||
| 39 | ptr += (max_delay + 1); | ||
| 40 | } | ||
| 41 | |||
| 42 | return *ptr; | ||
| 43 | } | ||
| 44 | |||
| 45 | f32 DelayLineBase::Tick(f32 sample) { | ||
| 46 | *(input++) = sample; | ||
| 47 | const auto out_sample = *(output++); | ||
| 48 | |||
| 49 | if (buffer_end < input) { | ||
| 50 | input = buffer; | ||
| 51 | } | ||
| 52 | |||
| 53 | if (buffer_end < output) { | ||
| 54 | output = buffer; | ||
| 55 | } | ||
| 56 | |||
| 57 | return out_sample; | ||
| 58 | } | ||
| 59 | |||
| 60 | float* DelayLineBase::GetInput() { | ||
| 61 | return input; | ||
| 62 | } | ||
| 63 | |||
| 64 | const float* DelayLineBase::GetInput() const { | ||
| 65 | return input; | ||
| 66 | } | ||
| 67 | |||
| 68 | f32 DelayLineBase::GetOutputSample() const { | ||
| 69 | return *output; | ||
| 70 | } | ||
| 71 | |||
| 72 | void DelayLineBase::Clear() { | ||
| 73 | std::memset(buffer, 0, sizeof(float) * max_delay); | ||
| 74 | } | ||
| 75 | |||
| 76 | void DelayLineBase::Reset() { | ||
| 77 | buffer = nullptr; | ||
| 78 | buffer_end = nullptr; | ||
| 79 | max_delay = 0; | ||
| 80 | input = nullptr; | ||
| 81 | output = nullptr; | ||
| 82 | delay = 0; | ||
| 83 | } | ||
| 84 | |||
| 85 | DelayLineAllPass::DelayLineAllPass() = default; | ||
| 86 | DelayLineAllPass::~DelayLineAllPass() = default; | ||
| 87 | |||
| 88 | void DelayLineAllPass::Initialize(u32 delay_, float coeffcient_, f32* src_buffer) { | ||
| 89 | DelayLineBase::Initialize(delay_, src_buffer); | ||
| 90 | SetCoefficient(coeffcient_); | ||
| 91 | } | ||
| 92 | |||
| 93 | void DelayLineAllPass::SetCoefficient(float coeffcient_) { | ||
| 94 | coefficient = coeffcient_; | ||
| 95 | } | ||
| 96 | |||
| 97 | f32 DelayLineAllPass::Tick(f32 sample) { | ||
| 98 | const auto temp = sample - coefficient * *output; | ||
| 99 | return coefficient * temp + DelayLineBase::Tick(temp); | ||
| 100 | } | ||
| 101 | |||
| 102 | void DelayLineAllPass::Reset() { | ||
| 103 | coefficient = 0.0f; | ||
| 104 | DelayLineBase::Reset(); | ||
| 105 | } | ||
| 106 | |||
| 107 | } // namespace AudioCore | ||
diff --git a/src/audio_core/delay_line.h b/src/audio_core/delay_line.h deleted file mode 100644 index 05fda536f..000000000 --- a/src/audio_core/delay_line.h +++ /dev/null | |||
| @@ -1,49 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_types.h" | ||
| 7 | |||
| 8 | namespace AudioCore { | ||
| 9 | |||
| 10 | class DelayLineBase { | ||
| 11 | public: | ||
| 12 | DelayLineBase(); | ||
| 13 | ~DelayLineBase(); | ||
| 14 | |||
| 15 | void Initialize(s32 max_delay_, float* src_buffer); | ||
| 16 | void SetDelay(s32 new_delay); | ||
| 17 | s32 GetDelay() const; | ||
| 18 | s32 GetMaxDelay() const; | ||
| 19 | f32 TapOut(s32 last_sample); | ||
| 20 | f32 Tick(f32 sample); | ||
| 21 | float* GetInput(); | ||
| 22 | const float* GetInput() const; | ||
| 23 | f32 GetOutputSample() const; | ||
| 24 | void Clear(); | ||
| 25 | void Reset(); | ||
| 26 | |||
| 27 | protected: | ||
| 28 | float* buffer{nullptr}; | ||
| 29 | float* buffer_end{nullptr}; | ||
| 30 | s32 max_delay{}; | ||
| 31 | float* input{nullptr}; | ||
| 32 | float* output{nullptr}; | ||
| 33 | s32 delay{}; | ||
| 34 | }; | ||
| 35 | |||
| 36 | class DelayLineAllPass final : public DelayLineBase { | ||
| 37 | public: | ||
| 38 | DelayLineAllPass(); | ||
| 39 | ~DelayLineAllPass(); | ||
| 40 | |||
| 41 | void Initialize(u32 delay, float coeffcient_, f32* src_buffer); | ||
| 42 | void SetCoefficient(float coeffcient_); | ||
| 43 | f32 Tick(f32 sample); | ||
| 44 | void Reset(); | ||
| 45 | |||
| 46 | private: | ||
| 47 | float coefficient{}; | ||
| 48 | }; | ||
| 49 | } // namespace AudioCore | ||
diff --git a/src/audio_core/device/audio_buffer.h b/src/audio_core/device/audio_buffer.h new file mode 100644 index 000000000..cae7fa970 --- /dev/null +++ b/src/audio_core/device/audio_buffer.h | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_types.h" | ||
| 7 | |||
| 8 | namespace AudioCore { | ||
| 9 | |||
| 10 | struct AudioBuffer { | ||
| 11 | /// Timestamp this buffer completed playing. | ||
| 12 | s64 played_timestamp; | ||
| 13 | /// Game memory address for these samples. | ||
| 14 | VAddr samples; | ||
| 15 | /// Unqiue identifier for this buffer. | ||
| 16 | u64 tag; | ||
| 17 | /// Size of the samples buffer. | ||
| 18 | u64 size; | ||
| 19 | }; | ||
| 20 | |||
| 21 | } // namespace AudioCore | ||
diff --git a/src/audio_core/device/audio_buffers.h b/src/audio_core/device/audio_buffers.h new file mode 100644 index 000000000..5d1979ea0 --- /dev/null +++ b/src/audio_core/device/audio_buffers.h | |||
| @@ -0,0 +1,304 @@ | |||
| 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 <mutex> | ||
| 8 | #include <span> | ||
| 9 | #include <vector> | ||
| 10 | |||
| 11 | #include "audio_buffer.h" | ||
| 12 | #include "audio_core/device/device_session.h" | ||
| 13 | #include "core/core_timing.h" | ||
| 14 | |||
| 15 | namespace AudioCore { | ||
| 16 | |||
| 17 | constexpr s32 BufferAppendLimit = 4; | ||
| 18 | |||
| 19 | /** | ||
| 20 | * A ringbuffer of N audio buffers. | ||
| 21 | * The buffer contains 3 sections: | ||
| 22 | * Appended - Buffers added to the ring, but have yet to be sent to the audio backend. | ||
| 23 | * Registered - Buffers sent to the backend and queued for playback. | ||
| 24 | * Released - Buffers which have been played, and can now be recycled. | ||
| 25 | * Any others are free/untracked. | ||
| 26 | * | ||
| 27 | * @tparam N - Maximum number of buffers in the ring. | ||
| 28 | */ | ||
| 29 | template <size_t N> | ||
| 30 | class AudioBuffers { | ||
| 31 | public: | ||
| 32 | explicit AudioBuffers(size_t limit) : append_limit{static_cast<u32>(limit)} {} | ||
| 33 | |||
| 34 | /** | ||
| 35 | * Append a new audio buffer to the ring. | ||
| 36 | * | ||
| 37 | * @param buffer - The new buffer. | ||
| 38 | */ | ||
| 39 | void AppendBuffer(AudioBuffer& buffer) { | ||
| 40 | std::scoped_lock l{lock}; | ||
| 41 | buffers[appended_index] = buffer; | ||
| 42 | appended_count++; | ||
| 43 | appended_index = (appended_index + 1) % append_limit; | ||
| 44 | } | ||
| 45 | |||
| 46 | /** | ||
| 47 | * Register waiting buffers, up to a maximum of BufferAppendLimit. | ||
| 48 | * | ||
| 49 | * @param out_buffers - The buffers which were registered. | ||
| 50 | */ | ||
| 51 | void RegisterBuffers(std::vector<AudioBuffer>& out_buffers) { | ||
| 52 | std::scoped_lock l{lock}; | ||
| 53 | const s32 to_register{std::min(std::min(appended_count, BufferAppendLimit), | ||
| 54 | BufferAppendLimit - registered_count)}; | ||
| 55 | |||
| 56 | for (s32 i = 0; i < to_register; i++) { | ||
| 57 | s32 index{appended_index - appended_count}; | ||
| 58 | if (index < 0) { | ||
| 59 | index += N; | ||
| 60 | } | ||
| 61 | out_buffers.push_back(buffers[index]); | ||
| 62 | registered_count++; | ||
| 63 | registered_index = (registered_index + 1) % append_limit; | ||
| 64 | |||
| 65 | appended_count--; | ||
| 66 | if (appended_count == 0) { | ||
| 67 | break; | ||
| 68 | } | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Release a single buffer. Must be already registered. | ||
| 74 | * | ||
| 75 | * @param index - The buffer index to release. | ||
| 76 | * @param timestamp - The released timestamp for this buffer. | ||
| 77 | */ | ||
| 78 | void ReleaseBuffer(s32 index, s64 timestamp) { | ||
| 79 | std::scoped_lock l{lock}; | ||
| 80 | buffers[index].played_timestamp = timestamp; | ||
| 81 | |||
| 82 | registered_count--; | ||
| 83 | released_count++; | ||
| 84 | released_index = (released_index + 1) % append_limit; | ||
| 85 | } | ||
| 86 | |||
| 87 | /** | ||
| 88 | * Release all registered buffers. | ||
| 89 | * | ||
| 90 | * @param timestamp - The released timestamp for this buffer. | ||
| 91 | * @return Is the buffer was released. | ||
| 92 | */ | ||
| 93 | bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) { | ||
| 94 | std::scoped_lock l{lock}; | ||
| 95 | bool buffer_released{false}; | ||
| 96 | while (registered_count > 0) { | ||
| 97 | auto index{registered_index - registered_count}; | ||
| 98 | if (index < 0) { | ||
| 99 | index += N; | ||
| 100 | } | ||
| 101 | |||
| 102 | // Check with the backend if this buffer can be released yet. | ||
| 103 | if (!session.IsBufferConsumed(buffers[index].tag)) { | ||
| 104 | break; | ||
| 105 | } | ||
| 106 | |||
| 107 | ReleaseBuffer(index, core_timing.GetGlobalTimeNs().count()); | ||
| 108 | buffer_released = true; | ||
| 109 | } | ||
| 110 | |||
| 111 | return buffer_released || registered_count == 0; | ||
| 112 | } | ||
| 113 | |||
| 114 | /** | ||
| 115 | * Get all released buffers. | ||
| 116 | * | ||
| 117 | * @param tags - Container to be filled with the released buffers' tags. | ||
| 118 | * @return The number of buffers released. | ||
| 119 | */ | ||
| 120 | u32 GetReleasedBuffers(std::span<u64> tags) { | ||
| 121 | std::scoped_lock l{lock}; | ||
| 122 | u32 released{0}; | ||
| 123 | |||
| 124 | while (released_count > 0) { | ||
| 125 | auto index{released_index - released_count}; | ||
| 126 | if (index < 0) { | ||
| 127 | index += N; | ||
| 128 | } | ||
| 129 | |||
| 130 | auto& buffer{buffers[index]}; | ||
| 131 | released_count--; | ||
| 132 | |||
| 133 | auto tag{buffer.tag}; | ||
| 134 | buffer.played_timestamp = 0; | ||
| 135 | buffer.samples = 0; | ||
| 136 | buffer.tag = 0; | ||
| 137 | buffer.size = 0; | ||
| 138 | |||
| 139 | if (tag == 0) { | ||
| 140 | break; | ||
| 141 | } | ||
| 142 | |||
| 143 | tags[released++] = tag; | ||
| 144 | |||
| 145 | if (released >= tags.size()) { | ||
| 146 | break; | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | return released; | ||
| 151 | } | ||
| 152 | |||
| 153 | /** | ||
| 154 | * Get all appended and registered buffers. | ||
| 155 | * | ||
| 156 | * @param buffers_flushed - Output vector for the buffers which are released. | ||
| 157 | * @param max_buffers - Maximum number of buffers to released. | ||
| 158 | * @return The number of buffers released. | ||
| 159 | */ | ||
| 160 | u32 GetRegisteredAppendedBuffers(std::vector<AudioBuffer>& buffers_flushed, u32 max_buffers) { | ||
| 161 | std::scoped_lock l{lock}; | ||
| 162 | if (registered_count + appended_count == 0) { | ||
| 163 | return 0; | ||
| 164 | } | ||
| 165 | |||
| 166 | size_t buffers_to_flush{ | ||
| 167 | std::min(static_cast<u32>(registered_count + appended_count), max_buffers)}; | ||
| 168 | if (buffers_to_flush == 0) { | ||
| 169 | return 0; | ||
| 170 | } | ||
| 171 | |||
| 172 | while (registered_count > 0) { | ||
| 173 | auto index{registered_index - registered_count}; | ||
| 174 | if (index < 0) { | ||
| 175 | index += N; | ||
| 176 | } | ||
| 177 | |||
| 178 | buffers_flushed.push_back(buffers[index]); | ||
| 179 | |||
| 180 | registered_count--; | ||
| 181 | released_count++; | ||
| 182 | released_index = (released_index + 1) % append_limit; | ||
| 183 | |||
| 184 | if (buffers_flushed.size() >= buffers_to_flush) { | ||
| 185 | break; | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | while (appended_count > 0) { | ||
| 190 | auto index{appended_index - appended_count}; | ||
| 191 | if (index < 0) { | ||
| 192 | index += N; | ||
| 193 | } | ||
| 194 | |||
| 195 | buffers_flushed.push_back(buffers[index]); | ||
| 196 | |||
| 197 | appended_count--; | ||
| 198 | released_count++; | ||
| 199 | released_index = (released_index + 1) % append_limit; | ||
| 200 | |||
| 201 | if (buffers_flushed.size() >= buffers_to_flush) { | ||
| 202 | break; | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | return static_cast<u32>(buffers_flushed.size()); | ||
| 207 | } | ||
| 208 | |||
| 209 | /** | ||
| 210 | * Check if the given tag is in the buffers. | ||
| 211 | * | ||
| 212 | * @param tag - Unique tag of the buffer to search for. | ||
| 213 | * @return True if the buffer is still in the ring, otherwise false. | ||
| 214 | */ | ||
| 215 | bool ContainsBuffer(const u64 tag) const { | ||
| 216 | std::scoped_lock l{lock}; | ||
| 217 | const auto registered_buffers{appended_count + registered_count + released_count}; | ||
| 218 | |||
| 219 | if (registered_buffers == 0) { | ||
| 220 | return false; | ||
| 221 | } | ||
| 222 | |||
| 223 | auto index{released_index - released_count}; | ||
| 224 | if (index < 0) { | ||
| 225 | index += append_limit; | ||
| 226 | } | ||
| 227 | |||
| 228 | for (s32 i = 0; i < registered_buffers; i++) { | ||
| 229 | if (buffers[index].tag == tag) { | ||
| 230 | return true; | ||
| 231 | } | ||
| 232 | index = (index + 1) % append_limit; | ||
| 233 | } | ||
| 234 | |||
| 235 | return false; | ||
| 236 | } | ||
| 237 | |||
| 238 | /** | ||
| 239 | * Get the number of active buffers in the ring. | ||
| 240 | * That is, appended, registered and released buffers. | ||
| 241 | * | ||
| 242 | * @return Number of active buffers. | ||
| 243 | */ | ||
| 244 | u32 GetAppendedRegisteredCount() const { | ||
| 245 | std::scoped_lock l{lock}; | ||
| 246 | return appended_count + registered_count; | ||
| 247 | } | ||
| 248 | |||
| 249 | /** | ||
| 250 | * Get the total number of active buffers in the ring. | ||
| 251 | * That is, appended, registered and released buffers. | ||
| 252 | * | ||
| 253 | * @return Number of active buffers. | ||
| 254 | */ | ||
| 255 | u32 GetTotalBufferCount() const { | ||
| 256 | std::scoped_lock l{lock}; | ||
| 257 | return static_cast<u32>(appended_count + registered_count + released_count); | ||
| 258 | } | ||
| 259 | |||
| 260 | /** | ||
| 261 | * Flush all of the currently appended and registered buffers | ||
| 262 | * | ||
| 263 | * @param buffers_released - Output count for the number of buffers released. | ||
| 264 | * @return True if buffers were successfully flushed, otherwise false. | ||
| 265 | */ | ||
| 266 | bool FlushBuffers(u32& buffers_released) { | ||
| 267 | std::scoped_lock l{lock}; | ||
| 268 | std::vector<AudioBuffer> buffers_flushed{}; | ||
| 269 | |||
| 270 | buffers_released = GetRegisteredAppendedBuffers(buffers_flushed, append_limit); | ||
| 271 | |||
| 272 | if (registered_count > 0) { | ||
| 273 | return false; | ||
| 274 | } | ||
| 275 | |||
| 276 | if (static_cast<u32>(released_count + appended_count) > append_limit) { | ||
| 277 | return false; | ||
| 278 | } | ||
| 279 | |||
| 280 | return true; | ||
| 281 | } | ||
| 282 | |||
| 283 | private: | ||
| 284 | /// Buffer lock | ||
| 285 | mutable std::recursive_mutex lock{}; | ||
| 286 | /// The audio buffers | ||
| 287 | std::array<AudioBuffer, N> buffers{}; | ||
| 288 | /// Current released index | ||
| 289 | s32 released_index{}; | ||
| 290 | /// Number of released buffers | ||
| 291 | s32 released_count{}; | ||
| 292 | /// Current registered index | ||
| 293 | s32 registered_index{}; | ||
| 294 | /// Number of registered buffers | ||
| 295 | s32 registered_count{}; | ||
| 296 | /// Current appended index | ||
| 297 | s32 appended_index{}; | ||
| 298 | /// Number of appended buffers | ||
| 299 | s32 appended_count{}; | ||
| 300 | /// Maximum number of buffers (default 32) | ||
| 301 | u32 append_limit{}; | ||
| 302 | }; | ||
| 303 | |||
| 304 | } // namespace AudioCore | ||
diff --git a/src/audio_core/device/device_session.cpp b/src/audio_core/device/device_session.cpp new file mode 100644 index 000000000..095fc96ce --- /dev/null +++ b/src/audio_core/device/device_session.cpp | |||
| @@ -0,0 +1,114 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/audio_core.h" | ||
| 5 | #include "audio_core/audio_manager.h" | ||
| 6 | #include "audio_core/device/audio_buffer.h" | ||
| 7 | #include "audio_core/device/device_session.h" | ||
| 8 | #include "audio_core/sink/sink_stream.h" | ||
| 9 | #include "core/core.h" | ||
| 10 | #include "core/memory.h" | ||
| 11 | |||
| 12 | namespace AudioCore { | ||
| 13 | |||
| 14 | DeviceSession::DeviceSession(Core::System& system_) : system{system_} {} | ||
| 15 | |||
| 16 | DeviceSession::~DeviceSession() { | ||
| 17 | Finalize(); | ||
| 18 | } | ||
| 19 | |||
| 20 | Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_format_, | ||
| 21 | u16 channel_count_, size_t session_id_, u32 handle_, | ||
| 22 | u64 applet_resource_user_id_, Sink::StreamType type_) { | ||
| 23 | if (stream) { | ||
| 24 | Finalize(); | ||
| 25 | } | ||
| 26 | name = fmt::format("{}-{}", name_, session_id_); | ||
| 27 | type = type_; | ||
| 28 | sample_format = sample_format_; | ||
| 29 | channel_count = channel_count_; | ||
| 30 | session_id = session_id_; | ||
| 31 | handle = handle_; | ||
| 32 | applet_resource_user_id = applet_resource_user_id_; | ||
| 33 | |||
| 34 | if (type == Sink::StreamType::In) { | ||
| 35 | sink = &system.AudioCore().GetInputSink(); | ||
| 36 | } else { | ||
| 37 | sink = &system.AudioCore().GetOutputSink(); | ||
| 38 | } | ||
| 39 | stream = sink->AcquireSinkStream(system, channel_count, name, type); | ||
| 40 | initialized = true; | ||
| 41 | return ResultSuccess; | ||
| 42 | } | ||
| 43 | |||
| 44 | void DeviceSession::Finalize() { | ||
| 45 | if (initialized) { | ||
| 46 | Stop(); | ||
| 47 | sink->CloseStream(stream); | ||
| 48 | stream = nullptr; | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | void DeviceSession::Start() { | ||
| 53 | stream->SetPlayedSampleCount(played_sample_count); | ||
| 54 | stream->Start(); | ||
| 55 | } | ||
| 56 | |||
| 57 | void DeviceSession::Stop() { | ||
| 58 | if (stream) { | ||
| 59 | played_sample_count = stream->GetPlayedSampleCount(); | ||
| 60 | stream->Stop(); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const { | ||
| 65 | auto& memory{system.Memory()}; | ||
| 66 | |||
| 67 | for (size_t i = 0; i < buffers.size(); i++) { | ||
| 68 | Sink::SinkBuffer new_buffer{ | ||
| 69 | .frames = buffers[i].size / (channel_count * sizeof(s16)), | ||
| 70 | .frames_played = 0, | ||
| 71 | .tag = buffers[i].tag, | ||
| 72 | .consumed = false, | ||
| 73 | }; | ||
| 74 | |||
| 75 | if (type == Sink::StreamType::In) { | ||
| 76 | std::vector<s16> samples{}; | ||
| 77 | stream->AppendBuffer(new_buffer, samples); | ||
| 78 | } else { | ||
| 79 | std::vector<s16> samples(buffers[i].size / sizeof(s16)); | ||
| 80 | memory.ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size); | ||
| 81 | stream->AppendBuffer(new_buffer, samples); | ||
| 82 | } | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const { | ||
| 87 | if (type == Sink::StreamType::In) { | ||
| 88 | auto& memory{system.Memory()}; | ||
| 89 | auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))}; | ||
| 90 | memory.WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size); | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | bool DeviceSession::IsBufferConsumed(u64 tag) const { | ||
| 95 | if (stream) { | ||
| 96 | return stream->IsBufferConsumed(tag); | ||
| 97 | } | ||
| 98 | return true; | ||
| 99 | } | ||
| 100 | |||
| 101 | void DeviceSession::SetVolume(f32 volume) const { | ||
| 102 | if (stream) { | ||
| 103 | stream->SetSystemVolume(volume); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | u64 DeviceSession::GetPlayedSampleCount() const { | ||
| 108 | if (stream) { | ||
| 109 | return stream->GetPlayedSampleCount(); | ||
| 110 | } | ||
| 111 | return 0; | ||
| 112 | } | ||
| 113 | |||
| 114 | } // namespace AudioCore | ||
diff --git a/src/audio_core/device/device_session.h b/src/audio_core/device/device_session.h new file mode 100644 index 000000000..4a031b765 --- /dev/null +++ b/src/audio_core/device/device_session.h | |||
| @@ -0,0 +1,126 @@ | |||
| 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 "audio_core/sink/sink.h" | ||
| 10 | #include "core/hle/service/audio/errors.h" | ||
| 11 | |||
| 12 | namespace Core { | ||
| 13 | class System; | ||
| 14 | } | ||
| 15 | |||
| 16 | namespace AudioCore { | ||
| 17 | namespace Sink { | ||
| 18 | class SinkStream; | ||
| 19 | struct SinkBuffer; | ||
| 20 | } // namespace Sink | ||
| 21 | |||
| 22 | struct AudioBuffer; | ||
| 23 | |||
| 24 | /** | ||
| 25 | * Represents an input or output device stream for audio in and audio out (not used for render). | ||
| 26 | **/ | ||
| 27 | class DeviceSession { | ||
| 28 | public: | ||
| 29 | explicit DeviceSession(Core::System& system); | ||
| 30 | ~DeviceSession(); | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Initialize this device session. | ||
| 34 | * | ||
| 35 | * @param name - Name of this device. | ||
| 36 | * @param sample_format - Sample format for this device's output. | ||
| 37 | * @param channel_count - Number of channels for this device (2 or 6). | ||
| 38 | * @param session_id - This session's id. | ||
| 39 | * @param handle - Handle for this device session (unused). | ||
| 40 | * @param applet_resource_user_id - Applet resource user id for this device session (unused). | ||
| 41 | * @param type - Type of this stream (Render, In, Out). | ||
| 42 | * @return Result code for this call. | ||
| 43 | */ | ||
| 44 | Result Initialize(std::string_view name, SampleFormat sample_format, u16 channel_count, | ||
| 45 | size_t session_id, u32 handle, u64 applet_resource_user_id, | ||
| 46 | Sink::StreamType type); | ||
| 47 | |||
| 48 | /** | ||
| 49 | * Finalize this device session. | ||
| 50 | */ | ||
| 51 | void Finalize(); | ||
| 52 | |||
| 53 | /** | ||
| 54 | * Append audio buffers to this device session to be played back. | ||
| 55 | * | ||
| 56 | * @param buffers - The buffers to play. | ||
| 57 | */ | ||
| 58 | void AppendBuffers(std::span<AudioBuffer> buffers) const; | ||
| 59 | |||
| 60 | /** | ||
| 61 | * (Audio In only) Pop samples from the backend, and write them back to this buffer's address. | ||
| 62 | * | ||
| 63 | * @param buffer - The buffer to write to. | ||
| 64 | */ | ||
| 65 | void ReleaseBuffer(AudioBuffer& buffer) const; | ||
| 66 | |||
| 67 | /** | ||
| 68 | * Check if the buffer for the given tag has been consumed by the backend. | ||
| 69 | * | ||
| 70 | * @param tag - Unqiue tag of the buffer to check. | ||
| 71 | * @return true if the buffer has been consumed, otherwise false. | ||
| 72 | */ | ||
| 73 | bool IsBufferConsumed(u64 tag) const; | ||
| 74 | |||
| 75 | /** | ||
| 76 | * Start this device session, starting the backend stream. | ||
| 77 | */ | ||
| 78 | void Start(); | ||
| 79 | |||
| 80 | /** | ||
| 81 | * Stop this device session, stopping the backend stream. | ||
| 82 | */ | ||
| 83 | void Stop(); | ||
| 84 | |||
| 85 | /** | ||
| 86 | * Set this device session's volume. | ||
| 87 | * | ||
| 88 | * @param volume - New volume for this session. | ||
| 89 | */ | ||
| 90 | void SetVolume(f32 volume) const; | ||
| 91 | |||
| 92 | /** | ||
| 93 | * Get this device session's total played sample count. | ||
| 94 | * | ||
| 95 | * @return Samples played by this session. | ||
| 96 | */ | ||
| 97 | u64 GetPlayedSampleCount() const; | ||
| 98 | |||
| 99 | private: | ||
| 100 | /// System | ||
| 101 | Core::System& system; | ||
| 102 | /// Output sink this device will use | ||
| 103 | Sink::Sink* sink{}; | ||
| 104 | /// The backend stream for this device session to send samples to | ||
| 105 | Sink::SinkStream* stream{}; | ||
| 106 | /// Name of this device session | ||
| 107 | std::string name{}; | ||
| 108 | /// Type of this device session (render/in/out) | ||
| 109 | Sink::StreamType type{}; | ||
| 110 | /// Sample format for this device. | ||
| 111 | SampleFormat sample_format{SampleFormat::PcmInt16}; | ||
| 112 | /// Channel count for this device session | ||
| 113 | u16 channel_count{}; | ||
| 114 | /// Session id of this device session | ||
| 115 | size_t session_id{}; | ||
| 116 | /// Handle of this device session | ||
| 117 | u32 handle{}; | ||
| 118 | /// Applet resource user id of this device session | ||
| 119 | u64 applet_resource_user_id{}; | ||
| 120 | /// Total number of samples played by this device session | ||
| 121 | u64 played_sample_count{}; | ||
| 122 | /// Is this session initialised? | ||
| 123 | bool initialized{}; | ||
| 124 | }; | ||
| 125 | |||
| 126 | } // namespace AudioCore | ||
diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp deleted file mode 100644 index 79bcd1192..000000000 --- a/src/audio_core/effect_context.cpp +++ /dev/null | |||
| @@ -1,320 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include "audio_core/effect_context.h" | ||
| 6 | |||
| 7 | namespace AudioCore { | ||
| 8 | namespace { | ||
| 9 | bool ValidChannelCountForEffect(s32 channel_count) { | ||
| 10 | return channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6; | ||
| 11 | } | ||
| 12 | } // namespace | ||
| 13 | |||
| 14 | EffectContext::EffectContext(std::size_t effect_count_) : effect_count(effect_count_) { | ||
| 15 | effects.reserve(effect_count); | ||
| 16 | std::generate_n(std::back_inserter(effects), effect_count, | ||
| 17 | [] { return std::make_unique<EffectStubbed>(); }); | ||
| 18 | } | ||
| 19 | EffectContext::~EffectContext() = default; | ||
| 20 | |||
| 21 | std::size_t EffectContext::GetCount() const { | ||
| 22 | return effect_count; | ||
| 23 | } | ||
| 24 | |||
| 25 | EffectBase* EffectContext::GetInfo(std::size_t i) { | ||
| 26 | return effects.at(i).get(); | ||
| 27 | } | ||
| 28 | |||
| 29 | EffectBase* EffectContext::RetargetEffect(std::size_t i, EffectType effect) { | ||
| 30 | switch (effect) { | ||
| 31 | case EffectType::Invalid: | ||
| 32 | effects[i] = std::make_unique<EffectStubbed>(); | ||
| 33 | break; | ||
| 34 | case EffectType::BufferMixer: | ||
| 35 | effects[i] = std::make_unique<EffectBufferMixer>(); | ||
| 36 | break; | ||
| 37 | case EffectType::Aux: | ||
| 38 | effects[i] = std::make_unique<EffectAuxInfo>(); | ||
| 39 | break; | ||
| 40 | case EffectType::Delay: | ||
| 41 | effects[i] = std::make_unique<EffectDelay>(); | ||
| 42 | break; | ||
| 43 | case EffectType::Reverb: | ||
| 44 | effects[i] = std::make_unique<EffectReverb>(); | ||
| 45 | break; | ||
| 46 | case EffectType::I3dl2Reverb: | ||
| 47 | effects[i] = std::make_unique<EffectI3dl2Reverb>(); | ||
| 48 | break; | ||
| 49 | case EffectType::BiquadFilter: | ||
| 50 | effects[i] = std::make_unique<EffectBiquadFilter>(); | ||
| 51 | break; | ||
| 52 | default: | ||
| 53 | ASSERT_MSG(false, "Unimplemented effect {}", effect); | ||
| 54 | effects[i] = std::make_unique<EffectStubbed>(); | ||
| 55 | } | ||
| 56 | return GetInfo(i); | ||
| 57 | } | ||
| 58 | |||
| 59 | const EffectBase* EffectContext::GetInfo(std::size_t i) const { | ||
| 60 | return effects.at(i).get(); | ||
| 61 | } | ||
| 62 | |||
| 63 | EffectStubbed::EffectStubbed() : EffectBase(EffectType::Invalid) {} | ||
| 64 | EffectStubbed::~EffectStubbed() = default; | ||
| 65 | |||
| 66 | void EffectStubbed::Update([[maybe_unused]] EffectInfo::InParams& in_params) {} | ||
| 67 | void EffectStubbed::UpdateForCommandGeneration() {} | ||
| 68 | |||
| 69 | EffectBase::EffectBase(EffectType effect_type_) : effect_type(effect_type_) {} | ||
| 70 | EffectBase::~EffectBase() = default; | ||
| 71 | |||
| 72 | UsageState EffectBase::GetUsage() const { | ||
| 73 | return usage; | ||
| 74 | } | ||
| 75 | |||
| 76 | EffectType EffectBase::GetType() const { | ||
| 77 | return effect_type; | ||
| 78 | } | ||
| 79 | |||
| 80 | bool EffectBase::IsEnabled() const { | ||
| 81 | return enabled; | ||
| 82 | } | ||
| 83 | |||
| 84 | s32 EffectBase::GetMixID() const { | ||
| 85 | return mix_id; | ||
| 86 | } | ||
| 87 | |||
| 88 | s32 EffectBase::GetProcessingOrder() const { | ||
| 89 | return processing_order; | ||
| 90 | } | ||
| 91 | |||
| 92 | std::vector<u8>& EffectBase::GetWorkBuffer() { | ||
| 93 | return work_buffer; | ||
| 94 | } | ||
| 95 | |||
| 96 | const std::vector<u8>& EffectBase::GetWorkBuffer() const { | ||
| 97 | return work_buffer; | ||
| 98 | } | ||
| 99 | |||
| 100 | EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {} | ||
| 101 | EffectI3dl2Reverb::~EffectI3dl2Reverb() = default; | ||
| 102 | |||
| 103 | void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) { | ||
| 104 | auto& params = GetParams(); | ||
| 105 | const auto* reverb_params = reinterpret_cast<I3dl2ReverbParams*>(in_params.raw.data()); | ||
| 106 | if (!ValidChannelCountForEffect(reverb_params->max_channels)) { | ||
| 107 | ASSERT_MSG(false, "Invalid reverb max channel count {}", reverb_params->max_channels); | ||
| 108 | return; | ||
| 109 | } | ||
| 110 | |||
| 111 | const auto last_status = params.status; | ||
| 112 | mix_id = in_params.mix_id; | ||
| 113 | processing_order = in_params.processing_order; | ||
| 114 | params = *reverb_params; | ||
| 115 | if (!ValidChannelCountForEffect(reverb_params->channel_count)) { | ||
| 116 | params.channel_count = params.max_channels; | ||
| 117 | } | ||
| 118 | enabled = in_params.is_enabled; | ||
| 119 | if (last_status != ParameterStatus::Updated) { | ||
| 120 | params.status = last_status; | ||
| 121 | } | ||
| 122 | |||
| 123 | if (in_params.is_new || skipped) { | ||
| 124 | usage = UsageState::Initialized; | ||
| 125 | params.status = ParameterStatus::Initialized; | ||
| 126 | skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; | ||
| 127 | if (!skipped) { | ||
| 128 | auto& cur_work_buffer = GetWorkBuffer(); | ||
| 129 | // Has two buffers internally | ||
| 130 | cur_work_buffer.resize(in_params.buffer_size * 2); | ||
| 131 | std::fill(cur_work_buffer.begin(), cur_work_buffer.end(), 0); | ||
| 132 | } | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | void EffectI3dl2Reverb::UpdateForCommandGeneration() { | ||
| 137 | if (enabled) { | ||
| 138 | usage = UsageState::Running; | ||
| 139 | } else { | ||
| 140 | usage = UsageState::Stopped; | ||
| 141 | } | ||
| 142 | GetParams().status = ParameterStatus::Updated; | ||
| 143 | } | ||
| 144 | |||
| 145 | I3dl2ReverbState& EffectI3dl2Reverb::GetState() { | ||
| 146 | return state; | ||
| 147 | } | ||
| 148 | |||
| 149 | const I3dl2ReverbState& EffectI3dl2Reverb::GetState() const { | ||
| 150 | return state; | ||
| 151 | } | ||
| 152 | |||
| 153 | EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {} | ||
| 154 | EffectBiquadFilter::~EffectBiquadFilter() = default; | ||
| 155 | |||
| 156 | void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) { | ||
| 157 | auto& params = GetParams(); | ||
| 158 | const auto* biquad_params = reinterpret_cast<BiquadFilterParams*>(in_params.raw.data()); | ||
| 159 | mix_id = in_params.mix_id; | ||
| 160 | processing_order = in_params.processing_order; | ||
| 161 | params = *biquad_params; | ||
| 162 | enabled = in_params.is_enabled; | ||
| 163 | } | ||
| 164 | |||
| 165 | void EffectBiquadFilter::UpdateForCommandGeneration() { | ||
| 166 | if (enabled) { | ||
| 167 | usage = UsageState::Running; | ||
| 168 | } else { | ||
| 169 | usage = UsageState::Stopped; | ||
| 170 | } | ||
| 171 | GetParams().status = ParameterStatus::Updated; | ||
| 172 | } | ||
| 173 | |||
| 174 | EffectAuxInfo::EffectAuxInfo() : EffectGeneric(EffectType::Aux) {} | ||
| 175 | EffectAuxInfo::~EffectAuxInfo() = default; | ||
| 176 | |||
| 177 | void EffectAuxInfo::Update(EffectInfo::InParams& in_params) { | ||
| 178 | const auto* aux_params = reinterpret_cast<AuxInfo*>(in_params.raw.data()); | ||
| 179 | mix_id = in_params.mix_id; | ||
| 180 | processing_order = in_params.processing_order; | ||
| 181 | GetParams() = *aux_params; | ||
| 182 | enabled = in_params.is_enabled; | ||
| 183 | |||
| 184 | if (in_params.is_new || skipped) { | ||
| 185 | skipped = aux_params->send_buffer_info == 0 || aux_params->return_buffer_info == 0; | ||
| 186 | if (skipped) { | ||
| 187 | return; | ||
| 188 | } | ||
| 189 | |||
| 190 | // There's two AuxInfos which are an identical size, the first one is managed by the cpu, | ||
| 191 | // the second is managed by the dsp. All we care about is managing the DSP one | ||
| 192 | send_info = aux_params->send_buffer_info + sizeof(AuxInfoDSP); | ||
| 193 | send_buffer = aux_params->send_buffer_info + (sizeof(AuxInfoDSP) * 2); | ||
| 194 | |||
| 195 | recv_info = aux_params->return_buffer_info + sizeof(AuxInfoDSP); | ||
| 196 | recv_buffer = aux_params->return_buffer_info + (sizeof(AuxInfoDSP) * 2); | ||
| 197 | } | ||
| 198 | } | ||
| 199 | |||
| 200 | void EffectAuxInfo::UpdateForCommandGeneration() { | ||
| 201 | if (enabled) { | ||
| 202 | usage = UsageState::Running; | ||
| 203 | } else { | ||
| 204 | usage = UsageState::Stopped; | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | VAddr EffectAuxInfo::GetSendInfo() const { | ||
| 209 | return send_info; | ||
| 210 | } | ||
| 211 | |||
| 212 | VAddr EffectAuxInfo::GetSendBuffer() const { | ||
| 213 | return send_buffer; | ||
| 214 | } | ||
| 215 | |||
| 216 | VAddr EffectAuxInfo::GetRecvInfo() const { | ||
| 217 | return recv_info; | ||
| 218 | } | ||
| 219 | |||
| 220 | VAddr EffectAuxInfo::GetRecvBuffer() const { | ||
| 221 | return recv_buffer; | ||
| 222 | } | ||
| 223 | |||
| 224 | EffectDelay::EffectDelay() : EffectGeneric(EffectType::Delay) {} | ||
| 225 | EffectDelay::~EffectDelay() = default; | ||
| 226 | |||
| 227 | void EffectDelay::Update(EffectInfo::InParams& in_params) { | ||
| 228 | const auto* delay_params = reinterpret_cast<DelayParams*>(in_params.raw.data()); | ||
| 229 | auto& params = GetParams(); | ||
| 230 | if (!ValidChannelCountForEffect(delay_params->max_channels)) { | ||
| 231 | return; | ||
| 232 | } | ||
| 233 | |||
| 234 | const auto last_status = params.status; | ||
| 235 | mix_id = in_params.mix_id; | ||
| 236 | processing_order = in_params.processing_order; | ||
| 237 | params = *delay_params; | ||
| 238 | if (!ValidChannelCountForEffect(delay_params->channels)) { | ||
| 239 | params.channels = params.max_channels; | ||
| 240 | } | ||
| 241 | enabled = in_params.is_enabled; | ||
| 242 | |||
| 243 | if (last_status != ParameterStatus::Updated) { | ||
| 244 | params.status = last_status; | ||
| 245 | } | ||
| 246 | |||
| 247 | if (in_params.is_new || skipped) { | ||
| 248 | usage = UsageState::Initialized; | ||
| 249 | params.status = ParameterStatus::Initialized; | ||
| 250 | skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; | ||
| 251 | } | ||
| 252 | } | ||
| 253 | |||
| 254 | void EffectDelay::UpdateForCommandGeneration() { | ||
| 255 | if (enabled) { | ||
| 256 | usage = UsageState::Running; | ||
| 257 | } else { | ||
| 258 | usage = UsageState::Stopped; | ||
| 259 | } | ||
| 260 | GetParams().status = ParameterStatus::Updated; | ||
| 261 | } | ||
| 262 | |||
| 263 | EffectBufferMixer::EffectBufferMixer() : EffectGeneric(EffectType::BufferMixer) {} | ||
| 264 | EffectBufferMixer::~EffectBufferMixer() = default; | ||
| 265 | |||
| 266 | void EffectBufferMixer::Update(EffectInfo::InParams& in_params) { | ||
| 267 | mix_id = in_params.mix_id; | ||
| 268 | processing_order = in_params.processing_order; | ||
| 269 | GetParams() = *reinterpret_cast<BufferMixerParams*>(in_params.raw.data()); | ||
| 270 | enabled = in_params.is_enabled; | ||
| 271 | } | ||
| 272 | |||
| 273 | void EffectBufferMixer::UpdateForCommandGeneration() { | ||
| 274 | if (enabled) { | ||
| 275 | usage = UsageState::Running; | ||
| 276 | } else { | ||
| 277 | usage = UsageState::Stopped; | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | EffectReverb::EffectReverb() : EffectGeneric(EffectType::Reverb) {} | ||
| 282 | EffectReverb::~EffectReverb() = default; | ||
| 283 | |||
| 284 | void EffectReverb::Update(EffectInfo::InParams& in_params) { | ||
| 285 | const auto* reverb_params = reinterpret_cast<ReverbParams*>(in_params.raw.data()); | ||
| 286 | auto& params = GetParams(); | ||
| 287 | if (!ValidChannelCountForEffect(reverb_params->max_channels)) { | ||
| 288 | return; | ||
| 289 | } | ||
| 290 | |||
| 291 | const auto last_status = params.status; | ||
| 292 | mix_id = in_params.mix_id; | ||
| 293 | processing_order = in_params.processing_order; | ||
| 294 | params = *reverb_params; | ||
| 295 | if (!ValidChannelCountForEffect(reverb_params->channels)) { | ||
| 296 | params.channels = params.max_channels; | ||
| 297 | } | ||
| 298 | enabled = in_params.is_enabled; | ||
| 299 | |||
| 300 | if (last_status != ParameterStatus::Updated) { | ||
| 301 | params.status = last_status; | ||
| 302 | } | ||
| 303 | |||
| 304 | if (in_params.is_new || skipped) { | ||
| 305 | usage = UsageState::Initialized; | ||
| 306 | params.status = ParameterStatus::Initialized; | ||
| 307 | skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; | ||
| 308 | } | ||
| 309 | } | ||
| 310 | |||
| 311 | void EffectReverb::UpdateForCommandGeneration() { | ||
| 312 | if (enabled) { | ||
| 313 | usage = UsageState::Running; | ||
| 314 | } else { | ||
| 315 | usage = UsageState::Stopped; | ||
| 316 | } | ||
| 317 | GetParams().status = ParameterStatus::Updated; | ||
| 318 | } | ||
| 319 | |||
| 320 | } // namespace AudioCore | ||
diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h deleted file mode 100644 index cb47df472..000000000 --- a/src/audio_core/effect_context.h +++ /dev/null | |||
| @@ -1,349 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <memory> | ||
| 8 | #include <vector> | ||
| 9 | #include "audio_core/common.h" | ||
| 10 | #include "audio_core/delay_line.h" | ||
| 11 | #include "common/common_funcs.h" | ||
| 12 | #include "common/common_types.h" | ||
| 13 | #include "common/swap.h" | ||
| 14 | |||
| 15 | namespace AudioCore { | ||
| 16 | enum class EffectType : u8 { | ||
| 17 | Invalid = 0, | ||
| 18 | BufferMixer = 1, | ||
| 19 | Aux = 2, | ||
| 20 | Delay = 3, | ||
| 21 | Reverb = 4, | ||
| 22 | I3dl2Reverb = 5, | ||
| 23 | BiquadFilter = 6, | ||
| 24 | }; | ||
| 25 | |||
| 26 | enum class UsageStatus : u8 { | ||
| 27 | Invalid = 0, | ||
| 28 | New = 1, | ||
| 29 | Initialized = 2, | ||
| 30 | Used = 3, | ||
| 31 | Removed = 4, | ||
| 32 | }; | ||
| 33 | |||
| 34 | enum class UsageState { | ||
| 35 | Invalid = 0, | ||
| 36 | Initialized = 1, | ||
| 37 | Running = 2, | ||
| 38 | Stopped = 3, | ||
| 39 | }; | ||
| 40 | |||
| 41 | enum class ParameterStatus : u8 { | ||
| 42 | Initialized = 0, | ||
| 43 | Updating = 1, | ||
| 44 | Updated = 2, | ||
| 45 | }; | ||
| 46 | |||
| 47 | struct BufferMixerParams { | ||
| 48 | std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input{}; | ||
| 49 | std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output{}; | ||
| 50 | std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> volume{}; | ||
| 51 | s32_le count{}; | ||
| 52 | }; | ||
| 53 | static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size"); | ||
| 54 | |||
| 55 | struct AuxInfoDSP { | ||
| 56 | u32_le read_offset{}; | ||
| 57 | u32_le write_offset{}; | ||
| 58 | u32_le remaining{}; | ||
| 59 | INSERT_PADDING_WORDS(13); | ||
| 60 | }; | ||
| 61 | static_assert(sizeof(AuxInfoDSP) == 0x40, "AuxInfoDSP is an invalid size"); | ||
| 62 | |||
| 63 | struct AuxInfo { | ||
| 64 | std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input_mix_buffers{}; | ||
| 65 | std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output_mix_buffers{}; | ||
| 66 | u32_le count{}; | ||
| 67 | s32_le sample_rate{}; | ||
| 68 | s32_le sample_count{}; | ||
| 69 | s32_le mix_buffer_count{}; | ||
| 70 | u64_le send_buffer_info{}; | ||
| 71 | u64_le send_buffer_base{}; | ||
| 72 | |||
| 73 | u64_le return_buffer_info{}; | ||
| 74 | u64_le return_buffer_base{}; | ||
| 75 | }; | ||
| 76 | static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size"); | ||
| 77 | |||
| 78 | struct I3dl2ReverbParams { | ||
| 79 | std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{}; | ||
| 80 | std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{}; | ||
| 81 | u16_le max_channels{}; | ||
| 82 | u16_le channel_count{}; | ||
| 83 | INSERT_PADDING_BYTES(1); | ||
| 84 | u32_le sample_rate{}; | ||
| 85 | f32 room_hf{}; | ||
| 86 | f32 hf_reference{}; | ||
| 87 | f32 decay_time{}; | ||
| 88 | f32 hf_decay_ratio{}; | ||
| 89 | f32 room{}; | ||
| 90 | f32 reflection{}; | ||
| 91 | f32 reverb{}; | ||
| 92 | f32 diffusion{}; | ||
| 93 | f32 reflection_delay{}; | ||
| 94 | f32 reverb_delay{}; | ||
| 95 | f32 density{}; | ||
| 96 | f32 dry_gain{}; | ||
| 97 | ParameterStatus status{}; | ||
| 98 | INSERT_PADDING_BYTES(3); | ||
| 99 | }; | ||
| 100 | static_assert(sizeof(I3dl2ReverbParams) == 0x4c, "I3dl2ReverbParams is an invalid size"); | ||
| 101 | |||
| 102 | struct BiquadFilterParams { | ||
| 103 | std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{}; | ||
| 104 | std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{}; | ||
| 105 | std::array<s16_le, 3> numerator; | ||
| 106 | std::array<s16_le, 2> denominator; | ||
| 107 | s8 channel_count{}; | ||
| 108 | ParameterStatus status{}; | ||
| 109 | }; | ||
| 110 | static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size"); | ||
| 111 | |||
| 112 | struct DelayParams { | ||
| 113 | std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{}; | ||
| 114 | std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{}; | ||
| 115 | u16_le max_channels{}; | ||
| 116 | u16_le channels{}; | ||
| 117 | s32_le max_delay{}; | ||
| 118 | s32_le delay{}; | ||
| 119 | s32_le sample_rate{}; | ||
| 120 | s32_le gain{}; | ||
| 121 | s32_le feedback_gain{}; | ||
| 122 | s32_le out_gain{}; | ||
| 123 | s32_le dry_gain{}; | ||
| 124 | s32_le channel_spread{}; | ||
| 125 | s32_le low_pass{}; | ||
| 126 | ParameterStatus status{}; | ||
| 127 | INSERT_PADDING_BYTES(3); | ||
| 128 | }; | ||
| 129 | static_assert(sizeof(DelayParams) == 0x38, "DelayParams is an invalid size"); | ||
| 130 | |||
| 131 | struct ReverbParams { | ||
| 132 | std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{}; | ||
| 133 | std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{}; | ||
| 134 | u16_le max_channels{}; | ||
| 135 | u16_le channels{}; | ||
| 136 | s32_le sample_rate{}; | ||
| 137 | s32_le mode0{}; | ||
| 138 | s32_le mode0_gain{}; | ||
| 139 | s32_le pre_delay{}; | ||
| 140 | s32_le mode1{}; | ||
| 141 | s32_le mode1_gain{}; | ||
| 142 | s32_le decay{}; | ||
| 143 | s32_le hf_decay_ratio{}; | ||
| 144 | s32_le coloration{}; | ||
| 145 | s32_le reverb_gain{}; | ||
| 146 | s32_le out_gain{}; | ||
| 147 | s32_le dry_gain{}; | ||
| 148 | ParameterStatus status{}; | ||
| 149 | INSERT_PADDING_BYTES(3); | ||
| 150 | }; | ||
| 151 | static_assert(sizeof(ReverbParams) == 0x44, "ReverbParams is an invalid size"); | ||
| 152 | |||
| 153 | class EffectInfo { | ||
| 154 | public: | ||
| 155 | struct InParams { | ||
| 156 | EffectType type{}; | ||
| 157 | u8 is_new{}; | ||
| 158 | u8 is_enabled{}; | ||
| 159 | INSERT_PADDING_BYTES(1); | ||
| 160 | s32_le mix_id{}; | ||
| 161 | u64_le buffer_address{}; | ||
| 162 | u64_le buffer_size{}; | ||
| 163 | s32_le processing_order{}; | ||
| 164 | INSERT_PADDING_BYTES(4); | ||
| 165 | union { | ||
| 166 | std::array<u8, 0xa0> raw; | ||
| 167 | }; | ||
| 168 | }; | ||
| 169 | static_assert(sizeof(InParams) == 0xc0, "InParams is an invalid size"); | ||
| 170 | |||
| 171 | struct OutParams { | ||
| 172 | UsageStatus status{}; | ||
| 173 | INSERT_PADDING_BYTES(15); | ||
| 174 | }; | ||
| 175 | static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size"); | ||
| 176 | }; | ||
| 177 | |||
| 178 | struct AuxAddress { | ||
| 179 | VAddr send_dsp_info{}; | ||
| 180 | VAddr send_buffer_base{}; | ||
| 181 | VAddr return_dsp_info{}; | ||
| 182 | VAddr return_buffer_base{}; | ||
| 183 | }; | ||
| 184 | |||
| 185 | class EffectBase { | ||
| 186 | public: | ||
| 187 | explicit EffectBase(EffectType effect_type_); | ||
| 188 | virtual ~EffectBase(); | ||
| 189 | |||
| 190 | virtual void Update(EffectInfo::InParams& in_params) = 0; | ||
| 191 | virtual void UpdateForCommandGeneration() = 0; | ||
| 192 | [[nodiscard]] UsageState GetUsage() const; | ||
| 193 | [[nodiscard]] EffectType GetType() const; | ||
| 194 | [[nodiscard]] bool IsEnabled() const; | ||
| 195 | [[nodiscard]] s32 GetMixID() const; | ||
| 196 | [[nodiscard]] s32 GetProcessingOrder() const; | ||
| 197 | [[nodiscard]] std::vector<u8>& GetWorkBuffer(); | ||
| 198 | [[nodiscard]] const std::vector<u8>& GetWorkBuffer() const; | ||
| 199 | |||
| 200 | protected: | ||
| 201 | UsageState usage{UsageState::Invalid}; | ||
| 202 | EffectType effect_type{}; | ||
| 203 | s32 mix_id{}; | ||
| 204 | s32 processing_order{}; | ||
| 205 | bool enabled = false; | ||
| 206 | std::vector<u8> work_buffer{}; | ||
| 207 | }; | ||
| 208 | |||
| 209 | template <typename T> | ||
| 210 | class EffectGeneric : public EffectBase { | ||
| 211 | public: | ||
| 212 | explicit EffectGeneric(EffectType effect_type_) : EffectBase(effect_type_) {} | ||
| 213 | |||
| 214 | T& GetParams() { | ||
| 215 | return internal_params; | ||
| 216 | } | ||
| 217 | |||
| 218 | const T& GetParams() const { | ||
| 219 | return internal_params; | ||
| 220 | } | ||
| 221 | |||
| 222 | private: | ||
| 223 | T internal_params{}; | ||
| 224 | }; | ||
| 225 | |||
| 226 | class EffectStubbed : public EffectBase { | ||
| 227 | public: | ||
| 228 | explicit EffectStubbed(); | ||
| 229 | ~EffectStubbed() override; | ||
| 230 | |||
| 231 | void Update(EffectInfo::InParams& in_params) override; | ||
| 232 | void UpdateForCommandGeneration() override; | ||
| 233 | }; | ||
| 234 | |||
| 235 | struct I3dl2ReverbState { | ||
| 236 | f32 lowpass_0{}; | ||
| 237 | f32 lowpass_1{}; | ||
| 238 | f32 lowpass_2{}; | ||
| 239 | |||
| 240 | DelayLineBase early_delay_line{}; | ||
| 241 | std::array<u32, AudioCommon::I3DL2REVERB_TAPS> early_tap_steps{}; | ||
| 242 | f32 early_gain{}; | ||
| 243 | f32 late_gain{}; | ||
| 244 | |||
| 245 | u32 early_to_late_taps{}; | ||
| 246 | std::array<DelayLineBase, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fdn_delay_line{}; | ||
| 247 | std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line0{}; | ||
| 248 | std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line1{}; | ||
| 249 | f32 last_reverb_echo{}; | ||
| 250 | DelayLineBase center_delay_line{}; | ||
| 251 | std::array<std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>, 3> lpf_coefficients{}; | ||
| 252 | std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> shelf_filter{}; | ||
| 253 | f32 dry_gain{}; | ||
| 254 | }; | ||
| 255 | |||
| 256 | class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> { | ||
| 257 | public: | ||
| 258 | explicit EffectI3dl2Reverb(); | ||
| 259 | ~EffectI3dl2Reverb() override; | ||
| 260 | |||
| 261 | void Update(EffectInfo::InParams& in_params) override; | ||
| 262 | void UpdateForCommandGeneration() override; | ||
| 263 | |||
| 264 | I3dl2ReverbState& GetState(); | ||
| 265 | const I3dl2ReverbState& GetState() const; | ||
| 266 | |||
| 267 | private: | ||
| 268 | bool skipped = false; | ||
| 269 | I3dl2ReverbState state{}; | ||
| 270 | }; | ||
| 271 | |||
| 272 | class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> { | ||
| 273 | public: | ||
| 274 | explicit EffectBiquadFilter(); | ||
| 275 | ~EffectBiquadFilter() override; | ||
| 276 | |||
| 277 | void Update(EffectInfo::InParams& in_params) override; | ||
| 278 | void UpdateForCommandGeneration() override; | ||
| 279 | }; | ||
| 280 | |||
| 281 | class EffectAuxInfo : public EffectGeneric<AuxInfo> { | ||
| 282 | public: | ||
| 283 | explicit EffectAuxInfo(); | ||
| 284 | ~EffectAuxInfo() override; | ||
| 285 | |||
| 286 | void Update(EffectInfo::InParams& in_params) override; | ||
| 287 | void UpdateForCommandGeneration() override; | ||
| 288 | [[nodiscard]] VAddr GetSendInfo() const; | ||
| 289 | [[nodiscard]] VAddr GetSendBuffer() const; | ||
| 290 | [[nodiscard]] VAddr GetRecvInfo() const; | ||
| 291 | [[nodiscard]] VAddr GetRecvBuffer() const; | ||
| 292 | |||
| 293 | private: | ||
| 294 | VAddr send_info{}; | ||
| 295 | VAddr send_buffer{}; | ||
| 296 | VAddr recv_info{}; | ||
| 297 | VAddr recv_buffer{}; | ||
| 298 | bool skipped = false; | ||
| 299 | AuxAddress addresses{}; | ||
| 300 | }; | ||
| 301 | |||
| 302 | class EffectDelay : public EffectGeneric<DelayParams> { | ||
| 303 | public: | ||
| 304 | explicit EffectDelay(); | ||
| 305 | ~EffectDelay() override; | ||
| 306 | |||
| 307 | void Update(EffectInfo::InParams& in_params) override; | ||
| 308 | void UpdateForCommandGeneration() override; | ||
| 309 | |||
| 310 | private: | ||
| 311 | bool skipped = false; | ||
| 312 | }; | ||
| 313 | |||
| 314 | class EffectBufferMixer : public EffectGeneric<BufferMixerParams> { | ||
| 315 | public: | ||
| 316 | explicit EffectBufferMixer(); | ||
| 317 | ~EffectBufferMixer() override; | ||
| 318 | |||
| 319 | void Update(EffectInfo::InParams& in_params) override; | ||
| 320 | void UpdateForCommandGeneration() override; | ||
| 321 | }; | ||
| 322 | |||
| 323 | class EffectReverb : public EffectGeneric<ReverbParams> { | ||
| 324 | public: | ||
| 325 | explicit EffectReverb(); | ||
| 326 | ~EffectReverb() override; | ||
| 327 | |||
| 328 | void Update(EffectInfo::InParams& in_params) override; | ||
| 329 | void UpdateForCommandGeneration() override; | ||
| 330 | |||
| 331 | private: | ||
| 332 | bool skipped = false; | ||
| 333 | }; | ||
| 334 | |||
| 335 | class EffectContext { | ||
| 336 | public: | ||
| 337 | explicit EffectContext(std::size_t effect_count_); | ||
| 338 | ~EffectContext(); | ||
| 339 | |||
| 340 | [[nodiscard]] std::size_t GetCount() const; | ||
| 341 | [[nodiscard]] EffectBase* GetInfo(std::size_t i); | ||
| 342 | [[nodiscard]] EffectBase* RetargetEffect(std::size_t i, EffectType effect); | ||
| 343 | [[nodiscard]] const EffectBase* GetInfo(std::size_t i) const; | ||
| 344 | |||
| 345 | private: | ||
| 346 | std::size_t effect_count{}; | ||
| 347 | std::vector<std::unique_ptr<EffectBase>> effects; | ||
| 348 | }; | ||
| 349 | } // namespace AudioCore | ||
diff --git a/src/audio_core/in/audio_in.cpp b/src/audio_core/in/audio_in.cpp new file mode 100644 index 000000000..c946895d6 --- /dev/null +++ b/src/audio_core/in/audio_in.cpp | |||
| @@ -0,0 +1,100 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/audio_in_manager.h" | ||
| 5 | #include "audio_core/in/audio_in.h" | ||
| 6 | #include "core/hle/kernel/k_event.h" | ||
| 7 | |||
| 8 | namespace AudioCore::AudioIn { | ||
| 9 | |||
| 10 | In::In(Core::System& system_, Manager& manager_, Kernel::KEvent* event_, size_t session_id_) | ||
| 11 | : manager{manager_}, parent_mutex{manager.mutex}, event{event_}, system{system_, event, | ||
| 12 | session_id_} {} | ||
| 13 | |||
| 14 | void In::Free() { | ||
| 15 | std::scoped_lock l{parent_mutex}; | ||
| 16 | manager.ReleaseSessionId(system.GetSessionId()); | ||
| 17 | } | ||
| 18 | |||
| 19 | System& In::GetSystem() { | ||
| 20 | return system; | ||
| 21 | } | ||
| 22 | |||
| 23 | AudioIn::State In::GetState() { | ||
| 24 | std::scoped_lock l{parent_mutex}; | ||
| 25 | return system.GetState(); | ||
| 26 | } | ||
| 27 | |||
| 28 | Result In::StartSystem() { | ||
| 29 | std::scoped_lock l{parent_mutex}; | ||
| 30 | return system.Start(); | ||
| 31 | } | ||
| 32 | |||
| 33 | void In::StartSession() { | ||
| 34 | std::scoped_lock l{parent_mutex}; | ||
| 35 | system.StartSession(); | ||
| 36 | } | ||
| 37 | |||
| 38 | Result In::StopSystem() { | ||
| 39 | std::scoped_lock l{parent_mutex}; | ||
| 40 | return system.Stop(); | ||
| 41 | } | ||
| 42 | |||
| 43 | Result In::AppendBuffer(const AudioInBuffer& buffer, u64 tag) { | ||
| 44 | std::scoped_lock l{parent_mutex}; | ||
| 45 | |||
| 46 | if (system.AppendBuffer(buffer, tag)) { | ||
| 47 | return ResultSuccess; | ||
| 48 | } | ||
| 49 | return Service::Audio::ERR_BUFFER_COUNT_EXCEEDED; | ||
| 50 | } | ||
| 51 | |||
| 52 | void In::ReleaseAndRegisterBuffers() { | ||
| 53 | std::scoped_lock l{parent_mutex}; | ||
| 54 | if (system.GetState() == State::Started) { | ||
| 55 | system.ReleaseBuffers(); | ||
| 56 | system.RegisterBuffers(); | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | bool In::FlushAudioInBuffers() { | ||
| 61 | std::scoped_lock l{parent_mutex}; | ||
| 62 | return system.FlushAudioInBuffers(); | ||
| 63 | } | ||
| 64 | |||
| 65 | u32 In::GetReleasedBuffers(std::span<u64> tags) { | ||
| 66 | std::scoped_lock l{parent_mutex}; | ||
| 67 | return system.GetReleasedBuffers(tags); | ||
| 68 | } | ||
| 69 | |||
| 70 | Kernel::KReadableEvent& In::GetBufferEvent() { | ||
| 71 | std::scoped_lock l{parent_mutex}; | ||
| 72 | return event->GetReadableEvent(); | ||
| 73 | } | ||
| 74 | |||
| 75 | f32 In::GetVolume() { | ||
| 76 | std::scoped_lock l{parent_mutex}; | ||
| 77 | return system.GetVolume(); | ||
| 78 | } | ||
| 79 | |||
| 80 | void In::SetVolume(f32 volume) { | ||
| 81 | std::scoped_lock l{parent_mutex}; | ||
| 82 | system.SetVolume(volume); | ||
| 83 | } | ||
| 84 | |||
| 85 | bool In::ContainsAudioBuffer(u64 tag) { | ||
| 86 | std::scoped_lock l{parent_mutex}; | ||
| 87 | return system.ContainsAudioBuffer(tag); | ||
| 88 | } | ||
| 89 | |||
| 90 | u32 In::GetBufferCount() { | ||
| 91 | std::scoped_lock l{parent_mutex}; | ||
| 92 | return system.GetBufferCount(); | ||
| 93 | } | ||
| 94 | |||
| 95 | u64 In::GetPlayedSampleCount() { | ||
| 96 | std::scoped_lock l{parent_mutex}; | ||
| 97 | return system.GetPlayedSampleCount(); | ||
| 98 | } | ||
| 99 | |||
| 100 | } // namespace AudioCore::AudioIn | ||
diff --git a/src/audio_core/in/audio_in.h b/src/audio_core/in/audio_in.h new file mode 100644 index 000000000..6253891d5 --- /dev/null +++ b/src/audio_core/in/audio_in.h | |||
| @@ -0,0 +1,147 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <mutex> | ||
| 7 | |||
| 8 | #include "audio_core/in/audio_in_system.h" | ||
| 9 | |||
| 10 | namespace Core { | ||
| 11 | class System; | ||
| 12 | } | ||
| 13 | |||
| 14 | namespace Kernel { | ||
| 15 | class KEvent; | ||
| 16 | class KReadableEvent; | ||
| 17 | } // namespace Kernel | ||
| 18 | |||
| 19 | namespace AudioCore::AudioIn { | ||
| 20 | class Manager; | ||
| 21 | |||
| 22 | /** | ||
| 23 | * Interface between the service and audio in system. Mainly responsible for forwarding service | ||
| 24 | * calls to the system. | ||
| 25 | */ | ||
| 26 | class In { | ||
| 27 | public: | ||
| 28 | explicit In(Core::System& system, Manager& manager, Kernel::KEvent* event, size_t session_id); | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Free this audio in from the audio in manager. | ||
| 32 | */ | ||
| 33 | void Free(); | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Get this audio in's system. | ||
| 37 | */ | ||
| 38 | System& GetSystem(); | ||
| 39 | |||
| 40 | /** | ||
| 41 | * Get the current state. | ||
| 42 | * | ||
| 43 | * @return Started or Stopped. | ||
| 44 | */ | ||
| 45 | AudioIn::State GetState(); | ||
| 46 | |||
| 47 | /** | ||
| 48 | * Start the system | ||
| 49 | * | ||
| 50 | * @return Result code | ||
| 51 | */ | ||
| 52 | Result StartSystem(); | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Start the system's device session. | ||
| 56 | */ | ||
| 57 | void StartSession(); | ||
| 58 | |||
| 59 | /** | ||
| 60 | * Stop the system. | ||
| 61 | * | ||
| 62 | * @return Result code | ||
| 63 | */ | ||
| 64 | Result StopSystem(); | ||
| 65 | |||
| 66 | /** | ||
| 67 | * Append a new buffer to the system, the buffer event will be signalled when it is filled. | ||
| 68 | * | ||
| 69 | * @param buffer - The new buffer to append. | ||
| 70 | * @param tag - Unique tag for this buffer. | ||
| 71 | * @return Result code. | ||
| 72 | */ | ||
| 73 | Result AppendBuffer(const AudioInBuffer& buffer, u64 tag); | ||
| 74 | |||
| 75 | /** | ||
| 76 | * Release all completed buffers, and register any appended. | ||
| 77 | */ | ||
| 78 | void ReleaseAndRegisterBuffers(); | ||
| 79 | |||
| 80 | /** | ||
| 81 | * Flush all buffers. | ||
| 82 | */ | ||
| 83 | bool FlushAudioInBuffers(); | ||
| 84 | |||
| 85 | /** | ||
| 86 | * Get all of the currently released buffers. | ||
| 87 | * | ||
| 88 | * @param tags - Output container for the buffer tags which were released. | ||
| 89 | * @return The number of buffers released. | ||
| 90 | */ | ||
| 91 | u32 GetReleasedBuffers(std::span<u64> tags); | ||
| 92 | |||
| 93 | /** | ||
| 94 | * Get the buffer event for this audio in, this event will be signalled when a buffer is filled. | ||
| 95 | * | ||
| 96 | * @return The buffer event. | ||
| 97 | */ | ||
| 98 | Kernel::KReadableEvent& GetBufferEvent(); | ||
| 99 | |||
| 100 | /** | ||
| 101 | * Get the current system volume. | ||
| 102 | * | ||
| 103 | * @return The current volume. | ||
| 104 | */ | ||
| 105 | f32 GetVolume(); | ||
| 106 | |||
| 107 | /** | ||
| 108 | * Set the system volume. | ||
| 109 | * | ||
| 110 | * @param volume - The volume to set. | ||
| 111 | */ | ||
| 112 | void SetVolume(f32 volume); | ||
| 113 | |||
| 114 | /** | ||
| 115 | * Check if a buffer is in the system. | ||
| 116 | * | ||
| 117 | * @param tag - The tag to search for. | ||
| 118 | * @return True if the buffer is in the system, otherwise false. | ||
| 119 | */ | ||
| 120 | bool ContainsAudioBuffer(u64 tag); | ||
| 121 | |||
| 122 | /** | ||
| 123 | * Get the maximum number of buffers. | ||
| 124 | * | ||
| 125 | * @return The maximum number of buffers. | ||
| 126 | */ | ||
| 127 | u32 GetBufferCount(); | ||
| 128 | |||
| 129 | /** | ||
| 130 | * Get the total played sample count for this audio in. | ||
| 131 | * | ||
| 132 | * @return The played sample count. | ||
| 133 | */ | ||
| 134 | u64 GetPlayedSampleCount(); | ||
| 135 | |||
| 136 | private: | ||
| 137 | /// The AudioIn::Manager this audio in is registered with | ||
| 138 | Manager& manager; | ||
| 139 | /// Manager's mutex | ||
| 140 | std::recursive_mutex& parent_mutex; | ||
| 141 | /// Buffer event, signalled when buffers are ready to be released | ||
| 142 | Kernel::KEvent* event; | ||
| 143 | /// Main audio in system | ||
| 144 | System system; | ||
| 145 | }; | ||
| 146 | |||
| 147 | } // namespace AudioCore::AudioIn | ||
diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp new file mode 100644 index 000000000..ec5d37ed4 --- /dev/null +++ b/src/audio_core/in/audio_in_system.cpp | |||
| @@ -0,0 +1,213 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <mutex> | ||
| 5 | #include "audio_core/audio_event.h" | ||
| 6 | #include "audio_core/audio_manager.h" | ||
| 7 | #include "audio_core/in/audio_in_system.h" | ||
| 8 | #include "common/logging/log.h" | ||
| 9 | #include "core/core.h" | ||
| 10 | #include "core/core_timing.h" | ||
| 11 | #include "core/hle/kernel/k_event.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioIn { | ||
| 14 | |||
| 15 | System::System(Core::System& system_, Kernel::KEvent* event_, const size_t session_id_) | ||
| 16 | : system{system_}, buffer_event{event_}, | ||
| 17 | session_id{session_id_}, session{std::make_unique<DeviceSession>(system_)} {} | ||
| 18 | |||
| 19 | System::~System() { | ||
| 20 | Finalize(); | ||
| 21 | } | ||
| 22 | |||
| 23 | void System::Finalize() { | ||
| 24 | Stop(); | ||
| 25 | session->Finalize(); | ||
| 26 | buffer_event->GetWritableEvent().Signal(); | ||
| 27 | } | ||
| 28 | |||
| 29 | void System::StartSession() { | ||
| 30 | session->Start(); | ||
| 31 | } | ||
| 32 | |||
| 33 | size_t System::GetSessionId() const { | ||
| 34 | return session_id; | ||
| 35 | } | ||
| 36 | |||
| 37 | std::string_view System::GetDefaultDeviceName() { | ||
| 38 | return "BuiltInHeadset"; | ||
| 39 | } | ||
| 40 | |||
| 41 | std::string_view System::GetDefaultUacDeviceName() { | ||
| 42 | return "Uac"; | ||
| 43 | } | ||
| 44 | |||
| 45 | Result System::IsConfigValid(const std::string_view device_name, | ||
| 46 | const AudioInParameter& in_params) { | ||
| 47 | if ((device_name.size() > 0) && | ||
| 48 | (device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) { | ||
| 49 | return Service::Audio::ERR_INVALID_DEVICE_NAME; | ||
| 50 | } | ||
| 51 | |||
| 52 | if (in_params.sample_rate != TargetSampleRate && in_params.sample_rate > 0) { | ||
| 53 | return Service::Audio::ERR_INVALID_SAMPLE_RATE; | ||
| 54 | } | ||
| 55 | |||
| 56 | return ResultSuccess; | ||
| 57 | } | ||
| 58 | |||
| 59 | Result System::Initialize(std::string& device_name, const AudioInParameter& in_params, | ||
| 60 | const u32 handle_, const u64 applet_resource_user_id_) { | ||
| 61 | auto result{IsConfigValid(device_name, in_params)}; | ||
| 62 | if (result.IsError()) { | ||
| 63 | return result; | ||
| 64 | } | ||
| 65 | |||
| 66 | handle = handle_; | ||
| 67 | applet_resource_user_id = applet_resource_user_id_; | ||
| 68 | if (device_name.empty() || device_name[0] == '\0') { | ||
| 69 | name = std::string(GetDefaultDeviceName()); | ||
| 70 | } else { | ||
| 71 | name = std::move(device_name); | ||
| 72 | } | ||
| 73 | |||
| 74 | sample_rate = TargetSampleRate; | ||
| 75 | sample_format = SampleFormat::PcmInt16; | ||
| 76 | channel_count = in_params.channel_count <= 2 ? 2 : 6; | ||
| 77 | volume = 1.0f; | ||
| 78 | is_uac = name == "Uac"; | ||
| 79 | return ResultSuccess; | ||
| 80 | } | ||
| 81 | |||
| 82 | Result System::Start() { | ||
| 83 | if (state != State::Stopped) { | ||
| 84 | return Service::Audio::ERR_OPERATION_FAILED; | ||
| 85 | } | ||
| 86 | |||
| 87 | session->Initialize(name, sample_format, channel_count, session_id, handle, | ||
| 88 | applet_resource_user_id, Sink::StreamType::In); | ||
| 89 | session->SetVolume(volume); | ||
| 90 | session->Start(); | ||
| 91 | state = State::Started; | ||
| 92 | |||
| 93 | std::vector<AudioBuffer> buffers_to_flush{}; | ||
| 94 | buffers.RegisterBuffers(buffers_to_flush); | ||
| 95 | session->AppendBuffers(buffers_to_flush); | ||
| 96 | |||
| 97 | return ResultSuccess; | ||
| 98 | } | ||
| 99 | |||
| 100 | Result System::Stop() { | ||
| 101 | if (state == State::Started) { | ||
| 102 | session->Stop(); | ||
| 103 | session->SetVolume(0.0f); | ||
| 104 | state = State::Stopped; | ||
| 105 | } | ||
| 106 | |||
| 107 | return ResultSuccess; | ||
| 108 | } | ||
| 109 | |||
| 110 | bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) { | ||
| 111 | if (buffers.GetTotalBufferCount() == BufferCount) { | ||
| 112 | return false; | ||
| 113 | } | ||
| 114 | |||
| 115 | AudioBuffer new_buffer{ | ||
| 116 | .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; | ||
| 117 | |||
| 118 | buffers.AppendBuffer(new_buffer); | ||
| 119 | RegisterBuffers(); | ||
| 120 | |||
| 121 | return true; | ||
| 122 | } | ||
| 123 | |||
| 124 | void System::RegisterBuffers() { | ||
| 125 | if (state == State::Started) { | ||
| 126 | std::vector<AudioBuffer> registered_buffers{}; | ||
| 127 | buffers.RegisterBuffers(registered_buffers); | ||
| 128 | session->AppendBuffers(registered_buffers); | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | void System::ReleaseBuffers() { | ||
| 133 | bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session)}; | ||
| 134 | |||
| 135 | if (signal) { | ||
| 136 | // Signal if any buffer was released, or if none are registered, we need more. | ||
| 137 | buffer_event->GetWritableEvent().Signal(); | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | u32 System::GetReleasedBuffers(std::span<u64> tags) { | ||
| 142 | return buffers.GetReleasedBuffers(tags); | ||
| 143 | } | ||
| 144 | |||
| 145 | bool System::FlushAudioInBuffers() { | ||
| 146 | if (state != State::Started) { | ||
| 147 | return false; | ||
| 148 | } | ||
| 149 | |||
| 150 | u32 buffers_released{}; | ||
| 151 | buffers.FlushBuffers(buffers_released); | ||
| 152 | |||
| 153 | if (buffers_released > 0) { | ||
| 154 | buffer_event->GetWritableEvent().Signal(); | ||
| 155 | } | ||
| 156 | return true; | ||
| 157 | } | ||
| 158 | |||
| 159 | u16 System::GetChannelCount() const { | ||
| 160 | return channel_count; | ||
| 161 | } | ||
| 162 | |||
| 163 | u32 System::GetSampleRate() const { | ||
| 164 | return sample_rate; | ||
| 165 | } | ||
| 166 | |||
| 167 | SampleFormat System::GetSampleFormat() const { | ||
| 168 | return sample_format; | ||
| 169 | } | ||
| 170 | |||
| 171 | State System::GetState() { | ||
| 172 | switch (state) { | ||
| 173 | case State::Started: | ||
| 174 | case State::Stopped: | ||
| 175 | return state; | ||
| 176 | default: | ||
| 177 | LOG_ERROR(Service_Audio, "AudioIn invalid state!"); | ||
| 178 | state = State::Stopped; | ||
| 179 | break; | ||
| 180 | } | ||
| 181 | return state; | ||
| 182 | } | ||
| 183 | |||
| 184 | std::string System::GetName() const { | ||
| 185 | return name; | ||
| 186 | } | ||
| 187 | |||
| 188 | f32 System::GetVolume() const { | ||
| 189 | return volume; | ||
| 190 | } | ||
| 191 | |||
| 192 | void System::SetVolume(const f32 volume_) { | ||
| 193 | volume = volume_; | ||
| 194 | session->SetVolume(volume_); | ||
| 195 | } | ||
| 196 | |||
| 197 | bool System::ContainsAudioBuffer(const u64 tag) { | ||
| 198 | return buffers.ContainsBuffer(tag); | ||
| 199 | } | ||
| 200 | |||
| 201 | u32 System::GetBufferCount() { | ||
| 202 | return buffers.GetAppendedRegisteredCount(); | ||
| 203 | } | ||
| 204 | |||
| 205 | u64 System::GetPlayedSampleCount() const { | ||
| 206 | return session->GetPlayedSampleCount(); | ||
| 207 | } | ||
| 208 | |||
| 209 | bool System::IsUac() const { | ||
| 210 | return is_uac; | ||
| 211 | } | ||
| 212 | |||
| 213 | } // namespace AudioCore::AudioIn | ||
diff --git a/src/audio_core/in/audio_in_system.h b/src/audio_core/in/audio_in_system.h new file mode 100644 index 000000000..165e35d83 --- /dev/null +++ b/src/audio_core/in/audio_in_system.h | |||
| @@ -0,0 +1,275 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <atomic> | ||
| 7 | #include <memory> | ||
| 8 | #include <span> | ||
| 9 | #include <string> | ||
| 10 | |||
| 11 | #include "audio_core/common/common.h" | ||
| 12 | #include "audio_core/device/audio_buffers.h" | ||
| 13 | #include "audio_core/device/device_session.h" | ||
| 14 | #include "core/hle/service/audio/errors.h" | ||
| 15 | |||
| 16 | namespace Core { | ||
| 17 | class System; | ||
| 18 | } | ||
| 19 | |||
| 20 | namespace Kernel { | ||
| 21 | class KEvent; | ||
| 22 | } | ||
| 23 | |||
| 24 | namespace AudioCore::AudioIn { | ||
| 25 | |||
| 26 | constexpr SessionTypes SessionType = SessionTypes::AudioIn; | ||
| 27 | |||
| 28 | struct AudioInParameter { | ||
| 29 | /* 0x0 */ s32_le sample_rate; | ||
| 30 | /* 0x4 */ u16_le channel_count; | ||
| 31 | /* 0x6 */ u16_le reserved; | ||
| 32 | }; | ||
| 33 | static_assert(sizeof(AudioInParameter) == 0x8, "AudioInParameter is an invalid size"); | ||
| 34 | |||
| 35 | struct AudioInParameterInternal { | ||
| 36 | /* 0x0 */ u32_le sample_rate; | ||
| 37 | /* 0x4 */ u32_le channel_count; | ||
| 38 | /* 0x8 */ u32_le sample_format; | ||
| 39 | /* 0xC */ u32_le state; | ||
| 40 | }; | ||
| 41 | static_assert(sizeof(AudioInParameterInternal) == 0x10, | ||
| 42 | "AudioInParameterInternal is an invalid size"); | ||
| 43 | |||
| 44 | struct AudioInBuffer { | ||
| 45 | /* 0x00 */ AudioInBuffer* next; | ||
| 46 | /* 0x08 */ VAddr samples; | ||
| 47 | /* 0x10 */ u64 capacity; | ||
| 48 | /* 0x18 */ u64 size; | ||
| 49 | /* 0x20 */ u64 offset; | ||
| 50 | }; | ||
| 51 | static_assert(sizeof(AudioInBuffer) == 0x28, "AudioInBuffer is an invalid size"); | ||
| 52 | |||
| 53 | enum class State { | ||
| 54 | Started, | ||
| 55 | Stopped, | ||
| 56 | }; | ||
| 57 | |||
| 58 | /** | ||
| 59 | * Controls and drives audio input. | ||
| 60 | */ | ||
| 61 | class System { | ||
| 62 | public: | ||
| 63 | explicit System(Core::System& system, Kernel::KEvent* event, size_t session_id); | ||
| 64 | ~System(); | ||
| 65 | |||
| 66 | /** | ||
| 67 | * Get the default audio input device name. | ||
| 68 | * | ||
| 69 | * @return The default audio input device name. | ||
| 70 | */ | ||
| 71 | std::string_view GetDefaultDeviceName(); | ||
| 72 | |||
| 73 | /** | ||
| 74 | * Get the default USB audio input device name. | ||
| 75 | * This is preferred over non-USB as some games refuse to work with the BuiltInHeadset | ||
| 76 | * (e.g Let's Sing). | ||
| 77 | * | ||
| 78 | * @return The default USB audio input device name. | ||
| 79 | */ | ||
| 80 | std::string_view GetDefaultUacDeviceName(); | ||
| 81 | |||
| 82 | /** | ||
| 83 | * Is the given initialize config valid? | ||
| 84 | * | ||
| 85 | * @param device_name - The name of the requested input device. | ||
| 86 | * @param in_params - Input parameters, see AudioInParameter. | ||
| 87 | * @return Result code. | ||
| 88 | */ | ||
| 89 | Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params); | ||
| 90 | |||
| 91 | /** | ||
| 92 | * Initialize this system. | ||
| 93 | * | ||
| 94 | * @param device_name - The name of the requested input device. | ||
| 95 | * @param in_params - Input parameters, see AudioInParameter. | ||
| 96 | * @param handle - Unused. | ||
| 97 | * @param applet_resource_user_id - Unused. | ||
| 98 | * @return Result code. | ||
| 99 | */ | ||
| 100 | Result Initialize(std::string& device_name, const AudioInParameter& in_params, u32 handle, | ||
| 101 | u64 applet_resource_user_id); | ||
| 102 | |||
| 103 | /** | ||
| 104 | * Start this system. | ||
| 105 | * | ||
| 106 | * @return Result code. | ||
| 107 | */ | ||
| 108 | Result Start(); | ||
| 109 | |||
| 110 | /** | ||
| 111 | * Stop this system. | ||
| 112 | * | ||
| 113 | * @return Result code. | ||
| 114 | */ | ||
| 115 | Result Stop(); | ||
| 116 | |||
| 117 | /** | ||
| 118 | * Finalize this system. | ||
| 119 | */ | ||
| 120 | void Finalize(); | ||
| 121 | |||
| 122 | /** | ||
| 123 | * Start this system's device session. | ||
| 124 | */ | ||
| 125 | void StartSession(); | ||
| 126 | |||
| 127 | /** | ||
| 128 | * Get this system's id. | ||
| 129 | */ | ||
| 130 | size_t GetSessionId() const; | ||
| 131 | |||
| 132 | /** | ||
| 133 | * Append a new buffer to the device. | ||
| 134 | * | ||
| 135 | * @param buffer - New buffer to append. | ||
| 136 | * @param tag - Unique tag of the buffer. | ||
| 137 | * @return True if the buffer was appended, otherwise false. | ||
| 138 | */ | ||
| 139 | bool AppendBuffer(const AudioInBuffer& buffer, u64 tag); | ||
| 140 | |||
| 141 | /** | ||
| 142 | * Register all appended buffers. | ||
| 143 | */ | ||
| 144 | void RegisterBuffers(); | ||
| 145 | |||
| 146 | /** | ||
| 147 | * Release all registered buffers. | ||
| 148 | */ | ||
| 149 | void ReleaseBuffers(); | ||
| 150 | |||
| 151 | /** | ||
| 152 | * Get all released buffers. | ||
| 153 | * | ||
| 154 | * @param tags - Container to be filled with the released buffers' tags. | ||
| 155 | * @return The number of buffers released. | ||
| 156 | */ | ||
| 157 | u32 GetReleasedBuffers(std::span<u64> tags); | ||
| 158 | |||
| 159 | /** | ||
| 160 | * Flush all appended and registered buffers. | ||
| 161 | * | ||
| 162 | * @return True if buffers were successfully flushed, otherwise false. | ||
| 163 | */ | ||
| 164 | bool FlushAudioInBuffers(); | ||
| 165 | |||
| 166 | /** | ||
| 167 | * Get this system's current channel count. | ||
| 168 | * | ||
| 169 | * @return The channel count. | ||
| 170 | */ | ||
| 171 | u16 GetChannelCount() const; | ||
| 172 | |||
| 173 | /** | ||
| 174 | * Get this system's current sample rate. | ||
| 175 | * | ||
| 176 | * @return The sample rate. | ||
| 177 | */ | ||
| 178 | u32 GetSampleRate() const; | ||
| 179 | |||
| 180 | /** | ||
| 181 | * Get this system's current sample format. | ||
| 182 | * | ||
| 183 | * @return The sample format. | ||
| 184 | */ | ||
| 185 | SampleFormat GetSampleFormat() const; | ||
| 186 | |||
| 187 | /** | ||
| 188 | * Get this system's current state. | ||
| 189 | * | ||
| 190 | * @return The current state. | ||
| 191 | */ | ||
| 192 | State GetState(); | ||
| 193 | |||
| 194 | /** | ||
| 195 | * Get this system's name. | ||
| 196 | * | ||
| 197 | * @return The system's name. | ||
| 198 | */ | ||
| 199 | std::string GetName() const; | ||
| 200 | |||
| 201 | /** | ||
| 202 | * Get this system's current volume. | ||
| 203 | * | ||
| 204 | * @return The system's current volume. | ||
| 205 | */ | ||
| 206 | f32 GetVolume() const; | ||
| 207 | |||
| 208 | /** | ||
| 209 | * Set this system's current volume. | ||
| 210 | * | ||
| 211 | * @param The new volume. | ||
| 212 | */ | ||
| 213 | void SetVolume(f32 volume); | ||
| 214 | |||
| 215 | /** | ||
| 216 | * Does the system contain this buffer? | ||
| 217 | * | ||
| 218 | * @param tag - Unique tag to search for. | ||
| 219 | * @return True if the buffer is in the system, otherwise false. | ||
| 220 | */ | ||
| 221 | bool ContainsAudioBuffer(u64 tag); | ||
| 222 | |||
| 223 | /** | ||
| 224 | * Get the maximum number of usable buffers (default 32). | ||
| 225 | * | ||
| 226 | * @return The number of buffers. | ||
| 227 | */ | ||
| 228 | u32 GetBufferCount(); | ||
| 229 | |||
| 230 | /** | ||
| 231 | * Get the total number of samples played by this system. | ||
| 232 | * | ||
| 233 | * @return The number of samples. | ||
| 234 | */ | ||
| 235 | u64 GetPlayedSampleCount() const; | ||
| 236 | |||
| 237 | /** | ||
| 238 | * Is this system using a USB device? | ||
| 239 | * | ||
| 240 | * @return True if using a USB device, otherwise false. | ||
| 241 | */ | ||
| 242 | bool IsUac() const; | ||
| 243 | |||
| 244 | private: | ||
| 245 | /// Core system | ||
| 246 | Core::System& system; | ||
| 247 | /// (Unused) | ||
| 248 | u32 handle{}; | ||
| 249 | /// (Unused) | ||
| 250 | u64 applet_resource_user_id{}; | ||
| 251 | /// Buffer event, signalled when a buffer is ready | ||
| 252 | Kernel::KEvent* buffer_event; | ||
| 253 | /// Session id of this system | ||
| 254 | size_t session_id{}; | ||
| 255 | /// Device session for this system | ||
| 256 | std::unique_ptr<DeviceSession> session; | ||
| 257 | /// Audio buffers in use by this system | ||
| 258 | AudioBuffers<BufferCount> buffers{BufferCount}; | ||
| 259 | /// Sample rate of this system | ||
| 260 | u32 sample_rate{}; | ||
| 261 | /// Sample format of this system | ||
| 262 | SampleFormat sample_format{SampleFormat::PcmInt16}; | ||
| 263 | /// Channel count of this system | ||
| 264 | u16 channel_count{}; | ||
| 265 | /// State of this system | ||
| 266 | std::atomic<State> state{State::Stopped}; | ||
| 267 | /// Name of this system | ||
| 268 | std::string name{}; | ||
| 269 | /// Volume of this system | ||
| 270 | f32 volume{1.0f}; | ||
| 271 | /// Is this system's device USB? | ||
| 272 | bool is_uac{false}; | ||
| 273 | }; | ||
| 274 | |||
| 275 | } // namespace AudioCore::AudioIn | ||
diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp deleted file mode 100644 index 0065e6e53..000000000 --- a/src/audio_core/info_updater.cpp +++ /dev/null | |||
| @@ -1,511 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/behavior_info.h" | ||
| 5 | #include "audio_core/effect_context.h" | ||
| 6 | #include "audio_core/info_updater.h" | ||
| 7 | #include "audio_core/memory_pool.h" | ||
| 8 | #include "audio_core/mix_context.h" | ||
| 9 | #include "audio_core/sink_context.h" | ||
| 10 | #include "audio_core/splitter_context.h" | ||
| 11 | #include "audio_core/voice_context.h" | ||
| 12 | #include "common/logging/log.h" | ||
| 13 | |||
| 14 | namespace AudioCore { | ||
| 15 | |||
| 16 | InfoUpdater::InfoUpdater(const std::vector<u8>& in_params_, std::vector<u8>& out_params_, | ||
| 17 | BehaviorInfo& behavior_info_) | ||
| 18 | : in_params(in_params_), out_params(out_params_), behavior_info(behavior_info_) { | ||
| 19 | ASSERT( | ||
| 20 | AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader))); | ||
| 21 | std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader)); | ||
| 22 | output_header.total_size = sizeof(AudioCommon::UpdateDataHeader); | ||
| 23 | } | ||
| 24 | |||
| 25 | InfoUpdater::~InfoUpdater() = default; | ||
| 26 | |||
| 27 | bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) { | ||
| 28 | if (input_header.size.behavior != sizeof(BehaviorInfo::InParams)) { | ||
| 29 | LOG_ERROR(Audio, "Behavior info is an invalid size, expecting 0x{:X} but got 0x{:X}", | ||
| 30 | sizeof(BehaviorInfo::InParams), input_header.size.behavior); | ||
| 31 | return false; | ||
| 32 | } | ||
| 33 | |||
| 34 | if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, | ||
| 35 | sizeof(BehaviorInfo::InParams))) { | ||
| 36 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 37 | return false; | ||
| 38 | } | ||
| 39 | |||
| 40 | BehaviorInfo::InParams behavior_in{}; | ||
| 41 | std::memcpy(&behavior_in, in_params.data() + input_offset, sizeof(BehaviorInfo::InParams)); | ||
| 42 | input_offset += sizeof(BehaviorInfo::InParams); | ||
| 43 | |||
| 44 | // Make sure it's an audio revision we can actually support | ||
| 45 | if (!AudioCommon::IsValidRevision(behavior_in.revision)) { | ||
| 46 | LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", behavior_in.revision); | ||
| 47 | return false; | ||
| 48 | } | ||
| 49 | |||
| 50 | // Make sure that our behavior info revision matches the input | ||
| 51 | if (in_behavior_info.GetUserRevision() != behavior_in.revision) { | ||
| 52 | LOG_ERROR(Audio, | ||
| 53 | "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}", | ||
| 54 | in_behavior_info.GetUserRevision(), behavior_in.revision); | ||
| 55 | return false; | ||
| 56 | } | ||
| 57 | |||
| 58 | // Update behavior info flags | ||
| 59 | in_behavior_info.ClearError(); | ||
| 60 | in_behavior_info.UpdateFlags(behavior_in.flags); | ||
| 61 | |||
| 62 | return true; | ||
| 63 | } | ||
| 64 | |||
| 65 | bool InfoUpdater::UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info) { | ||
| 66 | const auto memory_pool_count = memory_pool_info.size(); | ||
| 67 | const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count; | ||
| 68 | const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count; | ||
| 69 | |||
| 70 | if (input_header.size.memory_pool != total_memory_pool_in) { | ||
| 71 | LOG_ERROR(Audio, "Memory pools are an invalid size, expecting 0x{:X} but got 0x{:X}", | ||
| 72 | total_memory_pool_in, input_header.size.memory_pool); | ||
| 73 | return false; | ||
| 74 | } | ||
| 75 | |||
| 76 | if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_memory_pool_in)) { | ||
| 77 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 78 | return false; | ||
| 79 | } | ||
| 80 | |||
| 81 | std::vector<ServerMemoryPoolInfo::InParams> mempool_in(memory_pool_count); | ||
| 82 | std::vector<ServerMemoryPoolInfo::OutParams> mempool_out(memory_pool_count); | ||
| 83 | |||
| 84 | std::memcpy(mempool_in.data(), in_params.data() + input_offset, total_memory_pool_in); | ||
| 85 | input_offset += total_memory_pool_in; | ||
| 86 | |||
| 87 | // Update our memory pools | ||
| 88 | for (std::size_t i = 0; i < memory_pool_count; i++) { | ||
| 89 | if (!memory_pool_info[i].Update(mempool_in[i], mempool_out[i])) { | ||
| 90 | LOG_ERROR(Audio, "Failed to update memory pool {}!", i); | ||
| 91 | return false; | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, | ||
| 96 | sizeof(BehaviorInfo::InParams))) { | ||
| 97 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 98 | return false; | ||
| 99 | } | ||
| 100 | |||
| 101 | std::memcpy(out_params.data() + output_offset, mempool_out.data(), total_memory_pool_out); | ||
| 102 | output_offset += total_memory_pool_out; | ||
| 103 | output_header.size.memory_pool = static_cast<u32>(total_memory_pool_out); | ||
| 104 | return true; | ||
| 105 | } | ||
| 106 | |||
| 107 | bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) { | ||
| 108 | const auto voice_count = voice_context.GetVoiceCount(); | ||
| 109 | const auto voice_size = voice_count * sizeof(VoiceChannelResource::InParams); | ||
| 110 | std::vector<VoiceChannelResource::InParams> resources_in(voice_count); | ||
| 111 | |||
| 112 | if (input_header.size.voice_channel_resource != voice_size) { | ||
| 113 | LOG_ERROR(Audio, "VoiceChannelResource is an invalid size, expecting 0x{:X} but got 0x{:X}", | ||
| 114 | voice_size, input_header.size.voice_channel_resource); | ||
| 115 | return false; | ||
| 116 | } | ||
| 117 | |||
| 118 | if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_size)) { | ||
| 119 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 120 | return false; | ||
| 121 | } | ||
| 122 | |||
| 123 | std::memcpy(resources_in.data(), in_params.data() + input_offset, voice_size); | ||
| 124 | input_offset += voice_size; | ||
| 125 | |||
| 126 | // Update our channel resources | ||
| 127 | for (std::size_t i = 0; i < voice_count; i++) { | ||
| 128 | // Grab our channel resource | ||
| 129 | auto& resource = voice_context.GetChannelResource(i); | ||
| 130 | resource.Update(resources_in[i]); | ||
| 131 | } | ||
| 132 | |||
| 133 | return true; | ||
| 134 | } | ||
| 135 | |||
| 136 | bool InfoUpdater::UpdateVoices(VoiceContext& voice_context, | ||
| 137 | [[maybe_unused]] std::vector<ServerMemoryPoolInfo>& memory_pool_info, | ||
| 138 | [[maybe_unused]] VAddr audio_codec_dsp_addr) { | ||
| 139 | const auto voice_count = voice_context.GetVoiceCount(); | ||
| 140 | std::vector<VoiceInfo::InParams> voice_in(voice_count); | ||
| 141 | std::vector<VoiceInfo::OutParams> voice_out(voice_count); | ||
| 142 | |||
| 143 | const auto voice_in_size = voice_count * sizeof(VoiceInfo::InParams); | ||
| 144 | const auto voice_out_size = voice_count * sizeof(VoiceInfo::OutParams); | ||
| 145 | |||
| 146 | if (input_header.size.voice != voice_in_size) { | ||
| 147 | LOG_ERROR(Audio, "Voices are an invalid size, expecting 0x{:X} but got 0x{:X}", | ||
| 148 | voice_in_size, input_header.size.voice); | ||
| 149 | return false; | ||
| 150 | } | ||
| 151 | |||
| 152 | if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_in_size)) { | ||
| 153 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 154 | return false; | ||
| 155 | } | ||
| 156 | |||
| 157 | std::memcpy(voice_in.data(), in_params.data() + input_offset, voice_in_size); | ||
| 158 | input_offset += voice_in_size; | ||
| 159 | |||
| 160 | // Set all voices to not be in use | ||
| 161 | for (std::size_t i = 0; i < voice_count; i++) { | ||
| 162 | voice_context.GetInfo(i).GetInParams().in_use = false; | ||
| 163 | } | ||
| 164 | |||
| 165 | // Update our voices | ||
| 166 | for (std::size_t i = 0; i < voice_count; i++) { | ||
| 167 | auto& voice_in_params = voice_in[i]; | ||
| 168 | const auto channel_count = static_cast<std::size_t>(voice_in_params.channel_count); | ||
| 169 | // Skip if it's not currently in use | ||
| 170 | if (!voice_in_params.is_in_use) { | ||
| 171 | continue; | ||
| 172 | } | ||
| 173 | // Voice states for each channel | ||
| 174 | std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> voice_states{}; | ||
| 175 | ASSERT(static_cast<std::size_t>(voice_in_params.id) < voice_count); | ||
| 176 | |||
| 177 | // Grab our current voice info | ||
| 178 | auto& voice_info = voice_context.GetInfo(static_cast<std::size_t>(voice_in_params.id)); | ||
| 179 | |||
| 180 | ASSERT(channel_count <= AudioCommon::MAX_CHANNEL_COUNT); | ||
| 181 | |||
| 182 | // Get all our channel voice states | ||
| 183 | for (std::size_t channel = 0; channel < channel_count; channel++) { | ||
| 184 | voice_states[channel] = | ||
| 185 | &voice_context.GetState(voice_in_params.voice_channel_resource_ids[channel]); | ||
| 186 | } | ||
| 187 | |||
| 188 | if (voice_in_params.is_new) { | ||
| 189 | // Default our values for our voice | ||
| 190 | voice_info.Initialize(); | ||
| 191 | |||
| 192 | // Zero out our voice states | ||
| 193 | for (std::size_t channel = 0; channel < channel_count; channel++) { | ||
| 194 | std::memset(voice_states[channel], 0, sizeof(VoiceState)); | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | // Update our voice | ||
| 199 | voice_info.UpdateParameters(voice_in_params, behavior_info); | ||
| 200 | // TODO(ogniK): Handle mapping errors with behavior info based on in params response | ||
| 201 | |||
| 202 | // Update our wave buffers | ||
| 203 | voice_info.UpdateWaveBuffers(voice_in_params, voice_states, behavior_info); | ||
| 204 | voice_info.WriteOutStatus(voice_out[i], voice_in_params, voice_states); | ||
| 205 | } | ||
| 206 | |||
| 207 | if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, voice_out_size)) { | ||
| 208 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 209 | return false; | ||
| 210 | } | ||
| 211 | std::memcpy(out_params.data() + output_offset, voice_out.data(), voice_out_size); | ||
| 212 | output_offset += voice_out_size; | ||
| 213 | output_header.size.voice = static_cast<u32>(voice_out_size); | ||
| 214 | return true; | ||
| 215 | } | ||
| 216 | |||
| 217 | bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) { | ||
| 218 | const auto effect_count = effect_context.GetCount(); | ||
| 219 | std::vector<EffectInfo::InParams> effect_in(effect_count); | ||
| 220 | std::vector<EffectInfo::OutParams> effect_out(effect_count); | ||
| 221 | |||
| 222 | const auto total_effect_in = effect_count * sizeof(EffectInfo::InParams); | ||
| 223 | const auto total_effect_out = effect_count * sizeof(EffectInfo::OutParams); | ||
| 224 | |||
| 225 | if (input_header.size.effect != total_effect_in) { | ||
| 226 | LOG_ERROR(Audio, "Effects are an invalid size, expecting 0x{:X} but got 0x{:X}", | ||
| 227 | total_effect_in, input_header.size.effect); | ||
| 228 | return false; | ||
| 229 | } | ||
| 230 | |||
| 231 | if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_effect_in)) { | ||
| 232 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 233 | return false; | ||
| 234 | } | ||
| 235 | |||
| 236 | std::memcpy(effect_in.data(), in_params.data() + input_offset, total_effect_in); | ||
| 237 | input_offset += total_effect_in; | ||
| 238 | |||
| 239 | // Update effects | ||
| 240 | for (std::size_t i = 0; i < effect_count; i++) { | ||
| 241 | auto* info = effect_context.GetInfo(i); | ||
| 242 | if (effect_in[i].type != info->GetType()) { | ||
| 243 | info = effect_context.RetargetEffect(i, effect_in[i].type); | ||
| 244 | } | ||
| 245 | |||
| 246 | info->Update(effect_in[i]); | ||
| 247 | |||
| 248 | if ((!is_active && info->GetUsage() != UsageState::Initialized) || | ||
| 249 | info->GetUsage() == UsageState::Stopped) { | ||
| 250 | effect_out[i].status = UsageStatus::Removed; | ||
| 251 | } else { | ||
| 252 | effect_out[i].status = UsageStatus::Used; | ||
| 253 | } | ||
| 254 | } | ||
| 255 | |||
| 256 | if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_effect_out)) { | ||
| 257 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 258 | return false; | ||
| 259 | } | ||
| 260 | |||
| 261 | std::memcpy(out_params.data() + output_offset, effect_out.data(), total_effect_out); | ||
| 262 | output_offset += total_effect_out; | ||
| 263 | output_header.size.effect = static_cast<u32>(total_effect_out); | ||
| 264 | |||
| 265 | return true; | ||
| 266 | } | ||
| 267 | |||
| 268 | bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) { | ||
| 269 | std::size_t start_offset = input_offset; | ||
| 270 | std::size_t bytes_read{}; | ||
| 271 | // Update splitter context | ||
| 272 | if (!splitter_context.Update(in_params, input_offset, bytes_read)) { | ||
| 273 | LOG_ERROR(Audio, "Failed to update splitter context!"); | ||
| 274 | return false; | ||
| 275 | } | ||
| 276 | |||
| 277 | const auto consumed = input_offset - start_offset; | ||
| 278 | |||
| 279 | if (input_header.size.splitter != consumed) { | ||
| 280 | LOG_ERROR(Audio, "Splitters is an invalid size, expecting 0x{:X} but got 0x{:X}", | ||
| 281 | bytes_read, input_header.size.splitter); | ||
| 282 | return false; | ||
| 283 | } | ||
| 284 | |||
| 285 | return true; | ||
| 286 | } | ||
| 287 | |||
| 288 | Result InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count, | ||
| 289 | SplitterContext& splitter_context, EffectContext& effect_context) { | ||
| 290 | std::vector<MixInfo::InParams> mix_in_params; | ||
| 291 | |||
| 292 | if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) { | ||
| 293 | // If we're not dirty, get ALL mix in parameters | ||
| 294 | const auto context_mix_count = mix_context.GetCount(); | ||
| 295 | const auto total_mix_in = context_mix_count * sizeof(MixInfo::InParams); | ||
| 296 | if (input_header.size.mixer != total_mix_in) { | ||
| 297 | LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}", | ||
| 298 | total_mix_in, input_header.size.mixer); | ||
| 299 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 300 | } | ||
| 301 | |||
| 302 | if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_mix_in)) { | ||
| 303 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 304 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 305 | } | ||
| 306 | |||
| 307 | mix_in_params.resize(context_mix_count); | ||
| 308 | std::memcpy(mix_in_params.data(), in_params.data() + input_offset, total_mix_in); | ||
| 309 | |||
| 310 | input_offset += total_mix_in; | ||
| 311 | } else { | ||
| 312 | // Only update the "dirty" mixes | ||
| 313 | MixInfo::DirtyHeader dirty_header{}; | ||
| 314 | if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, | ||
| 315 | sizeof(MixInfo::DirtyHeader))) { | ||
| 316 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 317 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 318 | } | ||
| 319 | |||
| 320 | std::memcpy(&dirty_header, in_params.data() + input_offset, sizeof(MixInfo::DirtyHeader)); | ||
| 321 | input_offset += sizeof(MixInfo::DirtyHeader); | ||
| 322 | |||
| 323 | const auto total_mix_in = | ||
| 324 | dirty_header.mixer_count * sizeof(MixInfo::InParams) + sizeof(MixInfo::DirtyHeader); | ||
| 325 | |||
| 326 | if (input_header.size.mixer != total_mix_in) { | ||
| 327 | LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}", | ||
| 328 | total_mix_in, input_header.size.mixer); | ||
| 329 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 330 | } | ||
| 331 | |||
| 332 | if (dirty_header.mixer_count != 0) { | ||
| 333 | mix_in_params.resize(dirty_header.mixer_count); | ||
| 334 | std::memcpy(mix_in_params.data(), in_params.data() + input_offset, | ||
| 335 | mix_in_params.size() * sizeof(MixInfo::InParams)); | ||
| 336 | input_offset += mix_in_params.size() * sizeof(MixInfo::InParams); | ||
| 337 | } | ||
| 338 | } | ||
| 339 | |||
| 340 | // Get our total input count | ||
| 341 | const auto mix_count = mix_in_params.size(); | ||
| 342 | |||
| 343 | if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) { | ||
| 344 | // Only verify our buffer count if we're not dirty | ||
| 345 | std::size_t total_buffer_count{}; | ||
| 346 | for (std::size_t i = 0; i < mix_count; i++) { | ||
| 347 | const auto& in = mix_in_params[i]; | ||
| 348 | total_buffer_count += in.buffer_count; | ||
| 349 | if (static_cast<std::size_t>(in.dest_mix_id) > mix_count && | ||
| 350 | in.dest_mix_id != AudioCommon::NO_MIX && in.mix_id != AudioCommon::FINAL_MIX) { | ||
| 351 | LOG_ERROR( | ||
| 352 | Audio, | ||
| 353 | "Invalid mix destination, mix_id={:X}, dest_mix_id={:X}, mix_buffer_count={:X}", | ||
| 354 | in.mix_id, in.dest_mix_id, mix_buffer_count); | ||
| 355 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 356 | } | ||
| 357 | } | ||
| 358 | |||
| 359 | if (total_buffer_count > mix_buffer_count) { | ||
| 360 | LOG_ERROR(Audio, | ||
| 361 | "Too many mix buffers used! mix_buffer_count={:X}, requesting_buffers={:X}", | ||
| 362 | mix_buffer_count, total_buffer_count); | ||
| 363 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 364 | } | ||
| 365 | } | ||
| 366 | |||
| 367 | if (mix_buffer_count == 0) { | ||
| 368 | LOG_ERROR(Audio, "No mix buffers!"); | ||
| 369 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||
| 370 | } | ||
| 371 | |||
| 372 | bool should_sort = false; | ||
| 373 | for (std::size_t i = 0; i < mix_count; i++) { | ||
| 374 | const auto& mix_in = mix_in_params[i]; | ||
| 375 | std::size_t target_mix{}; | ||
| 376 | if (behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) { | ||
| 377 | target_mix = mix_in.mix_id; | ||
| 378 | } else { | ||
| 379 | // Non dirty supported games just use i instead of the actual mix_id | ||
| 380 | target_mix = i; | ||
| 381 | } | ||
| 382 | auto& mix_info = mix_context.GetInfo(target_mix); | ||
| 383 | auto& mix_info_params = mix_info.GetInParams(); | ||
| 384 | if (mix_info_params.in_use != mix_in.in_use) { | ||
| 385 | mix_info_params.in_use = mix_in.in_use; | ||
| 386 | mix_info.ResetEffectProcessingOrder(); | ||
| 387 | should_sort = true; | ||
| 388 | } | ||
| 389 | |||
| 390 | if (mix_in.in_use) { | ||
| 391 | should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info, | ||
| 392 | splitter_context, effect_context); | ||
| 393 | } | ||
| 394 | } | ||
| 395 | |||
| 396 | if (should_sort && behavior_info.IsSplitterSupported()) { | ||
| 397 | // Sort our splitter data | ||
| 398 | if (!mix_context.TsortInfo(splitter_context)) { | ||
| 399 | return AudioCommon::Audren::ERR_SPLITTER_SORT_FAILED; | ||
| 400 | } | ||
| 401 | } | ||
| 402 | |||
| 403 | // TODO(ogniK): Sort when splitter is suppoorted | ||
| 404 | |||
| 405 | return ResultSuccess; | ||
| 406 | } | ||
| 407 | |||
| 408 | bool InfoUpdater::UpdateSinks(SinkContext& sink_context) { | ||
| 409 | const auto sink_count = sink_context.GetCount(); | ||
| 410 | std::vector<SinkInfo::InParams> sink_in_params(sink_count); | ||
| 411 | const auto total_sink_in = sink_count * sizeof(SinkInfo::InParams); | ||
| 412 | |||
| 413 | if (input_header.size.sink != total_sink_in) { | ||
| 414 | LOG_ERROR(Audio, "Sinks are an invalid size, expecting 0x{:X} but got 0x{:X}", | ||
| 415 | total_sink_in, input_header.size.effect); | ||
| 416 | return false; | ||
| 417 | } | ||
| 418 | |||
| 419 | if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_sink_in)) { | ||
| 420 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 421 | return false; | ||
| 422 | } | ||
| 423 | |||
| 424 | std::memcpy(sink_in_params.data(), in_params.data() + input_offset, total_sink_in); | ||
| 425 | input_offset += total_sink_in; | ||
| 426 | |||
| 427 | // TODO(ogniK): Properly update sinks | ||
| 428 | if (!sink_in_params.empty()) { | ||
| 429 | sink_context.UpdateMainSink(sink_in_params[0]); | ||
| 430 | } | ||
| 431 | |||
| 432 | output_header.size.sink = static_cast<u32>(0x20 * sink_count); | ||
| 433 | output_offset += 0x20 * sink_count; | ||
| 434 | return true; | ||
| 435 | } | ||
| 436 | |||
| 437 | bool InfoUpdater::UpdatePerformanceBuffer() { | ||
| 438 | output_header.size.performance = 0x10; | ||
| 439 | output_offset += 0x10; | ||
| 440 | return true; | ||
| 441 | } | ||
| 442 | |||
| 443 | bool InfoUpdater::UpdateErrorInfo([[maybe_unused]] BehaviorInfo& in_behavior_info) { | ||
| 444 | const auto total_beahvior_info_out = sizeof(BehaviorInfo::OutParams); | ||
| 445 | |||
| 446 | if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_beahvior_info_out)) { | ||
| 447 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 448 | return false; | ||
| 449 | } | ||
| 450 | |||
| 451 | BehaviorInfo::OutParams behavior_info_out{}; | ||
| 452 | behavior_info.CopyErrorInfo(behavior_info_out); | ||
| 453 | |||
| 454 | std::memcpy(out_params.data() + output_offset, &behavior_info_out, total_beahvior_info_out); | ||
| 455 | output_offset += total_beahvior_info_out; | ||
| 456 | output_header.size.behavior = total_beahvior_info_out; | ||
| 457 | |||
| 458 | return true; | ||
| 459 | } | ||
| 460 | |||
| 461 | struct RendererInfo { | ||
| 462 | u64_le elasped_frame_count{}; | ||
| 463 | INSERT_PADDING_WORDS(2); | ||
| 464 | }; | ||
| 465 | static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size"); | ||
| 466 | |||
| 467 | bool InfoUpdater::UpdateRendererInfo(std::size_t elapsed_frame_count) { | ||
| 468 | const auto total_renderer_info_out = sizeof(RendererInfo); | ||
| 469 | if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_renderer_info_out)) { | ||
| 470 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 471 | return false; | ||
| 472 | } | ||
| 473 | RendererInfo out{}; | ||
| 474 | out.elasped_frame_count = elapsed_frame_count; | ||
| 475 | std::memcpy(out_params.data() + output_offset, &out, total_renderer_info_out); | ||
| 476 | output_offset += total_renderer_info_out; | ||
| 477 | output_header.size.render_info = total_renderer_info_out; | ||
| 478 | |||
| 479 | return true; | ||
| 480 | } | ||
| 481 | |||
| 482 | bool InfoUpdater::CheckConsumedSize() const { | ||
| 483 | if (output_offset != out_params.size()) { | ||
| 484 | LOG_ERROR(Audio, "Output is not consumed! Consumed {}, but requires {}. {} bytes remaining", | ||
| 485 | output_offset, out_params.size(), out_params.size() - output_offset); | ||
| 486 | return false; | ||
| 487 | } | ||
| 488 | /*if (input_offset != in_params.size()) { | ||
| 489 | LOG_ERROR(Audio, "Input is not consumed!"); | ||
| 490 | return false; | ||
| 491 | }*/ | ||
| 492 | return true; | ||
| 493 | } | ||
| 494 | |||
| 495 | bool InfoUpdater::WriteOutputHeader() { | ||
| 496 | if (!AudioCommon::CanConsumeBuffer(out_params.size(), 0, | ||
| 497 | sizeof(AudioCommon::UpdateDataHeader))) { | ||
| 498 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 499 | return false; | ||
| 500 | } | ||
| 501 | output_header.revision = AudioCommon::CURRENT_PROCESS_REVISION; | ||
| 502 | const auto& sz = output_header.size; | ||
| 503 | output_header.total_size += sz.behavior + sz.memory_pool + sz.voice + | ||
| 504 | sz.voice_channel_resource + sz.effect + sz.mixer + sz.sink + | ||
| 505 | sz.performance + sz.splitter + sz.render_info; | ||
| 506 | |||
| 507 | std::memcpy(out_params.data(), &output_header, sizeof(AudioCommon::UpdateDataHeader)); | ||
| 508 | return true; | ||
| 509 | } | ||
| 510 | |||
| 511 | } // namespace AudioCore | ||
diff --git a/src/audio_core/info_updater.h b/src/audio_core/info_updater.h deleted file mode 100644 index 17e66b036..000000000 --- a/src/audio_core/info_updater.h +++ /dev/null | |||
| @@ -1,57 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <vector> | ||
| 7 | #include "audio_core/common.h" | ||
| 8 | #include "common/common_types.h" | ||
| 9 | |||
| 10 | namespace AudioCore { | ||
| 11 | |||
| 12 | class BehaviorInfo; | ||
| 13 | class ServerMemoryPoolInfo; | ||
| 14 | class VoiceContext; | ||
| 15 | class EffectContext; | ||
| 16 | class MixContext; | ||
| 17 | class SinkContext; | ||
| 18 | class SplitterContext; | ||
| 19 | |||
| 20 | class InfoUpdater { | ||
| 21 | public: | ||
| 22 | // TODO(ogniK): Pass process handle when we support it | ||
| 23 | InfoUpdater(const std::vector<u8>& in_params_, std::vector<u8>& out_params_, | ||
| 24 | BehaviorInfo& behavior_info_); | ||
| 25 | ~InfoUpdater(); | ||
| 26 | |||
| 27 | bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info); | ||
| 28 | bool UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info); | ||
| 29 | bool UpdateVoiceChannelResources(VoiceContext& voice_context); | ||
| 30 | bool UpdateVoices(VoiceContext& voice_context, | ||
| 31 | std::vector<ServerMemoryPoolInfo>& memory_pool_info, | ||
| 32 | VAddr audio_codec_dsp_addr); | ||
| 33 | bool UpdateEffects(EffectContext& effect_context, bool is_active); | ||
| 34 | bool UpdateSplitterInfo(SplitterContext& splitter_context); | ||
| 35 | Result UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count, | ||
| 36 | SplitterContext& splitter_context, EffectContext& effect_context); | ||
| 37 | bool UpdateSinks(SinkContext& sink_context); | ||
| 38 | bool UpdatePerformanceBuffer(); | ||
| 39 | bool UpdateErrorInfo(BehaviorInfo& in_behavior_info); | ||
| 40 | bool UpdateRendererInfo(std::size_t elapsed_frame_count); | ||
| 41 | bool CheckConsumedSize() const; | ||
| 42 | |||
| 43 | bool WriteOutputHeader(); | ||
| 44 | |||
| 45 | private: | ||
| 46 | const std::vector<u8>& in_params; | ||
| 47 | std::vector<u8>& out_params; | ||
| 48 | BehaviorInfo& behavior_info; | ||
| 49 | |||
| 50 | AudioCommon::UpdateDataHeader input_header{}; | ||
| 51 | AudioCommon::UpdateDataHeader output_header{}; | ||
| 52 | |||
| 53 | std::size_t input_offset{sizeof(AudioCommon::UpdateDataHeader)}; | ||
| 54 | std::size_t output_offset{sizeof(AudioCommon::UpdateDataHeader)}; | ||
| 55 | }; | ||
| 56 | |||
| 57 | } // namespace AudioCore | ||
diff --git a/src/audio_core/memory_pool.cpp b/src/audio_core/memory_pool.cpp deleted file mode 100644 index 627e5f15e..000000000 --- a/src/audio_core/memory_pool.cpp +++ /dev/null | |||
| @@ -1,60 +0,0 @@ | |||
| 1 | |||
| 2 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 3 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 4 | |||
| 5 | #include "audio_core/memory_pool.h" | ||
| 6 | #include "common/logging/log.h" | ||
| 7 | |||
| 8 | namespace AudioCore { | ||
| 9 | |||
| 10 | ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default; | ||
| 11 | ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default; | ||
| 12 | |||
| 13 | bool ServerMemoryPoolInfo::Update(const InParams& in_params, OutParams& out_params) { | ||
| 14 | // Our state does not need to be changed | ||
| 15 | if (in_params.state != State::RequestAttach && in_params.state != State::RequestDetach) { | ||
| 16 | return true; | ||
| 17 | } | ||
| 18 | |||
| 19 | // Address or size is null | ||
| 20 | if (in_params.address == 0 || in_params.size == 0) { | ||
| 21 | LOG_ERROR(Audio, "Memory pool address or size is zero! address={:X}, size={:X}", | ||
| 22 | in_params.address, in_params.size); | ||
| 23 | return false; | ||
| 24 | } | ||
| 25 | |||
| 26 | // Address or size is not aligned | ||
| 27 | if ((in_params.address % 0x1000) != 0 || (in_params.size % 0x1000) != 0) { | ||
| 28 | LOG_ERROR(Audio, "Memory pool address or size is not aligned! address={:X}, size={:X}", | ||
| 29 | in_params.address, in_params.size); | ||
| 30 | return false; | ||
| 31 | } | ||
| 32 | |||
| 33 | if (in_params.state == State::RequestAttach) { | ||
| 34 | cpu_address = in_params.address; | ||
| 35 | size = in_params.size; | ||
| 36 | used = true; | ||
| 37 | out_params.state = State::Attached; | ||
| 38 | } else { | ||
| 39 | // Unexpected address | ||
| 40 | if (cpu_address != in_params.address) { | ||
| 41 | LOG_ERROR(Audio, "Memory pool address differs! Expecting {:X} but address is {:X}", | ||
| 42 | cpu_address, in_params.address); | ||
| 43 | return false; | ||
| 44 | } | ||
| 45 | |||
| 46 | if (size != in_params.size) { | ||
| 47 | LOG_ERROR(Audio, "Memory pool size differs! Expecting {:X} but size is {:X}", size, | ||
| 48 | in_params.size); | ||
| 49 | return false; | ||
| 50 | } | ||
| 51 | |||
| 52 | cpu_address = 0; | ||
| 53 | size = 0; | ||
| 54 | used = false; | ||
| 55 | out_params.state = State::Detached; | ||
| 56 | } | ||
| 57 | return true; | ||
| 58 | } | ||
| 59 | |||
| 60 | } // namespace AudioCore | ||
diff --git a/src/audio_core/memory_pool.h b/src/audio_core/memory_pool.h deleted file mode 100644 index e71bc025b..000000000 --- a/src/audio_core/memory_pool.h +++ /dev/null | |||
| @@ -1,51 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | #include "common/swap.h" | ||
| 9 | |||
| 10 | namespace AudioCore { | ||
| 11 | |||
| 12 | class ServerMemoryPoolInfo { | ||
| 13 | public: | ||
| 14 | ServerMemoryPoolInfo(); | ||
| 15 | ~ServerMemoryPoolInfo(); | ||
| 16 | |||
| 17 | enum class State : u32_le { | ||
| 18 | Invalid = 0x0, | ||
| 19 | Aquired = 0x1, | ||
| 20 | RequestDetach = 0x2, | ||
| 21 | Detached = 0x3, | ||
| 22 | RequestAttach = 0x4, | ||
| 23 | Attached = 0x5, | ||
| 24 | Released = 0x6, | ||
| 25 | }; | ||
| 26 | |||
| 27 | struct InParams { | ||
| 28 | u64_le address{}; | ||
| 29 | u64_le size{}; | ||
| 30 | State state{}; | ||
| 31 | INSERT_PADDING_WORDS(3); | ||
| 32 | }; | ||
| 33 | static_assert(sizeof(InParams) == 0x20, "InParams are an invalid size"); | ||
| 34 | |||
| 35 | struct OutParams { | ||
| 36 | State state{}; | ||
| 37 | INSERT_PADDING_WORDS(3); | ||
| 38 | }; | ||
| 39 | static_assert(sizeof(OutParams) == 0x10, "OutParams are an invalid size"); | ||
| 40 | |||
| 41 | bool Update(const InParams& in_params, OutParams& out_params); | ||
| 42 | |||
| 43 | private: | ||
| 44 | // There's another entry here which is the DSP address, however since we're not talking to the | ||
| 45 | // DSP we can just use the same address provided by the guest without needing to remap | ||
| 46 | u64_le cpu_address{}; | ||
| 47 | u64_le size{}; | ||
| 48 | bool used{}; | ||
| 49 | }; | ||
| 50 | |||
| 51 | } // namespace AudioCore | ||
diff --git a/src/audio_core/mix_context.cpp b/src/audio_core/mix_context.cpp deleted file mode 100644 index bcaa7afab..000000000 --- a/src/audio_core/mix_context.cpp +++ /dev/null | |||
| @@ -1,297 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | |||
| 6 | #include "audio_core/behavior_info.h" | ||
| 7 | #include "audio_core/common.h" | ||
| 8 | #include "audio_core/effect_context.h" | ||
| 9 | #include "audio_core/mix_context.h" | ||
| 10 | #include "audio_core/splitter_context.h" | ||
| 11 | |||
| 12 | namespace AudioCore { | ||
| 13 | MixContext::MixContext() = default; | ||
| 14 | MixContext::~MixContext() = default; | ||
| 15 | |||
| 16 | void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count, | ||
| 17 | std::size_t effect_count) { | ||
| 18 | info_count = mix_count; | ||
| 19 | infos.resize(info_count); | ||
| 20 | auto& final_mix = GetInfo(AudioCommon::FINAL_MIX); | ||
| 21 | final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX; | ||
| 22 | sorted_info.reserve(infos.size()); | ||
| 23 | for (auto& info : infos) { | ||
| 24 | sorted_info.push_back(&info); | ||
| 25 | } | ||
| 26 | |||
| 27 | for (auto& info : infos) { | ||
| 28 | info.SetEffectCount(effect_count); | ||
| 29 | } | ||
| 30 | |||
| 31 | // Only initialize our edge matrix and node states if splitters are supported | ||
| 32 | if (behavior_info.IsSplitterSupported()) { | ||
| 33 | node_states.Initialize(mix_count); | ||
| 34 | edge_matrix.Initialize(mix_count); | ||
| 35 | } | ||
| 36 | } | ||
| 37 | |||
| 38 | void MixContext::UpdateDistancesFromFinalMix() { | ||
| 39 | // Set all distances to be invalid | ||
| 40 | for (std::size_t i = 0; i < info_count; i++) { | ||
| 41 | GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX; | ||
| 42 | } | ||
| 43 | |||
| 44 | for (std::size_t i = 0; i < info_count; i++) { | ||
| 45 | auto& info = GetInfo(i); | ||
| 46 | auto& in_params = info.GetInParams(); | ||
| 47 | // Populate our sorted info | ||
| 48 | sorted_info[i] = &info; | ||
| 49 | |||
| 50 | if (!in_params.in_use) { | ||
| 51 | continue; | ||
| 52 | } | ||
| 53 | |||
| 54 | auto mix_id = in_params.mix_id; | ||
| 55 | // Needs to be referenced out of scope | ||
| 56 | s32 distance_to_final_mix{AudioCommon::FINAL_MIX}; | ||
| 57 | for (; distance_to_final_mix < static_cast<s32>(info_count); distance_to_final_mix++) { | ||
| 58 | if (mix_id == AudioCommon::FINAL_MIX) { | ||
| 59 | // If we're at the final mix, we're done | ||
| 60 | break; | ||
| 61 | } else if (mix_id == AudioCommon::NO_MIX) { | ||
| 62 | // If we have no more mix ids, we're done | ||
| 63 | distance_to_final_mix = AudioCommon::NO_FINAL_MIX; | ||
| 64 | break; | ||
| 65 | } else { | ||
| 66 | const auto& dest_mix = GetInfo(mix_id); | ||
| 67 | const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance; | ||
| 68 | |||
| 69 | if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) { | ||
| 70 | // If our current mix isn't pointing to a final mix, follow through | ||
| 71 | mix_id = dest_mix.GetInParams().dest_mix_id; | ||
| 72 | } else { | ||
| 73 | // Our current mix + 1 = final distance | ||
| 74 | distance_to_final_mix = dest_mix_distance + 1; | ||
| 75 | break; | ||
| 76 | } | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | // If we're out of range for our distance, mark it as no final mix | ||
| 81 | if (distance_to_final_mix >= static_cast<s32>(info_count)) { | ||
| 82 | distance_to_final_mix = AudioCommon::NO_FINAL_MIX; | ||
| 83 | } | ||
| 84 | |||
| 85 | in_params.final_mix_distance = distance_to_final_mix; | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | void MixContext::CalcMixBufferOffset() { | ||
| 90 | s32 offset{}; | ||
| 91 | for (std::size_t i = 0; i < info_count; i++) { | ||
| 92 | auto& info = GetSortedInfo(i); | ||
| 93 | auto& in_params = info.GetInParams(); | ||
| 94 | if (in_params.in_use) { | ||
| 95 | // Only update if in use | ||
| 96 | in_params.buffer_offset = offset; | ||
| 97 | offset += in_params.buffer_count; | ||
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | void MixContext::SortInfo() { | ||
| 103 | // Get the distance to the final mix | ||
| 104 | UpdateDistancesFromFinalMix(); | ||
| 105 | |||
| 106 | // Sort based on the distance to the final mix | ||
| 107 | std::sort(sorted_info.begin(), sorted_info.end(), | ||
| 108 | [](const ServerMixInfo* lhs, const ServerMixInfo* rhs) { | ||
| 109 | return lhs->GetInParams().final_mix_distance > | ||
| 110 | rhs->GetInParams().final_mix_distance; | ||
| 111 | }); | ||
| 112 | |||
| 113 | // Calculate the mix buffer offset | ||
| 114 | CalcMixBufferOffset(); | ||
| 115 | } | ||
| 116 | |||
| 117 | bool MixContext::TsortInfo(SplitterContext& splitter_context) { | ||
| 118 | // If we're not using mixes, just calculate the mix buffer offset | ||
| 119 | if (!splitter_context.UsingSplitter()) { | ||
| 120 | CalcMixBufferOffset(); | ||
| 121 | return true; | ||
| 122 | } | ||
| 123 | // Sort our node states | ||
| 124 | if (!node_states.Tsort(edge_matrix)) { | ||
| 125 | return false; | ||
| 126 | } | ||
| 127 | |||
| 128 | // Get our sorted list | ||
| 129 | const auto sorted_list = node_states.GetIndexList(); | ||
| 130 | std::size_t info_id{}; | ||
| 131 | for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) { | ||
| 132 | // Set our sorted info | ||
| 133 | sorted_info[info_id++] = &GetInfo(*itr); | ||
| 134 | } | ||
| 135 | |||
| 136 | // Calculate the mix buffer offset | ||
| 137 | CalcMixBufferOffset(); | ||
| 138 | return true; | ||
| 139 | } | ||
| 140 | |||
| 141 | std::size_t MixContext::GetCount() const { | ||
| 142 | return info_count; | ||
| 143 | } | ||
| 144 | |||
| 145 | ServerMixInfo& MixContext::GetInfo(std::size_t i) { | ||
| 146 | ASSERT(i < info_count); | ||
| 147 | return infos.at(i); | ||
| 148 | } | ||
| 149 | |||
| 150 | const ServerMixInfo& MixContext::GetInfo(std::size_t i) const { | ||
| 151 | ASSERT(i < info_count); | ||
| 152 | return infos.at(i); | ||
| 153 | } | ||
| 154 | |||
| 155 | ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) { | ||
| 156 | ASSERT(i < info_count); | ||
| 157 | return *sorted_info.at(i); | ||
| 158 | } | ||
| 159 | |||
| 160 | const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const { | ||
| 161 | ASSERT(i < info_count); | ||
| 162 | return *sorted_info.at(i); | ||
| 163 | } | ||
| 164 | |||
| 165 | ServerMixInfo& MixContext::GetFinalMixInfo() { | ||
| 166 | return infos.at(AudioCommon::FINAL_MIX); | ||
| 167 | } | ||
| 168 | |||
| 169 | const ServerMixInfo& MixContext::GetFinalMixInfo() const { | ||
| 170 | return infos.at(AudioCommon::FINAL_MIX); | ||
| 171 | } | ||
| 172 | |||
| 173 | EdgeMatrix& MixContext::GetEdgeMatrix() { | ||
| 174 | return edge_matrix; | ||
| 175 | } | ||
| 176 | |||
| 177 | const EdgeMatrix& MixContext::GetEdgeMatrix() const { | ||
| 178 | return edge_matrix; | ||
| 179 | } | ||
| 180 | |||
| 181 | ServerMixInfo::ServerMixInfo() { | ||
| 182 | Cleanup(); | ||
| 183 | } | ||
| 184 | ServerMixInfo::~ServerMixInfo() = default; | ||
| 185 | |||
| 186 | const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const { | ||
| 187 | return in_params; | ||
| 188 | } | ||
| 189 | |||
| 190 | ServerMixInfo::InParams& ServerMixInfo::GetInParams() { | ||
| 191 | return in_params; | ||
| 192 | } | ||
| 193 | |||
| 194 | bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, | ||
| 195 | BehaviorInfo& behavior_info, SplitterContext& splitter_context, | ||
| 196 | EffectContext& effect_context) { | ||
| 197 | in_params.volume = mix_in.volume; | ||
| 198 | in_params.sample_rate = mix_in.sample_rate; | ||
| 199 | in_params.buffer_count = mix_in.buffer_count; | ||
| 200 | in_params.in_use = mix_in.in_use; | ||
| 201 | in_params.mix_id = mix_in.mix_id; | ||
| 202 | in_params.node_id = mix_in.node_id; | ||
| 203 | for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) { | ||
| 204 | std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(), | ||
| 205 | in_params.mix_volume[i].begin()); | ||
| 206 | } | ||
| 207 | |||
| 208 | bool require_sort = false; | ||
| 209 | |||
| 210 | if (behavior_info.IsSplitterSupported()) { | ||
| 211 | require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context); | ||
| 212 | } else { | ||
| 213 | in_params.dest_mix_id = mix_in.dest_mix_id; | ||
| 214 | in_params.splitter_id = AudioCommon::NO_SPLITTER; | ||
| 215 | } | ||
| 216 | |||
| 217 | ResetEffectProcessingOrder(); | ||
| 218 | const auto effect_count = effect_context.GetCount(); | ||
| 219 | for (std::size_t i = 0; i < effect_count; i++) { | ||
| 220 | auto* effect_info = effect_context.GetInfo(i); | ||
| 221 | if (effect_info->GetMixID() == in_params.mix_id) { | ||
| 222 | effect_processing_order[effect_info->GetProcessingOrder()] = static_cast<s32>(i); | ||
| 223 | } | ||
| 224 | } | ||
| 225 | |||
| 226 | // TODO(ogniK): Update effect processing order | ||
| 227 | return require_sort; | ||
| 228 | } | ||
| 229 | |||
| 230 | bool ServerMixInfo::HasAnyConnection() const { | ||
| 231 | return in_params.splitter_id != AudioCommon::NO_SPLITTER || | ||
| 232 | in_params.mix_id != AudioCommon::NO_MIX; | ||
| 233 | } | ||
| 234 | |||
| 235 | void ServerMixInfo::Cleanup() { | ||
| 236 | in_params.volume = 0.0f; | ||
| 237 | in_params.sample_rate = 0; | ||
| 238 | in_params.buffer_count = 0; | ||
| 239 | in_params.in_use = false; | ||
| 240 | in_params.mix_id = AudioCommon::NO_MIX; | ||
| 241 | in_params.node_id = 0; | ||
| 242 | in_params.buffer_offset = 0; | ||
| 243 | in_params.dest_mix_id = AudioCommon::NO_MIX; | ||
| 244 | in_params.splitter_id = AudioCommon::NO_SPLITTER; | ||
| 245 | std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size()); | ||
| 246 | } | ||
| 247 | |||
| 248 | void ServerMixInfo::SetEffectCount(std::size_t count) { | ||
| 249 | effect_processing_order.resize(count); | ||
| 250 | ResetEffectProcessingOrder(); | ||
| 251 | } | ||
| 252 | |||
| 253 | void ServerMixInfo::ResetEffectProcessingOrder() { | ||
| 254 | for (auto& order : effect_processing_order) { | ||
| 255 | order = AudioCommon::NO_EFFECT_ORDER; | ||
| 256 | } | ||
| 257 | } | ||
| 258 | |||
| 259 | s32 ServerMixInfo::GetEffectOrder(std::size_t i) const { | ||
| 260 | return effect_processing_order.at(i); | ||
| 261 | } | ||
| 262 | |||
| 263 | bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, | ||
| 264 | SplitterContext& splitter_context) { | ||
| 265 | // Mixes are identical | ||
| 266 | if (in_params.dest_mix_id == mix_in.dest_mix_id && | ||
| 267 | in_params.splitter_id == mix_in.splitter_id && | ||
| 268 | ((in_params.splitter_id == AudioCommon::NO_SPLITTER) || | ||
| 269 | !splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) { | ||
| 270 | return false; | ||
| 271 | } | ||
| 272 | // Remove current edges for mix id | ||
| 273 | edge_matrix.RemoveEdges(in_params.mix_id); | ||
| 274 | if (mix_in.dest_mix_id != AudioCommon::NO_MIX) { | ||
| 275 | // If we have a valid destination mix id, set our edge matrix | ||
| 276 | edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id); | ||
| 277 | } else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) { | ||
| 278 | // Recurse our splitter linked and set our edges | ||
| 279 | auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id); | ||
| 280 | const auto length = splitter_info.GetLength(); | ||
| 281 | for (s32 i = 0; i < length; i++) { | ||
| 282 | const auto* splitter_destination = | ||
| 283 | splitter_context.GetDestinationData(mix_in.splitter_id, i); | ||
| 284 | if (splitter_destination == nullptr) { | ||
| 285 | continue; | ||
| 286 | } | ||
| 287 | if (splitter_destination->ValidMixId()) { | ||
| 288 | edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId()); | ||
| 289 | } | ||
| 290 | } | ||
| 291 | } | ||
| 292 | in_params.dest_mix_id = mix_in.dest_mix_id; | ||
| 293 | in_params.splitter_id = mix_in.splitter_id; | ||
| 294 | return true; | ||
| 295 | } | ||
| 296 | |||
| 297 | } // namespace AudioCore | ||
diff --git a/src/audio_core/mix_context.h b/src/audio_core/mix_context.h deleted file mode 100644 index 3939c77e9..000000000 --- a/src/audio_core/mix_context.h +++ /dev/null | |||
| @@ -1,113 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <vector> | ||
| 8 | #include "audio_core/common.h" | ||
| 9 | #include "audio_core/splitter_context.h" | ||
| 10 | #include "common/common_funcs.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace AudioCore { | ||
| 14 | class BehaviorInfo; | ||
| 15 | class EffectContext; | ||
| 16 | |||
| 17 | class MixInfo { | ||
| 18 | public: | ||
| 19 | struct DirtyHeader { | ||
| 20 | u32_le magic{}; | ||
| 21 | u32_le mixer_count{}; | ||
| 22 | INSERT_PADDING_BYTES(0x18); | ||
| 23 | }; | ||
| 24 | static_assert(sizeof(DirtyHeader) == 0x20, "MixInfo::DirtyHeader is an invalid size"); | ||
| 25 | |||
| 26 | struct InParams { | ||
| 27 | float_le volume{}; | ||
| 28 | s32_le sample_rate{}; | ||
| 29 | s32_le buffer_count{}; | ||
| 30 | bool in_use{}; | ||
| 31 | INSERT_PADDING_BYTES(3); | ||
| 32 | s32_le mix_id{}; | ||
| 33 | s32_le effect_count{}; | ||
| 34 | u32_le node_id{}; | ||
| 35 | INSERT_PADDING_WORDS(2); | ||
| 36 | std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS> | ||
| 37 | mix_volume{}; | ||
| 38 | s32_le dest_mix_id{}; | ||
| 39 | s32_le splitter_id{}; | ||
| 40 | INSERT_PADDING_WORDS(1); | ||
| 41 | }; | ||
| 42 | static_assert(sizeof(MixInfo::InParams) == 0x930, "MixInfo::InParams is an invalid size"); | ||
| 43 | }; | ||
| 44 | |||
| 45 | class ServerMixInfo { | ||
| 46 | public: | ||
| 47 | struct InParams { | ||
| 48 | float volume{}; | ||
| 49 | s32 sample_rate{}; | ||
| 50 | s32 buffer_count{}; | ||
| 51 | bool in_use{}; | ||
| 52 | s32 mix_id{}; | ||
| 53 | u32 node_id{}; | ||
| 54 | std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS> | ||
| 55 | mix_volume{}; | ||
| 56 | s32 dest_mix_id{}; | ||
| 57 | s32 splitter_id{}; | ||
| 58 | s32 buffer_offset{}; | ||
| 59 | s32 final_mix_distance{}; | ||
| 60 | }; | ||
| 61 | ServerMixInfo(); | ||
| 62 | ~ServerMixInfo(); | ||
| 63 | |||
| 64 | [[nodiscard]] const ServerMixInfo::InParams& GetInParams() const; | ||
| 65 | [[nodiscard]] ServerMixInfo::InParams& GetInParams(); | ||
| 66 | |||
| 67 | bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, | ||
| 68 | BehaviorInfo& behavior_info, SplitterContext& splitter_context, | ||
| 69 | EffectContext& effect_context); | ||
| 70 | [[nodiscard]] bool HasAnyConnection() const; | ||
| 71 | void Cleanup(); | ||
| 72 | void SetEffectCount(std::size_t count); | ||
| 73 | void ResetEffectProcessingOrder(); | ||
| 74 | [[nodiscard]] s32 GetEffectOrder(std::size_t i) const; | ||
| 75 | |||
| 76 | private: | ||
| 77 | std::vector<s32> effect_processing_order; | ||
| 78 | InParams in_params{}; | ||
| 79 | bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, | ||
| 80 | SplitterContext& splitter_context); | ||
| 81 | }; | ||
| 82 | |||
| 83 | class MixContext { | ||
| 84 | public: | ||
| 85 | MixContext(); | ||
| 86 | ~MixContext(); | ||
| 87 | |||
| 88 | void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count, | ||
| 89 | std::size_t effect_count); | ||
| 90 | void SortInfo(); | ||
| 91 | bool TsortInfo(SplitterContext& splitter_context); | ||
| 92 | |||
| 93 | [[nodiscard]] std::size_t GetCount() const; | ||
| 94 | [[nodiscard]] ServerMixInfo& GetInfo(std::size_t i); | ||
| 95 | [[nodiscard]] const ServerMixInfo& GetInfo(std::size_t i) const; | ||
| 96 | [[nodiscard]] ServerMixInfo& GetSortedInfo(std::size_t i); | ||
| 97 | [[nodiscard]] const ServerMixInfo& GetSortedInfo(std::size_t i) const; | ||
| 98 | [[nodiscard]] ServerMixInfo& GetFinalMixInfo(); | ||
| 99 | [[nodiscard]] const ServerMixInfo& GetFinalMixInfo() const; | ||
| 100 | [[nodiscard]] EdgeMatrix& GetEdgeMatrix(); | ||
| 101 | [[nodiscard]] const EdgeMatrix& GetEdgeMatrix() const; | ||
| 102 | |||
| 103 | private: | ||
| 104 | void CalcMixBufferOffset(); | ||
| 105 | void UpdateDistancesFromFinalMix(); | ||
| 106 | |||
| 107 | NodeStates node_states{}; | ||
| 108 | EdgeMatrix edge_matrix{}; | ||
| 109 | std::size_t info_count{}; | ||
| 110 | std::vector<ServerMixInfo> infos{}; | ||
| 111 | std::vector<ServerMixInfo*> sorted_info{}; | ||
| 112 | }; | ||
| 113 | } // namespace AudioCore | ||
diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h deleted file mode 100644 index 37b2f7eff..000000000 --- a/src/audio_core/null_sink.h +++ /dev/null | |||
| @@ -1,32 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "audio_core/sink.h" | ||
| 7 | |||
| 8 | namespace AudioCore { | ||
| 9 | |||
| 10 | class NullSink final : public Sink { | ||
| 11 | public: | ||
| 12 | explicit NullSink(std::string_view) {} | ||
| 13 | ~NullSink() override = default; | ||
| 14 | |||
| 15 | SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/, | ||
| 16 | const std::string& /*name*/) override { | ||
| 17 | return null_sink_stream; | ||
| 18 | } | ||
| 19 | |||
| 20 | private: | ||
| 21 | struct NullSinkStreamImpl final : SinkStream { | ||
| 22 | void EnqueueSamples(u32 /*num_channels*/, const std::vector<s16>& /*samples*/) override {} | ||
| 23 | |||
| 24 | std::size_t SamplesInQueue(u32 /*num_channels*/) const override { | ||
| 25 | return 0; | ||
| 26 | } | ||
| 27 | |||
| 28 | void Flush() override {} | ||
| 29 | } null_sink_stream; | ||
| 30 | }; | ||
| 31 | |||
| 32 | } // namespace AudioCore | ||
diff --git a/src/audio_core/out/audio_out.cpp b/src/audio_core/out/audio_out.cpp new file mode 100644 index 000000000..9a8d8a742 --- /dev/null +++ b/src/audio_core/out/audio_out.cpp | |||
| @@ -0,0 +1,100 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/audio_out_manager.h" | ||
| 5 | #include "audio_core/out/audio_out.h" | ||
| 6 | #include "core/hle/kernel/k_event.h" | ||
| 7 | |||
| 8 | namespace AudioCore::AudioOut { | ||
| 9 | |||
| 10 | Out::Out(Core::System& system_, Manager& manager_, Kernel::KEvent* event_, size_t session_id_) | ||
| 11 | : manager{manager_}, parent_mutex{manager.mutex}, event{event_}, system{system_, event, | ||
| 12 | session_id_} {} | ||
| 13 | |||
| 14 | void Out::Free() { | ||
| 15 | std::scoped_lock l{parent_mutex}; | ||
| 16 | manager.ReleaseSessionId(system.GetSessionId()); | ||
| 17 | } | ||
| 18 | |||
| 19 | System& Out::GetSystem() { | ||
| 20 | return system; | ||
| 21 | } | ||
| 22 | |||
| 23 | AudioOut::State Out::GetState() { | ||
| 24 | std::scoped_lock l{parent_mutex}; | ||
| 25 | return system.GetState(); | ||
| 26 | } | ||
| 27 | |||
| 28 | Result Out::StartSystem() { | ||
| 29 | std::scoped_lock l{parent_mutex}; | ||
| 30 | return system.Start(); | ||
| 31 | } | ||
| 32 | |||
| 33 | void Out::StartSession() { | ||
| 34 | std::scoped_lock l{parent_mutex}; | ||
| 35 | system.StartSession(); | ||
| 36 | } | ||
| 37 | |||
| 38 | Result Out::StopSystem() { | ||
| 39 | std::scoped_lock l{parent_mutex}; | ||
| 40 | return system.Stop(); | ||
| 41 | } | ||
| 42 | |||
| 43 | Result Out::AppendBuffer(const AudioOutBuffer& buffer, const u64 tag) { | ||
| 44 | std::scoped_lock l{parent_mutex}; | ||
| 45 | |||
| 46 | if (system.AppendBuffer(buffer, tag)) { | ||
| 47 | return ResultSuccess; | ||
| 48 | } | ||
| 49 | return Service::Audio::ERR_BUFFER_COUNT_EXCEEDED; | ||
| 50 | } | ||
| 51 | |||
| 52 | void Out::ReleaseAndRegisterBuffers() { | ||
| 53 | std::scoped_lock l{parent_mutex}; | ||
| 54 | if (system.GetState() == State::Started) { | ||
| 55 | system.ReleaseBuffers(); | ||
| 56 | system.RegisterBuffers(); | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | bool Out::FlushAudioOutBuffers() { | ||
| 61 | std::scoped_lock l{parent_mutex}; | ||
| 62 | return system.FlushAudioOutBuffers(); | ||
| 63 | } | ||
| 64 | |||
| 65 | u32 Out::GetReleasedBuffers(std::span<u64> tags) { | ||
| 66 | std::scoped_lock l{parent_mutex}; | ||
| 67 | return system.GetReleasedBuffers(tags); | ||
| 68 | } | ||
| 69 | |||
| 70 | Kernel::KReadableEvent& Out::GetBufferEvent() { | ||
| 71 | std::scoped_lock l{parent_mutex}; | ||
| 72 | return event->GetReadableEvent(); | ||
| 73 | } | ||
| 74 | |||
| 75 | f32 Out::GetVolume() { | ||
| 76 | std::scoped_lock l{parent_mutex}; | ||
| 77 | return system.GetVolume(); | ||
| 78 | } | ||
| 79 | |||
| 80 | void Out::SetVolume(const f32 volume) { | ||
| 81 | std::scoped_lock l{parent_mutex}; | ||
| 82 | system.SetVolume(volume); | ||
| 83 | } | ||
| 84 | |||
| 85 | bool Out::ContainsAudioBuffer(const u64 tag) { | ||
| 86 | std::scoped_lock l{parent_mutex}; | ||
| 87 | return system.ContainsAudioBuffer(tag); | ||
| 88 | } | ||
| 89 | |||
| 90 | u32 Out::GetBufferCount() { | ||
| 91 | std::scoped_lock l{parent_mutex}; | ||
| 92 | return system.GetBufferCount(); | ||
| 93 | } | ||
| 94 | |||
| 95 | u64 Out::GetPlayedSampleCount() { | ||
| 96 | std::scoped_lock l{parent_mutex}; | ||
| 97 | return system.GetPlayedSampleCount(); | ||
| 98 | } | ||
| 99 | |||
| 100 | } // namespace AudioCore::AudioOut | ||
diff --git a/src/audio_core/out/audio_out.h b/src/audio_core/out/audio_out.h new file mode 100644 index 000000000..f6b921645 --- /dev/null +++ b/src/audio_core/out/audio_out.h | |||
| @@ -0,0 +1,147 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <mutex> | ||
| 7 | |||
| 8 | #include "audio_core/out/audio_out_system.h" | ||
| 9 | |||
| 10 | namespace Core { | ||
| 11 | class System; | ||
| 12 | } | ||
| 13 | |||
| 14 | namespace Kernel { | ||
| 15 | class KEvent; | ||
| 16 | class KReadableEvent; | ||
| 17 | } // namespace Kernel | ||
| 18 | |||
| 19 | namespace AudioCore::AudioOut { | ||
| 20 | class Manager; | ||
| 21 | |||
| 22 | /** | ||
| 23 | * Interface between the service and audio out system. Mainly responsible for forwarding service | ||
| 24 | * calls to the system. | ||
| 25 | */ | ||
| 26 | class Out { | ||
| 27 | public: | ||
| 28 | explicit Out(Core::System& system, Manager& manager, Kernel::KEvent* event, size_t session_id); | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Free this audio out from the audio out manager. | ||
| 32 | */ | ||
| 33 | void Free(); | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Get this audio out's system. | ||
| 37 | */ | ||
| 38 | System& GetSystem(); | ||
| 39 | |||
| 40 | /** | ||
| 41 | * Get the current state. | ||
| 42 | * | ||
| 43 | * @return Started or Stopped. | ||
| 44 | */ | ||
| 45 | AudioOut::State GetState(); | ||
| 46 | |||
| 47 | /** | ||
| 48 | * Start the system | ||
| 49 | * | ||
| 50 | * @return Result code | ||
| 51 | */ | ||
| 52 | Result StartSystem(); | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Start the system's device session. | ||
| 56 | */ | ||
| 57 | void StartSession(); | ||
| 58 | |||
| 59 | /** | ||
| 60 | * Stop the system. | ||
| 61 | * | ||
| 62 | * @return Result code | ||
| 63 | */ | ||
| 64 | Result StopSystem(); | ||
| 65 | |||
| 66 | /** | ||
| 67 | * Append a new buffer to the system, the buffer event will be signalled when it is filled. | ||
| 68 | * | ||
| 69 | * @param buffer - The new buffer to append. | ||
| 70 | * @param tag - Unique tag for this buffer. | ||
| 71 | * @return Result code. | ||
| 72 | */ | ||
| 73 | Result AppendBuffer(const AudioOutBuffer& buffer, u64 tag); | ||
| 74 | |||
| 75 | /** | ||
| 76 | * Release all completed buffers, and register any appended. | ||
| 77 | */ | ||
| 78 | void ReleaseAndRegisterBuffers(); | ||
| 79 | |||
| 80 | /** | ||
| 81 | * Flush all buffers. | ||
| 82 | */ | ||
| 83 | bool FlushAudioOutBuffers(); | ||
| 84 | |||
| 85 | /** | ||
| 86 | * Get all of the currently released buffers. | ||
| 87 | * | ||
| 88 | * @param tags - Output container for the buffer tags which were released. | ||
| 89 | * @return The number of buffers released. | ||
| 90 | */ | ||
| 91 | u32 GetReleasedBuffers(std::span<u64> tags); | ||
| 92 | |||
| 93 | /** | ||
| 94 | * Get the buffer event for this audio out, this event will be signalled when a buffer is | ||
| 95 | * filled. | ||
| 96 | * @return The buffer event. | ||
| 97 | */ | ||
| 98 | Kernel::KReadableEvent& GetBufferEvent(); | ||
| 99 | |||
| 100 | /** | ||
| 101 | * Get the current system volume. | ||
| 102 | * | ||
| 103 | * @return The current volume. | ||
| 104 | */ | ||
| 105 | f32 GetVolume(); | ||
| 106 | |||
| 107 | /** | ||
| 108 | * Set the system volume. | ||
| 109 | * | ||
| 110 | * @param volume - The volume to set. | ||
| 111 | */ | ||
| 112 | void SetVolume(f32 volume); | ||
| 113 | |||
| 114 | /** | ||
| 115 | * Check if a buffer is in the system. | ||
| 116 | * | ||
| 117 | * @param tag - The tag to search for. | ||
| 118 | * @return True if the buffer is in the system, otherwise false. | ||
| 119 | */ | ||
| 120 | bool ContainsAudioBuffer(u64 tag); | ||
| 121 | |||
| 122 | /** | ||
| 123 | * Get the maximum number of buffers. | ||
| 124 | * | ||
| 125 | * @return The maximum number of buffers. | ||
| 126 | */ | ||
| 127 | u32 GetBufferCount(); | ||
| 128 | |||
| 129 | /** | ||
| 130 | * Get the total played sample count for this audio out. | ||
| 131 | * | ||
| 132 | * @return The played sample count. | ||
| 133 | */ | ||
| 134 | u64 GetPlayedSampleCount(); | ||
| 135 | |||
| 136 | private: | ||
| 137 | /// The AudioOut::Manager this audio out is registered with | ||
| 138 | Manager& manager; | ||
| 139 | /// Manager's mutex | ||
| 140 | std::recursive_mutex& parent_mutex; | ||
| 141 | /// Buffer event, signalled when buffers are ready to be released | ||
| 142 | Kernel::KEvent* event; | ||
| 143 | /// Main audio out system | ||
| 144 | System system; | ||
| 145 | }; | ||
| 146 | |||
| 147 | } // namespace AudioCore::AudioOut | ||
diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp new file mode 100644 index 000000000..35afddf06 --- /dev/null +++ b/src/audio_core/out/audio_out_system.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 <mutex> | ||
| 5 | |||
| 6 | #include "audio_core/audio_event.h" | ||
| 7 | #include "audio_core/audio_manager.h" | ||
| 8 | #include "audio_core/out/audio_out_system.h" | ||
| 9 | #include "common/logging/log.h" | ||
| 10 | #include "core/core.h" | ||
| 11 | #include "core/core_timing.h" | ||
| 12 | #include "core/hle/kernel/k_event.h" | ||
| 13 | |||
| 14 | namespace AudioCore::AudioOut { | ||
| 15 | |||
| 16 | System::System(Core::System& system_, Kernel::KEvent* event_, size_t session_id_) | ||
| 17 | : system{system_}, buffer_event{event_}, | ||
| 18 | session_id{session_id_}, session{std::make_unique<DeviceSession>(system_)} {} | ||
| 19 | |||
| 20 | System::~System() { | ||
| 21 | Finalize(); | ||
| 22 | } | ||
| 23 | |||
| 24 | void System::Finalize() { | ||
| 25 | Stop(); | ||
| 26 | session->Finalize(); | ||
| 27 | buffer_event->GetWritableEvent().Signal(); | ||
| 28 | } | ||
| 29 | |||
| 30 | std::string_view System::GetDefaultOutputDeviceName() { | ||
| 31 | return "DeviceOut"; | ||
| 32 | } | ||
| 33 | |||
| 34 | Result System::IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) { | ||
| 35 | if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) { | ||
| 36 | return Service::Audio::ERR_INVALID_DEVICE_NAME; | ||
| 37 | } | ||
| 38 | |||
| 39 | if (in_params.sample_rate != TargetSampleRate && in_params.sample_rate > 0) { | ||
| 40 | return Service::Audio::ERR_INVALID_SAMPLE_RATE; | ||
| 41 | } | ||
| 42 | |||
| 43 | if (in_params.channel_count == 0 || in_params.channel_count == 2 || | ||
| 44 | in_params.channel_count == 6) { | ||
| 45 | return ResultSuccess; | ||
| 46 | } | ||
| 47 | |||
| 48 | return Service::Audio::ERR_INVALID_CHANNEL_COUNT; | ||
| 49 | } | ||
| 50 | |||
| 51 | Result System::Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle_, | ||
| 52 | u64& applet_resource_user_id_) { | ||
| 53 | auto result = IsConfigValid(device_name, in_params); | ||
| 54 | if (result.IsError()) { | ||
| 55 | return result; | ||
| 56 | } | ||
| 57 | |||
| 58 | handle = handle_; | ||
| 59 | applet_resource_user_id = applet_resource_user_id_; | ||
| 60 | if (device_name.empty() || device_name[0] == '\0') { | ||
| 61 | name = std::string(GetDefaultOutputDeviceName()); | ||
| 62 | } else { | ||
| 63 | name = std::move(device_name); | ||
| 64 | } | ||
| 65 | |||
| 66 | sample_rate = TargetSampleRate; | ||
| 67 | sample_format = SampleFormat::PcmInt16; | ||
| 68 | channel_count = in_params.channel_count <= 2 ? 2 : 6; | ||
| 69 | volume = 1.0f; | ||
| 70 | return ResultSuccess; | ||
| 71 | } | ||
| 72 | |||
| 73 | void System::StartSession() { | ||
| 74 | session->Start(); | ||
| 75 | } | ||
| 76 | |||
| 77 | size_t System::GetSessionId() const { | ||
| 78 | return session_id; | ||
| 79 | } | ||
| 80 | |||
| 81 | Result System::Start() { | ||
| 82 | if (state != State::Stopped) { | ||
| 83 | return Service::Audio::ERR_OPERATION_FAILED; | ||
| 84 | } | ||
| 85 | |||
| 86 | session->Initialize(name, sample_format, channel_count, session_id, handle, | ||
| 87 | applet_resource_user_id, Sink::StreamType::Out); | ||
| 88 | session->SetVolume(volume); | ||
| 89 | session->Start(); | ||
| 90 | state = State::Started; | ||
| 91 | |||
| 92 | std::vector<AudioBuffer> buffers_to_flush{}; | ||
| 93 | buffers.RegisterBuffers(buffers_to_flush); | ||
| 94 | session->AppendBuffers(buffers_to_flush); | ||
| 95 | |||
| 96 | return ResultSuccess; | ||
| 97 | } | ||
| 98 | |||
| 99 | Result System::Stop() { | ||
| 100 | if (state == State::Started) { | ||
| 101 | session->Stop(); | ||
| 102 | session->SetVolume(0.0f); | ||
| 103 | state = State::Stopped; | ||
| 104 | } | ||
| 105 | |||
| 106 | return ResultSuccess; | ||
| 107 | } | ||
| 108 | |||
| 109 | bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) { | ||
| 110 | if (buffers.GetTotalBufferCount() == BufferCount) { | ||
| 111 | return false; | ||
| 112 | } | ||
| 113 | |||
| 114 | AudioBuffer new_buffer{ | ||
| 115 | .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; | ||
| 116 | |||
| 117 | buffers.AppendBuffer(new_buffer); | ||
| 118 | RegisterBuffers(); | ||
| 119 | |||
| 120 | return true; | ||
| 121 | } | ||
| 122 | |||
| 123 | void System::RegisterBuffers() { | ||
| 124 | if (state == State::Started) { | ||
| 125 | std::vector<AudioBuffer> registered_buffers{}; | ||
| 126 | buffers.RegisterBuffers(registered_buffers); | ||
| 127 | session->AppendBuffers(registered_buffers); | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | void System::ReleaseBuffers() { | ||
| 132 | bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session)}; | ||
| 133 | if (signal) { | ||
| 134 | // Signal if any buffer was released, or if none are registered, we need more. | ||
| 135 | buffer_event->GetWritableEvent().Signal(); | ||
| 136 | } | ||
| 137 | } | ||
| 138 | |||
| 139 | u32 System::GetReleasedBuffers(std::span<u64> tags) { | ||
| 140 | return buffers.GetReleasedBuffers(tags); | ||
| 141 | } | ||
| 142 | |||
| 143 | bool System::FlushAudioOutBuffers() { | ||
| 144 | if (state != State::Started) { | ||
| 145 | return false; | ||
| 146 | } | ||
| 147 | |||
| 148 | u32 buffers_released{}; | ||
| 149 | buffers.FlushBuffers(buffers_released); | ||
| 150 | |||
| 151 | if (buffers_released > 0) { | ||
| 152 | buffer_event->GetWritableEvent().Signal(); | ||
| 153 | } | ||
| 154 | return true; | ||
| 155 | } | ||
| 156 | |||
| 157 | u16 System::GetChannelCount() const { | ||
| 158 | return channel_count; | ||
| 159 | } | ||
| 160 | |||
| 161 | u32 System::GetSampleRate() const { | ||
| 162 | return sample_rate; | ||
| 163 | } | ||
| 164 | |||
| 165 | SampleFormat System::GetSampleFormat() const { | ||
| 166 | return sample_format; | ||
| 167 | } | ||
| 168 | |||
| 169 | State System::GetState() { | ||
| 170 | switch (state) { | ||
| 171 | case State::Started: | ||
| 172 | case State::Stopped: | ||
| 173 | return state; | ||
| 174 | default: | ||
| 175 | LOG_ERROR(Service_Audio, "AudioOut invalid state!"); | ||
| 176 | state = State::Stopped; | ||
| 177 | break; | ||
| 178 | } | ||
| 179 | return state; | ||
| 180 | } | ||
| 181 | |||
| 182 | std::string System::GetName() const { | ||
| 183 | return name; | ||
| 184 | } | ||
| 185 | |||
| 186 | f32 System::GetVolume() const { | ||
| 187 | return volume; | ||
| 188 | } | ||
| 189 | |||
| 190 | void System::SetVolume(const f32 volume_) { | ||
| 191 | volume = volume_; | ||
| 192 | session->SetVolume(volume_); | ||
| 193 | } | ||
| 194 | |||
| 195 | bool System::ContainsAudioBuffer(const u64 tag) { | ||
| 196 | return buffers.ContainsBuffer(tag); | ||
| 197 | } | ||
| 198 | |||
| 199 | u32 System::GetBufferCount() { | ||
| 200 | return buffers.GetAppendedRegisteredCount(); | ||
| 201 | } | ||
| 202 | |||
| 203 | u64 System::GetPlayedSampleCount() const { | ||
| 204 | return session->GetPlayedSampleCount(); | ||
| 205 | } | ||
| 206 | |||
| 207 | } // namespace AudioCore::AudioOut | ||
diff --git a/src/audio_core/out/audio_out_system.h b/src/audio_core/out/audio_out_system.h new file mode 100644 index 000000000..4ca2f3417 --- /dev/null +++ b/src/audio_core/out/audio_out_system.h | |||
| @@ -0,0 +1,257 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <atomic> | ||
| 7 | #include <memory> | ||
| 8 | #include <span> | ||
| 9 | #include <string> | ||
| 10 | |||
| 11 | #include "audio_core/common/common.h" | ||
| 12 | #include "audio_core/device/audio_buffers.h" | ||
| 13 | #include "audio_core/device/device_session.h" | ||
| 14 | #include "core/hle/service/audio/errors.h" | ||
| 15 | |||
| 16 | namespace Core { | ||
| 17 | class System; | ||
| 18 | } | ||
| 19 | |||
| 20 | namespace Kernel { | ||
| 21 | class KEvent; | ||
| 22 | } | ||
| 23 | |||
| 24 | namespace AudioCore::AudioOut { | ||
| 25 | |||
| 26 | constexpr SessionTypes SessionType = SessionTypes::AudioOut; | ||
| 27 | |||
| 28 | struct AudioOutParameter { | ||
| 29 | /* 0x0 */ s32_le sample_rate; | ||
| 30 | /* 0x4 */ u16_le channel_count; | ||
| 31 | /* 0x6 */ u16_le reserved; | ||
| 32 | }; | ||
| 33 | static_assert(sizeof(AudioOutParameter) == 0x8, "AudioOutParameter is an invalid size"); | ||
| 34 | |||
| 35 | struct AudioOutParameterInternal { | ||
| 36 | /* 0x0 */ u32_le sample_rate; | ||
| 37 | /* 0x4 */ u32_le channel_count; | ||
| 38 | /* 0x8 */ u32_le sample_format; | ||
| 39 | /* 0xC */ u32_le state; | ||
| 40 | }; | ||
| 41 | static_assert(sizeof(AudioOutParameterInternal) == 0x10, | ||
| 42 | "AudioOutParameterInternal is an invalid size"); | ||
| 43 | |||
| 44 | struct AudioOutBuffer { | ||
| 45 | /* 0x00 */ AudioOutBuffer* next; | ||
| 46 | /* 0x08 */ VAddr samples; | ||
| 47 | /* 0x10 */ u64 capacity; | ||
| 48 | /* 0x18 */ u64 size; | ||
| 49 | /* 0x20 */ u64 offset; | ||
| 50 | }; | ||
| 51 | static_assert(sizeof(AudioOutBuffer) == 0x28, "AudioOutBuffer is an invalid size"); | ||
| 52 | |||
| 53 | enum class State { | ||
| 54 | Started, | ||
| 55 | Stopped, | ||
| 56 | }; | ||
| 57 | |||
| 58 | /** | ||
| 59 | * Controls and drives audio output. | ||
| 60 | */ | ||
| 61 | class System { | ||
| 62 | public: | ||
| 63 | explicit System(Core::System& system, Kernel::KEvent* event, size_t session_id); | ||
| 64 | ~System(); | ||
| 65 | |||
| 66 | /** | ||
| 67 | * Get the default audio output device name. | ||
| 68 | * | ||
| 69 | * @return The default audio output device name. | ||
| 70 | */ | ||
| 71 | std::string_view GetDefaultOutputDeviceName(); | ||
| 72 | |||
| 73 | /** | ||
| 74 | * Is the given initialize config valid? | ||
| 75 | * | ||
| 76 | * @param device_name - The name of the requested output device. | ||
| 77 | * @param in_params - Input parameters, see AudioOutParameter. | ||
| 78 | * @return Result code. | ||
| 79 | */ | ||
| 80 | Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params); | ||
| 81 | |||
| 82 | /** | ||
| 83 | * Initialize this system. | ||
| 84 | * | ||
| 85 | * @param device_name - The name of the requested output device. | ||
| 86 | * @param in_params - Input parameters, see AudioOutParameter. | ||
| 87 | * @param handle - Unused. | ||
| 88 | * @param applet_resource_user_id - Unused. | ||
| 89 | * @return Result code. | ||
| 90 | */ | ||
| 91 | Result Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle, | ||
| 92 | u64& applet_resource_user_id); | ||
| 93 | |||
| 94 | /** | ||
| 95 | * Start this system. | ||
| 96 | * | ||
| 97 | * @return Result code. | ||
| 98 | */ | ||
| 99 | Result Start(); | ||
| 100 | |||
| 101 | /** | ||
| 102 | * Stop this system. | ||
| 103 | * | ||
| 104 | * @return Result code. | ||
| 105 | */ | ||
| 106 | Result Stop(); | ||
| 107 | |||
| 108 | /** | ||
| 109 | * Finalize this system. | ||
| 110 | */ | ||
| 111 | void Finalize(); | ||
| 112 | |||
| 113 | /** | ||
| 114 | * Start this system's device session. | ||
| 115 | */ | ||
| 116 | void StartSession(); | ||
| 117 | |||
| 118 | /** | ||
| 119 | * Get this system's id. | ||
| 120 | */ | ||
| 121 | size_t GetSessionId() const; | ||
| 122 | |||
| 123 | /** | ||
| 124 | * Append a new buffer to the device. | ||
| 125 | * | ||
| 126 | * @param buffer - New buffer to append. | ||
| 127 | * @param tag - Unique tag of the buffer. | ||
| 128 | * @return True if the buffer was appended, otherwise false. | ||
| 129 | */ | ||
| 130 | bool AppendBuffer(const AudioOutBuffer& buffer, u64 tag); | ||
| 131 | |||
| 132 | /** | ||
| 133 | * Register all appended buffers. | ||
| 134 | */ | ||
| 135 | void RegisterBuffers(); | ||
| 136 | |||
| 137 | /** | ||
| 138 | * Release all registered buffers. | ||
| 139 | */ | ||
| 140 | void ReleaseBuffers(); | ||
| 141 | |||
| 142 | /** | ||
| 143 | * Get all released buffers. | ||
| 144 | * | ||
| 145 | * @param tags - Container to be filled with the released buffers' tags. | ||
| 146 | * @return The number of buffers released. | ||
| 147 | */ | ||
| 148 | u32 GetReleasedBuffers(std::span<u64> tags); | ||
| 149 | |||
| 150 | /** | ||
| 151 | * Flush all appended and registered buffers. | ||
| 152 | * | ||
| 153 | * @return True if buffers were successfully flushed, otherwise false. | ||
| 154 | */ | ||
| 155 | bool FlushAudioOutBuffers(); | ||
| 156 | |||
| 157 | /** | ||
| 158 | * Get this system's current channel count. | ||
| 159 | * | ||
| 160 | * @return The channel count. | ||
| 161 | */ | ||
| 162 | u16 GetChannelCount() const; | ||
| 163 | |||
| 164 | /** | ||
| 165 | * Get this system's current sample rate. | ||
| 166 | * | ||
| 167 | * @return The sample rate. | ||
| 168 | */ | ||
| 169 | u32 GetSampleRate() const; | ||
| 170 | |||
| 171 | /** | ||
| 172 | * Get this system's current sample format. | ||
| 173 | * | ||
| 174 | * @return The sample format. | ||
| 175 | */ | ||
| 176 | SampleFormat GetSampleFormat() const; | ||
| 177 | |||
| 178 | /** | ||
| 179 | * Get this system's current state. | ||
| 180 | * | ||
| 181 | * @return The current state. | ||
| 182 | */ | ||
| 183 | State GetState(); | ||
| 184 | |||
| 185 | /** | ||
| 186 | * Get this system's name. | ||
| 187 | * | ||
| 188 | * @return The system's name. | ||
| 189 | */ | ||
| 190 | std::string GetName() const; | ||
| 191 | |||
| 192 | /** | ||
| 193 | * Get this system's current volume. | ||
| 194 | * | ||
| 195 | * @return The system's current volume. | ||
| 196 | */ | ||
| 197 | f32 GetVolume() const; | ||
| 198 | |||
| 199 | /** | ||
| 200 | * Set this system's current volume. | ||
| 201 | * | ||
| 202 | * @param The new volume. | ||
| 203 | */ | ||
| 204 | void SetVolume(f32 volume); | ||
| 205 | |||
| 206 | /** | ||
| 207 | * Does the system contain this buffer? | ||
| 208 | * | ||
| 209 | * @param tag - Unique tag to search for. | ||
| 210 | * @return True if the buffer is in the system, otherwise false. | ||
| 211 | */ | ||
| 212 | bool ContainsAudioBuffer(u64 tag); | ||
| 213 | |||
| 214 | /** | ||
| 215 | * Get the maximum number of usable buffers (default 32). | ||
| 216 | * | ||
| 217 | * @return The number of buffers. | ||
| 218 | */ | ||
| 219 | u32 GetBufferCount(); | ||
| 220 | |||
| 221 | /** | ||
| 222 | * Get the total number of samples played by this system. | ||
| 223 | * | ||
| 224 | * @return The number of samples. | ||
| 225 | */ | ||
| 226 | u64 GetPlayedSampleCount() const; | ||
| 227 | |||
| 228 | private: | ||
| 229 | /// Core system | ||
| 230 | Core::System& system; | ||
| 231 | /// (Unused) | ||
| 232 | u32 handle{}; | ||
| 233 | /// (Unused) | ||
| 234 | u64 applet_resource_user_id{}; | ||
| 235 | /// Buffer event, signalled when a buffer is ready | ||
| 236 | Kernel::KEvent* buffer_event; | ||
| 237 | /// Session id of this system | ||
| 238 | size_t session_id{}; | ||
| 239 | /// Device session for this system | ||
| 240 | std::unique_ptr<DeviceSession> session; | ||
| 241 | /// Audio buffers in use by this system | ||
| 242 | AudioBuffers<BufferCount> buffers{BufferCount}; | ||
| 243 | /// Sample rate of this system | ||
| 244 | u32 sample_rate{}; | ||
| 245 | /// Sample format of this system | ||
| 246 | SampleFormat sample_format{SampleFormat::PcmInt16}; | ||
| 247 | /// Channel count of this system | ||
| 248 | u16 channel_count{}; | ||
| 249 | /// State of this system | ||
| 250 | std::atomic<State> state{State::Stopped}; | ||
| 251 | /// Name of this system | ||
| 252 | std::string name{}; | ||
| 253 | /// Volume of this system | ||
| 254 | f32 volume{1.0f}; | ||
| 255 | }; | ||
| 256 | |||
| 257 | } // namespace AudioCore::AudioOut | ||
diff --git a/src/audio_core/renderer/adsp/adsp.cpp b/src/audio_core/renderer/adsp/adsp.cpp new file mode 100644 index 000000000..e05a22d86 --- /dev/null +++ b/src/audio_core/renderer/adsp/adsp.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/adsp.h" | ||
| 5 | #include "audio_core/renderer/adsp/command_buffer.h" | ||
| 6 | #include "audio_core/sink/sink.h" | ||
| 7 | #include "common/logging/log.h" | ||
| 8 | #include "core/core.h" | ||
| 9 | #include "core/core_timing.h" | ||
| 10 | #include "core/core_timing_util.h" | ||
| 11 | #include "core/memory.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioRenderer::ADSP { | ||
| 14 | |||
| 15 | ADSP::ADSP(Core::System& system_, Sink::Sink& sink_) | ||
| 16 | : system{system_}, memory{system.Memory()}, sink{sink_} {} | ||
| 17 | |||
| 18 | ADSP::~ADSP() { | ||
| 19 | ClearCommandBuffers(); | ||
| 20 | } | ||
| 21 | |||
| 22 | State ADSP::GetState() const { | ||
| 23 | if (running) { | ||
| 24 | return State::Started; | ||
| 25 | } | ||
| 26 | return State::Stopped; | ||
| 27 | } | ||
| 28 | |||
| 29 | AudioRenderer_Mailbox* ADSP::GetRenderMailbox() { | ||
| 30 | return &render_mailbox; | ||
| 31 | } | ||
| 32 | |||
| 33 | void ADSP::ClearRemainCount(const u32 session_id) { | ||
| 34 | render_mailbox.ClearRemainCount(session_id); | ||
| 35 | } | ||
| 36 | |||
| 37 | u64 ADSP::GetSignalledTick() const { | ||
| 38 | return render_mailbox.GetSignalledTick(); | ||
| 39 | } | ||
| 40 | |||
| 41 | u64 ADSP::GetTimeTaken() const { | ||
| 42 | return render_mailbox.GetRenderTimeTaken(); | ||
| 43 | } | ||
| 44 | |||
| 45 | u64 ADSP::GetRenderTimeTaken(const u32 session_id) { | ||
| 46 | return render_mailbox.GetCommandBuffer(session_id).render_time_taken; | ||
| 47 | } | ||
| 48 | |||
| 49 | u32 ADSP::GetRemainCommandCount(const u32 session_id) const { | ||
| 50 | return render_mailbox.GetRemainCommandCount(session_id); | ||
| 51 | } | ||
| 52 | |||
| 53 | void ADSP::SendCommandBuffer(const u32 session_id, CommandBuffer& command_buffer) { | ||
| 54 | render_mailbox.SetCommandBuffer(session_id, command_buffer); | ||
| 55 | } | ||
| 56 | |||
| 57 | u64 ADSP::GetRenderingStartTick(const u32 session_id) { | ||
| 58 | return render_mailbox.GetSignalledTick() + | ||
| 59 | render_mailbox.GetCommandBuffer(session_id).render_time_taken; | ||
| 60 | } | ||
| 61 | |||
| 62 | bool ADSP::Start() { | ||
| 63 | if (running) { | ||
| 64 | return running; | ||
| 65 | } | ||
| 66 | |||
| 67 | running = true; | ||
| 68 | systems_active++; | ||
| 69 | audio_renderer = std::make_unique<AudioRenderer>(system); | ||
| 70 | audio_renderer->Start(&render_mailbox); | ||
| 71 | render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_InitializeOK); | ||
| 72 | if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) { | ||
| 73 | LOG_ERROR( | ||
| 74 | Service_Audio, | ||
| 75 | "Host Audio Renderer -- Failed to receive initialize message response from ADSP!"); | ||
| 76 | } | ||
| 77 | return running; | ||
| 78 | } | ||
| 79 | |||
| 80 | void ADSP::Stop() { | ||
| 81 | systems_active--; | ||
| 82 | if (running && systems_active == 0) { | ||
| 83 | { | ||
| 84 | std::scoped_lock l{mailbox_lock}; | ||
| 85 | render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Shutdown); | ||
| 86 | if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_Shutdown) { | ||
| 87 | LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown " | ||
| 88 | "message response from ADSP!"); | ||
| 89 | } | ||
| 90 | } | ||
| 91 | audio_renderer->Stop(); | ||
| 92 | running = false; | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | void ADSP::Signal() { | ||
| 97 | const auto signalled_tick{system.CoreTiming().GetClockTicks()}; | ||
| 98 | render_mailbox.SetSignalledTick(signalled_tick); | ||
| 99 | render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Render); | ||
| 100 | } | ||
| 101 | |||
| 102 | void ADSP::Wait() { | ||
| 103 | std::scoped_lock l{mailbox_lock}; | ||
| 104 | auto response{render_mailbox.HostWaitMessage()}; | ||
| 105 | if (response != RenderMessage::AudioRenderer_RenderResponse) { | ||
| 106 | LOG_ERROR(Service_Audio, "Invalid ADSP response message, expected 0x{:02X}, got 0x{:02X}", | ||
| 107 | static_cast<u32>(RenderMessage::AudioRenderer_RenderResponse), | ||
| 108 | static_cast<u32>(response)); | ||
| 109 | } | ||
| 110 | |||
| 111 | ClearCommandBuffers(); | ||
| 112 | } | ||
| 113 | |||
| 114 | void ADSP::ClearCommandBuffers() { | ||
| 115 | render_mailbox.ClearCommandBuffers(); | ||
| 116 | } | ||
| 117 | |||
| 118 | } // namespace AudioCore::AudioRenderer::ADSP | ||
diff --git a/src/audio_core/renderer/adsp/adsp.h b/src/audio_core/renderer/adsp/adsp.h new file mode 100644 index 000000000..4dfcef4a5 --- /dev/null +++ b/src/audio_core/renderer/adsp/adsp.h | |||
| @@ -0,0 +1,173 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <memory> | ||
| 7 | #include <mutex> | ||
| 8 | |||
| 9 | #include "audio_core/renderer/adsp/audio_renderer.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace Core { | ||
| 13 | namespace Memory { | ||
| 14 | class Memory; | ||
| 15 | } | ||
| 16 | class System; | ||
| 17 | } // namespace Core | ||
| 18 | |||
| 19 | namespace AudioCore { | ||
| 20 | namespace Sink { | ||
| 21 | class Sink; | ||
| 22 | } | ||
| 23 | |||
| 24 | namespace AudioRenderer::ADSP { | ||
| 25 | struct CommandBuffer; | ||
| 26 | |||
| 27 | enum class State { | ||
| 28 | Started, | ||
| 29 | Stopped, | ||
| 30 | }; | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Represents the ADSP embedded within the audio sysmodule. | ||
| 34 | * This is a 32-bit Linux4Tegra kernel from nVidia, which is launched with the sysmodule on boot. | ||
| 35 | * | ||
| 36 | * The kernel will run apps you program for it, Nintendo have the following: | ||
| 37 | * | ||
| 38 | * Gmix - Responsible for mixing final audio and sending it out to hardware. This is last place all | ||
| 39 | * audio samples end up, and we skip it entirely, since we have very different backends and | ||
| 40 | * mixing is implicitly handled by the OS (but also due to lack of research/simplicity). | ||
| 41 | * | ||
| 42 | * AudioRenderer - Receives command lists generated by the audio render | ||
| 43 | * system, processes them, and sends the samples to Gmix. | ||
| 44 | * | ||
| 45 | * OpusDecoder - Contains libopus, and controls processing Opus audio and sends it to Gmix. | ||
| 46 | * Not much research done here, TODO if needed. | ||
| 47 | * | ||
| 48 | * We only implement the AudioRenderer for now. | ||
| 49 | * | ||
| 50 | * Communication for the apps is done through mailboxes, and some shared memory. | ||
| 51 | */ | ||
| 52 | class ADSP { | ||
| 53 | public: | ||
| 54 | explicit ADSP(Core::System& system, Sink::Sink& sink); | ||
| 55 | ~ADSP(); | ||
| 56 | |||
| 57 | /** | ||
| 58 | * Start the ADSP. | ||
| 59 | * | ||
| 60 | * @return True if started or already running, otherwise false. | ||
| 61 | */ | ||
| 62 | bool Start(); | ||
| 63 | |||
| 64 | /** | ||
| 65 | * Stop the ADSP. | ||
| 66 | * | ||
| 67 | * @return True if started or already running, otherwise false. | ||
| 68 | */ | ||
| 69 | void Stop(); | ||
| 70 | |||
| 71 | /** | ||
| 72 | * Get the ADSP's state. | ||
| 73 | * | ||
| 74 | * @return Started or Stopped. | ||
| 75 | */ | ||
| 76 | State GetState() const; | ||
| 77 | |||
| 78 | /** | ||
| 79 | * Get the AudioRenderer mailbox to communicate with it. | ||
| 80 | * | ||
| 81 | * @return The AudioRenderer mailbox. | ||
| 82 | */ | ||
| 83 | AudioRenderer_Mailbox* GetRenderMailbox(); | ||
| 84 | |||
| 85 | /** | ||
| 86 | * Get the tick the ADSP was signalled. | ||
| 87 | * | ||
| 88 | * @return The tick the ADSP was signalled. | ||
| 89 | */ | ||
| 90 | u64 GetSignalledTick() const; | ||
| 91 | |||
| 92 | /** | ||
| 93 | * Get the total time it took for the ADSP to run the last command lists (both command lists). | ||
| 94 | * | ||
| 95 | * @return The tick the ADSP was signalled. | ||
| 96 | */ | ||
| 97 | u64 GetTimeTaken() const; | ||
| 98 | |||
| 99 | /** | ||
| 100 | * Get the last time a given command list took to run. | ||
| 101 | * | ||
| 102 | * @param session_id - The session id to check (0 or 1). | ||
| 103 | * @return The time it took. | ||
| 104 | */ | ||
| 105 | u64 GetRenderTimeTaken(u32 session_id); | ||
| 106 | |||
| 107 | /** | ||
| 108 | * Clear the remaining command count for a given session. | ||
| 109 | * | ||
| 110 | * @param session_id - The session id to check (0 or 1). | ||
| 111 | */ | ||
| 112 | void ClearRemainCount(u32 session_id); | ||
| 113 | |||
| 114 | /** | ||
| 115 | * Get the remaining number of commands left to process for a command list. | ||
| 116 | * | ||
| 117 | * @param session_id - The session id to check (0 or 1). | ||
| 118 | * @return The number of commands remaining. | ||
| 119 | */ | ||
| 120 | u32 GetRemainCommandCount(u32 session_id) const; | ||
| 121 | |||
| 122 | /** | ||
| 123 | * Get the last tick a command list started processing. | ||
| 124 | * | ||
| 125 | * @param session_id - The session id to check (0 or 1). | ||
| 126 | * @return The last tick the given command list started. | ||
| 127 | */ | ||
| 128 | u64 GetRenderingStartTick(u32 session_id); | ||
| 129 | |||
| 130 | /** | ||
| 131 | * Set a command buffer to be processed. | ||
| 132 | * | ||
| 133 | * @param session_id - The session id to check (0 or 1). | ||
| 134 | * @param command_buffer - The command buffer to process. | ||
| 135 | */ | ||
| 136 | void SendCommandBuffer(u32 session_id, CommandBuffer& command_buffer); | ||
| 137 | |||
| 138 | /** | ||
| 139 | * Clear the command buffers (does not clear the time taken or the remaining command count) | ||
| 140 | */ | ||
| 141 | void ClearCommandBuffers(); | ||
| 142 | |||
| 143 | /** | ||
| 144 | * Signal the AudioRenderer to begin processing. | ||
| 145 | */ | ||
| 146 | void Signal(); | ||
| 147 | |||
| 148 | /** | ||
| 149 | * Wait for the AudioRenderer to finish processing. | ||
| 150 | */ | ||
| 151 | void Wait(); | ||
| 152 | |||
| 153 | private: | ||
| 154 | /// Core system | ||
| 155 | Core::System& system; | ||
| 156 | /// Core memory | ||
| 157 | Core::Memory::Memory& memory; | ||
| 158 | /// Number of systems active, used to prevent accidental shutdowns | ||
| 159 | u8 systems_active{0}; | ||
| 160 | /// ADSP running state | ||
| 161 | std::atomic<bool> running{false}; | ||
| 162 | /// Output sink used by the ADSP | ||
| 163 | Sink::Sink& sink; | ||
| 164 | /// AudioRenderer app | ||
| 165 | std::unique_ptr<AudioRenderer> audio_renderer{}; | ||
| 166 | /// Communication for the AudioRenderer | ||
| 167 | AudioRenderer_Mailbox render_mailbox{}; | ||
| 168 | /// Mailbox lock ffor the render mailbox | ||
| 169 | std::mutex mailbox_lock; | ||
| 170 | }; | ||
| 171 | |||
| 172 | } // namespace AudioRenderer::ADSP | ||
| 173 | } // namespace AudioCore | ||
diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp new file mode 100644 index 000000000..3967ccfe6 --- /dev/null +++ b/src/audio_core/renderer/adsp/audio_renderer.cpp | |||
| @@ -0,0 +1,226 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <array> | ||
| 5 | #include <chrono> | ||
| 6 | |||
| 7 | #include "audio_core/audio_core.h" | ||
| 8 | #include "audio_core/common/common.h" | ||
| 9 | #include "audio_core/renderer/adsp/audio_renderer.h" | ||
| 10 | #include "audio_core/sink/sink.h" | ||
| 11 | #include "common/logging/log.h" | ||
| 12 | #include "common/microprofile.h" | ||
| 13 | #include "common/thread.h" | ||
| 14 | #include "core/core.h" | ||
| 15 | #include "core/core_timing.h" | ||
| 16 | #include "core/core_timing_util.h" | ||
| 17 | |||
| 18 | MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97)); | ||
| 19 | |||
| 20 | namespace AudioCore::AudioRenderer::ADSP { | ||
| 21 | |||
| 22 | void AudioRenderer_Mailbox::HostSendMessage(RenderMessage message_) { | ||
| 23 | adsp_messages.enqueue(message_); | ||
| 24 | adsp_event.Set(); | ||
| 25 | } | ||
| 26 | |||
| 27 | RenderMessage AudioRenderer_Mailbox::HostWaitMessage() { | ||
| 28 | host_event.Wait(); | ||
| 29 | RenderMessage msg{RenderMessage::Invalid}; | ||
| 30 | if (!host_messages.try_dequeue(msg)) { | ||
| 31 | LOG_ERROR(Service_Audio, "Failed to dequeue host message!"); | ||
| 32 | } | ||
| 33 | return msg; | ||
| 34 | } | ||
| 35 | |||
| 36 | void AudioRenderer_Mailbox::ADSPSendMessage(const RenderMessage message_) { | ||
| 37 | host_messages.enqueue(message_); | ||
| 38 | host_event.Set(); | ||
| 39 | } | ||
| 40 | |||
| 41 | RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() { | ||
| 42 | adsp_event.Wait(); | ||
| 43 | RenderMessage msg{RenderMessage::Invalid}; | ||
| 44 | if (!adsp_messages.try_dequeue(msg)) { | ||
| 45 | LOG_ERROR(Service_Audio, "Failed to dequeue ADSP message!"); | ||
| 46 | } | ||
| 47 | return msg; | ||
| 48 | } | ||
| 49 | |||
| 50 | CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) { | ||
| 51 | return command_buffers[session_id]; | ||
| 52 | } | ||
| 53 | |||
| 54 | void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, CommandBuffer& buffer) { | ||
| 55 | command_buffers[session_id] = buffer; | ||
| 56 | } | ||
| 57 | |||
| 58 | u64 AudioRenderer_Mailbox::GetRenderTimeTaken() const { | ||
| 59 | return command_buffers[0].render_time_taken + command_buffers[1].render_time_taken; | ||
| 60 | } | ||
| 61 | |||
| 62 | u64 AudioRenderer_Mailbox::GetSignalledTick() const { | ||
| 63 | return signalled_tick; | ||
| 64 | } | ||
| 65 | |||
| 66 | void AudioRenderer_Mailbox::SetSignalledTick(const u64 tick) { | ||
| 67 | signalled_tick = tick; | ||
| 68 | } | ||
| 69 | |||
| 70 | void AudioRenderer_Mailbox::ClearRemainCount(const u32 session_id) { | ||
| 71 | command_buffers[session_id].remaining_command_count = 0; | ||
| 72 | } | ||
| 73 | |||
| 74 | u32 AudioRenderer_Mailbox::GetRemainCommandCount(const u32 session_id) const { | ||
| 75 | return command_buffers[session_id].remaining_command_count; | ||
| 76 | } | ||
| 77 | |||
| 78 | void AudioRenderer_Mailbox::ClearCommandBuffers() { | ||
| 79 | command_buffers[0].buffer = 0; | ||
| 80 | command_buffers[0].size = 0; | ||
| 81 | command_buffers[0].reset_buffers = false; | ||
| 82 | command_buffers[1].buffer = 0; | ||
| 83 | command_buffers[1].size = 0; | ||
| 84 | command_buffers[1].reset_buffers = false; | ||
| 85 | } | ||
| 86 | |||
| 87 | AudioRenderer::AudioRenderer(Core::System& system_) | ||
| 88 | : system{system_}, sink{system.AudioCore().GetOutputSink()} { | ||
| 89 | CreateSinkStreams(); | ||
| 90 | } | ||
| 91 | |||
| 92 | AudioRenderer::~AudioRenderer() { | ||
| 93 | Stop(); | ||
| 94 | for (auto& stream : streams) { | ||
| 95 | if (stream) { | ||
| 96 | sink.CloseStream(stream); | ||
| 97 | } | ||
| 98 | stream = nullptr; | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) { | ||
| 103 | if (running) { | ||
| 104 | return; | ||
| 105 | } | ||
| 106 | |||
| 107 | mailbox = mailbox_; | ||
| 108 | thread = std::thread(&AudioRenderer::ThreadFunc, this); | ||
| 109 | for (auto& stream : streams) { | ||
| 110 | stream->Start(); | ||
| 111 | } | ||
| 112 | running = true; | ||
| 113 | } | ||
| 114 | |||
| 115 | void AudioRenderer::Stop() { | ||
| 116 | if (!running) { | ||
| 117 | return; | ||
| 118 | } | ||
| 119 | |||
| 120 | for (auto& stream : streams) { | ||
| 121 | stream->Stop(); | ||
| 122 | } | ||
| 123 | thread.join(); | ||
| 124 | running = false; | ||
| 125 | } | ||
| 126 | |||
| 127 | void AudioRenderer::CreateSinkStreams() { | ||
| 128 | u32 channels{sink.GetDeviceChannels()}; | ||
| 129 | for (u32 i = 0; i < MaxRendererSessions; i++) { | ||
| 130 | std::string name{fmt::format("ADSP_RenderStream-{}", i)}; | ||
| 131 | streams[i] = | ||
| 132 | sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render); | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | void AudioRenderer::ThreadFunc() { | ||
| 137 | constexpr char name[]{"yuzu:AudioRenderer"}; | ||
| 138 | MicroProfileOnThreadCreate(name); | ||
| 139 | Common::SetCurrentThreadName(name); | ||
| 140 | Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical); | ||
| 141 | if (mailbox->ADSPWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) { | ||
| 142 | LOG_ERROR(Service_Audio, | ||
| 143 | "ADSP Audio Renderer -- Failed to receive initialize message from host!"); | ||
| 144 | return; | ||
| 145 | } | ||
| 146 | |||
| 147 | mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK); | ||
| 148 | |||
| 149 | constexpr u64 max_process_time{2'304'000ULL}; | ||
| 150 | |||
| 151 | while (true) { | ||
| 152 | auto message{mailbox->ADSPWaitMessage()}; | ||
| 153 | switch (message) { | ||
| 154 | case RenderMessage::AudioRenderer_Shutdown: | ||
| 155 | mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_Shutdown); | ||
| 156 | return; | ||
| 157 | |||
| 158 | case RenderMessage::AudioRenderer_Render: { | ||
| 159 | std::array<bool, MaxRendererSessions> buffers_reset{}; | ||
| 160 | std::array<u64, MaxRendererSessions> render_times_taken{}; | ||
| 161 | const auto start_time{system.CoreTiming().GetClockTicks()}; | ||
| 162 | |||
| 163 | for (u32 index = 0; index < 2; index++) { | ||
| 164 | auto& command_buffer{mailbox->GetCommandBuffer(index)}; | ||
| 165 | auto& command_list_processor{command_list_processors[index]}; | ||
| 166 | |||
| 167 | // Check this buffer is valid, as it may not be used. | ||
| 168 | if (command_buffer.buffer != 0) { | ||
| 169 | // If there are no remaining commands (from the previous list), | ||
| 170 | // this is a new command list, initalize it. | ||
| 171 | if (command_buffer.remaining_command_count == 0) { | ||
| 172 | command_list_processor.Initialize(system, command_buffer.buffer, | ||
| 173 | command_buffer.size, streams[index]); | ||
| 174 | } | ||
| 175 | |||
| 176 | if (command_buffer.reset_buffers && !buffers_reset[index]) { | ||
| 177 | streams[index]->ClearQueue(); | ||
| 178 | buffers_reset[index] = true; | ||
| 179 | } | ||
| 180 | |||
| 181 | u64 max_time{max_process_time}; | ||
| 182 | if (index == 1 && command_buffer.applet_resource_user_id == | ||
| 183 | mailbox->GetCommandBuffer(0).applet_resource_user_id) { | ||
| 184 | max_time = max_process_time - | ||
| 185 | Core::Timing::CyclesToNs(render_times_taken[0]).count(); | ||
| 186 | if (render_times_taken[0] > max_process_time) { | ||
| 187 | max_time = 0; | ||
| 188 | } | ||
| 189 | } | ||
| 190 | |||
| 191 | max_time = std::min(command_buffer.time_limit, max_time); | ||
| 192 | command_list_processor.SetProcessTimeMax(max_time); | ||
| 193 | |||
| 194 | // Process the command list | ||
| 195 | { | ||
| 196 | MICROPROFILE_SCOPE(Audio_Renderer); | ||
| 197 | render_times_taken[index] = | ||
| 198 | command_list_processor.Process(index) - start_time; | ||
| 199 | } | ||
| 200 | |||
| 201 | if (index == 0) { | ||
| 202 | auto stream{command_list_processor.GetOutputSinkStream()}; | ||
| 203 | system.AudioCore().SetStreamQueue(stream->GetQueueSize()); | ||
| 204 | } | ||
| 205 | |||
| 206 | const auto end_time{system.CoreTiming().GetClockTicks()}; | ||
| 207 | |||
| 208 | command_buffer.remaining_command_count = | ||
| 209 | command_list_processor.GetRemainingCommandCount(); | ||
| 210 | command_buffer.render_time_taken = end_time - start_time; | ||
| 211 | } | ||
| 212 | } | ||
| 213 | |||
| 214 | mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse); | ||
| 215 | } break; | ||
| 216 | |||
| 217 | default: | ||
| 218 | LOG_WARNING(Service_Audio, | ||
| 219 | "ADSP AudioRenderer received an invalid message, msg={:02X}!", | ||
| 220 | static_cast<u32>(message)); | ||
| 221 | break; | ||
| 222 | } | ||
| 223 | } | ||
| 224 | } | ||
| 225 | |||
| 226 | } // namespace AudioCore::AudioRenderer::ADSP | ||
diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h new file mode 100644 index 000000000..b6ced9d2b --- /dev/null +++ b/src/audio_core/renderer/adsp/audio_renderer.h | |||
| @@ -0,0 +1,203 @@ | |||
| 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 <memory> | ||
| 8 | #include <thread> | ||
| 9 | |||
| 10 | #include "audio_core/renderer/adsp/command_buffer.h" | ||
| 11 | #include "audio_core/renderer/adsp/command_list_processor.h" | ||
| 12 | #include "common/common_types.h" | ||
| 13 | #include "common/reader_writer_queue.h" | ||
| 14 | #include "common/thread.h" | ||
| 15 | |||
| 16 | namespace Core { | ||
| 17 | namespace Timing { | ||
| 18 | struct EventType; | ||
| 19 | } | ||
| 20 | class System; | ||
| 21 | } // namespace Core | ||
| 22 | |||
| 23 | namespace AudioCore { | ||
| 24 | namespace Sink { | ||
| 25 | class Sink; | ||
| 26 | } | ||
| 27 | |||
| 28 | namespace AudioRenderer::ADSP { | ||
| 29 | |||
| 30 | enum class RenderMessage { | ||
| 31 | /* 0x00 */ Invalid, | ||
| 32 | /* 0x01 */ AudioRenderer_MapUnmap_Map, | ||
| 33 | /* 0x02 */ AudioRenderer_MapUnmap_MapResponse, | ||
| 34 | /* 0x03 */ AudioRenderer_MapUnmap_Unmap, | ||
| 35 | /* 0x04 */ AudioRenderer_MapUnmap_UnmapResponse, | ||
| 36 | /* 0x05 */ AudioRenderer_MapUnmap_InvalidateCache, | ||
| 37 | /* 0x06 */ AudioRenderer_MapUnmap_InvalidateCacheResponse, | ||
| 38 | /* 0x07 */ AudioRenderer_MapUnmap_Shutdown, | ||
| 39 | /* 0x08 */ AudioRenderer_MapUnmap_ShutdownResponse, | ||
| 40 | /* 0x16 */ AudioRenderer_InitializeOK = 0x16, | ||
| 41 | /* 0x20 */ AudioRenderer_RenderResponse = 0x20, | ||
| 42 | /* 0x2A */ AudioRenderer_Render = 0x2A, | ||
| 43 | /* 0x34 */ AudioRenderer_Shutdown = 0x34, | ||
| 44 | }; | ||
| 45 | |||
| 46 | /** | ||
| 47 | * A mailbox for the AudioRenderer, allowing communication between the host and the AudioRenderer | ||
| 48 | * running on the ADSP. | ||
| 49 | */ | ||
| 50 | class AudioRenderer_Mailbox { | ||
| 51 | public: | ||
| 52 | /** | ||
| 53 | * Send a message from the host to the AudioRenderer. | ||
| 54 | * | ||
| 55 | * @param message_ - The message to send to the AudioRenderer. | ||
| 56 | */ | ||
| 57 | void HostSendMessage(RenderMessage message); | ||
| 58 | |||
| 59 | /** | ||
| 60 | * Host wait for a message from the AudioRenderer. | ||
| 61 | * | ||
| 62 | * @return The message returned from the AudioRenderer. | ||
| 63 | */ | ||
| 64 | RenderMessage HostWaitMessage(); | ||
| 65 | |||
| 66 | /** | ||
| 67 | * Send a message from the AudioRenderer to the host. | ||
| 68 | * | ||
| 69 | * @param message_ - The message to send to the host. | ||
| 70 | */ | ||
| 71 | void ADSPSendMessage(RenderMessage message); | ||
| 72 | |||
| 73 | /** | ||
| 74 | * AudioRenderer wait for a message from the host. | ||
| 75 | * | ||
| 76 | * @return The message returned from the AudioRenderer. | ||
| 77 | */ | ||
| 78 | RenderMessage ADSPWaitMessage(); | ||
| 79 | |||
| 80 | /** | ||
| 81 | * Get the command buffer with the given session id (0 or 1). | ||
| 82 | * | ||
| 83 | * @param session_id - The session id to get (0 or 1). | ||
| 84 | * @return The command buffer. | ||
| 85 | */ | ||
| 86 | CommandBuffer& GetCommandBuffer(s32 session_id); | ||
| 87 | |||
| 88 | /** | ||
| 89 | * Set the command buffer with the given session id (0 or 1). | ||
| 90 | * | ||
| 91 | * @param session_id - The session id to get (0 or 1). | ||
| 92 | * @param buffer - The command buffer to set. | ||
| 93 | */ | ||
| 94 | void SetCommandBuffer(u32 session_id, CommandBuffer& buffer); | ||
| 95 | |||
| 96 | /** | ||
| 97 | * Get the total render time taken for the last command lists sent. | ||
| 98 | * | ||
| 99 | * @return Total render time taken for the last command lists. | ||
| 100 | */ | ||
| 101 | u64 GetRenderTimeTaken() const; | ||
| 102 | |||
| 103 | /** | ||
| 104 | * Get the tick the AudioRenderer was signalled. | ||
| 105 | * | ||
| 106 | * @return The tick the AudioRenderer was signalled. | ||
| 107 | */ | ||
| 108 | u64 GetSignalledTick() const; | ||
| 109 | |||
| 110 | /** | ||
| 111 | * Set the tick the AudioRenderer was signalled. | ||
| 112 | * | ||
| 113 | * @param tick - The tick the AudioRenderer was signalled. | ||
| 114 | */ | ||
| 115 | void SetSignalledTick(u64 tick); | ||
| 116 | |||
| 117 | /** | ||
| 118 | * Clear the remaining command count. | ||
| 119 | * | ||
| 120 | * @param session_id - Index for which command list to clear (0 or 1). | ||
| 121 | */ | ||
| 122 | void ClearRemainCount(u32 session_id); | ||
| 123 | |||
| 124 | /** | ||
| 125 | * Get the remaining command count for a given command list. | ||
| 126 | * | ||
| 127 | * @param session_id - Index for which command list to clear (0 or 1). | ||
| 128 | * @return The remaining command count. | ||
| 129 | */ | ||
| 130 | u32 GetRemainCommandCount(u32 session_id) const; | ||
| 131 | |||
| 132 | /** | ||
| 133 | * Clear the command buffers (does not clear the time taken or the remaining command count). | ||
| 134 | */ | ||
| 135 | void ClearCommandBuffers(); | ||
| 136 | |||
| 137 | private: | ||
| 138 | /// Host signalling event | ||
| 139 | Common::Event host_event{}; | ||
| 140 | /// AudioRenderer signalling event | ||
| 141 | Common::Event adsp_event{}; | ||
| 142 | /// Host message queue | ||
| 143 | |||
| 144 | Common::ReaderWriterQueue<RenderMessage> host_messages{}; | ||
| 145 | /// AudioRenderer message queue | ||
| 146 | |||
| 147 | Common::ReaderWriterQueue<RenderMessage> adsp_messages{}; | ||
| 148 | /// Command buffers | ||
| 149 | |||
| 150 | std::array<CommandBuffer, MaxRendererSessions> command_buffers{}; | ||
| 151 | /// Tick the AudioRnederer was signalled | ||
| 152 | u64 signalled_tick{}; | ||
| 153 | }; | ||
| 154 | |||
| 155 | /** | ||
| 156 | * The AudioRenderer application running on the ADSP. | ||
| 157 | */ | ||
| 158 | class AudioRenderer { | ||
| 159 | public: | ||
| 160 | explicit AudioRenderer(Core::System& system); | ||
| 161 | ~AudioRenderer(); | ||
| 162 | |||
| 163 | /** | ||
| 164 | * Start the AudioRenderer. | ||
| 165 | * | ||
| 166 | * @param The mailbox to use for this session. | ||
| 167 | */ | ||
| 168 | void Start(AudioRenderer_Mailbox* mailbox); | ||
| 169 | |||
| 170 | /** | ||
| 171 | * Stop the AudioRenderer. | ||
| 172 | */ | ||
| 173 | void Stop(); | ||
| 174 | |||
| 175 | private: | ||
| 176 | /** | ||
| 177 | * Main AudioRenderer thread, responsible for processing the command lists. | ||
| 178 | */ | ||
| 179 | void ThreadFunc(); | ||
| 180 | |||
| 181 | /** | ||
| 182 | * Creates the streams which will receive the processed samples. | ||
| 183 | */ | ||
| 184 | void CreateSinkStreams(); | ||
| 185 | |||
| 186 | /// Core system | ||
| 187 | Core::System& system; | ||
| 188 | /// Main thread | ||
| 189 | std::thread thread{}; | ||
| 190 | /// The current state | ||
| 191 | std::atomic<bool> running{}; | ||
| 192 | /// The active mailbox | ||
| 193 | AudioRenderer_Mailbox* mailbox{}; | ||
| 194 | /// The command lists to process | ||
| 195 | std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{}; | ||
| 196 | /// The output sink the AudioRenderer will use | ||
| 197 | Sink::Sink& sink; | ||
| 198 | /// The streams which will receive the processed samples | ||
| 199 | std::array<Sink::SinkStream*, MaxRendererSessions> streams; | ||
| 200 | }; | ||
| 201 | |||
| 202 | } // namespace AudioRenderer::ADSP | ||
| 203 | } // namespace AudioCore | ||
diff --git a/src/audio_core/renderer/adsp/command_buffer.h b/src/audio_core/renderer/adsp/command_buffer.h new file mode 100644 index 000000000..880b279d8 --- /dev/null +++ b/src/audio_core/renderer/adsp/command_buffer.h | |||
| @@ -0,0 +1,21 @@ | |||
| 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::ADSP { | ||
| 10 | |||
| 11 | struct CommandBuffer { | ||
| 12 | CpuAddr buffer; | ||
| 13 | u64 size; | ||
| 14 | u64 time_limit; | ||
| 15 | u32 remaining_command_count; | ||
| 16 | bool reset_buffers; | ||
| 17 | u64 applet_resource_user_id; | ||
| 18 | u64 render_time_taken; | ||
| 19 | }; | ||
| 20 | |||
| 21 | } // namespace AudioCore::AudioRenderer::ADSP | ||
diff --git a/src/audio_core/renderer/adsp/command_list_processor.cpp b/src/audio_core/renderer/adsp/command_list_processor.cpp new file mode 100644 index 000000000..e3bf2d7ec --- /dev/null +++ b/src/audio_core/renderer/adsp/command_list_processor.cpp | |||
| @@ -0,0 +1,109 @@ | |||
| 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/command_list_header.h" | ||
| 8 | #include "audio_core/renderer/command/commands.h" | ||
| 9 | #include "common/settings.h" | ||
| 10 | #include "core/core.h" | ||
| 11 | #include "core/core_timing.h" | ||
| 12 | #include "core/core_timing_util.h" | ||
| 13 | #include "core/memory.h" | ||
| 14 | |||
| 15 | namespace AudioCore::AudioRenderer::ADSP { | ||
| 16 | |||
| 17 | void CommandListProcessor::Initialize(Core::System& system_, CpuAddr buffer, u64 size, | ||
| 18 | Sink::SinkStream* stream_) { | ||
| 19 | system = &system_; | ||
| 20 | memory = &system->Memory(); | ||
| 21 | stream = stream_; | ||
| 22 | header = reinterpret_cast<CommandListHeader*>(buffer); | ||
| 23 | commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader)); | ||
| 24 | commands_buffer_size = size; | ||
| 25 | command_count = header->command_count; | ||
| 26 | sample_count = header->sample_count; | ||
| 27 | target_sample_rate = header->sample_rate; | ||
| 28 | mix_buffers = header->samples_buffer; | ||
| 29 | buffer_count = header->buffer_count; | ||
| 30 | processed_command_count = 0; | ||
| 31 | } | ||
| 32 | |||
| 33 | void CommandListProcessor::SetProcessTimeMax(const u64 time) { | ||
| 34 | max_process_time = time; | ||
| 35 | } | ||
| 36 | |||
| 37 | u32 CommandListProcessor::GetRemainingCommandCount() const { | ||
| 38 | return command_count - processed_command_count; | ||
| 39 | } | ||
| 40 | |||
| 41 | void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) { | ||
| 42 | commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader)); | ||
| 43 | commands_buffer_size = size; | ||
| 44 | } | ||
| 45 | |||
| 46 | Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const { | ||
| 47 | return stream; | ||
| 48 | } | ||
| 49 | |||
| 50 | u64 CommandListProcessor::Process(u32 session_id) { | ||
| 51 | const auto start_time_{system->CoreTiming().GetClockTicks()}; | ||
| 52 | const auto command_base{CpuAddr(commands)}; | ||
| 53 | |||
| 54 | if (processed_command_count > 0) { | ||
| 55 | current_processing_time += start_time_ - end_time; | ||
| 56 | } else { | ||
| 57 | start_time = start_time_; | ||
| 58 | current_processing_time = 0; | ||
| 59 | } | ||
| 60 | |||
| 61 | std::string dump{fmt::format("\nSession {}\n", session_id)}; | ||
| 62 | |||
| 63 | for (u32 index = 0; index < command_count; index++) { | ||
| 64 | auto& command{*reinterpret_cast<ICommand*>(commands)}; | ||
| 65 | |||
| 66 | if (command.magic != 0xCAFEBABE) { | ||
| 67 | LOG_ERROR(Service_Audio, "Command has invalid magic! Expected 0xCAFEBABE, got {:08X}", | ||
| 68 | command.magic); | ||
| 69 | return system->CoreTiming().GetClockTicks() - start_time_; | ||
| 70 | } | ||
| 71 | |||
| 72 | auto current_offset{CpuAddr(commands) - command_base}; | ||
| 73 | |||
| 74 | if (current_offset + command.size > commands_buffer_size) { | ||
| 75 | LOG_ERROR(Service_Audio, | ||
| 76 | "Command exceeded command buffer, buffer size {:08X}, command ends at {:08X}", | ||
| 77 | commands_buffer_size, | ||
| 78 | CpuAddr(commands) + command.size - sizeof(CommandListHeader)); | ||
| 79 | return system->CoreTiming().GetClockTicks() - start_time_; | ||
| 80 | } | ||
| 81 | |||
| 82 | if (Settings::values.dump_audio_commands) { | ||
| 83 | command.Dump(*this, dump); | ||
| 84 | } | ||
| 85 | |||
| 86 | if (!command.Verify(*this)) { | ||
| 87 | break; | ||
| 88 | } | ||
| 89 | |||
| 90 | if (command.enabled) { | ||
| 91 | command.Process(*this); | ||
| 92 | } else { | ||
| 93 | dump += fmt::format("\tDisabled!\n"); | ||
| 94 | } | ||
| 95 | |||
| 96 | processed_command_count++; | ||
| 97 | commands += command.size; | ||
| 98 | } | ||
| 99 | |||
| 100 | if (Settings::values.dump_audio_commands && dump != last_dump) { | ||
| 101 | LOG_WARNING(Service_Audio, "{}", dump); | ||
| 102 | last_dump = dump; | ||
| 103 | } | ||
| 104 | |||
| 105 | end_time = system->CoreTiming().GetClockTicks(); | ||
| 106 | return end_time - start_time_; | ||
| 107 | } | ||
| 108 | |||
| 109 | } // namespace AudioCore::AudioRenderer::ADSP | ||
diff --git a/src/audio_core/renderer/adsp/command_list_processor.h b/src/audio_core/renderer/adsp/command_list_processor.h new file mode 100644 index 000000000..3f99173e3 --- /dev/null +++ b/src/audio_core/renderer/adsp/command_list_processor.h | |||
| @@ -0,0 +1,118 @@ | |||
| 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 Core { | ||
| 12 | namespace Memory { | ||
| 13 | class Memory; | ||
| 14 | } | ||
| 15 | class System; | ||
| 16 | } // namespace Core | ||
| 17 | |||
| 18 | namespace AudioCore { | ||
| 19 | namespace Sink { | ||
| 20 | class SinkStream; | ||
| 21 | } | ||
| 22 | |||
| 23 | namespace AudioRenderer { | ||
| 24 | struct CommandListHeader; | ||
| 25 | |||
| 26 | namespace ADSP { | ||
| 27 | |||
| 28 | /** | ||
| 29 | * A processor for command lists given to the AudioRenderer. | ||
| 30 | */ | ||
| 31 | class CommandListProcessor { | ||
| 32 | public: | ||
| 33 | /** | ||
| 34 | * Initialize the processor. | ||
| 35 | * | ||
| 36 | * @param system_ - The core system. | ||
| 37 | * @param buffer - The command buffer to process. | ||
| 38 | * @param size - The size of the buffer. | ||
| 39 | * @param stream_ - The stream to be used for sending the samples. | ||
| 40 | */ | ||
| 41 | void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream); | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Set the maximum processing time for this command list. | ||
| 45 | * | ||
| 46 | * @param time - The maximum process time. | ||
| 47 | */ | ||
| 48 | void SetProcessTimeMax(u64 time); | ||
| 49 | |||
| 50 | /** | ||
| 51 | * Get the remaining command count for this list. | ||
| 52 | * | ||
| 53 | * @return The remaining command count. | ||
| 54 | */ | ||
| 55 | u32 GetRemainingCommandCount() const; | ||
| 56 | |||
| 57 | /** | ||
| 58 | * Set the command buffer. | ||
| 59 | * | ||
| 60 | * @param buffer - The buffer to use. | ||
| 61 | * @param size - The size of the buffer. | ||
| 62 | */ | ||
| 63 | void SetBuffer(CpuAddr buffer, u64 size); | ||
| 64 | |||
| 65 | /** | ||
| 66 | * Get the stream for this command list. | ||
| 67 | * | ||
| 68 | * @return The stream associated with this command list. | ||
| 69 | */ | ||
| 70 | Sink::SinkStream* GetOutputSinkStream() const; | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Process the command list. | ||
| 74 | * | ||
| 75 | * @param index - Index of the current command list. | ||
| 76 | * @return The time taken to process. | ||
| 77 | */ | ||
| 78 | u64 Process(u32 session_id); | ||
| 79 | |||
| 80 | /// Core system | ||
| 81 | Core::System* system{}; | ||
| 82 | /// Core memory | ||
| 83 | Core::Memory::Memory* memory{}; | ||
| 84 | /// Stream for the processed samples | ||
| 85 | Sink::SinkStream* stream{}; | ||
| 86 | /// Header info for this command list | ||
| 87 | CommandListHeader* header{}; | ||
| 88 | /// The command buffer | ||
| 89 | u8* commands{}; | ||
| 90 | /// The command buffer size | ||
| 91 | u64 commands_buffer_size{}; | ||
| 92 | /// The maximum processing time alloted | ||
| 93 | u64 max_process_time{}; | ||
| 94 | /// The number of commands in the buffer | ||
| 95 | u32 command_count{}; | ||
| 96 | /// The target sample count for output | ||
| 97 | u32 sample_count{}; | ||
| 98 | /// The target sample rate for output | ||
| 99 | u32 target_sample_rate{}; | ||
| 100 | /// The mixing buffers used by the commands | ||
| 101 | std::span<s32> mix_buffers{}; | ||
| 102 | /// The number of mix buffers | ||
| 103 | u32 buffer_count{}; | ||
| 104 | /// The number of processed commands so far | ||
| 105 | u32 processed_command_count{}; | ||
| 106 | /// The processing start time of this list | ||
| 107 | u64 start_time{}; | ||
| 108 | /// The current processing time for this list | ||
| 109 | u64 current_processing_time{}; | ||
| 110 | /// The end processing time for this list | ||
| 111 | u64 end_time{}; | ||
| 112 | /// Last command list string generated, used for dumping audio commands to console | ||
| 113 | std::string last_dump{}; | ||
| 114 | }; | ||
| 115 | |||
| 116 | } // namespace ADSP | ||
| 117 | } // namespace AudioRenderer | ||
| 118 | } // namespace AudioCore | ||
diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp new file mode 100644 index 000000000..d5886e55e --- /dev/null +++ b/src/audio_core/renderer/audio_device.cpp | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/audio_core.h" | ||
| 5 | #include "audio_core/common/feature_support.h" | ||
| 6 | #include "audio_core/renderer/audio_device.h" | ||
| 7 | #include "audio_core/sink/sink.h" | ||
| 8 | #include "core/core.h" | ||
| 9 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_, | ||
| 13 | const u32 revision) | ||
| 14 | : output_sink{system.AudioCore().GetOutputSink()}, | ||
| 15 | applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {} | ||
| 16 | |||
| 17 | u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, | ||
| 18 | const size_t max_count) { | ||
| 19 | std::span<AudioDeviceName> names{}; | ||
| 20 | |||
| 21 | if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) { | ||
| 22 | names = usb_device_names; | ||
| 23 | } else { | ||
| 24 | names = device_names; | ||
| 25 | } | ||
| 26 | |||
| 27 | u32 out_count{static_cast<u32>(std::min(max_count, names.size()))}; | ||
| 28 | for (u32 i = 0; i < out_count; i++) { | ||
| 29 | out_buffer.push_back(names[i]); | ||
| 30 | } | ||
| 31 | return out_count; | ||
| 32 | } | ||
| 33 | |||
| 34 | u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, | ||
| 35 | const size_t max_count) { | ||
| 36 | u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))}; | ||
| 37 | |||
| 38 | for (u32 i = 0; i < out_count; i++) { | ||
| 39 | out_buffer.push_back(output_device_names[i]); | ||
| 40 | } | ||
| 41 | return out_count; | ||
| 42 | } | ||
| 43 | |||
| 44 | void AudioDevice::SetDeviceVolumes(const f32 volume) { | ||
| 45 | output_sink.SetDeviceVolume(volume); | ||
| 46 | } | ||
| 47 | |||
| 48 | f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) { | ||
| 49 | return output_sink.GetDeviceVolume(); | ||
| 50 | } | ||
| 51 | |||
| 52 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/audio_device.h b/src/audio_core/renderer/audio_device.h new file mode 100644 index 000000000..1f449f261 --- /dev/null +++ b/src/audio_core/renderer/audio_device.h | |||
| @@ -0,0 +1,88 @@ | |||
| 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/audio_render_manager.h" | ||
| 9 | |||
| 10 | namespace Core { | ||
| 11 | class System; | ||
| 12 | } | ||
| 13 | |||
| 14 | namespace AudioCore { | ||
| 15 | namespace Sink { | ||
| 16 | class Sink; | ||
| 17 | } | ||
| 18 | |||
| 19 | namespace AudioRenderer { | ||
| 20 | /** | ||
| 21 | * An interface to an output audio device available to the Switch. | ||
| 22 | */ | ||
| 23 | class AudioDevice { | ||
| 24 | public: | ||
| 25 | struct AudioDeviceName { | ||
| 26 | std::array<char, 0x100> name; | ||
| 27 | |||
| 28 | AudioDeviceName(const char* name_) { | ||
| 29 | std::strncpy(name.data(), name_, name.size()); | ||
| 30 | } | ||
| 31 | }; | ||
| 32 | |||
| 33 | std::array<AudioDeviceName, 4> usb_device_names{"AudioStereoJackOutput", | ||
| 34 | "AudioBuiltInSpeakerOutput", "AudioTvOutput", | ||
| 35 | "AudioUsbDeviceOutput"}; | ||
| 36 | std::array<AudioDeviceName, 3> device_names{"AudioStereoJackOutput", | ||
| 37 | "AudioBuiltInSpeakerOutput", "AudioTvOutput"}; | ||
| 38 | std::array<AudioDeviceName, 3> output_device_names{"AudioBuiltInSpeakerOutput", "AudioTvOutput", | ||
| 39 | "AudioExternalOutput"}; | ||
| 40 | |||
| 41 | explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision); | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Get a list of the available output devices. | ||
| 45 | * | ||
| 46 | * @param out_buffer - Output buffer to write the available device names. | ||
| 47 | * @param max_count - Maximum number of devices to write (count of out_buffer). | ||
| 48 | * @return Number of device names written. | ||
| 49 | */ | ||
| 50 | u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); | ||
| 51 | |||
| 52 | /** | ||
| 53 | * Get a list of the available output devices. | ||
| 54 | * Different to above somehow... | ||
| 55 | * | ||
| 56 | * @param out_buffer - Output buffer to write the available device names. | ||
| 57 | * @param max_count - Maximum number of devices to write (count of out_buffer). | ||
| 58 | * @return Number of device names written. | ||
| 59 | */ | ||
| 60 | u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); | ||
| 61 | |||
| 62 | /** | ||
| 63 | * Set the volume of all streams in the backend sink. | ||
| 64 | * | ||
| 65 | * @param volume - Volume to set. | ||
| 66 | */ | ||
| 67 | void SetDeviceVolumes(f32 volume); | ||
| 68 | |||
| 69 | /** | ||
| 70 | * Get the volume for a given device name. | ||
| 71 | * Note: This is not fully implemented, we only assume 1 device for all streams. | ||
| 72 | * | ||
| 73 | * @param name - Name of the device to check. Unused. | ||
| 74 | * @return Volume of the device. | ||
| 75 | */ | ||
| 76 | f32 GetDeviceVolume(std::string_view name); | ||
| 77 | |||
| 78 | private: | ||
| 79 | /// Backend output sink for the device | ||
| 80 | Sink::Sink& output_sink; | ||
| 81 | /// Resource id this device is used for | ||
| 82 | const u64 applet_resource_user_id; | ||
| 83 | /// User audio renderer revision | ||
| 84 | const u32 user_revision; | ||
| 85 | }; | ||
| 86 | |||
| 87 | } // namespace AudioRenderer | ||
| 88 | } // namespace AudioCore | ||
diff --git a/src/audio_core/renderer/audio_renderer.cpp b/src/audio_core/renderer/audio_renderer.cpp new file mode 100644 index 000000000..51aa17599 --- /dev/null +++ b/src/audio_core/renderer/audio_renderer.cpp | |||
| @@ -0,0 +1,67 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/audio_render_manager.h" | ||
| 5 | #include "audio_core/common/audio_renderer_parameter.h" | ||
| 6 | #include "audio_core/renderer/audio_renderer.h" | ||
| 7 | #include "audio_core/renderer/system_manager.h" | ||
| 8 | #include "core/core.h" | ||
| 9 | #include "core/hle/kernel/k_transfer_memory.h" | ||
| 10 | #include "core/hle/service/audio/errors.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | |||
| 14 | Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event) | ||
| 15 | : core{system_}, manager{manager_}, system{system_, rendered_event} {} | ||
| 16 | |||
| 17 | Result Renderer::Initialize(const AudioRendererParameterInternal& params, | ||
| 18 | Kernel::KTransferMemory* transfer_memory, | ||
| 19 | const u64 transfer_memory_size, const u32 process_handle, | ||
| 20 | const u64 applet_resource_user_id, const s32 session_id) { | ||
| 21 | if (params.execution_mode == ExecutionMode::Auto) { | ||
| 22 | if (!manager.AddSystem(system)) { | ||
| 23 | LOG_ERROR(Service_Audio, | ||
| 24 | "Both Audio Render sessions are in use, cannot create any more"); | ||
| 25 | return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED; | ||
| 26 | } | ||
| 27 | system_registered = true; | ||
| 28 | } | ||
| 29 | |||
| 30 | initialized = true; | ||
| 31 | system.Initialize(params, transfer_memory, transfer_memory_size, process_handle, | ||
| 32 | applet_resource_user_id, session_id); | ||
| 33 | |||
| 34 | return ResultSuccess; | ||
| 35 | } | ||
| 36 | |||
| 37 | void Renderer::Finalize() { | ||
| 38 | auto session_id{system.GetSessionId()}; | ||
| 39 | |||
| 40 | system.Finalize(); | ||
| 41 | |||
| 42 | if (system_registered) { | ||
| 43 | manager.RemoveSystem(system); | ||
| 44 | system_registered = false; | ||
| 45 | } | ||
| 46 | |||
| 47 | manager.ReleaseSessionId(session_id); | ||
| 48 | } | ||
| 49 | |||
| 50 | System& Renderer::GetSystem() { | ||
| 51 | return system; | ||
| 52 | } | ||
| 53 | |||
| 54 | void Renderer::Start() { | ||
| 55 | system.Start(); | ||
| 56 | } | ||
| 57 | |||
| 58 | void Renderer::Stop() { | ||
| 59 | system.Stop(); | ||
| 60 | } | ||
| 61 | |||
| 62 | Result Renderer::RequestUpdate(std::span<const u8> input, std::span<u8> performance, | ||
| 63 | std::span<u8> output) { | ||
| 64 | return system.Update(input, performance, output); | ||
| 65 | } | ||
| 66 | |||
| 67 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/audio_renderer.h b/src/audio_core/renderer/audio_renderer.h new file mode 100644 index 000000000..90c6f9727 --- /dev/null +++ b/src/audio_core/renderer/audio_renderer.h | |||
| @@ -0,0 +1,97 @@ | |||
| 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/system.h" | ||
| 9 | #include "core/hle/service/audio/errors.h" | ||
| 10 | |||
| 11 | namespace Core { | ||
| 12 | class System; | ||
| 13 | } | ||
| 14 | |||
| 15 | namespace Kernel { | ||
| 16 | class KTransferMemory; | ||
| 17 | } | ||
| 18 | |||
| 19 | namespace AudioCore { | ||
| 20 | struct AudioRendererParameterInternal; | ||
| 21 | |||
| 22 | namespace AudioRenderer { | ||
| 23 | class Manager; | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Audio Renderer, wraps the main audio system and is mainly responsible for handling service calls. | ||
| 27 | */ | ||
| 28 | class Renderer { | ||
| 29 | public: | ||
| 30 | explicit Renderer(Core::System& system, Manager& manager, Kernel::KEvent* rendered_event); | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Initialize the renderer. | ||
| 34 | * Registers the system with the AudioRenderer::Manager, allocates workbuffers and initializes | ||
| 35 | * everything to a default state. | ||
| 36 | * | ||
| 37 | * @param params - Input parameters to initialize the system with. | ||
| 38 | * @param transfer_memory - Game-supplied memory for all workbuffers. Unused. | ||
| 39 | * @param transfer_memory_size - Size of the transfer memory. Unused. | ||
| 40 | * @param process_handle - Process handle, also used for memory. Unused. | ||
| 41 | * @param applet_resource_user_id - Applet id for this renderer. Unused. | ||
| 42 | * @param session_id - Session id of this renderer. | ||
| 43 | * @return Result code. | ||
| 44 | */ | ||
| 45 | Result Initialize(const AudioRendererParameterInternal& params, | ||
| 46 | Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size, | ||
| 47 | u32 process_handle, u64 applet_resource_user_id, s32 session_id); | ||
| 48 | |||
| 49 | /** | ||
| 50 | * Finalize the renderer for shutdown. | ||
| 51 | */ | ||
| 52 | void Finalize(); | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Get the renderer's system. | ||
| 56 | * | ||
| 57 | * @return Reference to the system. | ||
| 58 | */ | ||
| 59 | System& GetSystem(); | ||
| 60 | |||
| 61 | /** | ||
| 62 | * Start the renderer. | ||
| 63 | */ | ||
| 64 | void Start(); | ||
| 65 | |||
| 66 | /** | ||
| 67 | * Stop the renderer. | ||
| 68 | */ | ||
| 69 | void Stop(); | ||
| 70 | |||
| 71 | /** | ||
| 72 | * Update the audio renderer with new information. | ||
| 73 | * Called via RequestUpdate from the AudRen:U service. | ||
| 74 | * | ||
| 75 | * @param input - Input buffer containing the new data. | ||
| 76 | * @param performance - Optional performance buffer for outputting performance metrics. | ||
| 77 | * @param output - Output data from the renderer. | ||
| 78 | * @return Result code. | ||
| 79 | */ | ||
| 80 | Result RequestUpdate(std::span<const u8> input, std::span<u8> performance, | ||
| 81 | std::span<u8> output); | ||
| 82 | |||
| 83 | private: | ||
| 84 | /// System core | ||
| 85 | Core::System& core; | ||
| 86 | /// Manager this renderer is registered with | ||
| 87 | Manager& manager; | ||
| 88 | /// Is the audio renderer initialized? | ||
| 89 | bool initialized{}; | ||
| 90 | /// Is the system registered with the manager? | ||
| 91 | bool system_registered{}; | ||
| 92 | /// Audio render system, main driver of audio rendering | ||
| 93 | System system; | ||
| 94 | }; | ||
| 95 | |||
| 96 | } // namespace AudioRenderer | ||
| 97 | } // namespace AudioCore | ||
diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp new file mode 100644 index 000000000..c5d4d66d8 --- /dev/null +++ b/src/audio_core/renderer/behavior/behavior_info.cpp | |||
| @@ -0,0 +1,191 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/common/feature_support.h" | ||
| 5 | #include "audio_core/renderer/behavior/behavior_info.h" | ||
| 6 | |||
| 7 | namespace AudioCore::AudioRenderer { | ||
| 8 | |||
| 9 | BehaviorInfo::BehaviorInfo() : process_revision{CurrentRevision} {} | ||
| 10 | |||
| 11 | u32 BehaviorInfo::GetProcessRevisionNum() const { | ||
| 12 | return process_revision; | ||
| 13 | } | ||
| 14 | |||
| 15 | u32 BehaviorInfo::GetProcessRevision() const { | ||
| 16 | return Common::MakeMagic('R', 'E', 'V', | ||
| 17 | static_cast<char>(static_cast<u8>('0') + process_revision)); | ||
| 18 | } | ||
| 19 | |||
| 20 | u32 BehaviorInfo::GetUserRevisionNum() const { | ||
| 21 | return user_revision; | ||
| 22 | } | ||
| 23 | |||
| 24 | u32 BehaviorInfo::GetUserRevision() const { | ||
| 25 | return Common::MakeMagic('R', 'E', 'V', | ||
| 26 | static_cast<char>(static_cast<u8>('0') + user_revision)); | ||
| 27 | } | ||
| 28 | |||
| 29 | void BehaviorInfo::SetUserLibRevision(const u32 user_revision_) { | ||
| 30 | user_revision = GetRevisionNum(user_revision_); | ||
| 31 | } | ||
| 32 | |||
| 33 | void BehaviorInfo::ClearError() { | ||
| 34 | error_count = 0; | ||
| 35 | } | ||
| 36 | |||
| 37 | void BehaviorInfo::AppendError(ErrorInfo& error) { | ||
| 38 | LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}", | ||
| 39 | error.error_code.raw, error.address); | ||
| 40 | if (error_count < MaxErrors) { | ||
| 41 | errors[error_count++] = error; | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) { | ||
| 46 | auto error_count_{std::min(error_count, MaxErrors)}; | ||
| 47 | std::memset(out_errors.data(), 0, MaxErrors * sizeof(ErrorInfo)); | ||
| 48 | |||
| 49 | for (size_t i = 0; i < error_count_; i++) { | ||
| 50 | out_errors[i] = errors[i]; | ||
| 51 | } | ||
| 52 | out_count = error_count_; | ||
| 53 | } | ||
| 54 | |||
| 55 | void BehaviorInfo::UpdateFlags(const Flags flags_) { | ||
| 56 | flags = flags_; | ||
| 57 | } | ||
| 58 | |||
| 59 | bool BehaviorInfo::IsMemoryForceMappingEnabled() const { | ||
| 60 | return flags.IsMemoryForceMappingEnabled; | ||
| 61 | } | ||
| 62 | |||
| 63 | bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const { | ||
| 64 | return CheckFeatureSupported(SupportTags::AdpcmLoopContextBugFix, user_revision); | ||
| 65 | } | ||
| 66 | |||
| 67 | bool BehaviorInfo::IsSplitterSupported() const { | ||
| 68 | return CheckFeatureSupported(SupportTags::Splitter, user_revision); | ||
| 69 | } | ||
| 70 | |||
| 71 | bool BehaviorInfo::IsSplitterBugFixed() const { | ||
| 72 | return CheckFeatureSupported(SupportTags::SplitterBugFix, user_revision); | ||
| 73 | } | ||
| 74 | |||
| 75 | bool BehaviorInfo::IsEffectInfoVersion2Supported() const { | ||
| 76 | return CheckFeatureSupported(SupportTags::EffectInfoVer2, user_revision); | ||
| 77 | } | ||
| 78 | |||
| 79 | bool BehaviorInfo::IsVariadicCommandBufferSizeSupported() const { | ||
| 80 | return CheckFeatureSupported(SupportTags::AudioRendererVariadicCommandBufferSize, | ||
| 81 | user_revision); | ||
| 82 | } | ||
| 83 | |||
| 84 | bool BehaviorInfo::IsWaveBufferVer2Supported() const { | ||
| 85 | return CheckFeatureSupported(SupportTags::WaveBufferVer2, user_revision); | ||
| 86 | } | ||
| 87 | |||
| 88 | bool BehaviorInfo::IsLongSizePreDelaySupported() const { | ||
| 89 | return CheckFeatureSupported(SupportTags::LongSizePreDelay, user_revision); | ||
| 90 | } | ||
| 91 | |||
| 92 | bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion2Supported() const { | ||
| 93 | return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion2, | ||
| 94 | user_revision); | ||
| 95 | } | ||
| 96 | |||
| 97 | bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion3Supported() const { | ||
| 98 | return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion3, | ||
| 99 | user_revision); | ||
| 100 | } | ||
| 101 | |||
| 102 | bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion4Supported() const { | ||
| 103 | return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4, | ||
| 104 | user_revision); | ||
| 105 | } | ||
| 106 | |||
| 107 | bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion5Supported() const { | ||
| 108 | return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4, | ||
| 109 | user_revision); | ||
| 110 | } | ||
| 111 | |||
| 112 | bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const { | ||
| 113 | return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit70Percent, | ||
| 114 | user_revision); | ||
| 115 | } | ||
| 116 | |||
| 117 | bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const { | ||
| 118 | return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit75Percent, | ||
| 119 | user_revision); | ||
| 120 | } | ||
| 121 | |||
| 122 | bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const { | ||
| 123 | return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit80Percent, | ||
| 124 | user_revision); | ||
| 125 | } | ||
| 126 | |||
| 127 | bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const { | ||
| 128 | return CheckFeatureSupported(SupportTags::FlushVoiceWaveBuffers, user_revision); | ||
| 129 | } | ||
| 130 | |||
| 131 | bool BehaviorInfo::IsElapsedFrameCountSupported() const { | ||
| 132 | return CheckFeatureSupported(SupportTags::ElapsedFrameCount, user_revision); | ||
| 133 | } | ||
| 134 | |||
| 135 | bool BehaviorInfo::IsPerformanceMetricsDataFormatVersion2Supported() const { | ||
| 136 | return CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision); | ||
| 137 | } | ||
| 138 | |||
| 139 | size_t BehaviorInfo::GetPerformanceMetricsDataFormat() const { | ||
| 140 | if (CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision)) { | ||
| 141 | return 2; | ||
| 142 | } | ||
| 143 | return 1; | ||
| 144 | } | ||
| 145 | |||
| 146 | bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const { | ||
| 147 | return CheckFeatureSupported(SupportTags::VoicePitchAndSrcSkipped, user_revision); | ||
| 148 | } | ||
| 149 | |||
| 150 | bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const { | ||
| 151 | return CheckFeatureSupported(SupportTags::VoicePlayedSampleCountResetAtLoopPoint, | ||
| 152 | user_revision); | ||
| 153 | } | ||
| 154 | |||
| 155 | bool BehaviorInfo::IsBiquadFilterEffectStateClearBugFixed() const { | ||
| 156 | return CheckFeatureSupported(SupportTags::BiquadFilterEffectStateClearBugFix, user_revision); | ||
| 157 | } | ||
| 158 | |||
| 159 | bool BehaviorInfo::IsVolumeMixParameterPrecisionQ23Supported() const { | ||
| 160 | return CheckFeatureSupported(SupportTags::VolumeMixParameterPrecisionQ23, user_revision); | ||
| 161 | } | ||
| 162 | |||
| 163 | bool BehaviorInfo::UseBiquadFilterFloatProcessing() const { | ||
| 164 | return CheckFeatureSupported(SupportTags::BiquadFilterFloatProcessing, user_revision); | ||
| 165 | } | ||
| 166 | |||
| 167 | bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const { | ||
| 168 | return CheckFeatureSupported(SupportTags::MixInParameterDirtyOnlyUpdate, user_revision); | ||
| 169 | } | ||
| 170 | |||
| 171 | bool BehaviorInfo::UseMultiTapBiquadFilterProcessing() const { | ||
| 172 | return CheckFeatureSupported(SupportTags::MultiTapBiquadFilterProcessing, user_revision); | ||
| 173 | } | ||
| 174 | |||
| 175 | bool BehaviorInfo::IsDeviceApiVersion2Supported() const { | ||
| 176 | return CheckFeatureSupported(SupportTags::DeviceApiVersion2, user_revision); | ||
| 177 | } | ||
| 178 | |||
| 179 | bool BehaviorInfo::IsDelayChannelMappingChanged() const { | ||
| 180 | return CheckFeatureSupported(SupportTags::DelayChannelMappingChange, user_revision); | ||
| 181 | } | ||
| 182 | |||
| 183 | bool BehaviorInfo::IsReverbChannelMappingChanged() const { | ||
| 184 | return CheckFeatureSupported(SupportTags::ReverbChannelMappingChange, user_revision); | ||
| 185 | } | ||
| 186 | |||
| 187 | bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const { | ||
| 188 | return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision); | ||
| 189 | } | ||
| 190 | |||
| 191 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/behavior/behavior_info.h b/src/audio_core/renderer/behavior/behavior_info.h new file mode 100644 index 000000000..7333c297f --- /dev/null +++ b/src/audio_core/renderer/behavior/behavior_info.h | |||
| @@ -0,0 +1,376 @@ | |||
| 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 "common/common_types.h" | ||
| 11 | #include "core/hle/service/audio/errors.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | /** | ||
| 15 | * Holds host and user revisions, checks whether render features can be enabled, and reports errors. | ||
| 16 | */ | ||
| 17 | class BehaviorInfo { | ||
| 18 | static constexpr u32 MaxErrors = 10; | ||
| 19 | |||
| 20 | public: | ||
| 21 | struct ErrorInfo { | ||
| 22 | /* 0x00 */ Result error_code{0}; | ||
| 23 | /* 0x04 */ u32 unk_04; | ||
| 24 | /* 0x08 */ CpuAddr address; | ||
| 25 | }; | ||
| 26 | static_assert(sizeof(ErrorInfo) == 0x10, "BehaviorInfo::ErrorInfo has the wrong size!"); | ||
| 27 | |||
| 28 | struct Flags { | ||
| 29 | u64 IsMemoryForceMappingEnabled : 1; | ||
| 30 | }; | ||
| 31 | |||
| 32 | struct InParameter { | ||
| 33 | /* 0x00 */ u32 revision; | ||
| 34 | /* 0x08 */ Flags flags; | ||
| 35 | }; | ||
| 36 | static_assert(sizeof(InParameter) == 0x10, "BehaviorInfo::InParameter has the wrong size!"); | ||
| 37 | |||
| 38 | struct OutStatus { | ||
| 39 | /* 0x00 */ std::array<ErrorInfo, MaxErrors> errors; | ||
| 40 | /* 0xA0 */ u32 error_count; | ||
| 41 | /* 0xA4 */ char unkA4[0xC]; | ||
| 42 | }; | ||
| 43 | static_assert(sizeof(OutStatus) == 0xB0, "BehaviorInfo::OutStatus has the wrong size!"); | ||
| 44 | |||
| 45 | BehaviorInfo(); | ||
| 46 | |||
| 47 | /** | ||
| 48 | * Get the host revision as a number. | ||
| 49 | * | ||
| 50 | * @return The host revision. | ||
| 51 | */ | ||
| 52 | u32 GetProcessRevisionNum() const; | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Get the host revision in chars, e.g REV8. | ||
| 56 | * Rev 10 and higher use the ascii characters above 9. | ||
| 57 | * E.g: | ||
| 58 | * Rev 10 = REV: | ||
| 59 | * Rev 11 = REV; | ||
| 60 | * | ||
| 61 | * @return The host revision. | ||
| 62 | */ | ||
| 63 | u32 GetProcessRevision() const; | ||
| 64 | |||
| 65 | /** | ||
| 66 | * Get the user revision as a number. | ||
| 67 | * | ||
| 68 | * @return The user revision. | ||
| 69 | */ | ||
| 70 | u32 GetUserRevisionNum() const; | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Get the user revision in chars, e.g REV8. | ||
| 74 | * Rev 10 and higher use the ascii characters above 9. REV: REV; etc. | ||
| 75 | * | ||
| 76 | * @return The user revision. | ||
| 77 | */ | ||
| 78 | u32 GetUserRevision() const; | ||
| 79 | |||
| 80 | /** | ||
| 81 | * Set the user revision. | ||
| 82 | * | ||
| 83 | * @param user_revision - The user's revision. | ||
| 84 | */ | ||
| 85 | void SetUserLibRevision(u32 user_revision); | ||
| 86 | |||
| 87 | /** | ||
| 88 | * Clear the current error count. | ||
| 89 | */ | ||
| 90 | void ClearError(); | ||
| 91 | |||
| 92 | /** | ||
| 93 | * Append an error to the error list. | ||
| 94 | * | ||
| 95 | * @param error - The new error. | ||
| 96 | */ | ||
| 97 | void AppendError(ErrorInfo& error); | ||
| 98 | |||
| 99 | /** | ||
| 100 | * Copy errors to the given output container. | ||
| 101 | * | ||
| 102 | * @param out_errors - Output container to receive the errors. | ||
| 103 | * @param out_count - The number of errors written. | ||
| 104 | */ | ||
| 105 | void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count); | ||
| 106 | |||
| 107 | /** | ||
| 108 | * Update the behaviour flags. | ||
| 109 | * | ||
| 110 | * @param flags - New flags to use. | ||
| 111 | */ | ||
| 112 | void UpdateFlags(Flags flags); | ||
| 113 | |||
| 114 | /** | ||
| 115 | * Check if memory pools can be forcibly mapped. | ||
| 116 | * | ||
| 117 | * @return True if enabled, otherwise false. | ||
| 118 | */ | ||
| 119 | bool IsMemoryForceMappingEnabled() const; | ||
| 120 | |||
| 121 | /** | ||
| 122 | * Check if the ADPCM context bug is fixed. | ||
| 123 | * The ADPCM context was not being sent to the AudioRenderer, leading to incorrect scaling being | ||
| 124 | * used. | ||
| 125 | * | ||
| 126 | * @return True if fixed, otherwise false. | ||
| 127 | */ | ||
| 128 | bool IsAdpcmLoopContextBugFixed() const; | ||
| 129 | |||
| 130 | /** | ||
| 131 | * Check if the splitter is supported. | ||
| 132 | * | ||
| 133 | * @return True if supported, otherwise false. | ||
| 134 | */ | ||
| 135 | bool IsSplitterSupported() const; | ||
| 136 | |||
| 137 | /** | ||
| 138 | * Check if the splitter bug is fixed. | ||
| 139 | * Update is given the wrong number of splitter destinations, leading to invalid data | ||
| 140 | * being processed. | ||
| 141 | * | ||
| 142 | * @return True if supported, otherwise false. | ||
| 143 | */ | ||
| 144 | bool IsSplitterBugFixed() const; | ||
| 145 | |||
| 146 | /** | ||
| 147 | * Check if effects version 2 are supported. | ||
| 148 | * This gives support for returning effect states from the AudioRenderer, currently only used | ||
| 149 | * for Limiter statistics. | ||
| 150 | * | ||
| 151 | * @return True if supported, otherwise false. | ||
| 152 | */ | ||
| 153 | bool IsEffectInfoVersion2Supported() const; | ||
| 154 | |||
| 155 | /** | ||
| 156 | * Check if a variadic command buffer is supported. | ||
| 157 | * As of Rev 5 with the added optional performance metric logging, the command | ||
| 158 | * buffer can be a variable size, so take that into account for calcualting its size. | ||
| 159 | * | ||
| 160 | * @return True if supported, otherwise false. | ||
| 161 | */ | ||
| 162 | bool IsVariadicCommandBufferSizeSupported() const; | ||
| 163 | |||
| 164 | /** | ||
| 165 | * Check if wave buffers version 2 are supported. | ||
| 166 | * See WaveBufferVersion1 and WaveBufferVersion2. | ||
| 167 | * | ||
| 168 | * @return True if supported, otherwise false. | ||
| 169 | */ | ||
| 170 | bool IsWaveBufferVer2Supported() const; | ||
| 171 | |||
| 172 | /** | ||
| 173 | * Check if long size pre delay is supported. | ||
| 174 | * This allows a longer initial delay time for the Reverb command. | ||
| 175 | * | ||
| 176 | * @return True if supported, otherwise false. | ||
| 177 | */ | ||
| 178 | bool IsLongSizePreDelaySupported() const; | ||
| 179 | |||
| 180 | /** | ||
| 181 | * Check if the command time estimator version 2 is supported. | ||
| 182 | * | ||
| 183 | * @return True if supported, otherwise false. | ||
| 184 | */ | ||
| 185 | bool IsCommandProcessingTimeEstimatorVersion2Supported() const; | ||
| 186 | |||
| 187 | /** | ||
| 188 | * Check if the command time estimator version 3 is supported. | ||
| 189 | * | ||
| 190 | * @return True if supported, otherwise false. | ||
| 191 | */ | ||
| 192 | bool IsCommandProcessingTimeEstimatorVersion3Supported() const; | ||
| 193 | |||
| 194 | /** | ||
| 195 | * Check if the command time estimator version 4 is supported. | ||
| 196 | * | ||
| 197 | * @return True if supported, otherwise false. | ||
| 198 | */ | ||
| 199 | bool IsCommandProcessingTimeEstimatorVersion4Supported() const; | ||
| 200 | |||
| 201 | /** | ||
| 202 | * Check if the command time estimator version 5 is supported. | ||
| 203 | * | ||
| 204 | * @return True if supported, otherwise false. | ||
| 205 | */ | ||
| 206 | bool IsCommandProcessingTimeEstimatorVersion5Supported() const; | ||
| 207 | |||
| 208 | /** | ||
| 209 | * Check if the AudioRenderer can use up to 70% of the allocated processing timeslice. | ||
| 210 | * | ||
| 211 | * @return True if supported, otherwise false. | ||
| 212 | */ | ||
| 213 | bool IsAudioRendererProcessingTimeLimit70PercentSupported() const; | ||
| 214 | |||
| 215 | /** | ||
| 216 | * Check if the AudioRenderer can use up to 75% of the allocated processing timeslice. | ||
| 217 | * | ||
| 218 | * @return True if supported, otherwise false. | ||
| 219 | */ | ||
| 220 | bool IsAudioRendererProcessingTimeLimit75PercentSupported() const; | ||
| 221 | |||
| 222 | /** | ||
| 223 | * Check if the AudioRenderer can use up to 80% of the allocated processing timeslice. | ||
| 224 | * | ||
| 225 | * @return True if supported, otherwise false. | ||
| 226 | */ | ||
| 227 | bool IsAudioRendererProcessingTimeLimit80PercentSupported() const; | ||
| 228 | |||
| 229 | /** | ||
| 230 | * Check if voice flushing is supported | ||
| 231 | * This allowws low-priority voices to be dropped if the AudioRenderer is running behind. | ||
| 232 | * | ||
| 233 | * @return True if supported, otherwise false. | ||
| 234 | */ | ||
| 235 | bool IsFlushVoiceWaveBuffersSupported() const; | ||
| 236 | |||
| 237 | /** | ||
| 238 | * Check if counting the number of elapsed frames is supported. | ||
| 239 | * This adds extra output to RequestUpdate, returning the number of times the AudioRenderer | ||
| 240 | * processed a command list. | ||
| 241 | * | ||
| 242 | * @return True if supported, otherwise false. | ||
| 243 | */ | ||
| 244 | bool IsElapsedFrameCountSupported() const; | ||
| 245 | |||
| 246 | /** | ||
| 247 | * Check if performance metrics version 2 are supported. | ||
| 248 | * This adds extra output to RequestUpdate, returning the number of times the AudioRenderer | ||
| 249 | * (Unused?). | ||
| 250 | * | ||
| 251 | * @return True if supported, otherwise false. | ||
| 252 | */ | ||
| 253 | bool IsPerformanceMetricsDataFormatVersion2Supported() const; | ||
| 254 | |||
| 255 | /** | ||
| 256 | * Get the supported performance metrics version. | ||
| 257 | * Version 2 logs some extra fields in output, such as number of voices dropped, | ||
| 258 | * processing start time, if the AudioRenderer exceeded its time, etc. | ||
| 259 | * | ||
| 260 | * @return Version supported, either 1 or 2. | ||
| 261 | */ | ||
| 262 | size_t GetPerformanceMetricsDataFormat() const; | ||
| 263 | |||
| 264 | /** | ||
| 265 | * Check if skipping voice pitch and sample rate conversion is supported. | ||
| 266 | * This speeds up the data source commands by skipping resampling if unwanted. | ||
| 267 | * See AudioCore::AudioRenderer::DecodeFromWaveBuffers | ||
| 268 | * | ||
| 269 | * @return True if supported, otherwise false. | ||
| 270 | */ | ||
| 271 | bool IsVoicePitchAndSrcSkippedSupported() const; | ||
| 272 | |||
| 273 | /** | ||
| 274 | * Check if resetting played sample count at loop points is supported. | ||
| 275 | * This resets the number of samples played in a voice state when a loop point is reached. | ||
| 276 | * See AudioCore::AudioRenderer::DecodeFromWaveBuffers | ||
| 277 | * | ||
| 278 | * @return True if supported, otherwise false. | ||
| 279 | */ | ||
| 280 | bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const; | ||
| 281 | |||
| 282 | /** | ||
| 283 | * Check if the clear state bug for biquad filters is fixed. | ||
| 284 | * The biquad state was not marked as needing re-initialisation when the effect was updated, it | ||
| 285 | * was only initialized once with a new effect. | ||
| 286 | * | ||
| 287 | * @return True if fixed, otherwise false. | ||
| 288 | */ | ||
| 289 | bool IsBiquadFilterEffectStateClearBugFixed() const; | ||
| 290 | |||
| 291 | /** | ||
| 292 | * Check if Q23 precision is supported for fixed point. | ||
| 293 | * | ||
| 294 | * @return True if supported, otherwise false. | ||
| 295 | */ | ||
| 296 | bool IsVolumeMixParameterPrecisionQ23Supported() const; | ||
| 297 | |||
| 298 | /** | ||
| 299 | * Check if float processing for biuad filters is supported. | ||
| 300 | * | ||
| 301 | * @return True if supported, otherwise false. | ||
| 302 | */ | ||
| 303 | bool UseBiquadFilterFloatProcessing() const; | ||
| 304 | |||
| 305 | /** | ||
| 306 | * Check if dirty-only mix updates are supported. | ||
| 307 | * This saves a lot of buffer size as mixes can be large and not change much. | ||
| 308 | * | ||
| 309 | * @return True if supported, otherwise false. | ||
| 310 | */ | ||
| 311 | bool IsMixInParameterDirtyOnlyUpdateSupported() const; | ||
| 312 | |||
| 313 | /** | ||
| 314 | * Check if multi-tap biquad filters are supported. | ||
| 315 | * | ||
| 316 | * @return True if supported, otherwise false. | ||
| 317 | */ | ||
| 318 | bool UseMultiTapBiquadFilterProcessing() const; | ||
| 319 | |||
| 320 | /** | ||
| 321 | * Check if device api version 2 is supported. | ||
| 322 | * In the SDK but not in any sysmodule? Not sure, left here for completeness anyway. | ||
| 323 | * | ||
| 324 | * @return True if supported, otherwise false. | ||
| 325 | */ | ||
| 326 | bool IsDeviceApiVersion2Supported() const; | ||
| 327 | |||
| 328 | /** | ||
| 329 | * Check if new channel mappings are used for Delay commands. | ||
| 330 | * Older commands used: | ||
| 331 | * front left/front right/back left/back right/center/lfe | ||
| 332 | * Whereas everywhere else in the code uses: | ||
| 333 | * front left/front right/center/lfe/back left/back right | ||
| 334 | * This corrects that and makes everything standardised. | ||
| 335 | * | ||
| 336 | * @return True if supported, otherwise false. | ||
| 337 | */ | ||
| 338 | bool IsDelayChannelMappingChanged() const; | ||
| 339 | |||
| 340 | /** | ||
| 341 | * Check if new channel mappings are used for Reverb commands. | ||
| 342 | * Older commands used: | ||
| 343 | * front left/front right/back left/back right/center/lfe | ||
| 344 | * Whereas everywhere else in the code uses: | ||
| 345 | * front left/front right/center/lfe/back left/back right | ||
| 346 | * This corrects that and makes everything standardised. | ||
| 347 | * | ||
| 348 | * @return True if supported, otherwise false. | ||
| 349 | */ | ||
| 350 | bool IsReverbChannelMappingChanged() const; | ||
| 351 | |||
| 352 | /** | ||
| 353 | * Check if new channel mappings are used for I3dl2Reverb commands. | ||
| 354 | * Older commands used: | ||
| 355 | * front left/front right/back left/back right/center/lfe | ||
| 356 | * Whereas everywhere else in the code uses: | ||
| 357 | * front left/front right/center/lfe/back left/back right | ||
| 358 | * This corrects that and makes everything standardised. | ||
| 359 | * | ||
| 360 | * @return True if supported, otherwise false. | ||
| 361 | */ | ||
| 362 | bool IsI3dl2ReverbChannelMappingChanged() const; | ||
| 363 | |||
| 364 | /// Host version | ||
| 365 | u32 process_revision; | ||
| 366 | /// User version | ||
| 367 | u32 user_revision{}; | ||
| 368 | /// Behaviour flags | ||
| 369 | Flags flags{}; | ||
| 370 | /// Errors generated and reported during Update | ||
| 371 | std::array<ErrorInfo, MaxErrors> errors{}; | ||
| 372 | /// Error count | ||
| 373 | u32 error_count{}; | ||
| 374 | }; | ||
| 375 | |||
| 376 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp new file mode 100644 index 000000000..06a37e1a6 --- /dev/null +++ b/src/audio_core/renderer/behavior/info_updater.cpp | |||
| @@ -0,0 +1,539 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/common/feature_support.h" | ||
| 5 | #include "audio_core/renderer/behavior/behavior_info.h" | ||
| 6 | #include "audio_core/renderer/behavior/info_updater.h" | ||
| 7 | #include "audio_core/renderer/effect/effect_context.h" | ||
| 8 | #include "audio_core/renderer/effect/effect_reset.h" | ||
| 9 | #include "audio_core/renderer/memory/memory_pool_info.h" | ||
| 10 | #include "audio_core/renderer/mix/mix_context.h" | ||
| 11 | #include "audio_core/renderer/performance/performance_manager.h" | ||
| 12 | #include "audio_core/renderer/sink/circular_buffer_sink_info.h" | ||
| 13 | #include "audio_core/renderer/sink/device_sink_info.h" | ||
| 14 | #include "audio_core/renderer/sink/sink_context.h" | ||
| 15 | #include "audio_core/renderer/splitter/splitter_context.h" | ||
| 16 | #include "audio_core/renderer/voice/voice_context.h" | ||
| 17 | |||
| 18 | namespace AudioCore::AudioRenderer { | ||
| 19 | |||
| 20 | InfoUpdater::InfoUpdater(std::span<const u8> input_, std::span<u8> output_, | ||
| 21 | const u32 process_handle_, BehaviorInfo& behaviour_) | ||
| 22 | : input{input_.data() + sizeof(UpdateDataHeader)}, | ||
| 23 | input_origin{input_}, output{output_.data() + sizeof(UpdateDataHeader)}, | ||
| 24 | output_origin{output_}, in_header{reinterpret_cast<const UpdateDataHeader*>( | ||
| 25 | input_origin.data())}, | ||
| 26 | out_header{reinterpret_cast<UpdateDataHeader*>(output_origin.data())}, | ||
| 27 | expected_input_size{input_.size()}, expected_output_size{output_.size()}, | ||
| 28 | process_handle{process_handle_}, behaviour{behaviour_} { | ||
| 29 | std::construct_at<UpdateDataHeader>(out_header, behaviour.GetProcessRevision()); | ||
| 30 | } | ||
| 31 | |||
| 32 | Result InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) { | ||
| 33 | const auto voice_count{voice_context.GetCount()}; | ||
| 34 | std::span<const VoiceChannelResource::InParameter> in_params{ | ||
| 35 | reinterpret_cast<const VoiceChannelResource::InParameter*>(input), voice_count}; | ||
| 36 | |||
| 37 | for (u32 i = 0; i < voice_count; i++) { | ||
| 38 | auto& resource{voice_context.GetChannelResource(i)}; | ||
| 39 | resource.in_use = in_params[i].in_use; | ||
| 40 | if (in_params[i].in_use) { | ||
| 41 | resource.mix_volumes = in_params[i].mix_volumes; | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | const auto consumed_input_size{voice_count * | ||
| 46 | static_cast<u32>(sizeof(VoiceChannelResource::InParameter))}; | ||
| 47 | if (consumed_input_size != in_header->voice_resources_size) { | ||
| 48 | LOG_ERROR(Service_Audio, | ||
| 49 | "Consumed an incorrect voice resource size, header size={}, consumed={}", | ||
| 50 | in_header->voice_resources_size, consumed_input_size); | ||
| 51 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 52 | } | ||
| 53 | |||
| 54 | input += consumed_input_size; | ||
| 55 | return ResultSuccess; | ||
| 56 | } | ||
| 57 | |||
| 58 | Result InfoUpdater::UpdateVoices(VoiceContext& voice_context, | ||
| 59 | std::span<MemoryPoolInfo> memory_pools, | ||
| 60 | const u32 memory_pool_count) { | ||
| 61 | const PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, | ||
| 62 | behaviour.IsMemoryForceMappingEnabled()); | ||
| 63 | const auto voice_count{voice_context.GetCount()}; | ||
| 64 | std::span<const VoiceInfo::InParameter> in_params{ | ||
| 65 | reinterpret_cast<const VoiceInfo::InParameter*>(input), voice_count}; | ||
| 66 | std::span<VoiceInfo::OutStatus> out_params{reinterpret_cast<VoiceInfo::OutStatus*>(output), | ||
| 67 | voice_count}; | ||
| 68 | |||
| 69 | for (u32 i = 0; i < voice_count; i++) { | ||
| 70 | auto& voice_info{voice_context.GetInfo(i)}; | ||
| 71 | voice_info.in_use = false; | ||
| 72 | } | ||
| 73 | |||
| 74 | u32 new_voice_count{0}; | ||
| 75 | |||
| 76 | for (u32 i = 0; i < voice_count; i++) { | ||
| 77 | const auto& in_param{in_params[i]}; | ||
| 78 | std::array<VoiceState*, MaxChannels> voice_states{}; | ||
| 79 | |||
| 80 | if (!in_param.in_use) { | ||
| 81 | continue; | ||
| 82 | } | ||
| 83 | |||
| 84 | auto& voice_info{voice_context.GetInfo(in_param.id)}; | ||
| 85 | |||
| 86 | for (u32 channel = 0; channel < in_param.channel_count; channel++) { | ||
| 87 | voice_states[channel] = &voice_context.GetState(in_param.channel_resource_ids[channel]); | ||
| 88 | } | ||
| 89 | |||
| 90 | if (in_param.is_new) { | ||
| 91 | voice_info.Initialize(); | ||
| 92 | |||
| 93 | for (u32 channel = 0; channel < in_param.channel_count; channel++) { | ||
| 94 | std::memset(voice_states[channel], 0, sizeof(VoiceState)); | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | BehaviorInfo::ErrorInfo update_error{}; | ||
| 99 | voice_info.UpdateParameters(update_error, in_param, pool_mapper, behaviour); | ||
| 100 | |||
| 101 | if (!update_error.error_code.IsSuccess()) { | ||
| 102 | behaviour.AppendError(update_error); | ||
| 103 | } | ||
| 104 | |||
| 105 | std::array<std::array<BehaviorInfo::ErrorInfo, 2>, MaxWaveBuffers> wavebuffer_errors{}; | ||
| 106 | voice_info.UpdateWaveBuffers(wavebuffer_errors, MaxWaveBuffers * 2, in_param, voice_states, | ||
| 107 | pool_mapper, behaviour); | ||
| 108 | |||
| 109 | for (auto& wavebuffer_error : wavebuffer_errors) { | ||
| 110 | for (auto& error : wavebuffer_error) { | ||
| 111 | if (error.error_code.IsError()) { | ||
| 112 | behaviour.AppendError(error); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | voice_info.WriteOutStatus(out_params[i], in_param, voice_states); | ||
| 118 | new_voice_count += in_param.channel_count; | ||
| 119 | } | ||
| 120 | |||
| 121 | auto consumed_input_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::InParameter))}; | ||
| 122 | auto consumed_output_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::OutStatus))}; | ||
| 123 | if (consumed_input_size != in_header->voices_size) { | ||
| 124 | LOG_ERROR(Service_Audio, "Consumed an incorrect voices size, header size={}, consumed={}", | ||
| 125 | in_header->voices_size, consumed_input_size); | ||
| 126 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 127 | } | ||
| 128 | |||
| 129 | out_header->voices_size = consumed_output_size; | ||
| 130 | out_header->size += consumed_output_size; | ||
| 131 | input += consumed_input_size; | ||
| 132 | output += consumed_output_size; | ||
| 133 | |||
| 134 | voice_context.SetActiveCount(new_voice_count); | ||
| 135 | |||
| 136 | return ResultSuccess; | ||
| 137 | } | ||
| 138 | |||
| 139 | Result InfoUpdater::UpdateEffects(EffectContext& effect_context, const bool renderer_active, | ||
| 140 | std::span<MemoryPoolInfo> memory_pools, | ||
| 141 | const u32 memory_pool_count) { | ||
| 142 | if (behaviour.IsEffectInfoVersion2Supported()) { | ||
| 143 | return UpdateEffectsVersion2(effect_context, renderer_active, memory_pools, | ||
| 144 | memory_pool_count); | ||
| 145 | } else { | ||
| 146 | return UpdateEffectsVersion1(effect_context, renderer_active, memory_pools, | ||
| 147 | memory_pool_count); | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | Result InfoUpdater::UpdateEffectsVersion1(EffectContext& effect_context, const bool renderer_active, | ||
| 152 | std::span<MemoryPoolInfo> memory_pools, | ||
| 153 | const u32 memory_pool_count) { | ||
| 154 | PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, | ||
| 155 | behaviour.IsMemoryForceMappingEnabled()); | ||
| 156 | |||
| 157 | const auto effect_count{effect_context.GetCount()}; | ||
| 158 | |||
| 159 | std::span<const EffectInfoBase::InParameterVersion1> in_params{ | ||
| 160 | reinterpret_cast<const EffectInfoBase::InParameterVersion1*>(input), effect_count}; | ||
| 161 | std::span<EffectInfoBase::OutStatusVersion1> out_params{ | ||
| 162 | reinterpret_cast<EffectInfoBase::OutStatusVersion1*>(output), effect_count}; | ||
| 163 | |||
| 164 | for (u32 i = 0; i < effect_count; i++) { | ||
| 165 | auto effect_info{&effect_context.GetInfo(i)}; | ||
| 166 | if (effect_info->GetType() != in_params[i].type) { | ||
| 167 | effect_info->ForceUnmapBuffers(pool_mapper); | ||
| 168 | ResetEffect(effect_info, in_params[i].type); | ||
| 169 | } | ||
| 170 | |||
| 171 | BehaviorInfo::ErrorInfo error_info{}; | ||
| 172 | effect_info->Update(error_info, in_params[i], pool_mapper); | ||
| 173 | if (error_info.error_code.IsError()) { | ||
| 174 | behaviour.AppendError(error_info); | ||
| 175 | } | ||
| 176 | |||
| 177 | effect_info->StoreStatus(out_params[i], renderer_active); | ||
| 178 | } | ||
| 179 | |||
| 180 | auto consumed_input_size{effect_count * | ||
| 181 | static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion1))}; | ||
| 182 | auto consumed_output_size{effect_count * | ||
| 183 | static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion1))}; | ||
| 184 | if (consumed_input_size != in_header->effects_size) { | ||
| 185 | LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}", | ||
| 186 | in_header->effects_size, consumed_input_size); | ||
| 187 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 188 | } | ||
| 189 | |||
| 190 | out_header->effects_size = consumed_output_size; | ||
| 191 | out_header->size += consumed_output_size; | ||
| 192 | input += consumed_input_size; | ||
| 193 | output += consumed_output_size; | ||
| 194 | |||
| 195 | return ResultSuccess; | ||
| 196 | } | ||
| 197 | |||
| 198 | Result InfoUpdater::UpdateEffectsVersion2(EffectContext& effect_context, const bool renderer_active, | ||
| 199 | std::span<MemoryPoolInfo> memory_pools, | ||
| 200 | const u32 memory_pool_count) { | ||
| 201 | PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, | ||
| 202 | behaviour.IsMemoryForceMappingEnabled()); | ||
| 203 | |||
| 204 | const auto effect_count{effect_context.GetCount()}; | ||
| 205 | |||
| 206 | std::span<const EffectInfoBase::InParameterVersion2> in_params{ | ||
| 207 | reinterpret_cast<const EffectInfoBase::InParameterVersion2*>(input), effect_count}; | ||
| 208 | std::span<EffectInfoBase::OutStatusVersion2> out_params{ | ||
| 209 | reinterpret_cast<EffectInfoBase::OutStatusVersion2*>(output), effect_count}; | ||
| 210 | |||
| 211 | for (u32 i = 0; i < effect_count; i++) { | ||
| 212 | auto effect_info{&effect_context.GetInfo(i)}; | ||
| 213 | if (effect_info->GetType() != in_params[i].type) { | ||
| 214 | effect_info->ForceUnmapBuffers(pool_mapper); | ||
| 215 | ResetEffect(effect_info, in_params[i].type); | ||
| 216 | } | ||
| 217 | |||
| 218 | BehaviorInfo::ErrorInfo error_info{}; | ||
| 219 | effect_info->Update(error_info, in_params[i], pool_mapper); | ||
| 220 | |||
| 221 | if (error_info.error_code.IsError()) { | ||
| 222 | behaviour.AppendError(error_info); | ||
| 223 | } | ||
| 224 | |||
| 225 | effect_info->StoreStatus(out_params[i], renderer_active); | ||
| 226 | |||
| 227 | if (in_params[i].is_new) { | ||
| 228 | effect_info->InitializeResultState(effect_context.GetDspSharedResultState(i)); | ||
| 229 | effect_info->InitializeResultState(effect_context.GetResultState(i)); | ||
| 230 | } | ||
| 231 | effect_info->UpdateResultState(out_params[i].result_state, | ||
| 232 | effect_context.GetResultState(i)); | ||
| 233 | } | ||
| 234 | |||
| 235 | auto consumed_input_size{effect_count * | ||
| 236 | static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion2))}; | ||
| 237 | auto consumed_output_size{effect_count * | ||
| 238 | static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion2))}; | ||
| 239 | if (consumed_input_size != in_header->effects_size) { | ||
| 240 | LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}", | ||
| 241 | in_header->effects_size, consumed_input_size); | ||
| 242 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 243 | } | ||
| 244 | |||
| 245 | out_header->effects_size = consumed_output_size; | ||
| 246 | out_header->size += consumed_output_size; | ||
| 247 | input += consumed_input_size; | ||
| 248 | output += consumed_output_size; | ||
| 249 | |||
| 250 | return ResultSuccess; | ||
| 251 | } | ||
| 252 | |||
| 253 | Result InfoUpdater::UpdateMixes(MixContext& mix_context, const u32 mix_buffer_count, | ||
| 254 | EffectContext& effect_context, SplitterContext& splitter_context) { | ||
| 255 | s32 mix_count{0}; | ||
| 256 | u32 consumed_input_size{0}; | ||
| 257 | |||
| 258 | if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) { | ||
| 259 | auto in_dirty_params{reinterpret_cast<const MixInfo::InDirtyParameter*>(input)}; | ||
| 260 | mix_count = in_dirty_params->count; | ||
| 261 | input += sizeof(MixInfo::InDirtyParameter); | ||
| 262 | consumed_input_size = static_cast<u32>(sizeof(MixInfo::InDirtyParameter) + | ||
| 263 | mix_count * sizeof(MixInfo::InParameter)); | ||
| 264 | } else { | ||
| 265 | mix_count = mix_context.GetCount(); | ||
| 266 | consumed_input_size = static_cast<u32>(mix_count * sizeof(MixInfo::InParameter)); | ||
| 267 | } | ||
| 268 | |||
| 269 | if (mix_buffer_count == 0) { | ||
| 270 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 271 | } | ||
| 272 | |||
| 273 | std::span<const MixInfo::InParameter> in_params{ | ||
| 274 | reinterpret_cast<const MixInfo::InParameter*>(input), static_cast<size_t>(mix_count)}; | ||
| 275 | |||
| 276 | u32 total_buffer_count{0}; | ||
| 277 | for (s32 i = 0; i < mix_count; i++) { | ||
| 278 | const auto& params{in_params[i]}; | ||
| 279 | |||
| 280 | if (params.in_use) { | ||
| 281 | total_buffer_count += params.buffer_count; | ||
| 282 | if (params.dest_mix_id > static_cast<s32>(mix_context.GetCount()) && | ||
| 283 | params.dest_mix_id != UnusedMixId && params.mix_id != FinalMixId) { | ||
| 284 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 285 | } | ||
| 286 | } | ||
| 287 | } | ||
| 288 | |||
| 289 | if (total_buffer_count > mix_buffer_count) { | ||
| 290 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 291 | } | ||
| 292 | |||
| 293 | bool mix_dirty{false}; | ||
| 294 | for (s32 i = 0; i < mix_count; i++) { | ||
| 295 | const auto& params{in_params[i]}; | ||
| 296 | |||
| 297 | s32 mix_id{i}; | ||
| 298 | if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) { | ||
| 299 | mix_id = params.mix_id; | ||
| 300 | } | ||
| 301 | |||
| 302 | auto mix_info{mix_context.GetInfo(mix_id)}; | ||
| 303 | if (mix_info->in_use != params.in_use) { | ||
| 304 | mix_info->in_use = params.in_use; | ||
| 305 | if (!params.in_use) { | ||
| 306 | mix_info->ClearEffectProcessingOrder(); | ||
| 307 | } | ||
| 308 | mix_dirty = true; | ||
| 309 | } | ||
| 310 | |||
| 311 | if (params.in_use) { | ||
| 312 | mix_dirty |= mix_info->Update(mix_context.GetEdgeMatrix(), params, effect_context, | ||
| 313 | splitter_context, behaviour); | ||
| 314 | } | ||
| 315 | } | ||
| 316 | |||
| 317 | if (mix_dirty) { | ||
| 318 | if (behaviour.IsSplitterSupported() && splitter_context.UsingSplitter()) { | ||
| 319 | if (!mix_context.TSortInfo(splitter_context)) { | ||
| 320 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 321 | } | ||
| 322 | } else { | ||
| 323 | mix_context.SortInfo(); | ||
| 324 | } | ||
| 325 | } | ||
| 326 | |||
| 327 | if (consumed_input_size != in_header->mix_size) { | ||
| 328 | LOG_ERROR(Service_Audio, "Consumed an incorrect mixes size, header size={}, consumed={}", | ||
| 329 | in_header->mix_size, consumed_input_size); | ||
| 330 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 331 | } | ||
| 332 | |||
| 333 | input += mix_count * sizeof(MixInfo::InParameter); | ||
| 334 | |||
| 335 | return ResultSuccess; | ||
| 336 | } | ||
| 337 | |||
| 338 | Result InfoUpdater::UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools, | ||
| 339 | const u32 memory_pool_count) { | ||
| 340 | PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, | ||
| 341 | behaviour.IsMemoryForceMappingEnabled()); | ||
| 342 | |||
| 343 | std::span<const SinkInfoBase::InParameter> in_params{ | ||
| 344 | reinterpret_cast<const SinkInfoBase::InParameter*>(input), memory_pool_count}; | ||
| 345 | std::span<SinkInfoBase::OutStatus> out_params{ | ||
| 346 | reinterpret_cast<SinkInfoBase::OutStatus*>(output), memory_pool_count}; | ||
| 347 | |||
| 348 | const auto sink_count{sink_context.GetCount()}; | ||
| 349 | |||
| 350 | for (u32 i = 0; i < sink_count; i++) { | ||
| 351 | const auto& params{in_params[i]}; | ||
| 352 | auto sink_info{sink_context.GetInfo(i)}; | ||
| 353 | |||
| 354 | if (sink_info->GetType() != params.type) { | ||
| 355 | sink_info->CleanUp(); | ||
| 356 | switch (params.type) { | ||
| 357 | case SinkInfoBase::Type::Invalid: | ||
| 358 | std::construct_at<SinkInfoBase>(reinterpret_cast<SinkInfoBase*>(sink_info)); | ||
| 359 | break; | ||
| 360 | case SinkInfoBase::Type::DeviceSink: | ||
| 361 | std::construct_at<DeviceSinkInfo>(reinterpret_cast<DeviceSinkInfo*>(sink_info)); | ||
| 362 | break; | ||
| 363 | case SinkInfoBase::Type::CircularBufferSink: | ||
| 364 | std::construct_at<CircularBufferSinkInfo>( | ||
| 365 | reinterpret_cast<CircularBufferSinkInfo*>(sink_info)); | ||
| 366 | break; | ||
| 367 | default: | ||
| 368 | LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(params.type)); | ||
| 369 | break; | ||
| 370 | } | ||
| 371 | } | ||
| 372 | |||
| 373 | BehaviorInfo::ErrorInfo error_info{}; | ||
| 374 | sink_info->Update(error_info, out_params[i], params, pool_mapper); | ||
| 375 | |||
| 376 | if (error_info.error_code.IsError()) { | ||
| 377 | behaviour.AppendError(error_info); | ||
| 378 | } | ||
| 379 | } | ||
| 380 | |||
| 381 | const auto consumed_input_size{sink_count * | ||
| 382 | static_cast<u32>(sizeof(SinkInfoBase::InParameter))}; | ||
| 383 | const auto consumed_output_size{sink_count * static_cast<u32>(sizeof(SinkInfoBase::OutStatus))}; | ||
| 384 | if (consumed_input_size != in_header->sinks_size) { | ||
| 385 | LOG_ERROR(Service_Audio, "Consumed an incorrect sinks size, header size={}, consumed={}", | ||
| 386 | in_header->sinks_size, consumed_input_size); | ||
| 387 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 388 | } | ||
| 389 | |||
| 390 | input += consumed_input_size; | ||
| 391 | output += consumed_output_size; | ||
| 392 | out_header->sinks_size = consumed_output_size; | ||
| 393 | out_header->size += consumed_output_size; | ||
| 394 | |||
| 395 | return ResultSuccess; | ||
| 396 | } | ||
| 397 | |||
| 398 | Result InfoUpdater::UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools, | ||
| 399 | const u32 memory_pool_count) { | ||
| 400 | PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, | ||
| 401 | behaviour.IsMemoryForceMappingEnabled()); | ||
| 402 | std::span<const MemoryPoolInfo::InParameter> in_params{ | ||
| 403 | reinterpret_cast<const MemoryPoolInfo::InParameter*>(input), memory_pool_count}; | ||
| 404 | std::span<MemoryPoolInfo::OutStatus> out_params{ | ||
| 405 | reinterpret_cast<MemoryPoolInfo::OutStatus*>(output), memory_pool_count}; | ||
| 406 | |||
| 407 | for (size_t i = 0; i < memory_pool_count; i++) { | ||
| 408 | auto state{pool_mapper.Update(memory_pools[i], in_params[i], out_params[i])}; | ||
| 409 | if (state != MemoryPoolInfo::ResultState::Success && | ||
| 410 | state != MemoryPoolInfo::ResultState::BadParam && | ||
| 411 | state != MemoryPoolInfo::ResultState::MapFailed && | ||
| 412 | state != MemoryPoolInfo::ResultState::InUse) { | ||
| 413 | LOG_WARNING(Service_Audio, "Invalid ResultState from updating memory pools"); | ||
| 414 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 415 | } | ||
| 416 | } | ||
| 417 | |||
| 418 | const auto consumed_input_size{memory_pool_count * | ||
| 419 | static_cast<u32>(sizeof(MemoryPoolInfo::InParameter))}; | ||
| 420 | const auto consumed_output_size{memory_pool_count * | ||
| 421 | static_cast<u32>(sizeof(MemoryPoolInfo::OutStatus))}; | ||
| 422 | if (consumed_input_size != in_header->memory_pool_size) { | ||
| 423 | LOG_ERROR(Service_Audio, | ||
| 424 | "Consumed an incorrect memory pool size, header size={}, consumed={}", | ||
| 425 | in_header->memory_pool_size, consumed_input_size); | ||
| 426 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 427 | } | ||
| 428 | |||
| 429 | input += consumed_input_size; | ||
| 430 | output += consumed_output_size; | ||
| 431 | out_header->memory_pool_size = consumed_output_size; | ||
| 432 | out_header->size += consumed_output_size; | ||
| 433 | return ResultSuccess; | ||
| 434 | } | ||
| 435 | |||
| 436 | Result InfoUpdater::UpdatePerformanceBuffer(std::span<u8> performance_output, | ||
| 437 | const u64 performance_output_size, | ||
| 438 | PerformanceManager* performance_manager) { | ||
| 439 | auto in_params{reinterpret_cast<const PerformanceManager::InParameter*>(input)}; | ||
| 440 | auto out_params{reinterpret_cast<PerformanceManager::OutStatus*>(output)}; | ||
| 441 | |||
| 442 | if (performance_manager != nullptr) { | ||
| 443 | out_params->history_size = | ||
| 444 | performance_manager->CopyHistories(performance_output.data(), performance_output_size); | ||
| 445 | performance_manager->SetDetailTarget(in_params->target_node_id); | ||
| 446 | } else { | ||
| 447 | out_params->history_size = 0; | ||
| 448 | } | ||
| 449 | |||
| 450 | const auto consumed_input_size{static_cast<u32>(sizeof(PerformanceManager::InParameter))}; | ||
| 451 | const auto consumed_output_size{static_cast<u32>(sizeof(PerformanceManager::OutStatus))}; | ||
| 452 | if (consumed_input_size != in_header->performance_buffer_size) { | ||
| 453 | LOG_ERROR(Service_Audio, | ||
| 454 | "Consumed an incorrect performance size, header size={}, consumed={}", | ||
| 455 | in_header->performance_buffer_size, consumed_input_size); | ||
| 456 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 457 | } | ||
| 458 | |||
| 459 | input += consumed_input_size; | ||
| 460 | output += consumed_output_size; | ||
| 461 | out_header->performance_buffer_size = consumed_output_size; | ||
| 462 | out_header->size += consumed_output_size; | ||
| 463 | return ResultSuccess; | ||
| 464 | } | ||
| 465 | |||
| 466 | Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) { | ||
| 467 | const auto in_params{reinterpret_cast<const BehaviorInfo::InParameter*>(input)}; | ||
| 468 | |||
| 469 | if (!CheckValidRevision(in_params->revision)) { | ||
| 470 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 471 | } | ||
| 472 | |||
| 473 | if (in_params->revision != behaviour_.GetUserRevision()) { | ||
| 474 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 475 | } | ||
| 476 | |||
| 477 | behaviour_.ClearError(); | ||
| 478 | behaviour_.UpdateFlags(in_params->flags); | ||
| 479 | |||
| 480 | if (in_header->behaviour_size != sizeof(BehaviorInfo::InParameter)) { | ||
| 481 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 482 | } | ||
| 483 | |||
| 484 | input += sizeof(BehaviorInfo::InParameter); | ||
| 485 | return ResultSuccess; | ||
| 486 | } | ||
| 487 | |||
| 488 | Result InfoUpdater::UpdateErrorInfo(BehaviorInfo& behaviour_) { | ||
| 489 | auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)}; | ||
| 490 | behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count); | ||
| 491 | |||
| 492 | const auto consumed_output_size{static_cast<u32>(sizeof(BehaviorInfo::OutStatus))}; | ||
| 493 | |||
| 494 | output += consumed_output_size; | ||
| 495 | out_header->behaviour_size = consumed_output_size; | ||
| 496 | out_header->size += consumed_output_size; | ||
| 497 | return ResultSuccess; | ||
| 498 | } | ||
| 499 | |||
| 500 | Result InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) { | ||
| 501 | u32 consumed_size{0}; | ||
| 502 | if (!splitter_context.Update(input, consumed_size)) { | ||
| 503 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 504 | } | ||
| 505 | |||
| 506 | input += consumed_size; | ||
| 507 | |||
| 508 | return ResultSuccess; | ||
| 509 | } | ||
| 510 | |||
| 511 | Result InfoUpdater::UpdateRendererInfo(const u64 elapsed_frames) { | ||
| 512 | struct RenderInfo { | ||
| 513 | /* 0x00 */ u64 frames_elapsed; | ||
| 514 | /* 0x08 */ char unk08[0x8]; | ||
| 515 | }; | ||
| 516 | static_assert(sizeof(RenderInfo) == 0x10, "RenderInfo has the wrong size!"); | ||
| 517 | |||
| 518 | auto out_params{reinterpret_cast<RenderInfo*>(output)}; | ||
| 519 | out_params->frames_elapsed = elapsed_frames; | ||
| 520 | |||
| 521 | const auto consumed_output_size{static_cast<u32>(sizeof(RenderInfo))}; | ||
| 522 | |||
| 523 | output += consumed_output_size; | ||
| 524 | out_header->render_info_size = consumed_output_size; | ||
| 525 | out_header->size += consumed_output_size; | ||
| 526 | |||
| 527 | return ResultSuccess; | ||
| 528 | } | ||
| 529 | |||
| 530 | Result InfoUpdater::CheckConsumedSize() { | ||
| 531 | if (CpuAddr(input) - CpuAddr(input_origin.data()) != expected_input_size) { | ||
| 532 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 533 | } else if (CpuAddr(output) - CpuAddr(output_origin.data()) != expected_output_size) { | ||
| 534 | return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 535 | } | ||
| 536 | return ResultSuccess; | ||
| 537 | } | ||
| 538 | |||
| 539 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/behavior/info_updater.h b/src/audio_core/renderer/behavior/info_updater.h new file mode 100644 index 000000000..f0b445d9c --- /dev/null +++ b/src/audio_core/renderer/behavior/info_updater.h | |||
| @@ -0,0 +1,205 @@ | |||
| 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 "common/common_types.h" | ||
| 9 | #include "core/hle/service/audio/errors.h" | ||
| 10 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | class BehaviorInfo; | ||
| 13 | class VoiceContext; | ||
| 14 | class MixContext; | ||
| 15 | class SinkContext; | ||
| 16 | class SplitterContext; | ||
| 17 | class EffectContext; | ||
| 18 | class MemoryPoolInfo; | ||
| 19 | class PerformanceManager; | ||
| 20 | |||
| 21 | class InfoUpdater { | ||
| 22 | struct UpdateDataHeader { | ||
| 23 | explicit UpdateDataHeader(u32 revision_) : revision{revision_} {} | ||
| 24 | |||
| 25 | /* 0x00 */ u32 revision; | ||
| 26 | /* 0x04 */ u32 behaviour_size{}; | ||
| 27 | /* 0x08 */ u32 memory_pool_size{}; | ||
| 28 | /* 0x0C */ u32 voices_size{}; | ||
| 29 | /* 0x10 */ u32 voice_resources_size{}; | ||
| 30 | /* 0x14 */ u32 effects_size{}; | ||
| 31 | /* 0x18 */ u32 mix_size{}; | ||
| 32 | /* 0x1C */ u32 sinks_size{}; | ||
| 33 | /* 0x20 */ u32 performance_buffer_size{}; | ||
| 34 | /* 0x24 */ char unk24[4]; | ||
| 35 | /* 0x28 */ u32 render_info_size{}; | ||
| 36 | /* 0x2C */ char unk2C[0x10]; | ||
| 37 | /* 0x3C */ u32 size{sizeof(UpdateDataHeader)}; | ||
| 38 | }; | ||
| 39 | static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has the wrong size!"); | ||
| 40 | |||
| 41 | public: | ||
| 42 | explicit InfoUpdater(std::span<const u8> input, std::span<u8> output, u32 process_handle, | ||
| 43 | BehaviorInfo& behaviour); | ||
| 44 | |||
| 45 | /** | ||
| 46 | * Update the voice channel resources. | ||
| 47 | * | ||
| 48 | * @param voice_context - Voice context to update. | ||
| 49 | * @return Result code. | ||
| 50 | */ | ||
| 51 | Result UpdateVoiceChannelResources(VoiceContext& voice_context); | ||
| 52 | |||
| 53 | /** | ||
| 54 | * Update voices. | ||
| 55 | * | ||
| 56 | * @param voice_context - Voice context to update. | ||
| 57 | * @param memory_pools - Memory pools to use for these voices. | ||
| 58 | * @param memory_pool_count - Number of memory pools. | ||
| 59 | * @return Result code. | ||
| 60 | */ | ||
| 61 | Result UpdateVoices(VoiceContext& voice_context, std::span<MemoryPoolInfo> memory_pools, | ||
| 62 | u32 memory_pool_count); | ||
| 63 | |||
| 64 | /** | ||
| 65 | * Update effects. | ||
| 66 | * | ||
| 67 | * @param effect_context - Effect context to update. | ||
| 68 | * @param renderer_active - Whether the AudioRenderer is active. | ||
| 69 | * @param memory_pools - Memory pools to use for these voices. | ||
| 70 | * @param memory_pool_count - Number of memory pools. | ||
| 71 | * @return Result code. | ||
| 72 | */ | ||
| 73 | Result UpdateEffects(EffectContext& effect_context, bool renderer_active, | ||
| 74 | std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count); | ||
| 75 | |||
| 76 | /** | ||
| 77 | * Update mixes. | ||
| 78 | * | ||
| 79 | * @param mix_context - Mix context to update. | ||
| 80 | * @param mix_buffer_count - Number of mix buffers. | ||
| 81 | * @param effect_context - Effect context to update effort order. | ||
| 82 | * @param splitter_context - Splitter context for the mixes. | ||
| 83 | * @return Result code. | ||
| 84 | */ | ||
| 85 | Result UpdateMixes(MixContext& mix_context, u32 mix_buffer_count, EffectContext& effect_context, | ||
| 86 | SplitterContext& splitter_context); | ||
| 87 | |||
| 88 | /** | ||
| 89 | * Update sinks. | ||
| 90 | * | ||
| 91 | * @param sink_context - Sink context to update. | ||
| 92 | * @param memory_pools - Memory pools to use for these voices. | ||
| 93 | * @param memory_pool_count - Number of memory pools. | ||
| 94 | * @return Result code. | ||
| 95 | */ | ||
| 96 | Result UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools, | ||
| 97 | u32 memory_pool_count); | ||
| 98 | |||
| 99 | /** | ||
| 100 | * Update memory pools. | ||
| 101 | * | ||
| 102 | * @param memory_pools - Memory pools to use for these voices. | ||
| 103 | * @param memory_pool_count - Number of memory pools. | ||
| 104 | * @return Result code. | ||
| 105 | */ | ||
| 106 | Result UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count); | ||
| 107 | |||
| 108 | /** | ||
| 109 | * Update the performance buffer. | ||
| 110 | * | ||
| 111 | * @param output - Output buffer for performance metrics. | ||
| 112 | * @param output_size - Output buffer size. | ||
| 113 | * @param performance_manager - Performance manager.. | ||
| 114 | * @return Result code. | ||
| 115 | */ | ||
| 116 | Result UpdatePerformanceBuffer(std::span<u8> output, u64 output_size, | ||
| 117 | PerformanceManager* performance_manager); | ||
| 118 | |||
| 119 | /** | ||
| 120 | * Update behaviour. | ||
| 121 | * | ||
| 122 | * @param behaviour - Behaviour to update. | ||
| 123 | * @return Result code. | ||
| 124 | */ | ||
| 125 | Result UpdateBehaviorInfo(BehaviorInfo& behaviour); | ||
| 126 | |||
| 127 | /** | ||
| 128 | * Update errors. | ||
| 129 | * | ||
| 130 | * @param behaviour - Behaviour to update. | ||
| 131 | * @return Result code. | ||
| 132 | */ | ||
| 133 | Result UpdateErrorInfo(BehaviorInfo& behaviour); | ||
| 134 | |||
| 135 | /** | ||
| 136 | * Update splitter. | ||
| 137 | * | ||
| 138 | * @param splitter_context - Splitter context to update. | ||
| 139 | * @return Result code. | ||
| 140 | */ | ||
| 141 | Result UpdateSplitterInfo(SplitterContext& splitter_context); | ||
| 142 | |||
| 143 | /** | ||
| 144 | * Update renderer info. | ||
| 145 | * | ||
| 146 | * @param elapsed_frames - Number of elapsed frames. | ||
| 147 | * @return Result code. | ||
| 148 | */ | ||
| 149 | Result UpdateRendererInfo(u64 elapsed_frames); | ||
| 150 | |||
| 151 | /** | ||
| 152 | * Check that the input.output sizes match their expected values. | ||
| 153 | * | ||
| 154 | * @return Result code. | ||
| 155 | */ | ||
| 156 | Result CheckConsumedSize(); | ||
| 157 | |||
| 158 | private: | ||
| 159 | /** | ||
| 160 | * Update effects version 1. | ||
| 161 | * | ||
| 162 | * @param effect_context - Effect context to update. | ||
| 163 | * @param renderer_active - Is the AudioRenderer active? | ||
| 164 | * @param memory_pools - Memory pools to use for these voices. | ||
| 165 | * @param memory_pool_count - Number of memory pools. | ||
| 166 | * @return Result code. | ||
| 167 | */ | ||
| 168 | Result UpdateEffectsVersion1(EffectContext& effect_context, bool renderer_active, | ||
| 169 | std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count); | ||
| 170 | |||
| 171 | /** | ||
| 172 | * Update effects version 2. | ||
| 173 | * | ||
| 174 | * @param effect_context - Effect context to update. | ||
| 175 | * @param renderer_active - Is the AudioRenderer active? | ||
| 176 | * @param memory_pools - Memory pools to use for these voices. | ||
| 177 | * @param memory_pool_count - Number of memory pools. | ||
| 178 | * @return Result code. | ||
| 179 | */ | ||
| 180 | Result UpdateEffectsVersion2(EffectContext& effect_context, bool renderer_active, | ||
| 181 | std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count); | ||
| 182 | |||
| 183 | /// Input buffer | ||
| 184 | u8 const* input; | ||
| 185 | /// Input buffer start | ||
| 186 | std::span<const u8> input_origin; | ||
| 187 | /// Output buffer start | ||
| 188 | u8* output; | ||
| 189 | /// Output buffer start | ||
| 190 | std::span<u8> output_origin; | ||
| 191 | /// Input header | ||
| 192 | const UpdateDataHeader* in_header; | ||
| 193 | /// Output header | ||
| 194 | UpdateDataHeader* out_header; | ||
| 195 | /// Expected input size, see CheckConsumedSize | ||
| 196 | u64 expected_input_size; | ||
| 197 | /// Expected output size, see CheckConsumedSize | ||
| 198 | u64 expected_output_size; | ||
| 199 | /// Unused | ||
| 200 | u32 process_handle; | ||
| 201 | /// Behaviour | ||
| 202 | BehaviorInfo& behaviour; | ||
| 203 | }; | ||
| 204 | |||
| 205 | } // namespace AudioCore::AudioRenderer | ||
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 | ||
diff --git a/src/audio_core/renderer/effect/aux_.cpp b/src/audio_core/renderer/effect/aux_.cpp new file mode 100644 index 000000000..51e780ef1 --- /dev/null +++ b/src/audio_core/renderer/effect/aux_.cpp | |||
| @@ -0,0 +1,93 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/effect/aux_.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, | ||
| 9 | const PoolMapper& pool_mapper) { | ||
| 10 | auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; | ||
| 11 | auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; | ||
| 12 | |||
| 13 | std::memcpy(params, in_specific, sizeof(ParameterVersion1)); | ||
| 14 | mix_id = in_params.mix_id; | ||
| 15 | process_order = in_params.process_order; | ||
| 16 | enabled = in_params.enabled; | ||
| 17 | if (buffer_unmapped || in_params.is_new) { | ||
| 18 | const bool send_unmapped{!pool_mapper.TryAttachBuffer( | ||
| 19 | error_info, workbuffers[0], in_specific->send_buffer_info_address, | ||
| 20 | sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))}; | ||
| 21 | const bool return_unmapped{!pool_mapper.TryAttachBuffer( | ||
| 22 | error_info, workbuffers[1], in_specific->return_buffer_info_address, | ||
| 23 | sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))}; | ||
| 24 | |||
| 25 | buffer_unmapped = send_unmapped || return_unmapped; | ||
| 26 | |||
| 27 | if (!buffer_unmapped) { | ||
| 28 | auto send{workbuffers[0].GetReference(false)}; | ||
| 29 | send_buffer_info = send + sizeof(AuxInfoDsp); | ||
| 30 | send_buffer = send + sizeof(AuxBufferInfo); | ||
| 31 | |||
| 32 | auto ret{workbuffers[1].GetReference(false)}; | ||
| 33 | return_buffer_info = ret + sizeof(AuxInfoDsp); | ||
| 34 | return_buffer = ret + sizeof(AuxBufferInfo); | ||
| 35 | } | ||
| 36 | } else { | ||
| 37 | error_info.error_code = ResultSuccess; | ||
| 38 | error_info.address = CpuAddr(0); | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, | ||
| 43 | const PoolMapper& pool_mapper) { | ||
| 44 | auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())}; | ||
| 45 | auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())}; | ||
| 46 | |||
| 47 | std::memcpy(params, in_specific, sizeof(ParameterVersion2)); | ||
| 48 | mix_id = in_params.mix_id; | ||
| 49 | process_order = in_params.process_order; | ||
| 50 | enabled = in_params.enabled; | ||
| 51 | |||
| 52 | if (buffer_unmapped || in_params.is_new) { | ||
| 53 | const bool send_unmapped{!pool_mapper.TryAttachBuffer( | ||
| 54 | error_info, workbuffers[0], params->send_buffer_info_address, | ||
| 55 | sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))}; | ||
| 56 | const bool return_unmapped{!pool_mapper.TryAttachBuffer( | ||
| 57 | error_info, workbuffers[1], params->return_buffer_info_address, | ||
| 58 | sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))}; | ||
| 59 | |||
| 60 | buffer_unmapped = send_unmapped || return_unmapped; | ||
| 61 | |||
| 62 | if (!buffer_unmapped) { | ||
| 63 | auto send{workbuffers[0].GetReference(false)}; | ||
| 64 | send_buffer_info = send + sizeof(AuxInfoDsp); | ||
| 65 | send_buffer = send + sizeof(AuxBufferInfo); | ||
| 66 | |||
| 67 | auto ret{workbuffers[1].GetReference(false)}; | ||
| 68 | return_buffer_info = ret + sizeof(AuxInfoDsp); | ||
| 69 | return_buffer = ret + sizeof(AuxBufferInfo); | ||
| 70 | } | ||
| 71 | } else { | ||
| 72 | error_info.error_code = ResultSuccess; | ||
| 73 | error_info.address = CpuAddr(0); | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | void AuxInfo::UpdateForCommandGeneration() { | ||
| 78 | if (enabled) { | ||
| 79 | usage_state = UsageState::Enabled; | ||
| 80 | } else { | ||
| 81 | usage_state = UsageState::Disabled; | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | void AuxInfo::InitializeResultState(EffectResultState& result_state) {} | ||
| 86 | |||
| 87 | void AuxInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {} | ||
| 88 | |||
| 89 | CpuAddr AuxInfo::GetWorkbuffer(s32 index) { | ||
| 90 | return workbuffers[index].GetReference(true); | ||
| 91 | } | ||
| 92 | |||
| 93 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/aux_.h b/src/audio_core/renderer/effect/aux_.h new file mode 100644 index 000000000..4d3d9e3d9 --- /dev/null +++ b/src/audio_core/renderer/effect/aux_.h | |||
| @@ -0,0 +1,123 @@ | |||
| 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 | |||
| 8 | #include "audio_core/common/common.h" | ||
| 9 | #include "audio_core/renderer/effect/effect_info_base.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | /** | ||
| 14 | * Auxiliary Buffer used for Aux commands. | ||
| 15 | * Send and return buffers are available (names from the game's perspective). | ||
| 16 | * Send is read by the host, containing a buffer of samples to be used for whatever purpose. | ||
| 17 | * Return is written by the host, writing a mix buffer back to the game. | ||
| 18 | * This allows the game to use pre-processed samples skipping the other render processing, | ||
| 19 | * and to examine or modify what the audio renderer has generated. | ||
| 20 | */ | ||
| 21 | class AuxInfo : public EffectInfoBase { | ||
| 22 | public: | ||
| 23 | struct ParameterVersion1 { | ||
| 24 | /* 0x00 */ std::array<s8, MaxMixBuffers> inputs; | ||
| 25 | /* 0x18 */ std::array<s8, MaxMixBuffers> outputs; | ||
| 26 | /* 0x30 */ u32 mix_buffer_count; | ||
| 27 | /* 0x34 */ u32 sample_rate; | ||
| 28 | /* 0x38 */ u32 count_max; | ||
| 29 | /* 0x3C */ u32 mix_buffer_count_max; | ||
| 30 | /* 0x40 */ CpuAddr send_buffer_info_address; | ||
| 31 | /* 0x48 */ CpuAddr send_buffer_address; | ||
| 32 | /* 0x50 */ CpuAddr return_buffer_info_address; | ||
| 33 | /* 0x58 */ CpuAddr return_buffer_address; | ||
| 34 | /* 0x60 */ u32 mix_buffer_sample_size; | ||
| 35 | /* 0x64 */ u32 sample_count; | ||
| 36 | /* 0x68 */ u32 mix_buffer_sample_count; | ||
| 37 | }; | ||
| 38 | static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), | ||
| 39 | "AuxInfo::ParameterVersion1 has the wrong size!"); | ||
| 40 | |||
| 41 | struct ParameterVersion2 { | ||
| 42 | /* 0x00 */ std::array<s8, MaxMixBuffers> inputs; | ||
| 43 | /* 0x18 */ std::array<s8, MaxMixBuffers> outputs; | ||
| 44 | /* 0x30 */ u32 mix_buffer_count; | ||
| 45 | /* 0x34 */ u32 sample_rate; | ||
| 46 | /* 0x38 */ u32 count_max; | ||
| 47 | /* 0x3C */ u32 mix_buffer_count_max; | ||
| 48 | /* 0x40 */ CpuAddr send_buffer_info_address; | ||
| 49 | /* 0x48 */ CpuAddr send_buffer_address; | ||
| 50 | /* 0x50 */ CpuAddr return_buffer_info_address; | ||
| 51 | /* 0x58 */ CpuAddr return_buffer_address; | ||
| 52 | /* 0x60 */ u32 mix_buffer_sample_size; | ||
| 53 | /* 0x64 */ u32 sample_count; | ||
| 54 | /* 0x68 */ u32 mix_buffer_sample_count; | ||
| 55 | }; | ||
| 56 | static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), | ||
| 57 | "AuxInfo::ParameterVersion2 has the wrong size!"); | ||
| 58 | |||
| 59 | struct AuxInfoDsp { | ||
| 60 | /* 0x00 */ u32 read_offset; | ||
| 61 | /* 0x04 */ u32 write_offset; | ||
| 62 | /* 0x08 */ u32 lost_sample_count; | ||
| 63 | /* 0x0C */ u32 total_sample_count; | ||
| 64 | /* 0x10 */ char unk10[0x30]; | ||
| 65 | }; | ||
| 66 | static_assert(sizeof(AuxInfoDsp) == 0x40, "AuxInfo::AuxInfoDsp has the wrong size!"); | ||
| 67 | |||
| 68 | struct AuxBufferInfo { | ||
| 69 | /* 0x00 */ AuxInfoDsp cpu_info; | ||
| 70 | /* 0x40 */ AuxInfoDsp dsp_info; | ||
| 71 | }; | ||
| 72 | static_assert(sizeof(AuxBufferInfo) == 0x80, "AuxInfo::AuxBufferInfo has the wrong size!"); | ||
| 73 | |||
| 74 | /** | ||
| 75 | * Update the info with new parameters, version 1. | ||
| 76 | * | ||
| 77 | * @param error_info - Used to write call result code. | ||
| 78 | * @param in_params - New parameters to update the info with. | ||
| 79 | * @param pool_mapper - Pool for mapping buffers. | ||
| 80 | */ | ||
| 81 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, | ||
| 82 | const PoolMapper& pool_mapper) override; | ||
| 83 | |||
| 84 | /** | ||
| 85 | * Update the info with new parameters, version 2. | ||
| 86 | * | ||
| 87 | * @param error_info - Used to write call result code. | ||
| 88 | * @param in_params - New parameters to update the info with. | ||
| 89 | * @param pool_mapper - Pool for mapping buffers. | ||
| 90 | */ | ||
| 91 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, | ||
| 92 | const PoolMapper& pool_mapper) override; | ||
| 93 | |||
| 94 | /** | ||
| 95 | * Update the info after command generation. Usually only changes its state. | ||
| 96 | */ | ||
| 97 | void UpdateForCommandGeneration() override; | ||
| 98 | |||
| 99 | /** | ||
| 100 | * Initialize a new result state. Version 2 only, unused. | ||
| 101 | * | ||
| 102 | * @param result_state - Result state to initialize. | ||
| 103 | */ | ||
| 104 | void InitializeResultState(EffectResultState& result_state) override; | ||
| 105 | |||
| 106 | /** | ||
| 107 | * Update the host-side state with the ADSP-side state. Version 2 only, unused. | ||
| 108 | * | ||
| 109 | * @param cpu_state - Host-side result state to update. | ||
| 110 | * @param dsp_state - AudioRenderer-side result state to update from. | ||
| 111 | */ | ||
| 112 | void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; | ||
| 113 | |||
| 114 | /** | ||
| 115 | * Get a workbuffer assigned to this effect with the given index. | ||
| 116 | * | ||
| 117 | * @param index - Workbuffer index. | ||
| 118 | * @return Address of the buffer. | ||
| 119 | */ | ||
| 120 | CpuAddr GetWorkbuffer(s32 index) override; | ||
| 121 | }; | ||
| 122 | |||
| 123 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/biquad_filter.cpp b/src/audio_core/renderer/effect/biquad_filter.cpp new file mode 100644 index 000000000..a1efb3231 --- /dev/null +++ b/src/audio_core/renderer/effect/biquad_filter.cpp | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/effect/biquad_filter.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info, | ||
| 9 | const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { | ||
| 10 | auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; | ||
| 11 | auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; | ||
| 12 | |||
| 13 | std::memcpy(params, in_specific, sizeof(ParameterVersion1)); | ||
| 14 | mix_id = in_params.mix_id; | ||
| 15 | process_order = in_params.process_order; | ||
| 16 | enabled = in_params.enabled; | ||
| 17 | |||
| 18 | error_info.error_code = ResultSuccess; | ||
| 19 | error_info.address = CpuAddr(0); | ||
| 20 | } | ||
| 21 | |||
| 22 | void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info, | ||
| 23 | const InParameterVersion2& in_params, const PoolMapper& pool_mapper) { | ||
| 24 | auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())}; | ||
| 25 | auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())}; | ||
| 26 | |||
| 27 | std::memcpy(params, in_specific, sizeof(ParameterVersion2)); | ||
| 28 | mix_id = in_params.mix_id; | ||
| 29 | process_order = in_params.process_order; | ||
| 30 | enabled = in_params.enabled; | ||
| 31 | |||
| 32 | error_info.error_code = ResultSuccess; | ||
| 33 | error_info.address = CpuAddr(0); | ||
| 34 | } | ||
| 35 | |||
| 36 | void BiquadFilterInfo::UpdateForCommandGeneration() { | ||
| 37 | if (enabled) { | ||
| 38 | usage_state = UsageState::Enabled; | ||
| 39 | } else { | ||
| 40 | usage_state = UsageState::Disabled; | ||
| 41 | } | ||
| 42 | |||
| 43 | auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; | ||
| 44 | params->state = ParameterState::Updated; | ||
| 45 | } | ||
| 46 | |||
| 47 | void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {} | ||
| 48 | |||
| 49 | void BiquadFilterInfo::UpdateResultState(EffectResultState& cpu_state, | ||
| 50 | EffectResultState& dsp_state) {} | ||
| 51 | |||
| 52 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/biquad_filter.h b/src/audio_core/renderer/effect/biquad_filter.h new file mode 100644 index 000000000..f53fd5bab --- /dev/null +++ b/src/audio_core/renderer/effect/biquad_filter.h | |||
| @@ -0,0 +1,79 @@ | |||
| 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 | |||
| 8 | #include "audio_core/common/common.h" | ||
| 9 | #include "audio_core/renderer/effect/effect_info_base.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | |||
| 14 | class BiquadFilterInfo : public EffectInfoBase { | ||
| 15 | public: | ||
| 16 | struct ParameterVersion1 { | ||
| 17 | /* 0x00 */ std::array<s8, MaxChannels> inputs; | ||
| 18 | /* 0x06 */ std::array<s8, MaxChannels> outputs; | ||
| 19 | /* 0x0C */ std::array<s16, 3> b; | ||
| 20 | /* 0x12 */ std::array<s16, 2> a; | ||
| 21 | /* 0x16 */ s8 channel_count; | ||
| 22 | /* 0x17 */ ParameterState state; | ||
| 23 | }; | ||
| 24 | static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), | ||
| 25 | "BiquadFilterInfo::ParameterVersion1 has the wrong size!"); | ||
| 26 | |||
| 27 | struct ParameterVersion2 { | ||
| 28 | /* 0x00 */ std::array<s8, MaxChannels> inputs; | ||
| 29 | /* 0x06 */ std::array<s8, MaxChannels> outputs; | ||
| 30 | /* 0x0C */ std::array<s16, 3> b; | ||
| 31 | /* 0x12 */ std::array<s16, 2> a; | ||
| 32 | /* 0x16 */ s8 channel_count; | ||
| 33 | /* 0x17 */ ParameterState state; | ||
| 34 | }; | ||
| 35 | static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), | ||
| 36 | "BiquadFilterInfo::ParameterVersion2 has the wrong size!"); | ||
| 37 | |||
| 38 | /** | ||
| 39 | * Update the info with new parameters, version 1. | ||
| 40 | * | ||
| 41 | * @param error_info - Used to write call result code. | ||
| 42 | * @param in_params - New parameters to update the info with. | ||
| 43 | * @param pool_mapper - Pool for mapping buffers. | ||
| 44 | */ | ||
| 45 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, | ||
| 46 | const PoolMapper& pool_mapper) override; | ||
| 47 | |||
| 48 | /** | ||
| 49 | * Update the info with new parameters, version 2. | ||
| 50 | * | ||
| 51 | * @param error_info - Used to write call result code. | ||
| 52 | * @param in_params - New parameters to update the info with. | ||
| 53 | * @param pool_mapper - Pool for mapping buffers. | ||
| 54 | */ | ||
| 55 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, | ||
| 56 | const PoolMapper& pool_mapper) override; | ||
| 57 | |||
| 58 | /** | ||
| 59 | * Update the info after command generation. Usually only changes its state. | ||
| 60 | */ | ||
| 61 | void UpdateForCommandGeneration() override; | ||
| 62 | |||
| 63 | /** | ||
| 64 | * Initialize a new result state. Version 2 only, unused. | ||
| 65 | * | ||
| 66 | * @param result_state - Result state to initialize. | ||
| 67 | */ | ||
| 68 | void InitializeResultState(EffectResultState& result_state) override; | ||
| 69 | |||
| 70 | /** | ||
| 71 | * Update the host-side state with the ADSP-side state. Version 2 only, unused. | ||
| 72 | * | ||
| 73 | * @param cpu_state - Host-side result state to update. | ||
| 74 | * @param dsp_state - AudioRenderer-side result state to update from. | ||
| 75 | */ | ||
| 76 | void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; | ||
| 77 | }; | ||
| 78 | |||
| 79 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/buffer_mixer.cpp b/src/audio_core/renderer/effect/buffer_mixer.cpp new file mode 100644 index 000000000..9c8877f01 --- /dev/null +++ b/src/audio_core/renderer/effect/buffer_mixer.cpp | |||
| @@ -0,0 +1,49 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/effect/buffer_mixer.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info, | ||
| 9 | const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { | ||
| 10 | auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; | ||
| 11 | auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; | ||
| 12 | |||
| 13 | std::memcpy(params, in_specific, sizeof(ParameterVersion1)); | ||
| 14 | mix_id = in_params.mix_id; | ||
| 15 | process_order = in_params.process_order; | ||
| 16 | enabled = in_params.enabled; | ||
| 17 | |||
| 18 | error_info.error_code = ResultSuccess; | ||
| 19 | error_info.address = CpuAddr(0); | ||
| 20 | } | ||
| 21 | |||
| 22 | void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info, | ||
| 23 | const InParameterVersion2& in_params, const PoolMapper& pool_mapper) { | ||
| 24 | auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())}; | ||
| 25 | auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())}; | ||
| 26 | |||
| 27 | std::memcpy(params, in_specific, sizeof(ParameterVersion2)); | ||
| 28 | mix_id = in_params.mix_id; | ||
| 29 | process_order = in_params.process_order; | ||
| 30 | enabled = in_params.enabled; | ||
| 31 | |||
| 32 | error_info.error_code = ResultSuccess; | ||
| 33 | error_info.address = CpuAddr(0); | ||
| 34 | } | ||
| 35 | |||
| 36 | void BufferMixerInfo::UpdateForCommandGeneration() { | ||
| 37 | if (enabled) { | ||
| 38 | usage_state = UsageState::Enabled; | ||
| 39 | } else { | ||
| 40 | usage_state = UsageState::Disabled; | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | void BufferMixerInfo::InitializeResultState(EffectResultState& result_state) {} | ||
| 45 | |||
| 46 | void BufferMixerInfo::UpdateResultState(EffectResultState& cpu_state, | ||
| 47 | EffectResultState& dsp_state) {} | ||
| 48 | |||
| 49 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/buffer_mixer.h b/src/audio_core/renderer/effect/buffer_mixer.h new file mode 100644 index 000000000..23eed4a8b --- /dev/null +++ b/src/audio_core/renderer/effect/buffer_mixer.h | |||
| @@ -0,0 +1,75 @@ | |||
| 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 | |||
| 8 | #include "audio_core/common/common.h" | ||
| 9 | #include "audio_core/renderer/effect/effect_info_base.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | |||
| 14 | class BufferMixerInfo : public EffectInfoBase { | ||
| 15 | public: | ||
| 16 | struct ParameterVersion1 { | ||
| 17 | /* 0x00 */ std::array<s8, MaxMixBuffers> inputs; | ||
| 18 | /* 0x18 */ std::array<s8, MaxMixBuffers> outputs; | ||
| 19 | /* 0x30 */ std::array<f32, MaxMixBuffers> volumes; | ||
| 20 | /* 0x90 */ u32 mix_count; | ||
| 21 | }; | ||
| 22 | static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), | ||
| 23 | "BufferMixerInfo::ParameterVersion1 has the wrong size!"); | ||
| 24 | |||
| 25 | struct ParameterVersion2 { | ||
| 26 | /* 0x00 */ std::array<s8, MaxMixBuffers> inputs; | ||
| 27 | /* 0x18 */ std::array<s8, MaxMixBuffers> outputs; | ||
| 28 | /* 0x30 */ std::array<f32, MaxMixBuffers> volumes; | ||
| 29 | /* 0x90 */ u32 mix_count; | ||
| 30 | }; | ||
| 31 | static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), | ||
| 32 | "BufferMixerInfo::ParameterVersion2 has the wrong size!"); | ||
| 33 | |||
| 34 | /** | ||
| 35 | * Update the info with new parameters, version 1. | ||
| 36 | * | ||
| 37 | * @param error_info - Used to write call result code. | ||
| 38 | * @param in_params - New parameters to update the info with. | ||
| 39 | * @param pool_mapper - Pool for mapping buffers. | ||
| 40 | */ | ||
| 41 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, | ||
| 42 | const PoolMapper& pool_mapper) override; | ||
| 43 | |||
| 44 | /** | ||
| 45 | * Update the info with new parameters, version 2. | ||
| 46 | * | ||
| 47 | * @param error_info - Used to write call result code. | ||
| 48 | * @param in_params - New parameters to update the info with. | ||
| 49 | * @param pool_mapper - Pool for mapping buffers. | ||
| 50 | */ | ||
| 51 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, | ||
| 52 | const PoolMapper& pool_mapper) override; | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Update the info after command generation. Usually only changes its state. | ||
| 56 | */ | ||
| 57 | void UpdateForCommandGeneration() override; | ||
| 58 | |||
| 59 | /** | ||
| 60 | * Initialize a new result state. Version 2 only, unused. | ||
| 61 | * | ||
| 62 | * @param result_state - Result state to initialize. | ||
| 63 | */ | ||
| 64 | void InitializeResultState(EffectResultState& result_state) override; | ||
| 65 | |||
| 66 | /** | ||
| 67 | * Update the host-side state with the ADSP-side state. Version 2 only, unused. | ||
| 68 | * | ||
| 69 | * @param cpu_state - Host-side result state to update. | ||
| 70 | * @param dsp_state - AudioRenderer-side result state to update from. | ||
| 71 | */ | ||
| 72 | void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; | ||
| 73 | }; | ||
| 74 | |||
| 75 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/capture.cpp b/src/audio_core/renderer/effect/capture.cpp new file mode 100644 index 000000000..3f038efdb --- /dev/null +++ b/src/audio_core/renderer/effect/capture.cpp | |||
| @@ -0,0 +1,82 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/effect/aux_.h" | ||
| 5 | #include "audio_core/renderer/effect/capture.h" | ||
| 6 | |||
| 7 | namespace AudioCore::AudioRenderer { | ||
| 8 | |||
| 9 | void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, | ||
| 10 | const PoolMapper& pool_mapper) { | ||
| 11 | auto in_specific{ | ||
| 12 | reinterpret_cast<const AuxInfo::ParameterVersion1*>(in_params.specific.data())}; | ||
| 13 | auto params{reinterpret_cast<AuxInfo::ParameterVersion1*>(parameter.data())}; | ||
| 14 | |||
| 15 | std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion1)); | ||
| 16 | mix_id = in_params.mix_id; | ||
| 17 | process_order = in_params.process_order; | ||
| 18 | enabled = in_params.enabled; | ||
| 19 | if (buffer_unmapped || in_params.is_new) { | ||
| 20 | buffer_unmapped = !pool_mapper.TryAttachBuffer( | ||
| 21 | error_info, workbuffers[0], in_specific->send_buffer_info_address, | ||
| 22 | in_specific->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo)); | ||
| 23 | |||
| 24 | if (!buffer_unmapped) { | ||
| 25 | const auto send_address{workbuffers[0].GetReference(false)}; | ||
| 26 | send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp); | ||
| 27 | send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo); | ||
| 28 | return_buffer_info = 0; | ||
| 29 | return_buffer = 0; | ||
| 30 | } | ||
| 31 | } else { | ||
| 32 | error_info.error_code = ResultSuccess; | ||
| 33 | error_info.address = CpuAddr(0); | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, | ||
| 38 | const PoolMapper& pool_mapper) { | ||
| 39 | auto in_specific{ | ||
| 40 | reinterpret_cast<const AuxInfo::ParameterVersion2*>(in_params.specific.data())}; | ||
| 41 | auto params{reinterpret_cast<AuxInfo::ParameterVersion2*>(parameter.data())}; | ||
| 42 | |||
| 43 | std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion2)); | ||
| 44 | mix_id = in_params.mix_id; | ||
| 45 | process_order = in_params.process_order; | ||
| 46 | enabled = in_params.enabled; | ||
| 47 | |||
| 48 | if (buffer_unmapped || in_params.is_new) { | ||
| 49 | buffer_unmapped = !pool_mapper.TryAttachBuffer( | ||
| 50 | error_info, workbuffers[0], params->send_buffer_info_address, | ||
| 51 | params->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo)); | ||
| 52 | |||
| 53 | if (!buffer_unmapped) { | ||
| 54 | const auto send_address{workbuffers[0].GetReference(false)}; | ||
| 55 | send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp); | ||
| 56 | send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo); | ||
| 57 | return_buffer_info = 0; | ||
| 58 | return_buffer = 0; | ||
| 59 | } | ||
| 60 | } else { | ||
| 61 | error_info.error_code = ResultSuccess; | ||
| 62 | error_info.address = CpuAddr(0); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | void CaptureInfo::UpdateForCommandGeneration() { | ||
| 67 | if (enabled) { | ||
| 68 | usage_state = UsageState::Enabled; | ||
| 69 | } else { | ||
| 70 | usage_state = UsageState::Disabled; | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | void CaptureInfo::InitializeResultState(EffectResultState& result_state) {} | ||
| 75 | |||
| 76 | void CaptureInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {} | ||
| 77 | |||
| 78 | CpuAddr CaptureInfo::GetWorkbuffer(s32 index) { | ||
| 79 | return workbuffers[index].GetReference(true); | ||
| 80 | } | ||
| 81 | |||
| 82 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/capture.h b/src/audio_core/renderer/effect/capture.h new file mode 100644 index 000000000..6fbed8e6b --- /dev/null +++ b/src/audio_core/renderer/effect/capture.h | |||
| @@ -0,0 +1,65 @@ | |||
| 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 | |||
| 8 | #include "audio_core/common/common.h" | ||
| 9 | #include "audio_core/renderer/effect/effect_info_base.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | |||
| 14 | class CaptureInfo : public EffectInfoBase { | ||
| 15 | public: | ||
| 16 | /** | ||
| 17 | * Update the info with new parameters, version 1. | ||
| 18 | * | ||
| 19 | * @param error_info - Used to write call result code. | ||
| 20 | * @param in_params - New parameters to update the info with. | ||
| 21 | * @param pool_mapper - Pool for mapping buffers. | ||
| 22 | */ | ||
| 23 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, | ||
| 24 | const PoolMapper& pool_mapper) override; | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Update the info with new parameters, version 2. | ||
| 28 | * | ||
| 29 | * @param error_info - Used to write call result code. | ||
| 30 | * @param in_params - New parameters to update the info with. | ||
| 31 | * @param pool_mapper - Pool for mapping buffers. | ||
| 32 | */ | ||
| 33 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, | ||
| 34 | const PoolMapper& pool_mapper) override; | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Update the info after command generation. Usually only changes its state. | ||
| 38 | */ | ||
| 39 | void UpdateForCommandGeneration() override; | ||
| 40 | |||
| 41 | /** | ||
| 42 | * Initialize a new result state. Version 2 only, unused. | ||
| 43 | * | ||
| 44 | * @param result_state - Result state to initialize. | ||
| 45 | */ | ||
| 46 | void InitializeResultState(EffectResultState& result_state) override; | ||
| 47 | |||
| 48 | /** | ||
| 49 | * Update the host-side state with the ADSP-side state. Version 2 only, unused. | ||
| 50 | * | ||
| 51 | * @param cpu_state - Host-side result state to update. | ||
| 52 | * @param dsp_state - AudioRenderer-side result state to update from. | ||
| 53 | */ | ||
| 54 | void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; | ||
| 55 | |||
| 56 | /** | ||
| 57 | * Get a workbuffer assigned to this effect with the given index. | ||
| 58 | * | ||
| 59 | * @param index - Workbuffer index. | ||
| 60 | * @return Address of the buffer. | ||
| 61 | */ | ||
| 62 | CpuAddr GetWorkbuffer(s32 index) override; | ||
| 63 | }; | ||
| 64 | |||
| 65 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/compressor.cpp b/src/audio_core/renderer/effect/compressor.cpp new file mode 100644 index 000000000..220ae02f9 --- /dev/null +++ b/src/audio_core/renderer/effect/compressor.cpp | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/effect/compressor.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info, | ||
| 9 | const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {} | ||
| 10 | |||
| 11 | void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info, | ||
| 12 | const InParameterVersion2& in_params, const PoolMapper& pool_mapper) { | ||
| 13 | auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; | ||
| 14 | auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; | ||
| 15 | |||
| 16 | std::memcpy(params, in_specific, sizeof(ParameterVersion1)); | ||
| 17 | mix_id = in_params.mix_id; | ||
| 18 | process_order = in_params.process_order; | ||
| 19 | enabled = in_params.enabled; | ||
| 20 | |||
| 21 | error_info.error_code = ResultSuccess; | ||
| 22 | error_info.address = CpuAddr(0); | ||
| 23 | } | ||
| 24 | |||
| 25 | void CompressorInfo::UpdateForCommandGeneration() { | ||
| 26 | if (enabled) { | ||
| 27 | usage_state = UsageState::Enabled; | ||
| 28 | } else { | ||
| 29 | usage_state = UsageState::Disabled; | ||
| 30 | } | ||
| 31 | |||
| 32 | auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; | ||
| 33 | params->state = ParameterState::Updated; | ||
| 34 | } | ||
| 35 | |||
| 36 | CpuAddr CompressorInfo::GetWorkbuffer(s32 index) { | ||
| 37 | return GetSingleBuffer(index); | ||
| 38 | } | ||
| 39 | |||
| 40 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/compressor.h b/src/audio_core/renderer/effect/compressor.h new file mode 100644 index 000000000..019a5ae58 --- /dev/null +++ b/src/audio_core/renderer/effect/compressor.h | |||
| @@ -0,0 +1,106 @@ | |||
| 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 | |||
| 8 | #include "audio_core/common/common.h" | ||
| 9 | #include "audio_core/renderer/effect/effect_info_base.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | #include "common/fixed_point.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | |||
| 15 | class CompressorInfo : public EffectInfoBase { | ||
| 16 | public: | ||
| 17 | struct ParameterVersion1 { | ||
| 18 | /* 0x00 */ std::array<s8, MaxChannels> inputs; | ||
| 19 | /* 0x06 */ std::array<s8, MaxChannels> outputs; | ||
| 20 | /* 0x0C */ s16 channel_count_max; | ||
| 21 | /* 0x0E */ s16 channel_count; | ||
| 22 | /* 0x10 */ s32 sample_rate; | ||
| 23 | /* 0x14 */ f32 threshold; | ||
| 24 | /* 0x18 */ f32 compressor_ratio; | ||
| 25 | /* 0x1C */ s32 attack_time; | ||
| 26 | /* 0x20 */ s32 release_time; | ||
| 27 | /* 0x24 */ f32 unk_24; | ||
| 28 | /* 0x28 */ f32 unk_28; | ||
| 29 | /* 0x2C */ f32 unk_2C; | ||
| 30 | /* 0x30 */ f32 out_gain; | ||
| 31 | /* 0x34 */ ParameterState state; | ||
| 32 | /* 0x35 */ bool makeup_gain_enabled; | ||
| 33 | }; | ||
| 34 | static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), | ||
| 35 | "CompressorInfo::ParameterVersion1 has the wrong size!"); | ||
| 36 | |||
| 37 | struct ParameterVersion2 { | ||
| 38 | /* 0x00 */ std::array<s8, MaxChannels> inputs; | ||
| 39 | /* 0x06 */ std::array<s8, MaxChannels> outputs; | ||
| 40 | /* 0x0C */ s16 channel_count_max; | ||
| 41 | /* 0x0E */ s16 channel_count; | ||
| 42 | /* 0x10 */ s32 sample_rate; | ||
| 43 | /* 0x14 */ f32 threshold; | ||
| 44 | /* 0x18 */ f32 compressor_ratio; | ||
| 45 | /* 0x1C */ s32 attack_time; | ||
| 46 | /* 0x20 */ s32 release_time; | ||
| 47 | /* 0x24 */ f32 unk_24; | ||
| 48 | /* 0x28 */ f32 unk_28; | ||
| 49 | /* 0x2C */ f32 unk_2C; | ||
| 50 | /* 0x30 */ f32 out_gain; | ||
| 51 | /* 0x34 */ ParameterState state; | ||
| 52 | /* 0x35 */ bool makeup_gain_enabled; | ||
| 53 | }; | ||
| 54 | static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), | ||
| 55 | "CompressorInfo::ParameterVersion2 has the wrong size!"); | ||
| 56 | |||
| 57 | struct State { | ||
| 58 | f32 unk_00; | ||
| 59 | f32 unk_04; | ||
| 60 | f32 unk_08; | ||
| 61 | f32 unk_0C; | ||
| 62 | f32 unk_10; | ||
| 63 | f32 unk_14; | ||
| 64 | f32 unk_18; | ||
| 65 | f32 makeup_gain; | ||
| 66 | f32 unk_20; | ||
| 67 | char unk_24[0x1C]; | ||
| 68 | }; | ||
| 69 | static_assert(sizeof(State) <= sizeof(EffectInfoBase::State), | ||
| 70 | "CompressorInfo::State has the wrong size!"); | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Update the info with new parameters, version 1. | ||
| 74 | * | ||
| 75 | * @param error_info - Used to write call result code. | ||
| 76 | * @param in_params - New parameters to update the info with. | ||
| 77 | * @param pool_mapper - Pool for mapping buffers. | ||
| 78 | */ | ||
| 79 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, | ||
| 80 | const PoolMapper& pool_mapper) override; | ||
| 81 | |||
| 82 | /** | ||
| 83 | * Update the info with new parameters, version 2. | ||
| 84 | * | ||
| 85 | * @param error_info - Used to write call result code. | ||
| 86 | * @param in_params - New parameters to update the info with. | ||
| 87 | * @param pool_mapper - Pool for mapping buffers. | ||
| 88 | */ | ||
| 89 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, | ||
| 90 | const PoolMapper& pool_mapper) override; | ||
| 91 | |||
| 92 | /** | ||
| 93 | * Update the info after command generation. Usually only changes its state. | ||
| 94 | */ | ||
| 95 | void UpdateForCommandGeneration() override; | ||
| 96 | |||
| 97 | /** | ||
| 98 | * Get a workbuffer assigned to this effect with the given index. | ||
| 99 | * | ||
| 100 | * @param index - Workbuffer index. | ||
| 101 | * @return Address of the buffer. | ||
| 102 | */ | ||
| 103 | CpuAddr GetWorkbuffer(s32 index) override; | ||
| 104 | }; | ||
| 105 | |||
| 106 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/delay.cpp b/src/audio_core/renderer/effect/delay.cpp new file mode 100644 index 000000000..d9853efd9 --- /dev/null +++ b/src/audio_core/renderer/effect/delay.cpp | |||
| @@ -0,0 +1,93 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/effect/delay.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, | ||
| 9 | const PoolMapper& pool_mapper) { | ||
| 10 | auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; | ||
| 11 | auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; | ||
| 12 | |||
| 13 | if (IsChannelCountValid(in_specific->channel_count_max)) { | ||
| 14 | const auto old_state{params->state}; | ||
| 15 | std::memcpy(params, in_specific, sizeof(ParameterVersion1)); | ||
| 16 | mix_id = in_params.mix_id; | ||
| 17 | process_order = in_params.process_order; | ||
| 18 | enabled = in_params.enabled; | ||
| 19 | |||
| 20 | if (!IsChannelCountValid(in_specific->channel_count)) { | ||
| 21 | params->channel_count = params->channel_count_max; | ||
| 22 | } | ||
| 23 | |||
| 24 | if (!IsChannelCountValid(in_specific->channel_count) || | ||
| 25 | old_state != ParameterState::Updated) { | ||
| 26 | params->state = old_state; | ||
| 27 | } | ||
| 28 | |||
| 29 | if (buffer_unmapped || in_params.is_new) { | ||
| 30 | usage_state = UsageState::New; | ||
| 31 | params->state = ParameterState::Initialized; | ||
| 32 | buffer_unmapped = !pool_mapper.TryAttachBuffer( | ||
| 33 | error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); | ||
| 34 | return; | ||
| 35 | } | ||
| 36 | } | ||
| 37 | error_info.error_code = ResultSuccess; | ||
| 38 | error_info.address = CpuAddr(0); | ||
| 39 | } | ||
| 40 | |||
| 41 | void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, | ||
| 42 | const PoolMapper& pool_mapper) { | ||
| 43 | auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; | ||
| 44 | auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; | ||
| 45 | |||
| 46 | if (IsChannelCountValid(in_specific->channel_count_max)) { | ||
| 47 | const auto old_state{params->state}; | ||
| 48 | std::memcpy(params, in_specific, sizeof(ParameterVersion1)); | ||
| 49 | mix_id = in_params.mix_id; | ||
| 50 | process_order = in_params.process_order; | ||
| 51 | enabled = in_params.enabled; | ||
| 52 | |||
| 53 | if (!IsChannelCountValid(in_specific->channel_count)) { | ||
| 54 | params->channel_count = params->channel_count_max; | ||
| 55 | } | ||
| 56 | |||
| 57 | if (!IsChannelCountValid(in_specific->channel_count) || | ||
| 58 | old_state != ParameterState::Updated) { | ||
| 59 | params->state = old_state; | ||
| 60 | } | ||
| 61 | |||
| 62 | if (buffer_unmapped || in_params.is_new) { | ||
| 63 | usage_state = UsageState::New; | ||
| 64 | params->state = ParameterState::Initialized; | ||
| 65 | buffer_unmapped = !pool_mapper.TryAttachBuffer( | ||
| 66 | error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); | ||
| 67 | return; | ||
| 68 | } | ||
| 69 | } | ||
| 70 | error_info.error_code = ResultSuccess; | ||
| 71 | error_info.address = CpuAddr(0); | ||
| 72 | } | ||
| 73 | |||
| 74 | void DelayInfo::UpdateForCommandGeneration() { | ||
| 75 | if (enabled) { | ||
| 76 | usage_state = UsageState::Enabled; | ||
| 77 | } else { | ||
| 78 | usage_state = UsageState::Disabled; | ||
| 79 | } | ||
| 80 | |||
| 81 | auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; | ||
| 82 | params->state = ParameterState::Updated; | ||
| 83 | } | ||
| 84 | |||
| 85 | void DelayInfo::InitializeResultState(EffectResultState& result_state) {} | ||
| 86 | |||
| 87 | void DelayInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {} | ||
| 88 | |||
| 89 | CpuAddr DelayInfo::GetWorkbuffer(s32 index) { | ||
| 90 | return GetSingleBuffer(index); | ||
| 91 | } | ||
| 92 | |||
| 93 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/delay.h b/src/audio_core/renderer/effect/delay.h new file mode 100644 index 000000000..accc42a06 --- /dev/null +++ b/src/audio_core/renderer/effect/delay.h | |||
| @@ -0,0 +1,135 @@ | |||
| 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 <vector> | ||
| 8 | |||
| 9 | #include "audio_core/common/common.h" | ||
| 10 | #include "audio_core/renderer/effect/effect_info_base.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/fixed_point.h" | ||
| 13 | |||
| 14 | namespace AudioCore::AudioRenderer { | ||
| 15 | |||
| 16 | class DelayInfo : public EffectInfoBase { | ||
| 17 | public: | ||
| 18 | struct ParameterVersion1 { | ||
| 19 | /* 0x00 */ std::array<s8, MaxChannels> inputs; | ||
| 20 | /* 0x06 */ std::array<s8, MaxChannels> outputs; | ||
| 21 | /* 0x0C */ u16 channel_count_max; | ||
| 22 | /* 0x0E */ u16 channel_count; | ||
| 23 | /* 0x10 */ u32 delay_time_max; | ||
| 24 | /* 0x14 */ u32 delay_time; | ||
| 25 | /* 0x18 */ Common::FixedPoint<18, 14> sample_rate; | ||
| 26 | /* 0x1C */ Common::FixedPoint<18, 14> in_gain; | ||
| 27 | /* 0x20 */ Common::FixedPoint<18, 14> feedback_gain; | ||
| 28 | /* 0x24 */ Common::FixedPoint<18, 14> wet_gain; | ||
| 29 | /* 0x28 */ Common::FixedPoint<18, 14> dry_gain; | ||
| 30 | /* 0x2C */ Common::FixedPoint<18, 14> channel_spread; | ||
| 31 | /* 0x30 */ Common::FixedPoint<18, 14> lowpass_amount; | ||
| 32 | /* 0x34 */ ParameterState state; | ||
| 33 | }; | ||
| 34 | static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), | ||
| 35 | "DelayInfo::ParameterVersion1 has the wrong size!"); | ||
| 36 | |||
| 37 | struct ParameterVersion2 { | ||
| 38 | /* 0x00 */ std::array<s8, MaxChannels> inputs; | ||
| 39 | /* 0x06 */ std::array<s8, MaxChannels> outputs; | ||
| 40 | /* 0x0C */ s16 channel_count_max; | ||
| 41 | /* 0x0E */ s16 channel_count; | ||
| 42 | /* 0x10 */ s32 delay_time_max; | ||
| 43 | /* 0x14 */ s32 delay_time; | ||
| 44 | /* 0x18 */ s32 sample_rate; | ||
| 45 | /* 0x1C */ s32 in_gain; | ||
| 46 | /* 0x20 */ s32 feedback_gain; | ||
| 47 | /* 0x24 */ s32 wet_gain; | ||
| 48 | /* 0x28 */ s32 dry_gain; | ||
| 49 | /* 0x2C */ s32 channel_spread; | ||
| 50 | /* 0x30 */ s32 lowpass_amount; | ||
| 51 | /* 0x34 */ ParameterState state; | ||
| 52 | }; | ||
| 53 | static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), | ||
| 54 | "DelayInfo::ParameterVersion2 has the wrong size!"); | ||
| 55 | |||
| 56 | struct DelayLine { | ||
| 57 | Common::FixedPoint<50, 14> Read() const { | ||
| 58 | return buffer[buffer_pos]; | ||
| 59 | } | ||
| 60 | |||
| 61 | void Write(const Common::FixedPoint<50, 14> value) { | ||
| 62 | buffer[buffer_pos] = value; | ||
| 63 | buffer_pos = static_cast<u32>((buffer_pos + 1) % buffer.size()); | ||
| 64 | } | ||
| 65 | |||
| 66 | s32 sample_count_max{}; | ||
| 67 | s32 sample_count{}; | ||
| 68 | std::vector<Common::FixedPoint<50, 14>> buffer{}; | ||
| 69 | u32 buffer_pos{}; | ||
| 70 | Common::FixedPoint<18, 14> decay_rate{}; | ||
| 71 | }; | ||
| 72 | |||
| 73 | struct State { | ||
| 74 | /* 0x000 */ std::array<s32, 8> unk_000; | ||
| 75 | /* 0x020 */ std::array<DelayLine, MaxChannels> delay_lines; | ||
| 76 | /* 0x0B0 */ Common::FixedPoint<18, 14> feedback_gain; | ||
| 77 | /* 0x0B4 */ Common::FixedPoint<18, 14> delay_feedback_gain; | ||
| 78 | /* 0x0B8 */ Common::FixedPoint<18, 14> delay_feedback_cross_gain; | ||
| 79 | /* 0x0BC */ Common::FixedPoint<18, 14> lowpass_gain; | ||
| 80 | /* 0x0C0 */ Common::FixedPoint<18, 14> lowpass_feedback_gain; | ||
| 81 | /* 0x0C4 */ std::array<Common::FixedPoint<50, 14>, MaxChannels> lowpass_z; | ||
| 82 | }; | ||
| 83 | static_assert(sizeof(State) <= sizeof(EffectInfoBase::State), | ||
| 84 | "DelayInfo::State has the wrong size!"); | ||
| 85 | |||
| 86 | /** | ||
| 87 | * Update the info with new parameters, version 1. | ||
| 88 | * | ||
| 89 | * @param error_info - Used to write call result code. | ||
| 90 | * @param in_params - New parameters to update the info with. | ||
| 91 | * @param pool_mapper - Pool for mapping buffers. | ||
| 92 | */ | ||
| 93 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, | ||
| 94 | const PoolMapper& pool_mapper) override; | ||
| 95 | |||
| 96 | /** | ||
| 97 | * Update the info with new parameters, version 2. | ||
| 98 | * | ||
| 99 | * @param error_info - Used to write call result code. | ||
| 100 | * @param in_params - New parameters to update the info with. | ||
| 101 | * @param pool_mapper - Pool for mapping buffers. | ||
| 102 | */ | ||
| 103 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, | ||
| 104 | const PoolMapper& pool_mapper) override; | ||
| 105 | |||
| 106 | /** | ||
| 107 | * Update the info after command generation. Usually only changes its state. | ||
| 108 | */ | ||
| 109 | void UpdateForCommandGeneration() override; | ||
| 110 | |||
| 111 | /** | ||
| 112 | * Initialize a new result state. Version 2 only, unused. | ||
| 113 | * | ||
| 114 | * @param result_state - Result state to initialize. | ||
| 115 | */ | ||
| 116 | void InitializeResultState(EffectResultState& result_state) override; | ||
| 117 | |||
| 118 | /** | ||
| 119 | * Update the host-side state with the ADSP-side state. Version 2 only, unused. | ||
| 120 | * | ||
| 121 | * @param cpu_state - Host-side result state to update. | ||
| 122 | * @param dsp_state - AudioRenderer-side result state to update from. | ||
| 123 | */ | ||
| 124 | void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; | ||
| 125 | |||
| 126 | /** | ||
| 127 | * Get a workbuffer assigned to this effect with the given index. | ||
| 128 | * | ||
| 129 | * @param index - Workbuffer index. | ||
| 130 | * @return Address of the buffer. | ||
| 131 | */ | ||
| 132 | CpuAddr GetWorkbuffer(s32 index) override; | ||
| 133 | }; | ||
| 134 | |||
| 135 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/effect_context.cpp b/src/audio_core/renderer/effect/effect_context.cpp new file mode 100644 index 000000000..74c7801c9 --- /dev/null +++ b/src/audio_core/renderer/effect/effect_context.cpp | |||
| @@ -0,0 +1,41 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/effect/effect_context.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void EffectContext::Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_, | ||
| 9 | std::span<EffectResultState> result_states_cpu_, | ||
| 10 | std::span<EffectResultState> result_states_dsp_, | ||
| 11 | const size_t dsp_state_count_) { | ||
| 12 | effect_infos = effect_infos_; | ||
| 13 | effect_count = effect_count_; | ||
| 14 | result_states_cpu = result_states_cpu_; | ||
| 15 | result_states_dsp = result_states_dsp_; | ||
| 16 | dsp_state_count = dsp_state_count_; | ||
| 17 | } | ||
| 18 | |||
| 19 | EffectInfoBase& EffectContext::GetInfo(const u32 index) { | ||
| 20 | return effect_infos[index]; | ||
| 21 | } | ||
| 22 | |||
| 23 | EffectResultState& EffectContext::GetResultState(const u32 index) { | ||
| 24 | return result_states_cpu[index]; | ||
| 25 | } | ||
| 26 | |||
| 27 | EffectResultState& EffectContext::GetDspSharedResultState(const u32 index) { | ||
| 28 | return result_states_dsp[index]; | ||
| 29 | } | ||
| 30 | |||
| 31 | u32 EffectContext::GetCount() const { | ||
| 32 | return effect_count; | ||
| 33 | } | ||
| 34 | |||
| 35 | void EffectContext::UpdateStateByDspShared() { | ||
| 36 | for (size_t i = 0; i < dsp_state_count; i++) { | ||
| 37 | effect_infos[i].UpdateResultState(result_states_cpu[i], result_states_dsp[i]); | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/effect_context.h b/src/audio_core/renderer/effect/effect_context.h new file mode 100644 index 000000000..85955bd9c --- /dev/null +++ b/src/audio_core/renderer/effect/effect_context.h | |||
| @@ -0,0 +1,75 @@ | |||
| 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/effect/effect_info_base.h" | ||
| 9 | #include "audio_core/renderer/effect/effect_result_state.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | |||
| 14 | class EffectContext { | ||
| 15 | public: | ||
| 16 | /** | ||
| 17 | * Initialize the effect context | ||
| 18 | * @param effect_infos List of effect infos for this context | ||
| 19 | * @param effect_count The number of effects in the list | ||
| 20 | * @param result_states_cpu The workbuffer of result states for the CPU for this context | ||
| 21 | * @param result_states_dsp The workbuffer of result states for the DSP for this context | ||
| 22 | * @param state_count The number of result states | ||
| 23 | */ | ||
| 24 | void Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_, | ||
| 25 | std::span<EffectResultState> result_states_cpu_, | ||
| 26 | std::span<EffectResultState> result_states_dsp_, const size_t dsp_state_count); | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Get the EffectInfo for a given index | ||
| 30 | * @param index Which effect to return | ||
| 31 | * @return Pointer to the effect | ||
| 32 | */ | ||
| 33 | EffectInfoBase& GetInfo(const u32 index); | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Get the CPU result state for a given index | ||
| 37 | * @param index Which result to return | ||
| 38 | * @return Pointer to the effect result state | ||
| 39 | */ | ||
| 40 | EffectResultState& GetResultState(const u32 index); | ||
| 41 | |||
| 42 | /** | ||
| 43 | * Get the DSP result state for a given index | ||
| 44 | * @param index Which result to return | ||
| 45 | * @return Pointer to the effect result state | ||
| 46 | */ | ||
| 47 | EffectResultState& GetDspSharedResultState(const u32 index); | ||
| 48 | |||
| 49 | /** | ||
| 50 | * Get the number of effects in this context | ||
| 51 | * @return The number of effects | ||
| 52 | */ | ||
| 53 | u32 GetCount() const; | ||
| 54 | |||
| 55 | /** | ||
| 56 | * Update the CPU and DSP result states for all effects | ||
| 57 | */ | ||
| 58 | void UpdateStateByDspShared(); | ||
| 59 | |||
| 60 | private: | ||
| 61 | /// Workbuffer for all of the effects | ||
| 62 | std::span<EffectInfoBase> effect_infos{}; | ||
| 63 | /// Number of effects in the workbuffer | ||
| 64 | u32 effect_count{}; | ||
| 65 | /// Workbuffer of states for all effects, kept host-side and not directly modified, dsp states | ||
| 66 | /// are copied here on the next render frame | ||
| 67 | std::span<EffectResultState> result_states_cpu{}; | ||
| 68 | /// Workbuffer of states for all effects, used by the AudioRenderer to track effect state | ||
| 69 | /// between calls | ||
| 70 | std::span<EffectResultState> result_states_dsp{}; | ||
| 71 | /// Number of result states in the workbuffers | ||
| 72 | size_t dsp_state_count{}; | ||
| 73 | }; | ||
| 74 | |||
| 75 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/effect_info_base.h b/src/audio_core/renderer/effect/effect_info_base.h new file mode 100644 index 000000000..43d0589cc --- /dev/null +++ b/src/audio_core/renderer/effect/effect_info_base.h | |||
| @@ -0,0 +1,435 @@ | |||
| 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 | |||
| 8 | #include "audio_core/common/common.h" | ||
| 9 | #include "audio_core/renderer/behavior/behavior_info.h" | ||
| 10 | #include "audio_core/renderer/effect/effect_result_state.h" | ||
| 11 | #include "audio_core/renderer/memory/address_info.h" | ||
| 12 | #include "audio_core/renderer/memory/pool_mapper.h" | ||
| 13 | #include "common/common_types.h" | ||
| 14 | |||
| 15 | namespace AudioCore::AudioRenderer { | ||
| 16 | /** | ||
| 17 | * Base of all effects. Holds various data and functions used for all derived effects. | ||
| 18 | * Should not be used directly. | ||
| 19 | */ | ||
| 20 | class EffectInfoBase { | ||
| 21 | public: | ||
| 22 | enum class Type : u8 { | ||
| 23 | Invalid, | ||
| 24 | Mix, | ||
| 25 | Aux, | ||
| 26 | Delay, | ||
| 27 | Reverb, | ||
| 28 | I3dl2Reverb, | ||
| 29 | BiquadFilter, | ||
| 30 | LightLimiter, | ||
| 31 | Capture, | ||
| 32 | Compressor, | ||
| 33 | }; | ||
| 34 | |||
| 35 | enum class UsageState { | ||
| 36 | Invalid, | ||
| 37 | New, | ||
| 38 | Enabled, | ||
| 39 | Disabled, | ||
| 40 | }; | ||
| 41 | |||
| 42 | enum class OutStatus : u8 { | ||
| 43 | Invalid, | ||
| 44 | New, | ||
| 45 | Initialized, | ||
| 46 | Used, | ||
| 47 | Removed, | ||
| 48 | }; | ||
| 49 | |||
| 50 | enum class ParameterState : u8 { | ||
| 51 | Initialized, | ||
| 52 | Updating, | ||
| 53 | Updated, | ||
| 54 | }; | ||
| 55 | |||
| 56 | struct InParameterVersion1 { | ||
| 57 | /* 0x00 */ Type type; | ||
| 58 | /* 0x01 */ bool is_new; | ||
| 59 | /* 0x02 */ bool enabled; | ||
| 60 | /* 0x04 */ u32 mix_id; | ||
| 61 | /* 0x08 */ CpuAddr workbuffer; | ||
| 62 | /* 0x10 */ CpuAddr workbuffer_size; | ||
| 63 | /* 0x18 */ u32 process_order; | ||
| 64 | /* 0x1C */ char unk1C[0x4]; | ||
| 65 | /* 0x20 */ std::array<u8, 0xA0> specific; | ||
| 66 | }; | ||
| 67 | static_assert(sizeof(InParameterVersion1) == 0xC0, | ||
| 68 | "EffectInfoBase::InParameterVersion1 has the wrong size!"); | ||
| 69 | |||
| 70 | struct InParameterVersion2 { | ||
| 71 | /* 0x00 */ Type type; | ||
| 72 | /* 0x01 */ bool is_new; | ||
| 73 | /* 0x02 */ bool enabled; | ||
| 74 | /* 0x04 */ u32 mix_id; | ||
| 75 | /* 0x08 */ CpuAddr workbuffer; | ||
| 76 | /* 0x10 */ CpuAddr workbuffer_size; | ||
| 77 | /* 0x18 */ u32 process_order; | ||
| 78 | /* 0x1C */ char unk1C[0x4]; | ||
| 79 | /* 0x20 */ std::array<u8, 0xA0> specific; | ||
| 80 | }; | ||
| 81 | static_assert(sizeof(InParameterVersion2) == 0xC0, | ||
| 82 | "EffectInfoBase::InParameterVersion2 has the wrong size!"); | ||
| 83 | |||
| 84 | struct OutStatusVersion1 { | ||
| 85 | /* 0x00 */ OutStatus state; | ||
| 86 | /* 0x01 */ char unk01[0xF]; | ||
| 87 | }; | ||
| 88 | static_assert(sizeof(OutStatusVersion1) == 0x10, | ||
| 89 | "EffectInfoBase::OutStatusVersion1 has the wrong size!"); | ||
| 90 | |||
| 91 | struct OutStatusVersion2 { | ||
| 92 | /* 0x00 */ OutStatus state; | ||
| 93 | /* 0x01 */ char unk01[0xF]; | ||
| 94 | /* 0x10 */ EffectResultState result_state; | ||
| 95 | }; | ||
| 96 | static_assert(sizeof(OutStatusVersion2) == 0x90, | ||
| 97 | "EffectInfoBase::OutStatusVersion2 has the wrong size!"); | ||
| 98 | |||
| 99 | struct State { | ||
| 100 | std::array<u8, 0x500> buffer; | ||
| 101 | }; | ||
| 102 | static_assert(sizeof(State) == 0x500, "EffectInfoBase::State has the wrong size!"); | ||
| 103 | |||
| 104 | EffectInfoBase() { | ||
| 105 | Cleanup(); | ||
| 106 | } | ||
| 107 | |||
| 108 | virtual ~EffectInfoBase() = default; | ||
| 109 | |||
| 110 | /** | ||
| 111 | * Cleanup this effect, resetting it to a starting state. | ||
| 112 | */ | ||
| 113 | void Cleanup() { | ||
| 114 | type = Type::Invalid; | ||
| 115 | enabled = false; | ||
| 116 | mix_id = UnusedMixId; | ||
| 117 | process_order = InvalidProcessOrder; | ||
| 118 | buffer_unmapped = false; | ||
| 119 | parameter = {}; | ||
| 120 | for (auto& workbuffer : workbuffers) { | ||
| 121 | workbuffer.Setup(CpuAddr(0), 0); | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | /** | ||
| 126 | * Forcibly unmap all assigned workbuffers from the AudioRenderer. | ||
| 127 | * | ||
| 128 | * @param pool_mapper - Mapper to unmap the buffers. | ||
| 129 | */ | ||
| 130 | void ForceUnmapBuffers(const PoolMapper& pool_mapper) { | ||
| 131 | for (auto& workbuffer : workbuffers) { | ||
| 132 | if (workbuffer.GetReference(false) != 0) { | ||
| 133 | pool_mapper.ForceUnmapPointer(workbuffer); | ||
| 134 | } | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | /** | ||
| 139 | * Check if this effect is enabled. | ||
| 140 | * | ||
| 141 | * @return True if effect is enabled, otherwise false. | ||
| 142 | */ | ||
| 143 | bool IsEnabled() const { | ||
| 144 | return enabled; | ||
| 145 | } | ||
| 146 | |||
| 147 | /** | ||
| 148 | * Check if this effect should not be generated. | ||
| 149 | * | ||
| 150 | * @return True if effect should be skipped, otherwise false. | ||
| 151 | */ | ||
| 152 | bool ShouldSkip() const { | ||
| 153 | return buffer_unmapped; | ||
| 154 | } | ||
| 155 | |||
| 156 | /** | ||
| 157 | * Get the type of this effect. | ||
| 158 | * | ||
| 159 | * @return The type of this effect. See EffectInfoBase::Type | ||
| 160 | */ | ||
| 161 | Type GetType() const { | ||
| 162 | return type; | ||
| 163 | } | ||
| 164 | |||
| 165 | /** | ||
| 166 | * Set the type of this effect. | ||
| 167 | * | ||
| 168 | * @param type_ - The new type of this effect. | ||
| 169 | */ | ||
| 170 | void SetType(const Type type_) { | ||
| 171 | type = type_; | ||
| 172 | } | ||
| 173 | |||
| 174 | /** | ||
| 175 | * Get the mix id of this effect. | ||
| 176 | * | ||
| 177 | * @return Mix id of this effect. | ||
| 178 | */ | ||
| 179 | s32 GetMixId() const { | ||
| 180 | return mix_id; | ||
| 181 | } | ||
| 182 | |||
| 183 | /** | ||
| 184 | * Get the processing order of this effect. | ||
| 185 | * | ||
| 186 | * @return Process order of this effect. | ||
| 187 | */ | ||
| 188 | s32 GetProcessingOrder() const { | ||
| 189 | return process_order; | ||
| 190 | } | ||
| 191 | |||
| 192 | /** | ||
| 193 | * Get this effect's parameter data. | ||
| 194 | * | ||
| 195 | * @return Pointer to the parametter, must be cast to the correct type. | ||
| 196 | */ | ||
| 197 | u8* GetParameter() { | ||
| 198 | return parameter.data(); | ||
| 199 | } | ||
| 200 | |||
| 201 | /** | ||
| 202 | * Get this effect's parameter data. | ||
| 203 | * | ||
| 204 | * @return Pointer to the parametter, must be cast to the correct type. | ||
| 205 | */ | ||
| 206 | u8* GetStateBuffer() { | ||
| 207 | return state.data(); | ||
| 208 | } | ||
| 209 | |||
| 210 | /** | ||
| 211 | * Set this effect's usage state. | ||
| 212 | * | ||
| 213 | * @param usage - new usage state of this effect. | ||
| 214 | */ | ||
| 215 | void SetUsage(const UsageState usage) { | ||
| 216 | usage_state = usage; | ||
| 217 | } | ||
| 218 | |||
| 219 | /** | ||
| 220 | * Check if this effects need to have its workbuffer information updated. | ||
| 221 | * Version 1. | ||
| 222 | * | ||
| 223 | * @param params - Input parameters. | ||
| 224 | * @return True if workbuffers need updating, otherwise false. | ||
| 225 | */ | ||
| 226 | bool ShouldUpdateWorkBufferInfo(const InParameterVersion1& params) const { | ||
| 227 | return buffer_unmapped || params.is_new; | ||
| 228 | } | ||
| 229 | |||
| 230 | /** | ||
| 231 | * Check if this effects need to have its workbuffer information updated. | ||
| 232 | * Version 2. | ||
| 233 | * | ||
| 234 | * @param params - Input parameters. | ||
| 235 | * @return True if workbuffers need updating, otherwise false. | ||
| 236 | */ | ||
| 237 | bool ShouldUpdateWorkBufferInfo(const InParameterVersion2& params) const { | ||
| 238 | return buffer_unmapped || params.is_new; | ||
| 239 | } | ||
| 240 | |||
| 241 | /** | ||
| 242 | * Get the current usage state of this effect. | ||
| 243 | * | ||
| 244 | * @return The current usage state. | ||
| 245 | */ | ||
| 246 | UsageState GetUsage() const { | ||
| 247 | return usage_state; | ||
| 248 | } | ||
| 249 | |||
| 250 | /** | ||
| 251 | * Write the current state. Version 1. | ||
| 252 | * | ||
| 253 | * @param out_status - Status to write. | ||
| 254 | * @param renderer_active - Is the AudioRenderer active? | ||
| 255 | */ | ||
| 256 | void StoreStatus(OutStatusVersion1& out_status, const bool renderer_active) const { | ||
| 257 | if (renderer_active) { | ||
| 258 | if (usage_state != UsageState::Disabled) { | ||
| 259 | out_status.state = OutStatus::Used; | ||
| 260 | } else { | ||
| 261 | out_status.state = OutStatus::Removed; | ||
| 262 | } | ||
| 263 | } else if (usage_state == UsageState::New) { | ||
| 264 | out_status.state = OutStatus::Used; | ||
| 265 | } else { | ||
| 266 | out_status.state = OutStatus::Removed; | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | /** | ||
| 271 | * Write the current state. Version 2. | ||
| 272 | * | ||
| 273 | * @param out_status - Status to write. | ||
| 274 | * @param renderer_active - Is the AudioRenderer active? | ||
| 275 | */ | ||
| 276 | void StoreStatus(OutStatusVersion2& out_status, const bool renderer_active) const { | ||
| 277 | if (renderer_active) { | ||
| 278 | if (usage_state != UsageState::Disabled) { | ||
| 279 | out_status.state = OutStatus::Used; | ||
| 280 | } else { | ||
| 281 | out_status.state = OutStatus::Removed; | ||
| 282 | } | ||
| 283 | } else if (usage_state == UsageState::New) { | ||
| 284 | out_status.state = OutStatus::Used; | ||
| 285 | } else { | ||
| 286 | out_status.state = OutStatus::Removed; | ||
| 287 | } | ||
| 288 | } | ||
| 289 | |||
| 290 | /** | ||
| 291 | * Update the info with new parameters, version 1. | ||
| 292 | * | ||
| 293 | * @param error_info - Used to write call result code. | ||
| 294 | * @param in_params - New parameters to update the info with. | ||
| 295 | * @param pool_mapper - Pool for mapping buffers. | ||
| 296 | */ | ||
| 297 | virtual void Update(BehaviorInfo::ErrorInfo& error_info, | ||
| 298 | [[maybe_unused]] const InParameterVersion1& params, | ||
| 299 | [[maybe_unused]] const PoolMapper& pool_mapper) { | ||
| 300 | error_info.error_code = ResultSuccess; | ||
| 301 | error_info.address = CpuAddr(0); | ||
| 302 | } | ||
| 303 | |||
| 304 | /** | ||
| 305 | * Update the info with new parameters, version 2. | ||
| 306 | * | ||
| 307 | * @param error_info - Used to write call result code. | ||
| 308 | * @param in_params - New parameters to update the info with. | ||
| 309 | * @param pool_mapper - Pool for mapping buffers. | ||
| 310 | */ | ||
| 311 | virtual void Update(BehaviorInfo::ErrorInfo& error_info, | ||
| 312 | [[maybe_unused]] const InParameterVersion2& params, | ||
| 313 | [[maybe_unused]] const PoolMapper& pool_mapper) { | ||
| 314 | error_info.error_code = ResultSuccess; | ||
| 315 | error_info.address = CpuAddr(0); | ||
| 316 | } | ||
| 317 | |||
| 318 | /** | ||
| 319 | * Update the info after command generation. Usually only changes its state. | ||
| 320 | */ | ||
| 321 | virtual void UpdateForCommandGeneration() {} | ||
| 322 | |||
| 323 | /** | ||
| 324 | * Initialize a new result state. Version 2 only, unused. | ||
| 325 | * | ||
| 326 | * @param result_state - Result state to initialize. | ||
| 327 | */ | ||
| 328 | virtual void InitializeResultState([[maybe_unused]] EffectResultState& result_state) {} | ||
| 329 | |||
| 330 | /** | ||
| 331 | * Update the host-side state with the ADSP-side state. Version 2 only, unused. | ||
| 332 | * | ||
| 333 | * @param cpu_state - Host-side result state to update. | ||
| 334 | * @param dsp_state - AudioRenderer-side result state to update from. | ||
| 335 | */ | ||
| 336 | virtual void UpdateResultState([[maybe_unused]] EffectResultState& cpu_state, | ||
| 337 | [[maybe_unused]] EffectResultState& dsp_state) {} | ||
| 338 | |||
| 339 | /** | ||
| 340 | * Get a workbuffer assigned to this effect with the given index. | ||
| 341 | * | ||
| 342 | * @param index - Workbuffer index. | ||
| 343 | * @return Address of the buffer. | ||
| 344 | */ | ||
| 345 | virtual CpuAddr GetWorkbuffer([[maybe_unused]] s32 index) { | ||
| 346 | return 0; | ||
| 347 | } | ||
| 348 | |||
| 349 | /** | ||
| 350 | * Get the first workbuffer assigned to this effect. | ||
| 351 | * | ||
| 352 | * @param index - Workbuffer index. Unused. | ||
| 353 | * @return Address of the buffer. | ||
| 354 | */ | ||
| 355 | CpuAddr GetSingleBuffer([[maybe_unused]] const s32 index) { | ||
| 356 | if (enabled) { | ||
| 357 | return workbuffers[0].GetReference(true); | ||
| 358 | } | ||
| 359 | |||
| 360 | if (usage_state != UsageState::Disabled) { | ||
| 361 | const auto ref{workbuffers[0].GetReference(false)}; | ||
| 362 | const auto size{workbuffers[0].GetSize()}; | ||
| 363 | if (ref != 0 && size > 0) { | ||
| 364 | // Invalidate DSP cache | ||
| 365 | } | ||
| 366 | } | ||
| 367 | return 0; | ||
| 368 | } | ||
| 369 | |||
| 370 | /** | ||
| 371 | * Get the send buffer info, used by Aux and Capture. | ||
| 372 | * | ||
| 373 | * @return Address of the buffer info. | ||
| 374 | */ | ||
| 375 | CpuAddr GetSendBufferInfo() const { | ||
| 376 | return send_buffer_info; | ||
| 377 | } | ||
| 378 | |||
| 379 | /** | ||
| 380 | * Get the send buffer, used by Aux and Capture. | ||
| 381 | * | ||
| 382 | * @return Address of the buffer. | ||
| 383 | */ | ||
| 384 | CpuAddr GetSendBuffer() const { | ||
| 385 | return send_buffer; | ||
| 386 | } | ||
| 387 | |||
| 388 | /** | ||
| 389 | * Get the return buffer info, used by Aux and Capture. | ||
| 390 | * | ||
| 391 | * @return Address of the buffer info. | ||
| 392 | */ | ||
| 393 | CpuAddr GetReturnBufferInfo() const { | ||
| 394 | return return_buffer_info; | ||
| 395 | } | ||
| 396 | |||
| 397 | /** | ||
| 398 | * Get the return buffer, used by Aux and Capture. | ||
| 399 | * | ||
| 400 | * @return Address of the buffer. | ||
| 401 | */ | ||
| 402 | CpuAddr GetReturnBuffer() const { | ||
| 403 | return return_buffer; | ||
| 404 | } | ||
| 405 | |||
| 406 | protected: | ||
| 407 | /// Type of this effect. May be changed | ||
| 408 | Type type{Type::Invalid}; | ||
| 409 | /// Is this effect enabled? | ||
| 410 | bool enabled{}; | ||
| 411 | /// Are this effect's buffers unmapped? | ||
| 412 | bool buffer_unmapped{}; | ||
| 413 | /// Current usage state | ||
| 414 | UsageState usage_state{UsageState::Invalid}; | ||
| 415 | /// Mix id of this effect | ||
| 416 | s32 mix_id{UnusedMixId}; | ||
| 417 | /// Process order of this effect | ||
| 418 | s32 process_order{InvalidProcessOrder}; | ||
| 419 | /// Workbuffers assigned to this effect | ||
| 420 | std::array<AddressInfo, 2> workbuffers{AddressInfo(CpuAddr(0), 0), AddressInfo(CpuAddr(0), 0)}; | ||
| 421 | /// Aux/Capture buffer info for reading | ||
| 422 | CpuAddr send_buffer_info; | ||
| 423 | /// Aux/Capture buffer for reading | ||
| 424 | CpuAddr send_buffer; | ||
| 425 | /// Aux/Capture buffer info for writing | ||
| 426 | CpuAddr return_buffer_info; | ||
| 427 | /// Aux/Capture buffer for writing | ||
| 428 | CpuAddr return_buffer; | ||
| 429 | /// Parameters of this effect | ||
| 430 | std::array<u8, sizeof(InParameterVersion2)> parameter{}; | ||
| 431 | /// State of this effect used by the AudioRenderer across calls | ||
| 432 | std::array<u8, sizeof(State)> state{}; | ||
| 433 | }; | ||
| 434 | |||
| 435 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/effect_reset.h b/src/audio_core/renderer/effect/effect_reset.h new file mode 100644 index 000000000..1ea67e334 --- /dev/null +++ b/src/audio_core/renderer/effect/effect_reset.h | |||
| @@ -0,0 +1,71 @@ | |||
| 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/effect/aux_.h" | ||
| 7 | #include "audio_core/renderer/effect/biquad_filter.h" | ||
| 8 | #include "audio_core/renderer/effect/buffer_mixer.h" | ||
| 9 | #include "audio_core/renderer/effect/capture.h" | ||
| 10 | #include "audio_core/renderer/effect/compressor.h" | ||
| 11 | #include "audio_core/renderer/effect/delay.h" | ||
| 12 | #include "audio_core/renderer/effect/i3dl2.h" | ||
| 13 | #include "audio_core/renderer/effect/light_limiter.h" | ||
| 14 | #include "audio_core/renderer/effect/reverb.h" | ||
| 15 | #include "common/common_types.h" | ||
| 16 | |||
| 17 | namespace AudioCore::AudioRenderer { | ||
| 18 | /** | ||
| 19 | * Reset an effect, and create a new one of the given type. | ||
| 20 | * | ||
| 21 | * @param effect - Effect to reset and re-construct. | ||
| 22 | * @param type - Type of the new effect to create. | ||
| 23 | */ | ||
| 24 | static void ResetEffect(EffectInfoBase* effect, const EffectInfoBase::Type type) { | ||
| 25 | *effect = {}; | ||
| 26 | |||
| 27 | switch (type) { | ||
| 28 | case EffectInfoBase::Type::Invalid: | ||
| 29 | std::construct_at<EffectInfoBase>(effect); | ||
| 30 | effect->SetType(EffectInfoBase::Type::Invalid); | ||
| 31 | break; | ||
| 32 | case EffectInfoBase::Type::Mix: | ||
| 33 | std::construct_at<BufferMixerInfo>(reinterpret_cast<BufferMixerInfo*>(effect)); | ||
| 34 | effect->SetType(EffectInfoBase::Type::Mix); | ||
| 35 | break; | ||
| 36 | case EffectInfoBase::Type::Aux: | ||
| 37 | std::construct_at<AuxInfo>(reinterpret_cast<AuxInfo*>(effect)); | ||
| 38 | effect->SetType(EffectInfoBase::Type::Aux); | ||
| 39 | break; | ||
| 40 | case EffectInfoBase::Type::Delay: | ||
| 41 | std::construct_at<DelayInfo>(reinterpret_cast<DelayInfo*>(effect)); | ||
| 42 | effect->SetType(EffectInfoBase::Type::Delay); | ||
| 43 | break; | ||
| 44 | case EffectInfoBase::Type::Reverb: | ||
| 45 | std::construct_at<ReverbInfo>(reinterpret_cast<ReverbInfo*>(effect)); | ||
| 46 | effect->SetType(EffectInfoBase::Type::Reverb); | ||
| 47 | break; | ||
| 48 | case EffectInfoBase::Type::I3dl2Reverb: | ||
| 49 | std::construct_at<I3dl2ReverbInfo>(reinterpret_cast<I3dl2ReverbInfo*>(effect)); | ||
| 50 | effect->SetType(EffectInfoBase::Type::I3dl2Reverb); | ||
| 51 | break; | ||
| 52 | case EffectInfoBase::Type::BiquadFilter: | ||
| 53 | std::construct_at<BiquadFilterInfo>(reinterpret_cast<BiquadFilterInfo*>(effect)); | ||
| 54 | effect->SetType(EffectInfoBase::Type::BiquadFilter); | ||
| 55 | break; | ||
| 56 | case EffectInfoBase::Type::LightLimiter: | ||
| 57 | std::construct_at<LightLimiterInfo>(reinterpret_cast<LightLimiterInfo*>(effect)); | ||
| 58 | effect->SetType(EffectInfoBase::Type::LightLimiter); | ||
| 59 | break; | ||
| 60 | case EffectInfoBase::Type::Capture: | ||
| 61 | std::construct_at<CaptureInfo>(reinterpret_cast<CaptureInfo*>(effect)); | ||
| 62 | effect->SetType(EffectInfoBase::Type::Capture); | ||
| 63 | break; | ||
| 64 | case EffectInfoBase::Type::Compressor: | ||
| 65 | std::construct_at<CompressorInfo>(reinterpret_cast<CompressorInfo*>(effect)); | ||
| 66 | effect->SetType(EffectInfoBase::Type::Compressor); | ||
| 67 | break; | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/effect_result_state.h b/src/audio_core/renderer/effect/effect_result_state.h new file mode 100644 index 000000000..ae096ad69 --- /dev/null +++ b/src/audio_core/renderer/effect/effect_result_state.h | |||
| @@ -0,0 +1,16 @@ | |||
| 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 | |||
| 8 | #include "common/common_types.h" | ||
| 9 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | struct EffectResultState { | ||
| 13 | std::array<u8, 0x80> state; | ||
| 14 | }; | ||
| 15 | |||
| 16 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/i3dl2.cpp b/src/audio_core/renderer/effect/i3dl2.cpp new file mode 100644 index 000000000..960b29cfc --- /dev/null +++ b/src/audio_core/renderer/effect/i3dl2.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/effect/i3dl2.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, | ||
| 9 | const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { | ||
| 10 | auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; | ||
| 11 | auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; | ||
| 12 | |||
| 13 | if (IsChannelCountValid(in_specific->channel_count_max)) { | ||
| 14 | const auto old_state{params->state}; | ||
| 15 | std::memcpy(params, in_specific, sizeof(ParameterVersion1)); | ||
| 16 | mix_id = in_params.mix_id; | ||
| 17 | process_order = in_params.process_order; | ||
| 18 | enabled = in_params.enabled; | ||
| 19 | |||
| 20 | if (!IsChannelCountValid(in_specific->channel_count)) { | ||
| 21 | params->channel_count = params->channel_count_max; | ||
| 22 | } | ||
| 23 | |||
| 24 | if (!IsChannelCountValid(in_specific->channel_count) || | ||
| 25 | old_state != ParameterState::Updated) { | ||
| 26 | params->state = old_state; | ||
| 27 | } | ||
| 28 | |||
| 29 | if (buffer_unmapped || in_params.is_new) { | ||
| 30 | usage_state = UsageState::New; | ||
| 31 | params->state = ParameterState::Initialized; | ||
| 32 | buffer_unmapped = !pool_mapper.TryAttachBuffer( | ||
| 33 | error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); | ||
| 34 | return; | ||
| 35 | } | ||
| 36 | } | ||
| 37 | error_info.error_code = ResultSuccess; | ||
| 38 | error_info.address = CpuAddr(0); | ||
| 39 | } | ||
| 40 | |||
| 41 | void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, | ||
| 42 | const InParameterVersion2& in_params, const PoolMapper& pool_mapper) { | ||
| 43 | auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; | ||
| 44 | auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; | ||
| 45 | |||
| 46 | if (IsChannelCountValid(in_specific->channel_count_max)) { | ||
| 47 | const auto old_state{params->state}; | ||
| 48 | std::memcpy(params, in_specific, sizeof(ParameterVersion1)); | ||
| 49 | mix_id = in_params.mix_id; | ||
| 50 | process_order = in_params.process_order; | ||
| 51 | enabled = in_params.enabled; | ||
| 52 | |||
| 53 | if (!IsChannelCountValid(in_specific->channel_count)) { | ||
| 54 | params->channel_count = params->channel_count_max; | ||
| 55 | } | ||
| 56 | |||
| 57 | if (!IsChannelCountValid(in_specific->channel_count) || | ||
| 58 | old_state != ParameterState::Updated) { | ||
| 59 | params->state = old_state; | ||
| 60 | } | ||
| 61 | |||
| 62 | if (buffer_unmapped || in_params.is_new) { | ||
| 63 | usage_state = UsageState::New; | ||
| 64 | params->state = ParameterState::Initialized; | ||
| 65 | buffer_unmapped = !pool_mapper.TryAttachBuffer( | ||
| 66 | error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); | ||
| 67 | return; | ||
| 68 | } | ||
| 69 | } | ||
| 70 | error_info.error_code = ResultSuccess; | ||
| 71 | error_info.address = CpuAddr(0); | ||
| 72 | } | ||
| 73 | |||
| 74 | void I3dl2ReverbInfo::UpdateForCommandGeneration() { | ||
| 75 | if (enabled) { | ||
| 76 | usage_state = UsageState::Enabled; | ||
| 77 | } else { | ||
| 78 | usage_state = UsageState::Disabled; | ||
| 79 | } | ||
| 80 | |||
| 81 | auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; | ||
| 82 | params->state = ParameterState::Updated; | ||
| 83 | } | ||
| 84 | |||
| 85 | void I3dl2ReverbInfo::InitializeResultState(EffectResultState& result_state) {} | ||
| 86 | |||
| 87 | void I3dl2ReverbInfo::UpdateResultState(EffectResultState& cpu_state, | ||
| 88 | EffectResultState& dsp_state) {} | ||
| 89 | |||
| 90 | CpuAddr I3dl2ReverbInfo::GetWorkbuffer(s32 index) { | ||
| 91 | return GetSingleBuffer(index); | ||
| 92 | } | ||
| 93 | |||
| 94 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/i3dl2.h b/src/audio_core/renderer/effect/i3dl2.h new file mode 100644 index 000000000..7a088a627 --- /dev/null +++ b/src/audio_core/renderer/effect/i3dl2.h | |||
| @@ -0,0 +1,200 @@ | |||
| 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 <vector> | ||
| 8 | |||
| 9 | #include "audio_core/common/common.h" | ||
| 10 | #include "audio_core/renderer/effect/effect_info_base.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/fixed_point.h" | ||
| 13 | |||
| 14 | namespace AudioCore::AudioRenderer { | ||
| 15 | |||
| 16 | class I3dl2ReverbInfo : public EffectInfoBase { | ||
| 17 | public: | ||
| 18 | struct ParameterVersion1 { | ||
| 19 | /* 0x00 */ std::array<s8, MaxChannels> inputs; | ||
| 20 | /* 0x06 */ std::array<s8, MaxChannels> outputs; | ||
| 21 | /* 0x0C */ u16 channel_count_max; | ||
| 22 | /* 0x0E */ u16 channel_count; | ||
| 23 | /* 0x10 */ char unk10[0x4]; | ||
| 24 | /* 0x14 */ u32 sample_rate; | ||
| 25 | /* 0x18 */ f32 room_HF_gain; | ||
| 26 | /* 0x1C */ f32 reference_HF; | ||
| 27 | /* 0x20 */ f32 late_reverb_decay_time; | ||
| 28 | /* 0x24 */ f32 late_reverb_HF_decay_ratio; | ||
| 29 | /* 0x28 */ f32 room_gain; | ||
| 30 | /* 0x2C */ f32 reflection_gain; | ||
| 31 | /* 0x30 */ f32 reverb_gain; | ||
| 32 | /* 0x34 */ f32 late_reverb_diffusion; | ||
| 33 | /* 0x38 */ f32 reflection_delay; | ||
| 34 | /* 0x3C */ f32 late_reverb_delay_time; | ||
| 35 | /* 0x40 */ f32 late_reverb_density; | ||
| 36 | /* 0x44 */ f32 dry_gain; | ||
| 37 | /* 0x48 */ ParameterState state; | ||
| 38 | /* 0x49 */ char unk49[0x3]; | ||
| 39 | }; | ||
| 40 | static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), | ||
| 41 | "I3dl2ReverbInfo::ParameterVersion1 has the wrong size!"); | ||
| 42 | |||
| 43 | struct ParameterVersion2 { | ||
| 44 | /* 0x00 */ std::array<s8, MaxChannels> inputs; | ||
| 45 | /* 0x06 */ std::array<s8, MaxChannels> outputs; | ||
| 46 | /* 0x0C */ u16 channel_count_max; | ||
| 47 | /* 0x0E */ u16 channel_count; | ||
| 48 | /* 0x10 */ char unk10[0x4]; | ||
| 49 | /* 0x14 */ u32 sample_rate; | ||
| 50 | /* 0x18 */ f32 room_HF_gain; | ||
| 51 | /* 0x1C */ f32 reference_HF; | ||
| 52 | /* 0x20 */ f32 late_reverb_decay_time; | ||
| 53 | /* 0x24 */ f32 late_reverb_HF_decay_ratio; | ||
| 54 | /* 0x28 */ f32 room_gain; | ||
| 55 | /* 0x2C */ f32 reflection_gain; | ||
| 56 | /* 0x30 */ f32 reverb_gain; | ||
| 57 | /* 0x34 */ f32 late_reverb_diffusion; | ||
| 58 | /* 0x38 */ f32 reflection_delay; | ||
| 59 | /* 0x3C */ f32 late_reverb_delay_time; | ||
| 60 | /* 0x40 */ f32 late_reverb_density; | ||
| 61 | /* 0x44 */ f32 dry_gain; | ||
| 62 | /* 0x48 */ ParameterState state; | ||
| 63 | /* 0x49 */ char unk49[0x3]; | ||
| 64 | }; | ||
| 65 | static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), | ||
| 66 | "I3dl2ReverbInfo::ParameterVersion2 has the wrong size!"); | ||
| 67 | |||
| 68 | static constexpr u32 MaxDelayLines = 4; | ||
| 69 | static constexpr u32 MaxDelayTaps = 20; | ||
| 70 | |||
| 71 | struct I3dl2DelayLine { | ||
| 72 | void Initialize(const s32 delay_time) { | ||
| 73 | max_delay = delay_time; | ||
| 74 | buffer.resize(delay_time + 1, 0); | ||
| 75 | buffer_end = &buffer[delay_time]; | ||
| 76 | output = &buffer[0]; | ||
| 77 | SetDelay(delay_time); | ||
| 78 | wet_gain = 0.0f; | ||
| 79 | } | ||
| 80 | |||
| 81 | void SetDelay(const s32 delay_time) { | ||
| 82 | if (max_delay < delay_time) { | ||
| 83 | return; | ||
| 84 | } | ||
| 85 | delay = delay_time; | ||
| 86 | input = &buffer[(output - buffer.data() + delay) % (max_delay + 1)]; | ||
| 87 | } | ||
| 88 | |||
| 89 | Common::FixedPoint<50, 14> Tick(const Common::FixedPoint<50, 14> sample) { | ||
| 90 | Write(sample); | ||
| 91 | |||
| 92 | auto out_sample{Read()}; | ||
| 93 | |||
| 94 | output++; | ||
| 95 | if (output >= buffer_end) { | ||
| 96 | output = buffer.data(); | ||
| 97 | } | ||
| 98 | |||
| 99 | return out_sample; | ||
| 100 | } | ||
| 101 | |||
| 102 | Common::FixedPoint<50, 14> Read() { | ||
| 103 | return *output; | ||
| 104 | } | ||
| 105 | |||
| 106 | void Write(const Common::FixedPoint<50, 14> sample) { | ||
| 107 | *(input++) = sample; | ||
| 108 | if (input >= buffer_end) { | ||
| 109 | input = buffer.data(); | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | Common::FixedPoint<50, 14> TapOut(const s32 index) { | ||
| 114 | auto out{input - (index + 1)}; | ||
| 115 | if (out < buffer.data()) { | ||
| 116 | out += max_delay + 1; | ||
| 117 | } | ||
| 118 | return *out; | ||
| 119 | } | ||
| 120 | |||
| 121 | std::vector<Common::FixedPoint<50, 14>> buffer{}; | ||
| 122 | Common::FixedPoint<50, 14>* buffer_end{}; | ||
| 123 | s32 max_delay{}; | ||
| 124 | Common::FixedPoint<50, 14>* input{}; | ||
| 125 | Common::FixedPoint<50, 14>* output{}; | ||
| 126 | s32 delay{}; | ||
| 127 | f32 wet_gain{}; | ||
| 128 | }; | ||
| 129 | |||
| 130 | struct State { | ||
| 131 | f32 lowpass_0; | ||
| 132 | f32 lowpass_1; | ||
| 133 | f32 lowpass_2; | ||
| 134 | I3dl2DelayLine early_delay_line; | ||
| 135 | std::array<s32, MaxDelayTaps> early_tap_steps; | ||
| 136 | f32 early_gain; | ||
| 137 | f32 late_gain; | ||
| 138 | s32 early_to_late_taps; | ||
| 139 | std::array<I3dl2DelayLine, MaxDelayLines> fdn_delay_lines; | ||
| 140 | std::array<I3dl2DelayLine, MaxDelayLines> decay_delay_lines0; | ||
| 141 | std::array<I3dl2DelayLine, MaxDelayLines> decay_delay_lines1; | ||
| 142 | f32 last_reverb_echo; | ||
| 143 | I3dl2DelayLine center_delay_line; | ||
| 144 | std::array<std::array<f32, 3>, MaxDelayLines> lowpass_coeff; | ||
| 145 | std::array<f32, MaxDelayLines> shelf_filter; | ||
| 146 | f32 dry_gain; | ||
| 147 | }; | ||
| 148 | static_assert(sizeof(State) <= sizeof(EffectInfoBase::State), | ||
| 149 | "I3dl2ReverbInfo::State is too large!"); | ||
| 150 | |||
| 151 | /** | ||
| 152 | * Update the info with new parameters, version 1. | ||
| 153 | * | ||
| 154 | * @param error_info - Used to write call result code. | ||
| 155 | * @param in_params - New parameters to update the info with. | ||
| 156 | * @param pool_mapper - Pool for mapping buffers. | ||
| 157 | */ | ||
| 158 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, | ||
| 159 | const PoolMapper& pool_mapper) override; | ||
| 160 | |||
| 161 | /** | ||
| 162 | * Update the info with new parameters, version 2. | ||
| 163 | * | ||
| 164 | * @param error_info - Used to write call result code. | ||
| 165 | * @param in_params - New parameters to update the info with. | ||
| 166 | * @param pool_mapper - Pool for mapping buffers. | ||
| 167 | */ | ||
| 168 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, | ||
| 169 | const PoolMapper& pool_mapper) override; | ||
| 170 | |||
| 171 | /** | ||
| 172 | * Update the info after command generation. Usually only changes its state. | ||
| 173 | */ | ||
| 174 | void UpdateForCommandGeneration() override; | ||
| 175 | |||
| 176 | /** | ||
| 177 | * Initialize a new result state. Version 2 only, unused. | ||
| 178 | * | ||
| 179 | * @param result_state - Result state to initialize. | ||
| 180 | */ | ||
| 181 | void InitializeResultState(EffectResultState& result_state) override; | ||
| 182 | |||
| 183 | /** | ||
| 184 | * Update the host-side state with the ADSP-side state. Version 2 only, unused. | ||
| 185 | * | ||
| 186 | * @param cpu_state - Host-side result state to update. | ||
| 187 | * @param dsp_state - AudioRenderer-side result state to update from. | ||
| 188 | */ | ||
| 189 | void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; | ||
| 190 | |||
| 191 | /** | ||
| 192 | * Get a workbuffer assigned to this effect with the given index. | ||
| 193 | * | ||
| 194 | * @param index - Workbuffer index. | ||
| 195 | * @return Address of the buffer. | ||
| 196 | */ | ||
| 197 | CpuAddr GetWorkbuffer(s32 index) override; | ||
| 198 | }; | ||
| 199 | |||
| 200 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/light_limiter.cpp b/src/audio_core/renderer/effect/light_limiter.cpp new file mode 100644 index 000000000..1635a952d --- /dev/null +++ b/src/audio_core/renderer/effect/light_limiter.cpp | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/effect/light_limiter.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info, | ||
| 9 | const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { | ||
| 10 | auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; | ||
| 11 | auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; | ||
| 12 | |||
| 13 | std::memcpy(params, in_specific, sizeof(ParameterVersion1)); | ||
| 14 | mix_id = in_params.mix_id; | ||
| 15 | process_order = in_params.process_order; | ||
| 16 | enabled = in_params.enabled; | ||
| 17 | |||
| 18 | if (buffer_unmapped || in_params.is_new) { | ||
| 19 | usage_state = UsageState::New; | ||
| 20 | params->state = ParameterState::Initialized; | ||
| 21 | buffer_unmapped = !pool_mapper.TryAttachBuffer( | ||
| 22 | error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); | ||
| 23 | } else { | ||
| 24 | error_info.error_code = ResultSuccess; | ||
| 25 | error_info.address = CpuAddr(0); | ||
| 26 | } | ||
| 27 | } | ||
| 28 | |||
| 29 | void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info, | ||
| 30 | const InParameterVersion2& in_params, const PoolMapper& pool_mapper) { | ||
| 31 | auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; | ||
| 32 | auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; | ||
| 33 | |||
| 34 | std::memcpy(params, in_specific, sizeof(ParameterVersion1)); | ||
| 35 | mix_id = in_params.mix_id; | ||
| 36 | process_order = in_params.process_order; | ||
| 37 | enabled = in_params.enabled; | ||
| 38 | |||
| 39 | if (buffer_unmapped || in_params.is_new) { | ||
| 40 | usage_state = UsageState::New; | ||
| 41 | params->state = ParameterState::Initialized; | ||
| 42 | buffer_unmapped = !pool_mapper.TryAttachBuffer( | ||
| 43 | error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); | ||
| 44 | } else { | ||
| 45 | error_info.error_code = ResultSuccess; | ||
| 46 | error_info.address = CpuAddr(0); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | void LightLimiterInfo::UpdateForCommandGeneration() { | ||
| 51 | if (enabled) { | ||
| 52 | usage_state = UsageState::Enabled; | ||
| 53 | } else { | ||
| 54 | usage_state = UsageState::Disabled; | ||
| 55 | } | ||
| 56 | |||
| 57 | auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; | ||
| 58 | params->state = ParameterState::Updated; | ||
| 59 | params->statistics_reset_required = false; | ||
| 60 | } | ||
| 61 | |||
| 62 | void LightLimiterInfo::InitializeResultState(EffectResultState& result_state) { | ||
| 63 | auto result_state_{reinterpret_cast<StatisticsInternal*>(result_state.state.data())}; | ||
| 64 | |||
| 65 | result_state_->channel_max_sample.fill(0); | ||
| 66 | result_state_->channel_compression_gain_min.fill(1.0f); | ||
| 67 | } | ||
| 68 | |||
| 69 | void LightLimiterInfo::UpdateResultState(EffectResultState& cpu_state, | ||
| 70 | EffectResultState& dsp_state) { | ||
| 71 | auto cpu_statistics{reinterpret_cast<StatisticsInternal*>(cpu_state.state.data())}; | ||
| 72 | auto dsp_statistics{reinterpret_cast<StatisticsInternal*>(dsp_state.state.data())}; | ||
| 73 | |||
| 74 | *cpu_statistics = *dsp_statistics; | ||
| 75 | } | ||
| 76 | |||
| 77 | CpuAddr LightLimiterInfo::GetWorkbuffer(s32 index) { | ||
| 78 | return GetSingleBuffer(index); | ||
| 79 | } | ||
| 80 | |||
| 81 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/light_limiter.h b/src/audio_core/renderer/effect/light_limiter.h new file mode 100644 index 000000000..338d67bbc --- /dev/null +++ b/src/audio_core/renderer/effect/light_limiter.h | |||
| @@ -0,0 +1,138 @@ | |||
| 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 <vector> | ||
| 8 | |||
| 9 | #include "audio_core/common/common.h" | ||
| 10 | #include "audio_core/renderer/effect/effect_info_base.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/fixed_point.h" | ||
| 13 | |||
| 14 | namespace AudioCore::AudioRenderer { | ||
| 15 | |||
| 16 | class LightLimiterInfo : public EffectInfoBase { | ||
| 17 | public: | ||
| 18 | enum class ProcessingMode { | ||
| 19 | Mode0, | ||
| 20 | Mode1, | ||
| 21 | }; | ||
| 22 | |||
| 23 | struct ParameterVersion1 { | ||
| 24 | /* 0x00 */ std::array<s8, MaxChannels> inputs; | ||
| 25 | /* 0x06 */ std::array<s8, MaxChannels> outputs; | ||
| 26 | /* 0x0C */ u16 channel_count_max; | ||
| 27 | /* 0x0E */ u16 channel_count; | ||
| 28 | /* 0x0C */ u32 sample_rate; | ||
| 29 | /* 0x14 */ s32 look_ahead_time_max; | ||
| 30 | /* 0x18 */ s32 attack_time; | ||
| 31 | /* 0x1C */ s32 release_time; | ||
| 32 | /* 0x20 */ s32 look_ahead_time; | ||
| 33 | /* 0x24 */ f32 attack_coeff; | ||
| 34 | /* 0x28 */ f32 release_coeff; | ||
| 35 | /* 0x2C */ f32 threshold; | ||
| 36 | /* 0x30 */ f32 input_gain; | ||
| 37 | /* 0x34 */ f32 output_gain; | ||
| 38 | /* 0x38 */ s32 look_ahead_samples_min; | ||
| 39 | /* 0x3C */ s32 look_ahead_samples_max; | ||
| 40 | /* 0x40 */ ParameterState state; | ||
| 41 | /* 0x41 */ bool statistics_enabled; | ||
| 42 | /* 0x42 */ bool statistics_reset_required; | ||
| 43 | /* 0x43 */ ProcessingMode processing_mode; | ||
| 44 | }; | ||
| 45 | static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), | ||
| 46 | "LightLimiterInfo::ParameterVersion1 has the wrong size!"); | ||
| 47 | |||
| 48 | struct ParameterVersion2 { | ||
| 49 | /* 0x00 */ std::array<s8, MaxChannels> inputs; | ||
| 50 | /* 0x06 */ std::array<s8, MaxChannels> outputs; | ||
| 51 | /* 0x0C */ u16 channel_count_max; | ||
| 52 | /* 0x0E */ u16 channel_count; | ||
| 53 | /* 0x0C */ u32 sample_rate; | ||
| 54 | /* 0x14 */ s32 look_ahead_time_max; | ||
| 55 | /* 0x18 */ s32 attack_time; | ||
| 56 | /* 0x1C */ s32 release_time; | ||
| 57 | /* 0x20 */ s32 look_ahead_time; | ||
| 58 | /* 0x24 */ f32 attack_coeff; | ||
| 59 | /* 0x28 */ f32 release_coeff; | ||
| 60 | /* 0x2C */ f32 threshold; | ||
| 61 | /* 0x30 */ f32 input_gain; | ||
| 62 | /* 0x34 */ f32 output_gain; | ||
| 63 | /* 0x38 */ s32 look_ahead_samples_min; | ||
| 64 | /* 0x3C */ s32 look_ahead_samples_max; | ||
| 65 | /* 0x40 */ ParameterState state; | ||
| 66 | /* 0x41 */ bool statistics_enabled; | ||
| 67 | /* 0x42 */ bool statistics_reset_required; | ||
| 68 | /* 0x43 */ ProcessingMode processing_mode; | ||
| 69 | }; | ||
| 70 | static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), | ||
| 71 | "LightLimiterInfo::ParameterVersion2 has the wrong size!"); | ||
| 72 | |||
| 73 | struct State { | ||
| 74 | std::array<Common::FixedPoint<49, 15>, MaxChannels> samples_average; | ||
| 75 | std::array<Common::FixedPoint<49, 15>, MaxChannels> compression_gain; | ||
| 76 | std::array<s32, MaxChannels> look_ahead_sample_offsets; | ||
| 77 | std::array<std::vector<Common::FixedPoint<49, 15>>, MaxChannels> look_ahead_sample_buffers; | ||
| 78 | }; | ||
| 79 | static_assert(sizeof(State) <= sizeof(EffectInfoBase::State), | ||
| 80 | "LightLimiterInfo::State has the wrong size!"); | ||
| 81 | |||
| 82 | struct StatisticsInternal { | ||
| 83 | /* 0x00 */ std::array<f32, MaxChannels> channel_max_sample; | ||
| 84 | /* 0x18 */ std::array<f32, MaxChannels> channel_compression_gain_min; | ||
| 85 | }; | ||
| 86 | static_assert(sizeof(StatisticsInternal) == 0x30, | ||
| 87 | "LightLimiterInfo::StatisticsInternal has the wrong size!"); | ||
| 88 | |||
| 89 | /** | ||
| 90 | * Update the info with new parameters, version 1. | ||
| 91 | * | ||
| 92 | * @param error_info - Used to write call result code. | ||
| 93 | * @param in_params - New parameters to update the info with. | ||
| 94 | * @param pool_mapper - Pool for mapping buffers. | ||
| 95 | */ | ||
| 96 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, | ||
| 97 | const PoolMapper& pool_mapper) override; | ||
| 98 | |||
| 99 | /** | ||
| 100 | * Update the info with new parameters, version 2. | ||
| 101 | * | ||
| 102 | * @param error_info - Used to write call result code. | ||
| 103 | * @param in_params - New parameters to update the info with. | ||
| 104 | * @param pool_mapper - Pool for mapping buffers. | ||
| 105 | */ | ||
| 106 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, | ||
| 107 | const PoolMapper& pool_mapper) override; | ||
| 108 | |||
| 109 | /** | ||
| 110 | * Update the info after command generation. Usually only changes its state. | ||
| 111 | */ | ||
| 112 | void UpdateForCommandGeneration() override; | ||
| 113 | |||
| 114 | /** | ||
| 115 | * Initialize a new limiter statistics result state. Version 2 only. | ||
| 116 | * | ||
| 117 | * @param result_state - Result state to initialize. | ||
| 118 | */ | ||
| 119 | void InitializeResultState(EffectResultState& result_state) override; | ||
| 120 | |||
| 121 | /** | ||
| 122 | * Update the host-side limiter statistics with the ADSP-side one. Version 2 only. | ||
| 123 | * | ||
| 124 | * @param cpu_state - Host-side result state to update. | ||
| 125 | * @param dsp_state - AudioRenderer-side result state to update from. | ||
| 126 | */ | ||
| 127 | void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; | ||
| 128 | |||
| 129 | /** | ||
| 130 | * Get a workbuffer assigned to this effect with the given index. | ||
| 131 | * | ||
| 132 | * @param index - Workbuffer index. | ||
| 133 | * @return Address of the buffer. | ||
| 134 | */ | ||
| 135 | CpuAddr GetWorkbuffer(s32 index) override; | ||
| 136 | }; | ||
| 137 | |||
| 138 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/reverb.cpp b/src/audio_core/renderer/effect/reverb.cpp new file mode 100644 index 000000000..2d32383d0 --- /dev/null +++ b/src/audio_core/renderer/effect/reverb.cpp | |||
| @@ -0,0 +1,93 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/effect/reverb.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, | ||
| 9 | const PoolMapper& pool_mapper) { | ||
| 10 | auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())}; | ||
| 11 | auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; | ||
| 12 | |||
| 13 | if (IsChannelCountValid(in_specific->channel_count_max)) { | ||
| 14 | const auto old_state{params->state}; | ||
| 15 | std::memcpy(params, in_specific, sizeof(ParameterVersion1)); | ||
| 16 | mix_id = in_params.mix_id; | ||
| 17 | process_order = in_params.process_order; | ||
| 18 | enabled = in_params.enabled; | ||
| 19 | |||
| 20 | if (!IsChannelCountValid(in_specific->channel_count)) { | ||
| 21 | params->channel_count = params->channel_count_max; | ||
| 22 | } | ||
| 23 | |||
| 24 | if (!IsChannelCountValid(in_specific->channel_count) || | ||
| 25 | old_state != ParameterState::Updated) { | ||
| 26 | params->state = old_state; | ||
| 27 | } | ||
| 28 | |||
| 29 | if (buffer_unmapped || in_params.is_new) { | ||
| 30 | usage_state = UsageState::New; | ||
| 31 | params->state = ParameterState::Initialized; | ||
| 32 | buffer_unmapped = !pool_mapper.TryAttachBuffer( | ||
| 33 | error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); | ||
| 34 | return; | ||
| 35 | } | ||
| 36 | } | ||
| 37 | error_info.error_code = ResultSuccess; | ||
| 38 | error_info.address = CpuAddr(0); | ||
| 39 | } | ||
| 40 | |||
| 41 | void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, | ||
| 42 | const PoolMapper& pool_mapper) { | ||
| 43 | auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())}; | ||
| 44 | auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())}; | ||
| 45 | |||
| 46 | if (IsChannelCountValid(in_specific->channel_count_max)) { | ||
| 47 | const auto old_state{params->state}; | ||
| 48 | std::memcpy(params, in_specific, sizeof(ParameterVersion2)); | ||
| 49 | mix_id = in_params.mix_id; | ||
| 50 | process_order = in_params.process_order; | ||
| 51 | enabled = in_params.enabled; | ||
| 52 | |||
| 53 | if (!IsChannelCountValid(in_specific->channel_count)) { | ||
| 54 | params->channel_count = params->channel_count_max; | ||
| 55 | } | ||
| 56 | |||
| 57 | if (!IsChannelCountValid(in_specific->channel_count) || | ||
| 58 | old_state != ParameterState::Updated) { | ||
| 59 | params->state = old_state; | ||
| 60 | } | ||
| 61 | |||
| 62 | if (buffer_unmapped || in_params.is_new) { | ||
| 63 | usage_state = UsageState::New; | ||
| 64 | params->state = ParameterState::Initialized; | ||
| 65 | buffer_unmapped = !pool_mapper.TryAttachBuffer( | ||
| 66 | error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); | ||
| 67 | return; | ||
| 68 | } | ||
| 69 | } | ||
| 70 | error_info.error_code = ResultSuccess; | ||
| 71 | error_info.address = CpuAddr(0); | ||
| 72 | } | ||
| 73 | |||
| 74 | void ReverbInfo::UpdateForCommandGeneration() { | ||
| 75 | if (enabled) { | ||
| 76 | usage_state = UsageState::Enabled; | ||
| 77 | } else { | ||
| 78 | usage_state = UsageState::Disabled; | ||
| 79 | } | ||
| 80 | |||
| 81 | auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; | ||
| 82 | params->state = ParameterState::Updated; | ||
| 83 | } | ||
| 84 | |||
| 85 | void ReverbInfo::InitializeResultState(EffectResultState& result_state) {} | ||
| 86 | |||
| 87 | void ReverbInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {} | ||
| 88 | |||
| 89 | CpuAddr ReverbInfo::GetWorkbuffer(s32 index) { | ||
| 90 | return GetSingleBuffer(index); | ||
| 91 | } | ||
| 92 | |||
| 93 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/effect/reverb.h b/src/audio_core/renderer/effect/reverb.h new file mode 100644 index 000000000..b4df9f6ef --- /dev/null +++ b/src/audio_core/renderer/effect/reverb.h | |||
| @@ -0,0 +1,190 @@ | |||
| 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 <vector> | ||
| 8 | |||
| 9 | #include "audio_core/common/common.h" | ||
| 10 | #include "audio_core/renderer/effect/effect_info_base.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/fixed_point.h" | ||
| 13 | |||
| 14 | namespace AudioCore::AudioRenderer { | ||
| 15 | |||
| 16 | class ReverbInfo : public EffectInfoBase { | ||
| 17 | public: | ||
| 18 | struct ParameterVersion1 { | ||
| 19 | /* 0x00 */ std::array<s8, MaxChannels> inputs; | ||
| 20 | /* 0x06 */ std::array<s8, MaxChannels> outputs; | ||
| 21 | /* 0x0C */ u16 channel_count_max; | ||
| 22 | /* 0x0E */ u16 channel_count; | ||
| 23 | /* 0x10 */ u32 sample_rate; | ||
| 24 | /* 0x14 */ u32 early_mode; | ||
| 25 | /* 0x18 */ s32 early_gain; | ||
| 26 | /* 0x1C */ s32 pre_delay; | ||
| 27 | /* 0x20 */ s32 late_mode; | ||
| 28 | /* 0x24 */ s32 late_gain; | ||
| 29 | /* 0x28 */ s32 decay_time; | ||
| 30 | /* 0x2C */ s32 high_freq_Decay_ratio; | ||
| 31 | /* 0x30 */ s32 colouration; | ||
| 32 | /* 0x34 */ s32 base_gain; | ||
| 33 | /* 0x38 */ s32 wet_gain; | ||
| 34 | /* 0x3C */ s32 dry_gain; | ||
| 35 | /* 0x40 */ ParameterState state; | ||
| 36 | }; | ||
| 37 | static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), | ||
| 38 | "ReverbInfo::ParameterVersion1 has the wrong size!"); | ||
| 39 | |||
| 40 | struct ParameterVersion2 { | ||
| 41 | /* 0x00 */ std::array<s8, MaxChannels> inputs; | ||
| 42 | /* 0x06 */ std::array<s8, MaxChannels> outputs; | ||
| 43 | /* 0x0C */ u16 channel_count_max; | ||
| 44 | /* 0x0E */ u16 channel_count; | ||
| 45 | /* 0x10 */ u32 sample_rate; | ||
| 46 | /* 0x14 */ u32 early_mode; | ||
| 47 | /* 0x18 */ s32 early_gain; | ||
| 48 | /* 0x1C */ s32 pre_delay; | ||
| 49 | /* 0x20 */ s32 late_mode; | ||
| 50 | /* 0x24 */ s32 late_gain; | ||
| 51 | /* 0x28 */ s32 decay_time; | ||
| 52 | /* 0x2C */ s32 high_freq_decay_ratio; | ||
| 53 | /* 0x30 */ s32 colouration; | ||
| 54 | /* 0x34 */ s32 base_gain; | ||
| 55 | /* 0x38 */ s32 wet_gain; | ||
| 56 | /* 0x3C */ s32 dry_gain; | ||
| 57 | /* 0x40 */ ParameterState state; | ||
| 58 | }; | ||
| 59 | static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), | ||
| 60 | "ReverbInfo::ParameterVersion2 has the wrong size!"); | ||
| 61 | |||
| 62 | static constexpr u32 MaxDelayLines = 4; | ||
| 63 | static constexpr u32 MaxDelayTaps = 10; | ||
| 64 | static constexpr u32 NumEarlyModes = 5; | ||
| 65 | static constexpr u32 NumLateModes = 5; | ||
| 66 | |||
| 67 | struct ReverbDelayLine { | ||
| 68 | void Initialize(const s32 delay_time, const f32 decay_rate) { | ||
| 69 | buffer.resize(delay_time + 1, 0); | ||
| 70 | buffer_end = &buffer[delay_time]; | ||
| 71 | output = &buffer[0]; | ||
| 72 | decay = decay_rate; | ||
| 73 | sample_count_max = delay_time; | ||
| 74 | SetDelay(delay_time); | ||
| 75 | } | ||
| 76 | |||
| 77 | void SetDelay(const s32 delay_time) { | ||
| 78 | if (sample_count_max < delay_time) { | ||
| 79 | return; | ||
| 80 | } | ||
| 81 | sample_count = delay_time; | ||
| 82 | input = &buffer[(output - buffer.data() + sample_count) % (sample_count_max + 1)]; | ||
| 83 | } | ||
| 84 | |||
| 85 | Common::FixedPoint<50, 14> Tick(const Common::FixedPoint<50, 14> sample) { | ||
| 86 | Write(sample); | ||
| 87 | |||
| 88 | auto out_sample{Read()}; | ||
| 89 | |||
| 90 | output++; | ||
| 91 | if (output >= buffer_end) { | ||
| 92 | output = buffer.data(); | ||
| 93 | } | ||
| 94 | |||
| 95 | return out_sample; | ||
| 96 | } | ||
| 97 | |||
| 98 | Common::FixedPoint<50, 14> Read() { | ||
| 99 | return *output; | ||
| 100 | } | ||
| 101 | |||
| 102 | void Write(const Common::FixedPoint<50, 14> sample) { | ||
| 103 | *(input++) = sample; | ||
| 104 | if (input >= buffer_end) { | ||
| 105 | input = buffer.data(); | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | Common::FixedPoint<50, 14> TapOut(const s32 index) { | ||
| 110 | auto out{input - (index + 1)}; | ||
| 111 | if (out < buffer.data()) { | ||
| 112 | out += sample_count; | ||
| 113 | } | ||
| 114 | return *out; | ||
| 115 | } | ||
| 116 | |||
| 117 | s32 sample_count{}; | ||
| 118 | s32 sample_count_max{}; | ||
| 119 | std::vector<Common::FixedPoint<50, 14>> buffer{}; | ||
| 120 | Common::FixedPoint<50, 14>* buffer_end; | ||
| 121 | Common::FixedPoint<50, 14>* input{}; | ||
| 122 | Common::FixedPoint<50, 14>* output{}; | ||
| 123 | Common::FixedPoint<50, 14> decay{}; | ||
| 124 | }; | ||
| 125 | |||
| 126 | struct State { | ||
| 127 | ReverbDelayLine pre_delay_line; | ||
| 128 | ReverbDelayLine center_delay_line; | ||
| 129 | std::array<s32, MaxDelayTaps> early_delay_times; | ||
| 130 | std::array<Common::FixedPoint<50, 14>, MaxDelayTaps> early_gains; | ||
| 131 | s32 pre_delay_time; | ||
| 132 | std::array<ReverbDelayLine, MaxDelayLines> decay_delay_lines; | ||
| 133 | std::array<ReverbDelayLine, MaxDelayLines> fdn_delay_lines; | ||
| 134 | std::array<Common::FixedPoint<50, 14>, MaxDelayLines> hf_decay_gain; | ||
| 135 | std::array<Common::FixedPoint<50, 14>, MaxDelayLines> hf_decay_prev_gain; | ||
| 136 | std::array<Common::FixedPoint<50, 14>, MaxDelayLines> prev_feedback_output; | ||
| 137 | }; | ||
| 138 | static_assert(sizeof(State) <= sizeof(EffectInfoBase::State), | ||
| 139 | "ReverbInfo::State is too large!"); | ||
| 140 | |||
| 141 | /** | ||
| 142 | * Update the info with new parameters, version 1. | ||
| 143 | * | ||
| 144 | * @param error_info - Used to write call result code. | ||
| 145 | * @param in_params - New parameters to update the info with. | ||
| 146 | * @param pool_mapper - Pool for mapping buffers. | ||
| 147 | */ | ||
| 148 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, | ||
| 149 | const PoolMapper& pool_mapper) override; | ||
| 150 | |||
| 151 | /** | ||
| 152 | * Update the info with new parameters, version 2. | ||
| 153 | * | ||
| 154 | * @param error_info - Used to write call result code. | ||
| 155 | * @param in_params - New parameters to update the info with. | ||
| 156 | * @param pool_mapper - Pool for mapping buffers. | ||
| 157 | */ | ||
| 158 | void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, | ||
| 159 | const PoolMapper& pool_mapper) override; | ||
| 160 | |||
| 161 | /** | ||
| 162 | * Update the info after command generation. Usually only changes its state. | ||
| 163 | */ | ||
| 164 | void UpdateForCommandGeneration() override; | ||
| 165 | |||
| 166 | /** | ||
| 167 | * Initialize a new result state. Version 2 only, unused. | ||
| 168 | * | ||
| 169 | * @param result_state - Result state to initialize. | ||
| 170 | */ | ||
| 171 | void InitializeResultState(EffectResultState& result_state) override; | ||
| 172 | |||
| 173 | /** | ||
| 174 | * Update the host-side state with the ADSP-side state. Version 2 only, unused. | ||
| 175 | * | ||
| 176 | * @param cpu_state - Host-side result state to update. | ||
| 177 | * @param dsp_state - AudioRenderer-side result state to update from. | ||
| 178 | */ | ||
| 179 | void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; | ||
| 180 | |||
| 181 | /** | ||
| 182 | * Get a workbuffer assigned to this effect with the given index. | ||
| 183 | * | ||
| 184 | * @param index - Workbuffer index. | ||
| 185 | * @return Address of the buffer. | ||
| 186 | */ | ||
| 187 | CpuAddr GetWorkbuffer(s32 index) override; | ||
| 188 | }; | ||
| 189 | |||
| 190 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/memory/address_info.h b/src/audio_core/renderer/memory/address_info.h new file mode 100644 index 000000000..4cfefea8e --- /dev/null +++ b/src/audio_core/renderer/memory/address_info.h | |||
| @@ -0,0 +1,125 @@ | |||
| 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/memory/memory_pool_info.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | /** | ||
| 12 | * Represents a region of mapped or unmapped memory. | ||
| 13 | */ | ||
| 14 | class AddressInfo { | ||
| 15 | public: | ||
| 16 | AddressInfo() = default; | ||
| 17 | AddressInfo(CpuAddr cpu_address_, u64 size_) : cpu_address{cpu_address_}, size{size_} {} | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Setup a new AddressInfo. | ||
| 21 | * | ||
| 22 | * @param cpu_address - The CPU address of this region. | ||
| 23 | * @param size - The size of this region. | ||
| 24 | */ | ||
| 25 | void Setup(CpuAddr cpu_address_, u64 size_) { | ||
| 26 | cpu_address = cpu_address_; | ||
| 27 | size = size_; | ||
| 28 | memory_pool = nullptr; | ||
| 29 | dsp_address = 0; | ||
| 30 | } | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Get the CPU address. | ||
| 34 | * | ||
| 35 | * @return The CpuAddr address | ||
| 36 | */ | ||
| 37 | CpuAddr GetCpuAddr() const { | ||
| 38 | return cpu_address; | ||
| 39 | } | ||
| 40 | |||
| 41 | /** | ||
| 42 | * Assign this region to a memory pool. | ||
| 43 | * | ||
| 44 | * @param memory_pool_ - Memory pool to assign. | ||
| 45 | * @return The CpuAddr address of this region. | ||
| 46 | */ | ||
| 47 | void SetPool(MemoryPoolInfo* memory_pool_) { | ||
| 48 | memory_pool = memory_pool_; | ||
| 49 | } | ||
| 50 | |||
| 51 | /** | ||
| 52 | * Get the size of this region. | ||
| 53 | * | ||
| 54 | * @return The size of this region. | ||
| 55 | */ | ||
| 56 | u64 GetSize() const { | ||
| 57 | return size; | ||
| 58 | } | ||
| 59 | |||
| 60 | /** | ||
| 61 | * Get the ADSP address for this region. | ||
| 62 | * | ||
| 63 | * @return The ADSP address for this region. | ||
| 64 | */ | ||
| 65 | CpuAddr GetForceMappedDspAddr() const { | ||
| 66 | return dsp_address; | ||
| 67 | } | ||
| 68 | |||
| 69 | /** | ||
| 70 | * Set the ADSP address for this region. | ||
| 71 | * | ||
| 72 | * @param dsp_addr - The new ADSP address for this region. | ||
| 73 | */ | ||
| 74 | void SetForceMappedDspAddr(CpuAddr dsp_addr) { | ||
| 75 | dsp_address = dsp_addr; | ||
| 76 | } | ||
| 77 | |||
| 78 | /** | ||
| 79 | * Check whether this region has an active memory pool. | ||
| 80 | * | ||
| 81 | * @return True if this region has a mapped memory pool, otherwise false. | ||
| 82 | */ | ||
| 83 | bool HasMappedMemoryPool() const { | ||
| 84 | return memory_pool != nullptr && memory_pool->GetDspAddress() != 0; | ||
| 85 | } | ||
| 86 | |||
| 87 | /** | ||
| 88 | * Check whether this region is mapped to the ADSP. | ||
| 89 | * | ||
| 90 | * @return True if this region is mapped, otherwise false. | ||
| 91 | */ | ||
| 92 | bool IsMapped() const { | ||
| 93 | return HasMappedMemoryPool() || dsp_address != 0; | ||
| 94 | } | ||
| 95 | |||
| 96 | /** | ||
| 97 | * Get a usable reference to this region of memory. | ||
| 98 | * | ||
| 99 | * @param mark_in_use - Whether this region should be marked as being in use. | ||
| 100 | * @return A valid memory address if valid, otherwise 0. | ||
| 101 | */ | ||
| 102 | CpuAddr GetReference(bool mark_in_use) { | ||
| 103 | if (!HasMappedMemoryPool()) { | ||
| 104 | return dsp_address; | ||
| 105 | } | ||
| 106 | |||
| 107 | if (mark_in_use) { | ||
| 108 | memory_pool->SetUsed(true); | ||
| 109 | } | ||
| 110 | |||
| 111 | return memory_pool->Translate(cpu_address, size); | ||
| 112 | } | ||
| 113 | |||
| 114 | private: | ||
| 115 | /// CPU address of this region | ||
| 116 | CpuAddr cpu_address; | ||
| 117 | /// Size of this region | ||
| 118 | u64 size; | ||
| 119 | /// The memory this region is mapped to | ||
| 120 | MemoryPoolInfo* memory_pool; | ||
| 121 | /// ADSP address of this region | ||
| 122 | CpuAddr dsp_address; | ||
| 123 | }; | ||
| 124 | |||
| 125 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/memory/memory_pool_info.cpp b/src/audio_core/renderer/memory/memory_pool_info.cpp new file mode 100644 index 000000000..9b7824af1 --- /dev/null +++ b/src/audio_core/renderer/memory/memory_pool_info.cpp | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/memory/memory_pool_info.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | CpuAddr MemoryPoolInfo::GetCpuAddress() const { | ||
| 9 | return cpu_address; | ||
| 10 | } | ||
| 11 | |||
| 12 | CpuAddr MemoryPoolInfo::GetDspAddress() const { | ||
| 13 | return dsp_address; | ||
| 14 | } | ||
| 15 | |||
| 16 | u64 MemoryPoolInfo::GetSize() const { | ||
| 17 | return size; | ||
| 18 | } | ||
| 19 | |||
| 20 | MemoryPoolInfo::Location MemoryPoolInfo::GetLocation() const { | ||
| 21 | return location; | ||
| 22 | } | ||
| 23 | |||
| 24 | void MemoryPoolInfo::SetCpuAddress(const CpuAddr address, const u64 size_) { | ||
| 25 | cpu_address = address; | ||
| 26 | size = size_; | ||
| 27 | } | ||
| 28 | |||
| 29 | void MemoryPoolInfo::SetDspAddress(const CpuAddr address) { | ||
| 30 | dsp_address = address; | ||
| 31 | } | ||
| 32 | |||
| 33 | bool MemoryPoolInfo::Contains(const CpuAddr address_, const u64 size_) const { | ||
| 34 | return cpu_address <= address_ && (address_ + size_) <= (cpu_address + size); | ||
| 35 | } | ||
| 36 | |||
| 37 | bool MemoryPoolInfo::IsMapped() const { | ||
| 38 | return dsp_address != 0; | ||
| 39 | } | ||
| 40 | |||
| 41 | CpuAddr MemoryPoolInfo::Translate(const CpuAddr address, const u64 size_) const { | ||
| 42 | if (!Contains(address, size_)) { | ||
| 43 | return 0; | ||
| 44 | } | ||
| 45 | |||
| 46 | if (!IsMapped()) { | ||
| 47 | return 0; | ||
| 48 | } | ||
| 49 | |||
| 50 | return dsp_address + (address - cpu_address); | ||
| 51 | } | ||
| 52 | |||
| 53 | void MemoryPoolInfo::SetUsed(const bool used) { | ||
| 54 | in_use = used; | ||
| 55 | } | ||
| 56 | |||
| 57 | bool MemoryPoolInfo::IsUsed() const { | ||
| 58 | return in_use; | ||
| 59 | } | ||
| 60 | |||
| 61 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/memory/memory_pool_info.h b/src/audio_core/renderer/memory/memory_pool_info.h new file mode 100644 index 000000000..537a466ec --- /dev/null +++ b/src/audio_core/renderer/memory/memory_pool_info.h | |||
| @@ -0,0 +1,170 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <memory> | ||
| 7 | |||
| 8 | #include "audio_core/common/common.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | /** | ||
| 13 | * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper). | ||
| 14 | */ | ||
| 15 | class MemoryPoolInfo { | ||
| 16 | public: | ||
| 17 | /** | ||
| 18 | * The location of this pool. | ||
| 19 | * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper). | ||
| 20 | * DSP pools are mapped in the current process sysmodule. | ||
| 21 | */ | ||
| 22 | enum class Location { | ||
| 23 | CPU = 1, | ||
| 24 | DSP = 2, | ||
| 25 | }; | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Current state of the pool | ||
| 29 | */ | ||
| 30 | enum class State { | ||
| 31 | Invalid, | ||
| 32 | Aquired, | ||
| 33 | RequestDetach, | ||
| 34 | Detached, | ||
| 35 | RequestAttach, | ||
| 36 | Attached, | ||
| 37 | Released, | ||
| 38 | }; | ||
| 39 | |||
| 40 | /** | ||
| 41 | * Result code for updating the pool (See InfoUpdater::Update) | ||
| 42 | */ | ||
| 43 | enum class ResultState { | ||
| 44 | Success, | ||
| 45 | BadParam, | ||
| 46 | MapFailed, | ||
| 47 | InUse, | ||
| 48 | }; | ||
| 49 | |||
| 50 | /** | ||
| 51 | * Input parameters coming from the game which are used to update current pools | ||
| 52 | * (See InfoUpdater::Update) | ||
| 53 | */ | ||
| 54 | struct InParameter { | ||
| 55 | /* 0x00 */ u64 address; | ||
| 56 | /* 0x08 */ u64 size; | ||
| 57 | /* 0x10 */ State state; | ||
| 58 | /* 0x14 */ bool in_use; | ||
| 59 | /* 0x18 */ char unk18[0x8]; | ||
| 60 | }; | ||
| 61 | static_assert(sizeof(InParameter) == 0x20, "MemoryPoolInfo::InParameter has the wrong size!"); | ||
| 62 | |||
| 63 | /** | ||
| 64 | * Output status sent back to the game on update (See InfoUpdater::Update) | ||
| 65 | */ | ||
| 66 | struct OutStatus { | ||
| 67 | /* 0x00 */ State state; | ||
| 68 | /* 0x04 */ char unk04[0xC]; | ||
| 69 | }; | ||
| 70 | static_assert(sizeof(OutStatus) == 0x10, "MemoryPoolInfo::OutStatus has the wrong size!"); | ||
| 71 | |||
| 72 | MemoryPoolInfo() = default; | ||
| 73 | MemoryPoolInfo(Location location_) : location{location_} {} | ||
| 74 | |||
| 75 | /** | ||
| 76 | * Get the CPU address for this pool. | ||
| 77 | * | ||
| 78 | * @return The CPU address of this pool. | ||
| 79 | */ | ||
| 80 | CpuAddr GetCpuAddress() const; | ||
| 81 | |||
| 82 | /** | ||
| 83 | * Get the DSP address for this pool. | ||
| 84 | * | ||
| 85 | * @return The DSP address of this pool. | ||
| 86 | */ | ||
| 87 | CpuAddr GetDspAddress() const; | ||
| 88 | |||
| 89 | /** | ||
| 90 | * Get the size of this pool. | ||
| 91 | * | ||
| 92 | * @return The size of this pool. | ||
| 93 | */ | ||
| 94 | u64 GetSize() const; | ||
| 95 | |||
| 96 | /** | ||
| 97 | * Get the location of this pool. | ||
| 98 | * | ||
| 99 | * @return The location for the pool (see MemoryPoolInfo::Location). | ||
| 100 | */ | ||
| 101 | Location GetLocation() const; | ||
| 102 | |||
| 103 | /** | ||
| 104 | * Set the CPU address for this pool. | ||
| 105 | * | ||
| 106 | * @param address - The new CPU address for this pool. | ||
| 107 | * @param size - The new size for this pool. | ||
| 108 | */ | ||
| 109 | void SetCpuAddress(CpuAddr address, u64 size); | ||
| 110 | |||
| 111 | /** | ||
| 112 | * Set the DSP address for this pool. | ||
| 113 | * | ||
| 114 | * @param address - The new DSP address for this pool. | ||
| 115 | */ | ||
| 116 | void SetDspAddress(CpuAddr address); | ||
| 117 | |||
| 118 | /** | ||
| 119 | * Check whether the pool contains a given range. | ||
| 120 | * | ||
| 121 | * @param address - The buffer address to look for. | ||
| 122 | * @param size - The size of the given buffer. | ||
| 123 | * @return True if the range is within this pool, otherwise false. | ||
| 124 | */ | ||
| 125 | bool Contains(CpuAddr address, u64 size) const; | ||
| 126 | |||
| 127 | /** | ||
| 128 | * Check whether this pool is mapped, which is when the dsp address is set. | ||
| 129 | * | ||
| 130 | * @return True if the pool is mapped, otherwise false. | ||
| 131 | */ | ||
| 132 | bool IsMapped() const; | ||
| 133 | |||
| 134 | /** | ||
| 135 | * Translates a given CPU range into a relative offset for the DSP. | ||
| 136 | * | ||
| 137 | * @param address - The buffer address to look for. | ||
| 138 | * @param size - The size of the given buffer. | ||
| 139 | * @return Pointer to the DSP-mapped memory. | ||
| 140 | */ | ||
| 141 | CpuAddr Translate(CpuAddr address, u64 size) const; | ||
| 142 | |||
| 143 | /** | ||
| 144 | * Set or unset whether this memory pool is in use. | ||
| 145 | * | ||
| 146 | * @param used - Use state for this pool. | ||
| 147 | */ | ||
| 148 | void SetUsed(bool used); | ||
| 149 | |||
| 150 | /** | ||
| 151 | * Get whether this pool is in use. | ||
| 152 | * | ||
| 153 | * @return True if in use, otherwise false. | ||
| 154 | */ | ||
| 155 | bool IsUsed() const; | ||
| 156 | |||
| 157 | private: | ||
| 158 | /// Base address for the CPU-side memory | ||
| 159 | CpuAddr cpu_address{}; | ||
| 160 | /// Base address for the DSP-side memory | ||
| 161 | CpuAddr dsp_address{}; | ||
| 162 | /// Size of this pool | ||
| 163 | u64 size{}; | ||
| 164 | /// Location of this pool, either CPU or DSP | ||
| 165 | Location location{Location::DSP}; | ||
| 166 | /// If this pool is in use | ||
| 167 | bool in_use{}; | ||
| 168 | }; | ||
| 169 | |||
| 170 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/memory/pool_mapper.cpp b/src/audio_core/renderer/memory/pool_mapper.cpp new file mode 100644 index 000000000..2baf2ce08 --- /dev/null +++ b/src/audio_core/renderer/memory/pool_mapper.cpp | |||
| @@ -0,0 +1,243 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/memory/address_info.h" | ||
| 5 | #include "audio_core/renderer/memory/pool_mapper.h" | ||
| 6 | #include "core/hle/kernel/k_process.h" | ||
| 7 | #include "core/hle/kernel/svc.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | PoolMapper::PoolMapper(u32 process_handle_, bool force_map_) | ||
| 12 | : process_handle{process_handle_}, force_map{force_map_} {} | ||
| 13 | |||
| 14 | PoolMapper::PoolMapper(u32 process_handle_, std::span<MemoryPoolInfo> pool_infos_, u32 pool_count_, | ||
| 15 | bool force_map_) | ||
| 16 | : process_handle{process_handle_}, pool_infos{pool_infos_.data()}, | ||
| 17 | pool_count{pool_count_}, force_map{force_map_} {} | ||
| 18 | |||
| 19 | void PoolMapper::ClearUseState(std::span<MemoryPoolInfo> pools, const u32 count) { | ||
| 20 | for (u32 i = 0; i < count; i++) { | ||
| 21 | pools[i].SetUsed(false); | ||
| 22 | } | ||
| 23 | } | ||
| 24 | |||
| 25 | MemoryPoolInfo* PoolMapper::FindMemoryPool(MemoryPoolInfo* pools, const u64 count, | ||
| 26 | const CpuAddr address, const u64 size) const { | ||
| 27 | auto pool{pools}; | ||
| 28 | for (u64 i = 0; i < count; i++, pool++) { | ||
| 29 | if (pool->Contains(address, size)) { | ||
| 30 | return pool; | ||
| 31 | } | ||
| 32 | } | ||
| 33 | return nullptr; | ||
| 34 | } | ||
| 35 | |||
| 36 | MemoryPoolInfo* PoolMapper::FindMemoryPool(const CpuAddr address, const u64 size) const { | ||
| 37 | auto pool{pool_infos}; | ||
| 38 | for (u64 i = 0; i < pool_count; i++, pool++) { | ||
| 39 | if (pool->Contains(address, size)) { | ||
| 40 | return pool; | ||
| 41 | } | ||
| 42 | } | ||
| 43 | return nullptr; | ||
| 44 | } | ||
| 45 | |||
| 46 | bool PoolMapper::FillDspAddr(AddressInfo& address_info, MemoryPoolInfo* pools, | ||
| 47 | const u32 count) const { | ||
| 48 | if (address_info.GetCpuAddr() == 0) { | ||
| 49 | address_info.SetPool(nullptr); | ||
| 50 | return false; | ||
| 51 | } | ||
| 52 | |||
| 53 | auto found_pool{ | ||
| 54 | FindMemoryPool(pools, count, address_info.GetCpuAddr(), address_info.GetSize())}; | ||
| 55 | if (found_pool != nullptr) { | ||
| 56 | address_info.SetPool(found_pool); | ||
| 57 | return true; | ||
| 58 | } | ||
| 59 | |||
| 60 | if (force_map) { | ||
| 61 | address_info.SetForceMappedDspAddr(address_info.GetCpuAddr()); | ||
| 62 | } else { | ||
| 63 | address_info.SetPool(nullptr); | ||
| 64 | } | ||
| 65 | |||
| 66 | return false; | ||
| 67 | } | ||
| 68 | |||
| 69 | bool PoolMapper::FillDspAddr(AddressInfo& address_info) const { | ||
| 70 | if (address_info.GetCpuAddr() == 0) { | ||
| 71 | address_info.SetPool(nullptr); | ||
| 72 | return false; | ||
| 73 | } | ||
| 74 | |||
| 75 | auto found_pool{FindMemoryPool(address_info.GetCpuAddr(), address_info.GetSize())}; | ||
| 76 | if (found_pool != nullptr) { | ||
| 77 | address_info.SetPool(found_pool); | ||
| 78 | return true; | ||
| 79 | } | ||
| 80 | |||
| 81 | if (force_map) { | ||
| 82 | address_info.SetForceMappedDspAddr(address_info.GetCpuAddr()); | ||
| 83 | } else { | ||
| 84 | address_info.SetPool(nullptr); | ||
| 85 | } | ||
| 86 | |||
| 87 | return false; | ||
| 88 | } | ||
| 89 | |||
| 90 | bool PoolMapper::TryAttachBuffer(BehaviorInfo::ErrorInfo& error_info, AddressInfo& address_info, | ||
| 91 | const CpuAddr address, const u64 size) const { | ||
| 92 | address_info.Setup(address, size); | ||
| 93 | |||
| 94 | if (!FillDspAddr(address_info)) { | ||
| 95 | error_info.error_code = Service::Audio::ERR_POOL_MAPPING_FAILED; | ||
| 96 | error_info.address = address; | ||
| 97 | return force_map; | ||
| 98 | } | ||
| 99 | |||
| 100 | error_info.error_code = ResultSuccess; | ||
| 101 | error_info.address = CpuAddr(0); | ||
| 102 | return true; | ||
| 103 | } | ||
| 104 | |||
| 105 | bool PoolMapper::IsForceMapEnabled() const { | ||
| 106 | return force_map; | ||
| 107 | } | ||
| 108 | |||
| 109 | u32 PoolMapper::GetProcessHandle(const MemoryPoolInfo* pool) const { | ||
| 110 | switch (pool->GetLocation()) { | ||
| 111 | case MemoryPoolInfo::Location::CPU: | ||
| 112 | return process_handle; | ||
| 113 | case MemoryPoolInfo::Location::DSP: | ||
| 114 | return Kernel::Svc::CurrentProcess; | ||
| 115 | } | ||
| 116 | LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location!"); | ||
| 117 | return Kernel::Svc::CurrentProcess; | ||
| 118 | } | ||
| 119 | |||
| 120 | bool PoolMapper::Map([[maybe_unused]] const u32 handle, [[maybe_unused]] const CpuAddr cpu_addr, | ||
| 121 | [[maybe_unused]] const u64 size) const { | ||
| 122 | // nn::audio::dsp::MapUserPointer(handle, cpu_addr, size); | ||
| 123 | return true; | ||
| 124 | } | ||
| 125 | |||
| 126 | bool PoolMapper::Map(MemoryPoolInfo& pool) const { | ||
| 127 | switch (pool.GetLocation()) { | ||
| 128 | case MemoryPoolInfo::Location::CPU: | ||
| 129 | // Map with process_handle | ||
| 130 | pool.SetDspAddress(pool.GetCpuAddress()); | ||
| 131 | return true; | ||
| 132 | case MemoryPoolInfo::Location::DSP: | ||
| 133 | // Map with Kernel::Svc::CurrentProcess | ||
| 134 | pool.SetDspAddress(pool.GetCpuAddress()); | ||
| 135 | return true; | ||
| 136 | default: | ||
| 137 | LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location={}!", | ||
| 138 | static_cast<u32>(pool.GetLocation())); | ||
| 139 | return false; | ||
| 140 | } | ||
| 141 | } | ||
| 142 | |||
| 143 | bool PoolMapper::Unmap([[maybe_unused]] const u32 handle, [[maybe_unused]] const CpuAddr cpu_addr, | ||
| 144 | [[maybe_unused]] const u64 size) const { | ||
| 145 | // nn::audio::dsp::UnmapUserPointer(handle, cpu_addr, size); | ||
| 146 | return true; | ||
| 147 | } | ||
| 148 | |||
| 149 | bool PoolMapper::Unmap(MemoryPoolInfo& pool) const { | ||
| 150 | [[maybe_unused]] u32 handle{0}; | ||
| 151 | |||
| 152 | switch (pool.GetLocation()) { | ||
| 153 | case MemoryPoolInfo::Location::CPU: | ||
| 154 | handle = process_handle; | ||
| 155 | break; | ||
| 156 | case MemoryPoolInfo::Location::DSP: | ||
| 157 | handle = Kernel::Svc::CurrentProcess; | ||
| 158 | break; | ||
| 159 | } | ||
| 160 | // nn::audio::dsp::UnmapUserPointer(handle, pool->cpu_address, pool->size); | ||
| 161 | pool.SetCpuAddress(0, 0); | ||
| 162 | pool.SetDspAddress(0); | ||
| 163 | return true; | ||
| 164 | } | ||
| 165 | |||
| 166 | void PoolMapper::ForceUnmapPointer(const AddressInfo& address_info) const { | ||
| 167 | if (force_map) { | ||
| 168 | [[maybe_unused]] auto found_pool{ | ||
| 169 | FindMemoryPool(address_info.GetCpuAddr(), address_info.GetSize())}; | ||
| 170 | // nn::audio::dsp::UnmapUserPointer(this->processHandle, address_info.GetCpuAddr(), 0); | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | MemoryPoolInfo::ResultState PoolMapper::Update(MemoryPoolInfo& pool, | ||
| 175 | const MemoryPoolInfo::InParameter& in_params, | ||
| 176 | MemoryPoolInfo::OutStatus& out_params) const { | ||
| 177 | if (in_params.state != MemoryPoolInfo::State::RequestAttach && | ||
| 178 | in_params.state != MemoryPoolInfo::State::RequestDetach) { | ||
| 179 | return MemoryPoolInfo::ResultState::Success; | ||
| 180 | } | ||
| 181 | |||
| 182 | if (in_params.address == 0 || in_params.size == 0 || !Common::Is4KBAligned(in_params.address) || | ||
| 183 | !Common::Is4KBAligned(in_params.size)) { | ||
| 184 | return MemoryPoolInfo::ResultState::BadParam; | ||
| 185 | } | ||
| 186 | |||
| 187 | switch (in_params.state) { | ||
| 188 | case MemoryPoolInfo::State::RequestAttach: | ||
| 189 | pool.SetCpuAddress(in_params.address, in_params.size); | ||
| 190 | |||
| 191 | Map(pool); | ||
| 192 | |||
| 193 | if (pool.IsMapped()) { | ||
| 194 | out_params.state = MemoryPoolInfo::State::Attached; | ||
| 195 | return MemoryPoolInfo::ResultState::Success; | ||
| 196 | } | ||
| 197 | pool.SetCpuAddress(0, 0); | ||
| 198 | return MemoryPoolInfo::ResultState::MapFailed; | ||
| 199 | |||
| 200 | case MemoryPoolInfo::State::RequestDetach: | ||
| 201 | if (pool.GetCpuAddress() != in_params.address || pool.GetSize() != in_params.size) { | ||
| 202 | return MemoryPoolInfo::ResultState::BadParam; | ||
| 203 | } | ||
| 204 | |||
| 205 | if (pool.IsUsed()) { | ||
| 206 | return MemoryPoolInfo::ResultState::InUse; | ||
| 207 | } | ||
| 208 | |||
| 209 | Unmap(pool); | ||
| 210 | |||
| 211 | pool.SetCpuAddress(0, 0); | ||
| 212 | pool.SetDspAddress(0); | ||
| 213 | out_params.state = MemoryPoolInfo::State::Detached; | ||
| 214 | return MemoryPoolInfo::ResultState::Success; | ||
| 215 | |||
| 216 | default: | ||
| 217 | LOG_ERROR(Service_Audio, "Invalid MemoryPoolInfo::State!"); | ||
| 218 | break; | ||
| 219 | } | ||
| 220 | |||
| 221 | return MemoryPoolInfo::ResultState::Success; | ||
| 222 | } | ||
| 223 | |||
| 224 | bool PoolMapper::InitializeSystemPool(MemoryPoolInfo& pool, const u8* memory, | ||
| 225 | const u64 size_) const { | ||
| 226 | switch (pool.GetLocation()) { | ||
| 227 | case MemoryPoolInfo::Location::CPU: | ||
| 228 | return false; | ||
| 229 | case MemoryPoolInfo::Location::DSP: | ||
| 230 | pool.SetCpuAddress(reinterpret_cast<u64>(memory), size_); | ||
| 231 | if (Map(Kernel::Svc::CurrentProcess, reinterpret_cast<u64>(memory), size_)) { | ||
| 232 | pool.SetDspAddress(pool.GetCpuAddress()); | ||
| 233 | return true; | ||
| 234 | } | ||
| 235 | return false; | ||
| 236 | default: | ||
| 237 | LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location={}!", | ||
| 238 | static_cast<u32>(pool.GetLocation())); | ||
| 239 | return false; | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/memory/pool_mapper.h b/src/audio_core/renderer/memory/pool_mapper.h new file mode 100644 index 000000000..9a691da7a --- /dev/null +++ b/src/audio_core/renderer/memory/pool_mapper.h | |||
| @@ -0,0 +1,179 @@ | |||
| 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/behavior/behavior_info.h" | ||
| 9 | #include "audio_core/renderer/memory/memory_pool_info.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | #include "core/hle/service/audio/errors.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | class AddressInfo; | ||
| 15 | |||
| 16 | /** | ||
| 17 | * Utility functions for managing MemoryPoolInfos | ||
| 18 | */ | ||
| 19 | class PoolMapper { | ||
| 20 | public: | ||
| 21 | explicit PoolMapper(u32 process_handle, bool force_map); | ||
| 22 | explicit PoolMapper(u32 process_handle, std::span<MemoryPoolInfo> pool_infos, u32 pool_count, | ||
| 23 | bool force_map); | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Clear the usage state for all given pools. | ||
| 27 | * | ||
| 28 | * @param pools - The memory pools to clear. | ||
| 29 | * @param count - The number of pools. | ||
| 30 | */ | ||
| 31 | static void ClearUseState(std::span<MemoryPoolInfo> pools, u32 count); | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Find the memory pool containing the given address and size from a given list of pools. | ||
| 35 | * | ||
| 36 | * @param pools - The memory pools to search within. | ||
| 37 | * @param count - The number of pools. | ||
| 38 | * @param address - The address of the region to find. | ||
| 39 | * @param size - The size of the region to find. | ||
| 40 | * @return Pointer to the memory pool if found, otherwise nullptr. | ||
| 41 | */ | ||
| 42 | MemoryPoolInfo* FindMemoryPool(MemoryPoolInfo* pools, u64 count, CpuAddr address, | ||
| 43 | u64 size) const; | ||
| 44 | |||
| 45 | /** | ||
| 46 | * Find the memory pool containing the given address and size from the PoolMapper's memory pool. | ||
| 47 | * | ||
| 48 | * @param address - The address of the region to find. | ||
| 49 | * @param size - The size of the region to find. | ||
| 50 | * @return Pointer to the memory pool if found, otherwise nullptr. | ||
| 51 | */ | ||
| 52 | MemoryPoolInfo* FindMemoryPool(CpuAddr address, u64 size) const; | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Set the PoolMapper's memory pool to one in the given list of pools, which contains | ||
| 56 | * address_info. | ||
| 57 | * | ||
| 58 | * @param address_info - The expected region to find within pools. | ||
| 59 | * @param pools - The list of pools to search within. | ||
| 60 | * @param count - The number of pools given. | ||
| 61 | * @return True if successfully mapped, otherwise false. | ||
| 62 | */ | ||
| 63 | bool FillDspAddr(AddressInfo& address_info, MemoryPoolInfo* pools, u32 count) const; | ||
| 64 | |||
| 65 | /** | ||
| 66 | * Set the PoolMapper's memory pool to the one containing address_info. | ||
| 67 | * | ||
| 68 | * @param address_info - The address to find the memory pool for. | ||
| 69 | * @return True if successfully mapped, otherwise false. | ||
| 70 | */ | ||
| 71 | bool FillDspAddr(AddressInfo& address_info) const; | ||
| 72 | |||
| 73 | /** | ||
| 74 | * Try to attach a {address, size} region to the given address_info, and map it. Fills in the | ||
| 75 | * given error_info and address_info. | ||
| 76 | * | ||
| 77 | * @param error_info - Output error info. | ||
| 78 | * @param address_info - Output address info, initialized with the given {address, size} and | ||
| 79 | * attempted to map. | ||
| 80 | * @param address - Address of the region to map. | ||
| 81 | * @param size - Size of the region to map. | ||
| 82 | * @return True if successfully attached, otherwise false. | ||
| 83 | */ | ||
| 84 | bool TryAttachBuffer(BehaviorInfo::ErrorInfo& error_info, AddressInfo& address_info, | ||
| 85 | CpuAddr address, u64 size) const; | ||
| 86 | |||
| 87 | /** | ||
| 88 | * Return whether force mapping is enabled. | ||
| 89 | * | ||
| 90 | * @return True if force mapping is enabled, otherwise false. | ||
| 91 | */ | ||
| 92 | bool IsForceMapEnabled() const; | ||
| 93 | |||
| 94 | /** | ||
| 95 | * Get the process handle, depending on location. | ||
| 96 | * | ||
| 97 | * @param pool - The pool to check the location of. | ||
| 98 | * @return CurrentProcessHandle if location == DSP, | ||
| 99 | * the PoolMapper's process_handle if location == CPU | ||
| 100 | */ | ||
| 101 | u32 GetProcessHandle(const MemoryPoolInfo* pool) const; | ||
| 102 | |||
| 103 | /** | ||
| 104 | * Map the given region with the given handle. This is a no-op. | ||
| 105 | * | ||
| 106 | * @param handle - The process handle to map to. | ||
| 107 | * @param cpu_addr - Address to map. | ||
| 108 | * @param size - Size to map. | ||
| 109 | * @return True if successfully mapped, otherwise false. | ||
| 110 | */ | ||
| 111 | bool Map(u32 handle, CpuAddr cpu_addr, u64 size) const; | ||
| 112 | |||
| 113 | /** | ||
| 114 | * Map the given memory pool. | ||
| 115 | * | ||
| 116 | * @param pool - The pool to map. | ||
| 117 | * @return True if successfully mapped, otherwise false. | ||
| 118 | */ | ||
| 119 | bool Map(MemoryPoolInfo& pool) const; | ||
| 120 | |||
| 121 | /** | ||
| 122 | * Unmap the given region with the given handle. | ||
| 123 | * | ||
| 124 | * @param handle - The process handle to unmap to. | ||
| 125 | * @param cpu_addr - Address to unmap. | ||
| 126 | * @param size - Size to unmap. | ||
| 127 | * @return True if successfully unmapped, otherwise false. | ||
| 128 | */ | ||
| 129 | bool Unmap(u32 handle, CpuAddr cpu_addr, u64 size) const; | ||
| 130 | |||
| 131 | /** | ||
| 132 | * Unmap the given memory pool. | ||
| 133 | * | ||
| 134 | * @param pool - The pool to unmap. | ||
| 135 | * @return True if successfully unmapped, otherwise false. | ||
| 136 | */ | ||
| 137 | bool Unmap(MemoryPoolInfo& pool) const; | ||
| 138 | |||
| 139 | /** | ||
| 140 | * Forcibly unmap the given region. | ||
| 141 | * | ||
| 142 | * @param address_info - The region to unmap. | ||
| 143 | */ | ||
| 144 | void ForceUnmapPointer(const AddressInfo& address_info) const; | ||
| 145 | |||
| 146 | /** | ||
| 147 | * Update the given memory pool. | ||
| 148 | * | ||
| 149 | * @param pool - Pool to update. | ||
| 150 | * @param in_params - Input parameters for the update. | ||
| 151 | * @param out_params - Output parameters for the update. | ||
| 152 | * @return The result of the update. See MemoryPoolInfo::ResultState | ||
| 153 | */ | ||
| 154 | MemoryPoolInfo::ResultState Update(MemoryPoolInfo& pool, | ||
| 155 | const MemoryPoolInfo::InParameter& in_params, | ||
| 156 | MemoryPoolInfo::OutStatus& out_params) const; | ||
| 157 | |||
| 158 | /** | ||
| 159 | * Initialize the PoolMapper's memory pool. | ||
| 160 | * | ||
| 161 | * @param pool - Input pool to initialize. | ||
| 162 | * @param memory - Pointer to the memory region for the pool. | ||
| 163 | * @param size - Size of the memory region for the pool. | ||
| 164 | * @return True if initialized successfully, otherwise false. | ||
| 165 | */ | ||
| 166 | bool InitializeSystemPool(MemoryPoolInfo& pool, const u8* memory, u64 size) const; | ||
| 167 | |||
| 168 | private: | ||
| 169 | /// Process handle for this mapper, used when location == CPU | ||
| 170 | u32 process_handle; | ||
| 171 | /// List of memory pools assigned to this mapper | ||
| 172 | MemoryPoolInfo* pool_infos{}; | ||
| 173 | /// The number of pools | ||
| 174 | u64 pool_count{}; | ||
| 175 | /// Is forced mapping enabled | ||
| 176 | bool force_map; | ||
| 177 | }; | ||
| 178 | |||
| 179 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/mix/mix_context.cpp b/src/audio_core/renderer/mix/mix_context.cpp new file mode 100644 index 000000000..2427c83ed --- /dev/null +++ b/src/audio_core/renderer/mix/mix_context.cpp | |||
| @@ -0,0 +1,141 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <ranges> | ||
| 5 | |||
| 6 | #include "audio_core/renderer/mix/mix_context.h" | ||
| 7 | #include "audio_core/renderer/splitter/splitter_context.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | void MixContext::Initialize(std::span<MixInfo*> sorted_mix_infos_, std::span<MixInfo> mix_infos_, | ||
| 12 | const u32 count_, std::span<s32> effect_process_order_buffer_, | ||
| 13 | const u32 effect_count_, std::span<u8> node_states_workbuffer, | ||
| 14 | const u64 node_buffer_size, std::span<u8> edge_matrix_workbuffer, | ||
| 15 | const u64 edge_matrix_size) { | ||
| 16 | count = count_; | ||
| 17 | sorted_mix_infos = sorted_mix_infos_; | ||
| 18 | mix_infos = mix_infos_; | ||
| 19 | effect_process_order_buffer = effect_process_order_buffer_; | ||
| 20 | effect_count = effect_count_; | ||
| 21 | |||
| 22 | if (node_states_workbuffer.size() > 0 && edge_matrix_workbuffer.size() > 0) { | ||
| 23 | node_states.Initialize(node_states_workbuffer, node_buffer_size, count); | ||
| 24 | edge_matrix.Initialize(edge_matrix_workbuffer, edge_matrix_size, count); | ||
| 25 | } | ||
| 26 | |||
| 27 | for (s32 i = 0; i < count; i++) { | ||
| 28 | sorted_mix_infos[i] = &mix_infos[i]; | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | MixInfo* MixContext::GetSortedInfo(const s32 index) { | ||
| 33 | return sorted_mix_infos[index]; | ||
| 34 | } | ||
| 35 | |||
| 36 | void MixContext::SetSortedInfo(const s32 index, MixInfo& mix_info) { | ||
| 37 | sorted_mix_infos[index] = &mix_info; | ||
| 38 | } | ||
| 39 | |||
| 40 | MixInfo* MixContext::GetInfo(const s32 index) { | ||
| 41 | return &mix_infos[index]; | ||
| 42 | } | ||
| 43 | |||
| 44 | MixInfo* MixContext::GetFinalMixInfo() { | ||
| 45 | return &mix_infos[0]; | ||
| 46 | } | ||
| 47 | |||
| 48 | s32 MixContext::GetCount() const { | ||
| 49 | return count; | ||
| 50 | } | ||
| 51 | |||
| 52 | void MixContext::UpdateDistancesFromFinalMix() { | ||
| 53 | for (s32 i = 0; i < count; i++) { | ||
| 54 | mix_infos[i].distance_from_final_mix = InvalidDistanceFromFinalMix; | ||
| 55 | } | ||
| 56 | |||
| 57 | for (s32 i = 0; i < count; i++) { | ||
| 58 | auto& mix_info{mix_infos[i]}; | ||
| 59 | sorted_mix_infos[i] = &mix_info; | ||
| 60 | |||
| 61 | if (!mix_info.in_use) { | ||
| 62 | continue; | ||
| 63 | } | ||
| 64 | |||
| 65 | auto mix_id{mix_info.mix_id}; | ||
| 66 | auto distance_to_final_mix{FinalMixId}; | ||
| 67 | |||
| 68 | while (distance_to_final_mix < count) { | ||
| 69 | if (mix_id == FinalMixId) { | ||
| 70 | break; | ||
| 71 | } | ||
| 72 | |||
| 73 | if (mix_id == UnusedMixId) { | ||
| 74 | distance_to_final_mix = InvalidDistanceFromFinalMix; | ||
| 75 | break; | ||
| 76 | } | ||
| 77 | |||
| 78 | auto distance_from_final_mix{mix_infos[mix_id].distance_from_final_mix}; | ||
| 79 | if (distance_from_final_mix != InvalidDistanceFromFinalMix) { | ||
| 80 | distance_to_final_mix = distance_from_final_mix + 1; | ||
| 81 | break; | ||
| 82 | } | ||
| 83 | |||
| 84 | distance_to_final_mix++; | ||
| 85 | mix_id = mix_infos[mix_id].dst_mix_id; | ||
| 86 | } | ||
| 87 | |||
| 88 | if (distance_to_final_mix >= count) { | ||
| 89 | distance_to_final_mix = InvalidDistanceFromFinalMix; | ||
| 90 | } | ||
| 91 | mix_info.distance_from_final_mix = distance_to_final_mix; | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | void MixContext::SortInfo() { | ||
| 96 | UpdateDistancesFromFinalMix(); | ||
| 97 | |||
| 98 | std::ranges::sort(sorted_mix_infos, [](const MixInfo* lhs, const MixInfo* rhs) { | ||
| 99 | return lhs->distance_from_final_mix > rhs->distance_from_final_mix; | ||
| 100 | }); | ||
| 101 | |||
| 102 | CalcMixBufferOffset(); | ||
| 103 | } | ||
| 104 | |||
| 105 | void MixContext::CalcMixBufferOffset() { | ||
| 106 | s16 offset{0}; | ||
| 107 | for (s32 i = 0; i < count; i++) { | ||
| 108 | auto mix_info{sorted_mix_infos[i]}; | ||
| 109 | if (mix_info->in_use) { | ||
| 110 | const auto buffer_count{mix_info->buffer_count}; | ||
| 111 | mix_info->buffer_offset = offset; | ||
| 112 | offset += buffer_count; | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | bool MixContext::TSortInfo(const SplitterContext& splitter_context) { | ||
| 118 | if (!splitter_context.UsingSplitter()) { | ||
| 119 | CalcMixBufferOffset(); | ||
| 120 | return true; | ||
| 121 | } | ||
| 122 | |||
| 123 | if (!node_states.Tsort(edge_matrix)) { | ||
| 124 | return false; | ||
| 125 | } | ||
| 126 | |||
| 127 | std::vector<s32> sorted_results{node_states.GetSortedResuls()}; | ||
| 128 | const auto result_size{std::min(count, static_cast<s32>(sorted_results.size()))}; | ||
| 129 | for (s32 i = 0; i < result_size; i++) { | ||
| 130 | sorted_mix_infos[i] = &mix_infos[sorted_results[i]]; | ||
| 131 | } | ||
| 132 | |||
| 133 | CalcMixBufferOffset(); | ||
| 134 | return true; | ||
| 135 | } | ||
| 136 | |||
| 137 | EdgeMatrix& MixContext::GetEdgeMatrix() { | ||
| 138 | return edge_matrix; | ||
| 139 | } | ||
| 140 | |||
| 141 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/mix/mix_context.h b/src/audio_core/renderer/mix/mix_context.h new file mode 100644 index 000000000..da3aa2829 --- /dev/null +++ b/src/audio_core/renderer/mix/mix_context.h | |||
| @@ -0,0 +1,124 @@ | |||
| 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/mix/mix_info.h" | ||
| 9 | #include "audio_core/renderer/nodes/edge_matrix.h" | ||
| 10 | #include "audio_core/renderer/nodes/node_states.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | class SplitterContext; | ||
| 15 | |||
| 16 | /* | ||
| 17 | * Manages mixing states, sorting and building a node graph to describe a mix order. | ||
| 18 | */ | ||
| 19 | class MixContext { | ||
| 20 | public: | ||
| 21 | /** | ||
| 22 | * Initialize the mix context. | ||
| 23 | * | ||
| 24 | * @param sorted_mix_infos - Buffer for the sorted mix infos. | ||
| 25 | * @param mix_infos - Buffer for the mix infos. | ||
| 26 | * @param effect_process_order_buffer - Buffer for the effect process orders. | ||
| 27 | * @param effect_count - Number of effects in the buffer. | ||
| 28 | * @param node_states_workbuffer - Buffer for node states. | ||
| 29 | * @param node_buffer_size - Size of the node states buffer. | ||
| 30 | * @param edge_matrix_workbuffer - Buffer for edge matrix. | ||
| 31 | * @param edge_matrix_size - Size of the edge matrix buffer. | ||
| 32 | */ | ||
| 33 | void Initialize(std::span<MixInfo*> sorted_mix_infos, std::span<MixInfo> mix_infos, u32 count_, | ||
| 34 | std::span<s32> effect_process_order_buffer, u32 effect_count, | ||
| 35 | std::span<u8> node_states_workbuffer, u64 node_buffer_size, | ||
| 36 | std::span<u8> edge_matrix_workbuffer, u64 edge_matrix_size); | ||
| 37 | |||
| 38 | /** | ||
| 39 | * Get a sorted mix at the given index. | ||
| 40 | * | ||
| 41 | * @param index - Index of sorted mix. | ||
| 42 | * @return The sorted mix. | ||
| 43 | */ | ||
| 44 | MixInfo* GetSortedInfo(s32 index); | ||
| 45 | |||
| 46 | /** | ||
| 47 | * Set the sorted info at the given index. | ||
| 48 | * | ||
| 49 | * @param index - Index of sorted mix. | ||
| 50 | * @param mix_info - The new mix for this index. | ||
| 51 | */ | ||
| 52 | void SetSortedInfo(s32 index, MixInfo& mix_info); | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Get a mix at the given index. | ||
| 56 | * | ||
| 57 | * @param index - Index of mix. | ||
| 58 | * @return The mix. | ||
| 59 | */ | ||
| 60 | MixInfo* GetInfo(s32 index); | ||
| 61 | |||
| 62 | /** | ||
| 63 | * Get the final mix. | ||
| 64 | * | ||
| 65 | * @return The final mix. | ||
| 66 | */ | ||
| 67 | MixInfo* GetFinalMixInfo(); | ||
| 68 | |||
| 69 | /** | ||
| 70 | * Get the current number of mixes. | ||
| 71 | * | ||
| 72 | * @return The number of active mixes. | ||
| 73 | */ | ||
| 74 | s32 GetCount() const; | ||
| 75 | |||
| 76 | /** | ||
| 77 | * Update all of the mixes' distance from the final mix. | ||
| 78 | * Needs to be called after altering the mix graph. | ||
| 79 | */ | ||
| 80 | void UpdateDistancesFromFinalMix(); | ||
| 81 | |||
| 82 | /** | ||
| 83 | * Non-splitter sort, sorts the sorted mixes based on their distance from the final mix. | ||
| 84 | */ | ||
| 85 | void SortInfo(); | ||
| 86 | |||
| 87 | /** | ||
| 88 | * Re-calculate the mix buffer offsets for each mix after altering the mix. | ||
| 89 | */ | ||
| 90 | void CalcMixBufferOffset(); | ||
| 91 | |||
| 92 | /** | ||
| 93 | * Splitter sort, traverse the splitter node graph and sort the sorted mixes from results. | ||
| 94 | * | ||
| 95 | * @param splitter_context - Splitter context for the sort. | ||
| 96 | * @return True if the sort was successful, othewise false. | ||
| 97 | */ | ||
| 98 | bool TSortInfo(const SplitterContext& splitter_context); | ||
| 99 | |||
| 100 | /** | ||
| 101 | * Get the edge matrix used for the mix graph. | ||
| 102 | * | ||
| 103 | * @return The edge matrix used. | ||
| 104 | */ | ||
| 105 | EdgeMatrix& GetEdgeMatrix(); | ||
| 106 | |||
| 107 | private: | ||
| 108 | /// Array of sorted mixes | ||
| 109 | std::span<MixInfo*> sorted_mix_infos{}; | ||
| 110 | /// Array of mixes | ||
| 111 | std::span<MixInfo> mix_infos{}; | ||
| 112 | /// Number of active mixes | ||
| 113 | s32 count{}; | ||
| 114 | /// Array of effect process orderings | ||
| 115 | std::span<s32> effect_process_order_buffer{}; | ||
| 116 | /// Number of effects in the process ordering buffer | ||
| 117 | u64 effect_count{}; | ||
| 118 | /// Node states used in splitter sort | ||
| 119 | NodeStates node_states{}; | ||
| 120 | /// Edge matrix for connected nodes used in splitter sort | ||
| 121 | EdgeMatrix edge_matrix{}; | ||
| 122 | }; | ||
| 123 | |||
| 124 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/mix/mix_info.cpp b/src/audio_core/renderer/mix/mix_info.cpp new file mode 100644 index 000000000..cc18e57ee --- /dev/null +++ b/src/audio_core/renderer/mix/mix_info.cpp | |||
| @@ -0,0 +1,120 @@ | |||
| 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/effect/effect_context.h" | ||
| 6 | #include "audio_core/renderer/mix/mix_info.h" | ||
| 7 | #include "audio_core/renderer/nodes/edge_matrix.h" | ||
| 8 | #include "audio_core/renderer/splitter/splitter_context.h" | ||
| 9 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | MixInfo::MixInfo(std::span<s32> effect_order_buffer_, s32 effect_count_, BehaviorInfo& behavior) | ||
| 13 | : effect_order_buffer{effect_order_buffer_}, effect_count{effect_count_}, | ||
| 14 | long_size_pre_delay_supported{behavior.IsLongSizePreDelaySupported()} { | ||
| 15 | ClearEffectProcessingOrder(); | ||
| 16 | } | ||
| 17 | |||
| 18 | void MixInfo::Cleanup() { | ||
| 19 | mix_id = UnusedMixId; | ||
| 20 | dst_mix_id = UnusedMixId; | ||
| 21 | dst_splitter_id = UnusedSplitterId; | ||
| 22 | } | ||
| 23 | |||
| 24 | void MixInfo::ClearEffectProcessingOrder() { | ||
| 25 | for (s32 i = 0; i < effect_count; i++) { | ||
| 26 | effect_order_buffer[i] = -1; | ||
| 27 | } | ||
| 28 | } | ||
| 29 | |||
| 30 | bool MixInfo::Update(EdgeMatrix& edge_matrix, const InParameter& in_params, | ||
| 31 | EffectContext& effect_context, SplitterContext& splitter_context, | ||
| 32 | const BehaviorInfo& behavior) { | ||
| 33 | volume = in_params.volume; | ||
| 34 | sample_rate = in_params.sample_rate; | ||
| 35 | buffer_count = static_cast<s16>(in_params.buffer_count); | ||
| 36 | in_use = in_params.in_use; | ||
| 37 | mix_id = in_params.mix_id; | ||
| 38 | node_id = in_params.node_id; | ||
| 39 | mix_volumes = in_params.mix_volumes; | ||
| 40 | |||
| 41 | bool sort_required{false}; | ||
| 42 | if (behavior.IsSplitterSupported()) { | ||
| 43 | sort_required = UpdateConnection(edge_matrix, in_params, splitter_context); | ||
| 44 | } else { | ||
| 45 | if (dst_mix_id != in_params.dest_mix_id) { | ||
| 46 | dst_mix_id = in_params.dest_mix_id; | ||
| 47 | sort_required = true; | ||
| 48 | } | ||
| 49 | dst_splitter_id = UnusedSplitterId; | ||
| 50 | } | ||
| 51 | |||
| 52 | ClearEffectProcessingOrder(); | ||
| 53 | |||
| 54 | // Check all effects, and set their order if they belong to this mix. | ||
| 55 | const auto count{effect_context.GetCount()}; | ||
| 56 | for (u32 i = 0; i < count; i++) { | ||
| 57 | const auto& info{effect_context.GetInfo(i)}; | ||
| 58 | if (mix_id == info.GetMixId()) { | ||
| 59 | const auto processing_order{info.GetProcessingOrder()}; | ||
| 60 | if (processing_order > effect_count) { | ||
| 61 | break; | ||
| 62 | } | ||
| 63 | effect_order_buffer[processing_order] = i; | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | return sort_required; | ||
| 68 | } | ||
| 69 | |||
| 70 | bool MixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params, | ||
| 71 | SplitterContext& splitter_context) { | ||
| 72 | auto has_new_connection{false}; | ||
| 73 | if (dst_splitter_id != UnusedSplitterId) { | ||
| 74 | auto& splitter_info{splitter_context.GetInfo(dst_splitter_id)}; | ||
| 75 | has_new_connection = splitter_info.HasNewConnection(); | ||
| 76 | } | ||
| 77 | |||
| 78 | // Check if this mix matches the input parameters. | ||
| 79 | // If everything is the same, don't bother updating. | ||
| 80 | if (dst_mix_id == in_params.dest_mix_id && dst_splitter_id == in_params.dest_splitter_id && | ||
| 81 | !has_new_connection) { | ||
| 82 | return false; | ||
| 83 | } | ||
| 84 | |||
| 85 | // Reset the mix in the graph, as we're about to update it. | ||
| 86 | edge_matrix.RemoveEdges(mix_id); | ||
| 87 | |||
| 88 | if (in_params.dest_mix_id == UnusedMixId) { | ||
| 89 | if (in_params.dest_splitter_id != UnusedSplitterId) { | ||
| 90 | // If the splitter is used, connect this mix to each active destination. | ||
| 91 | auto& splitter_info{splitter_context.GetInfo(in_params.dest_splitter_id)}; | ||
| 92 | auto const destination_count{splitter_info.GetDestinationCount()}; | ||
| 93 | |||
| 94 | for (u32 i = 0; i < destination_count; i++) { | ||
| 95 | auto destination{ | ||
| 96 | splitter_context.GetDesintationData(in_params.dest_splitter_id, i)}; | ||
| 97 | |||
| 98 | if (destination) { | ||
| 99 | const auto destination_id{destination->GetMixId()}; | ||
| 100 | if (destination_id != UnusedMixId) { | ||
| 101 | edge_matrix.Connect(mix_id, destination_id); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | } | ||
| 105 | } | ||
| 106 | } else { | ||
| 107 | // If the splitter is not used, only connect this mix to its destination. | ||
| 108 | edge_matrix.Connect(mix_id, in_params.dest_mix_id); | ||
| 109 | } | ||
| 110 | |||
| 111 | dst_mix_id = in_params.dest_mix_id; | ||
| 112 | dst_splitter_id = in_params.dest_splitter_id; | ||
| 113 | return true; | ||
| 114 | } | ||
| 115 | |||
| 116 | bool MixInfo::HasAnyConnection() const { | ||
| 117 | return dst_mix_id != UnusedMixId || dst_splitter_id != UnusedSplitterId; | ||
| 118 | } | ||
| 119 | |||
| 120 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/mix/mix_info.h b/src/audio_core/renderer/mix/mix_info.h new file mode 100644 index 000000000..b5fa4c0c7 --- /dev/null +++ b/src/audio_core/renderer/mix/mix_info.h | |||
| @@ -0,0 +1,124 @@ | |||
| 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 "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | class EdgeMatrix; | ||
| 14 | class SplitterContext; | ||
| 15 | class EffectContext; | ||
| 16 | class BehaviorInfo; | ||
| 17 | |||
| 18 | /** | ||
| 19 | * A single mix, which may feed through other mixes in a chain until reaching the final output mix. | ||
| 20 | */ | ||
| 21 | class MixInfo { | ||
| 22 | public: | ||
| 23 | struct InParameter { | ||
| 24 | /* 0x000 */ f32 volume; | ||
| 25 | /* 0x004 */ u32 sample_rate; | ||
| 26 | /* 0x008 */ u32 buffer_count; | ||
| 27 | /* 0x00C */ bool in_use; | ||
| 28 | /* 0x00D */ bool is_dirty; | ||
| 29 | /* 0x010 */ s32 mix_id; | ||
| 30 | /* 0x014 */ u32 effect_count; | ||
| 31 | /* 0x018 */ s32 node_id; | ||
| 32 | /* 0x01C */ char unk01C[0x8]; | ||
| 33 | /* 0x024 */ std::array<std::array<f32, MaxMixBuffers>, MaxMixBuffers> mix_volumes; | ||
| 34 | /* 0x924 */ s32 dest_mix_id; | ||
| 35 | /* 0x928 */ s32 dest_splitter_id; | ||
| 36 | /* 0x92C */ char unk92C[0x4]; | ||
| 37 | }; | ||
| 38 | static_assert(sizeof(InParameter) == 0x930, "MixInfo::InParameter has the wrong size!"); | ||
| 39 | |||
| 40 | struct InDirtyParameter { | ||
| 41 | /* 0x00 */ u32 magic; | ||
| 42 | /* 0x04 */ s32 count; | ||
| 43 | /* 0x08 */ char unk08[0x18]; | ||
| 44 | }; | ||
| 45 | static_assert(sizeof(InDirtyParameter) == 0x20, | ||
| 46 | "MixInfo::InDirtyParameter has the wrong size!"); | ||
| 47 | |||
| 48 | MixInfo(std::span<s32> effect_order_buffer, s32 effect_count, BehaviorInfo& behavior); | ||
| 49 | |||
| 50 | /** | ||
| 51 | * Clean up the mix, resetting it to a default state. | ||
| 52 | */ | ||
| 53 | void Cleanup(); | ||
| 54 | |||
| 55 | /** | ||
| 56 | * Clear the effect process order for all effects in this mix. | ||
| 57 | */ | ||
| 58 | void ClearEffectProcessingOrder(); | ||
| 59 | |||
| 60 | /** | ||
| 61 | * Update the mix according to the given parameters. | ||
| 62 | * | ||
| 63 | * @param edge_matrix - Updated with new splitter node connections, if supported. | ||
| 64 | * @param in_params - Input parameters. | ||
| 65 | * @param effect_context - Used to update the effect orderings. | ||
| 66 | * @param splitter_context - Used to update the mix graph if supported. | ||
| 67 | * @param behavior - Used for checking which features are supported. | ||
| 68 | * @return True if the mix was updated and a sort is required, otherwise false. | ||
| 69 | */ | ||
| 70 | bool Update(EdgeMatrix& edge_matrix, const InParameter& in_params, | ||
| 71 | EffectContext& effect_context, SplitterContext& splitter_context, | ||
| 72 | const BehaviorInfo& behavior); | ||
| 73 | |||
| 74 | /** | ||
| 75 | * Update the mix's connection in the node graph according to the given parameters. | ||
| 76 | * | ||
| 77 | * @param edge_matrix - Updated with new splitter node connections, if supported. | ||
| 78 | * @param in_params - Input parameters. | ||
| 79 | * @param splitter_context - Used to update the mix graph if supported. | ||
| 80 | * @return True if the mix was updated and a sort is required, otherwise false. | ||
| 81 | */ | ||
| 82 | bool UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params, | ||
| 83 | SplitterContext& splitter_context); | ||
| 84 | |||
| 85 | /** | ||
| 86 | * Check if this mix is connected to any other. | ||
| 87 | * | ||
| 88 | * @return True if the mix has a connection, otherwise false. | ||
| 89 | */ | ||
| 90 | bool HasAnyConnection() const; | ||
| 91 | |||
| 92 | /// Volume of this mix | ||
| 93 | f32 volume{}; | ||
| 94 | /// Sample rate of this mix | ||
| 95 | u32 sample_rate{}; | ||
| 96 | /// Number of buffers in this mix | ||
| 97 | s16 buffer_count{}; | ||
| 98 | /// Is this mix in use? | ||
| 99 | bool in_use{}; | ||
| 100 | /// Is this mix enabled? | ||
| 101 | bool enabled{}; | ||
| 102 | /// Id of this mix | ||
| 103 | s32 mix_id{UnusedMixId}; | ||
| 104 | /// Node id of this mix | ||
| 105 | s32 node_id{}; | ||
| 106 | /// Buffer offset for this mix | ||
| 107 | s16 buffer_offset{}; | ||
| 108 | /// Distance to the final mix | ||
| 109 | s32 distance_from_final_mix{InvalidDistanceFromFinalMix}; | ||
| 110 | /// Array of effect orderings of all effects in this mix | ||
| 111 | std::span<s32> effect_order_buffer; | ||
| 112 | /// Number of effects in this mix | ||
| 113 | const s32 effect_count; | ||
| 114 | /// Id for next mix in the chain | ||
| 115 | s32 dst_mix_id{UnusedMixId}; | ||
| 116 | /// Mixing volumes for this mix used when this mix is chained with another | ||
| 117 | std::array<std::array<f32, MaxMixBuffers>, MaxMixBuffers> mix_volumes{}; | ||
| 118 | /// Id for next mix in the graph when splitter is used | ||
| 119 | s32 dst_splitter_id{UnusedSplitterId}; | ||
| 120 | /// Is a longer pre-delay time supported for the reverb effect? | ||
| 121 | const bool long_size_pre_delay_supported; | ||
| 122 | }; | ||
| 123 | |||
| 124 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/nodes/bit_array.h b/src/audio_core/renderer/nodes/bit_array.h new file mode 100644 index 000000000..b0d53cd51 --- /dev/null +++ b/src/audio_core/renderer/nodes/bit_array.h | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <vector> | ||
| 7 | |||
| 8 | #include "common/common_types.h" | ||
| 9 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | /** | ||
| 12 | * Represents an array of bits used for nodes and edges for the mixing graph. | ||
| 13 | */ | ||
| 14 | struct BitArray { | ||
| 15 | void reset() { | ||
| 16 | buffer.assign(buffer.size(), false); | ||
| 17 | } | ||
| 18 | |||
| 19 | /// Bits | ||
| 20 | std::vector<bool> buffer{}; | ||
| 21 | /// Size of the buffer | ||
| 22 | u32 size{}; | ||
| 23 | }; | ||
| 24 | |||
| 25 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/nodes/edge_matrix.cpp b/src/audio_core/renderer/nodes/edge_matrix.cpp new file mode 100644 index 000000000..5573f33b9 --- /dev/null +++ b/src/audio_core/renderer/nodes/edge_matrix.cpp | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/nodes/edge_matrix.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void EdgeMatrix::Initialize([[maybe_unused]] std::span<u8> buffer, | ||
| 9 | [[maybe_unused]] const u64 node_buffer_size, const u32 count_) { | ||
| 10 | count = count_; | ||
| 11 | edges.buffer.resize(count_ * count_); | ||
| 12 | edges.size = count_ * count_; | ||
| 13 | edges.reset(); | ||
| 14 | } | ||
| 15 | |||
| 16 | bool EdgeMatrix::Connected(const u32 id, const u32 destination_id) const { | ||
| 17 | return edges.buffer[count * id + destination_id]; | ||
| 18 | } | ||
| 19 | |||
| 20 | void EdgeMatrix::Connect(const u32 id, const u32 destination_id) { | ||
| 21 | edges.buffer[count * id + destination_id] = true; | ||
| 22 | } | ||
| 23 | |||
| 24 | void EdgeMatrix::Disconnect(const u32 id, const u32 destination_id) { | ||
| 25 | edges.buffer[count * id + destination_id] = false; | ||
| 26 | } | ||
| 27 | |||
| 28 | void EdgeMatrix::RemoveEdges(const u32 id) { | ||
| 29 | for (u32 dest = 0; dest < count; dest++) { | ||
| 30 | Disconnect(id, dest); | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | u32 EdgeMatrix::GetNodeCount() const { | ||
| 35 | return count; | ||
| 36 | } | ||
| 37 | |||
| 38 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/nodes/edge_matrix.h b/src/audio_core/renderer/nodes/edge_matrix.h new file mode 100644 index 000000000..27a20e43e --- /dev/null +++ b/src/audio_core/renderer/nodes/edge_matrix.h | |||
| @@ -0,0 +1,82 @@ | |||
| 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/nodes/bit_array.h" | ||
| 9 | #include "common/alignment.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | /** | ||
| 14 | * An edge matrix, holding the connections for each node to every other node in the graph. | ||
| 15 | */ | ||
| 16 | class EdgeMatrix { | ||
| 17 | public: | ||
| 18 | /** | ||
| 19 | * Calculate the size required for its workbuffer. | ||
| 20 | * | ||
| 21 | * @param count - The number of nodes in the graph. | ||
| 22 | * @return The required workbuffer size. | ||
| 23 | */ | ||
| 24 | static u64 GetWorkBufferSize(u32 count) { | ||
| 25 | return Common::AlignUp(count * count, 0x40) / sizeof(u64); | ||
| 26 | } | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Initialize this edge matrix. | ||
| 30 | * | ||
| 31 | * @param buffer - The workbuffer to use. Unused. | ||
| 32 | * @param node_buffer_size - The size of the workbuffer. Unused. | ||
| 33 | * @param count - The number of nodes in the graph. | ||
| 34 | */ | ||
| 35 | void Initialize(std::span<u8> buffer, u64 node_buffer_size, u32 count); | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Check if a node is connected to another. | ||
| 39 | * | ||
| 40 | * @param id - The node id to check. | ||
| 41 | * @param destination_id - Node id to check connection with. | ||
| 42 | */ | ||
| 43 | bool Connected(u32 id, u32 destination_id) const; | ||
| 44 | |||
| 45 | /** | ||
| 46 | * Connect a node to another. | ||
| 47 | * | ||
| 48 | * @param id - The node id to connect. | ||
| 49 | * @param destination_id - Destination to connect it to. | ||
| 50 | */ | ||
| 51 | void Connect(u32 id, u32 destination_id); | ||
| 52 | |||
| 53 | /** | ||
| 54 | * Disconnect a node from another. | ||
| 55 | * | ||
| 56 | * @param id - The node id to disconnect. | ||
| 57 | * @param destination_id - Destination to disconnect it from. | ||
| 58 | */ | ||
| 59 | void Disconnect(u32 id, u32 destination_id); | ||
| 60 | |||
| 61 | /** | ||
| 62 | * Remove all connections for a given node. | ||
| 63 | * | ||
| 64 | * @param id - The node id to disconnect. | ||
| 65 | */ | ||
| 66 | void RemoveEdges(u32 id); | ||
| 67 | |||
| 68 | /** | ||
| 69 | * Get the number of nodes in the graph. | ||
| 70 | * | ||
| 71 | * @return Number of nodes. | ||
| 72 | */ | ||
| 73 | u32 GetNodeCount() const; | ||
| 74 | |||
| 75 | private: | ||
| 76 | /// Edges for the current graph | ||
| 77 | BitArray edges; | ||
| 78 | /// Number of nodes (not edges) in the graph | ||
| 79 | u32 count; | ||
| 80 | }; | ||
| 81 | |||
| 82 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/nodes/node_states.cpp b/src/audio_core/renderer/nodes/node_states.cpp new file mode 100644 index 000000000..1821a51e6 --- /dev/null +++ b/src/audio_core/renderer/nodes/node_states.cpp | |||
| @@ -0,0 +1,141 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/nodes/node_states.h" | ||
| 5 | #include "common/logging/log.h" | ||
| 6 | |||
| 7 | namespace AudioCore::AudioRenderer { | ||
| 8 | |||
| 9 | void NodeStates::Initialize(std::span<u8> buffer_, [[maybe_unused]] const u64 node_buffer_size, | ||
| 10 | const u32 count) { | ||
| 11 | u64 num_blocks{Common::AlignUp(count, 0x40) / sizeof(u64)}; | ||
| 12 | u64 offset{0}; | ||
| 13 | |||
| 14 | node_count = count; | ||
| 15 | |||
| 16 | nodes_found.buffer.resize(count); | ||
| 17 | nodes_found.size = count; | ||
| 18 | nodes_found.reset(); | ||
| 19 | |||
| 20 | offset += num_blocks; | ||
| 21 | |||
| 22 | nodes_complete.buffer.resize(count); | ||
| 23 | nodes_complete.size = count; | ||
| 24 | nodes_complete.reset(); | ||
| 25 | |||
| 26 | offset += num_blocks; | ||
| 27 | |||
| 28 | results = {reinterpret_cast<u32*>(&buffer_[offset]), count}; | ||
| 29 | |||
| 30 | offset += count * sizeof(u32); | ||
| 31 | |||
| 32 | stack.stack = {reinterpret_cast<u32*>(&buffer_[offset]), count * count}; | ||
| 33 | stack.size = count * count; | ||
| 34 | stack.unk_10 = count * count; | ||
| 35 | |||
| 36 | offset += count * count * sizeof(u32); | ||
| 37 | } | ||
| 38 | |||
| 39 | bool NodeStates::Tsort(const EdgeMatrix& edge_matrix) { | ||
| 40 | return DepthFirstSearch(edge_matrix, stack); | ||
| 41 | } | ||
| 42 | |||
| 43 | bool NodeStates::DepthFirstSearch(const EdgeMatrix& edge_matrix, Stack& stack_) { | ||
| 44 | ResetState(); | ||
| 45 | |||
| 46 | for (u32 node_id = 0; node_id < node_count; node_id++) { | ||
| 47 | if (GetState(node_id) == SearchState::Unknown) { | ||
| 48 | stack_.push(node_id); | ||
| 49 | } | ||
| 50 | |||
| 51 | while (stack_.Count() > 0) { | ||
| 52 | auto current_node{stack_.top()}; | ||
| 53 | switch (GetState(current_node)) { | ||
| 54 | case SearchState::Unknown: | ||
| 55 | SetState(current_node, SearchState::Found); | ||
| 56 | break; | ||
| 57 | case SearchState::Found: | ||
| 58 | SetState(current_node, SearchState::Complete); | ||
| 59 | PushTsortResult(current_node); | ||
| 60 | stack_.pop(); | ||
| 61 | continue; | ||
| 62 | case SearchState::Complete: | ||
| 63 | stack_.pop(); | ||
| 64 | continue; | ||
| 65 | } | ||
| 66 | |||
| 67 | const auto edge_count{edge_matrix.GetNodeCount()}; | ||
| 68 | for (u32 edge_id = 0; edge_id < edge_count; edge_id++) { | ||
| 69 | if (!edge_matrix.Connected(current_node, edge_id)) { | ||
| 70 | continue; | ||
| 71 | } | ||
| 72 | |||
| 73 | switch (GetState(edge_id)) { | ||
| 74 | case SearchState::Unknown: | ||
| 75 | stack_.push(edge_id); | ||
| 76 | break; | ||
| 77 | case SearchState::Found: | ||
| 78 | LOG_ERROR(Service_Audio, | ||
| 79 | "Cycle detected in the node graph, graph is not a DAG! " | ||
| 80 | "Bailing to avoid an infinite loop"); | ||
| 81 | ResetState(); | ||
| 82 | return false; | ||
| 83 | case SearchState::Complete: | ||
| 84 | break; | ||
| 85 | } | ||
| 86 | } | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | return true; | ||
| 91 | } | ||
| 92 | |||
| 93 | NodeStates::SearchState NodeStates::GetState(const u32 id) const { | ||
| 94 | if (nodes_found.buffer[id]) { | ||
| 95 | return SearchState::Found; | ||
| 96 | } else if (nodes_complete.buffer[id]) { | ||
| 97 | return SearchState::Complete; | ||
| 98 | } | ||
| 99 | return SearchState::Unknown; | ||
| 100 | } | ||
| 101 | |||
| 102 | void NodeStates::PushTsortResult(const u32 id) { | ||
| 103 | results[result_pos++] = id; | ||
| 104 | } | ||
| 105 | |||
| 106 | void NodeStates::SetState(const u32 id, const SearchState state) { | ||
| 107 | switch (state) { | ||
| 108 | case SearchState::Complete: | ||
| 109 | nodes_found.buffer[id] = false; | ||
| 110 | nodes_complete.buffer[id] = true; | ||
| 111 | break; | ||
| 112 | case SearchState::Found: | ||
| 113 | nodes_found.buffer[id] = true; | ||
| 114 | nodes_complete.buffer[id] = false; | ||
| 115 | break; | ||
| 116 | case SearchState::Unknown: | ||
| 117 | nodes_found.buffer[id] = false; | ||
| 118 | nodes_complete.buffer[id] = false; | ||
| 119 | break; | ||
| 120 | default: | ||
| 121 | LOG_ERROR(Service_Audio, "Unknown node SearchState {}", static_cast<u32>(state)); | ||
| 122 | break; | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | void NodeStates::ResetState() { | ||
| 127 | nodes_found.reset(); | ||
| 128 | nodes_complete.reset(); | ||
| 129 | std::fill(results.begin(), results.end(), -1); | ||
| 130 | result_pos = 0; | ||
| 131 | } | ||
| 132 | |||
| 133 | u32 NodeStates::GetNodeCount() const { | ||
| 134 | return node_count; | ||
| 135 | } | ||
| 136 | |||
| 137 | std::vector<s32> NodeStates::GetSortedResuls() const { | ||
| 138 | return {results.rbegin(), results.rbegin() + result_pos}; | ||
| 139 | } | ||
| 140 | |||
| 141 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/nodes/node_states.h b/src/audio_core/renderer/nodes/node_states.h new file mode 100644 index 000000000..a1e0958a2 --- /dev/null +++ b/src/audio_core/renderer/nodes/node_states.h | |||
| @@ -0,0 +1,195 @@ | |||
| 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 <vector> | ||
| 8 | |||
| 9 | #include "audio_core/renderer/nodes/edge_matrix.h" | ||
| 10 | #include "common/alignment.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | /** | ||
| 15 | * Graph utility functions for sorting and getting results from the DAG. | ||
| 16 | */ | ||
| 17 | class NodeStates { | ||
| 18 | /** | ||
| 19 | * State of a node in the depth first search. | ||
| 20 | */ | ||
| 21 | enum class SearchState { | ||
| 22 | Unknown, | ||
| 23 | Found, | ||
| 24 | Complete, | ||
| 25 | }; | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Stack used for a depth first search. | ||
| 29 | */ | ||
| 30 | struct Stack { | ||
| 31 | /** | ||
| 32 | * Calculate the workbuffer size required for this stack. | ||
| 33 | * | ||
| 34 | * @param count - Maximum number of nodes for the stack. | ||
| 35 | * @return Required buffer size. | ||
| 36 | */ | ||
| 37 | static u32 CalcBufferSize(u32 count) { | ||
| 38 | return count * sizeof(u32); | ||
| 39 | } | ||
| 40 | |||
| 41 | /** | ||
| 42 | * Reset the stack back to default. | ||
| 43 | * | ||
| 44 | * @param buffer_ - The new buffer to use. | ||
| 45 | * @param size_ - The size of the new buffer. | ||
| 46 | */ | ||
| 47 | void Reset(u32* buffer_, u32 size_) { | ||
| 48 | stack = {buffer_, size_}; | ||
| 49 | size = size_; | ||
| 50 | pos = 0; | ||
| 51 | unk_10 = size_; | ||
| 52 | } | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Get the current stack position. | ||
| 56 | * | ||
| 57 | * @return The current stack position. | ||
| 58 | */ | ||
| 59 | u32 Count() { | ||
| 60 | return pos; | ||
| 61 | } | ||
| 62 | |||
| 63 | /** | ||
| 64 | * Push a new node to the stack. | ||
| 65 | * | ||
| 66 | * @param data - The node to push. | ||
| 67 | */ | ||
| 68 | void push(u32 data) { | ||
| 69 | stack[pos++] = data; | ||
| 70 | } | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Pop a node from the stack. | ||
| 74 | * | ||
| 75 | * @return The node on the top of the stack. | ||
| 76 | */ | ||
| 77 | u32 pop() { | ||
| 78 | return stack[--pos]; | ||
| 79 | } | ||
| 80 | |||
| 81 | /** | ||
| 82 | * Get the top of the stack without popping. | ||
| 83 | * | ||
| 84 | * @return The node on the top of the stack. | ||
| 85 | */ | ||
| 86 | u32 top() { | ||
| 87 | return stack[pos - 1]; | ||
| 88 | } | ||
| 89 | |||
| 90 | /// Buffer for the stack | ||
| 91 | std::span<u32> stack{}; | ||
| 92 | /// Size of the stack buffer | ||
| 93 | u32 size{}; | ||
| 94 | /// Current stack position | ||
| 95 | u32 pos{}; | ||
| 96 | /// Unknown | ||
| 97 | u32 unk_10{}; | ||
| 98 | }; | ||
| 99 | |||
| 100 | public: | ||
| 101 | /** | ||
| 102 | * Calculate the workbuffer size required for the node states. | ||
| 103 | * | ||
| 104 | * @param count - The number of nodes. | ||
| 105 | * @return The required workbuffer size. | ||
| 106 | */ | ||
| 107 | static u64 GetWorkBufferSize(u32 count) { | ||
| 108 | return (Common::AlignUp(count, 0x40) / sizeof(u64)) * 2 + count * sizeof(BitArray) + | ||
| 109 | count * Stack::CalcBufferSize(count); | ||
| 110 | } | ||
| 111 | |||
| 112 | /** | ||
| 113 | * Initialize the node states. | ||
| 114 | * | ||
| 115 | * @param buffer - The workbuffer to use. Unused. | ||
| 116 | * @param node_buffer_size - The size of the workbuffer. Unused. | ||
| 117 | * @param count - The number of nodes in the graph. | ||
| 118 | */ | ||
| 119 | void Initialize(std::span<u8> nodes, u64 node_buffer_size, u32 count); | ||
| 120 | |||
| 121 | /** | ||
| 122 | * Sort the graph. Only calls DepthFirstSearch. | ||
| 123 | * | ||
| 124 | * @param edge_matrix - The edge matrix used to hold the connections between nodes. | ||
| 125 | * @return True if the sort was successful, otherwise false. | ||
| 126 | */ | ||
| 127 | bool Tsort(const EdgeMatrix& edge_matrix); | ||
| 128 | |||
| 129 | /** | ||
| 130 | * Sort the graph via depth first search. | ||
| 131 | * | ||
| 132 | * @param edge_matrix - The edge matrix used to hold the connections between nodes. | ||
| 133 | * @param stack - The stack used for pushing and popping nodes. | ||
| 134 | * @return True if the sort was successful, otherwise false. | ||
| 135 | */ | ||
| 136 | bool DepthFirstSearch(const EdgeMatrix& edge_matrix, Stack& stack); | ||
| 137 | |||
| 138 | /** | ||
| 139 | * Get the search state of a given node. | ||
| 140 | * | ||
| 141 | * @param id - The node id to check. | ||
| 142 | * @return The node's search state. See SearchState | ||
| 143 | */ | ||
| 144 | SearchState GetState(u32 id) const; | ||
| 145 | |||
| 146 | /** | ||
| 147 | * Push a node id to the results buffer when found in the DFS. | ||
| 148 | * | ||
| 149 | * @param id - The node id to push. | ||
| 150 | */ | ||
| 151 | void PushTsortResult(u32 id); | ||
| 152 | |||
| 153 | /** | ||
| 154 | * Set the state of a node. | ||
| 155 | * | ||
| 156 | * @param id - The node id to alter. | ||
| 157 | * @param state - The new search state. | ||
| 158 | */ | ||
| 159 | void SetState(u32 id, SearchState state); | ||
| 160 | |||
| 161 | /** | ||
| 162 | * Reset the nodes found, complete and the results. | ||
| 163 | */ | ||
| 164 | void ResetState(); | ||
| 165 | |||
| 166 | /** | ||
| 167 | * Get the number of nodes in the graph. | ||
| 168 | * | ||
| 169 | * @return The number of nodes. | ||
| 170 | */ | ||
| 171 | u32 GetNodeCount() const; | ||
| 172 | |||
| 173 | /** | ||
| 174 | * Get the sorted results from the DFS. | ||
| 175 | * | ||
| 176 | * @return Vector of nodes in reverse order. | ||
| 177 | */ | ||
| 178 | std::vector<s32> GetSortedResuls() const; | ||
| 179 | |||
| 180 | private: | ||
| 181 | /// Number of nodes in the graph | ||
| 182 | u32 node_count{}; | ||
| 183 | /// Position in results buffer | ||
| 184 | u32 result_pos{}; | ||
| 185 | /// List of nodes found | ||
| 186 | BitArray nodes_found{}; | ||
| 187 | /// List of nodes completed | ||
| 188 | BitArray nodes_complete{}; | ||
| 189 | /// List of results from the depth first search | ||
| 190 | std::span<u32> results{}; | ||
| 191 | /// Stack used during the depth first search | ||
| 192 | Stack stack{}; | ||
| 193 | }; | ||
| 194 | |||
| 195 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/performance/detail_aspect.cpp b/src/audio_core/renderer/performance/detail_aspect.cpp new file mode 100644 index 000000000..f6405937f --- /dev/null +++ b/src/audio_core/renderer/performance/detail_aspect.cpp | |||
| @@ -0,0 +1,25 @@ | |||
| 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_buffer.h" | ||
| 5 | #include "audio_core/renderer/command/command_generator.h" | ||
| 6 | #include "audio_core/renderer/performance/detail_aspect.h" | ||
| 7 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | DetailAspect::DetailAspect(CommandGenerator& command_generator_, | ||
| 11 | const PerformanceEntryType entry_type, const s32 node_id_, | ||
| 12 | const PerformanceDetailType detail_type) | ||
| 13 | : command_generator{command_generator_}, node_id{node_id_} { | ||
| 14 | auto perf_manager{command_generator.GetPerformanceManager()}; | ||
| 15 | if (perf_manager != nullptr && perf_manager->IsInitialized() && | ||
| 16 | perf_manager->IsDetailTarget(node_id) && | ||
| 17 | perf_manager->GetNextEntry(performance_entry_address, detail_type, entry_type, node_id)) { | ||
| 18 | command_generator.GeneratePerformanceCommand(node_id, PerformanceState::Start, | ||
| 19 | performance_entry_address); | ||
| 20 | |||
| 21 | initialized = true; | ||
| 22 | } | ||
| 23 | } | ||
| 24 | |||
| 25 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/performance/detail_aspect.h b/src/audio_core/renderer/performance/detail_aspect.h new file mode 100644 index 000000000..ee4ac2f76 --- /dev/null +++ b/src/audio_core/renderer/performance/detail_aspect.h | |||
| @@ -0,0 +1,33 @@ | |||
| 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/performance/performance_entry_addresses.h" | ||
| 7 | #include "audio_core/renderer/performance/performance_manager.h" | ||
| 8 | #include "common/common_types.h" | ||
| 9 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | class CommandGenerator; | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Holds detailed information about performance metrics, filled in by the AudioRenderer during | ||
| 15 | * Performance commands. | ||
| 16 | */ | ||
| 17 | class DetailAspect { | ||
| 18 | public: | ||
| 19 | DetailAspect() = default; | ||
| 20 | DetailAspect(CommandGenerator& command_generator, PerformanceEntryType entry_type, s32 node_id, | ||
| 21 | PerformanceDetailType detail_type); | ||
| 22 | |||
| 23 | /// Command generator the command will be generated into | ||
| 24 | CommandGenerator& command_generator; | ||
| 25 | /// Addresses to be filled by the AudioRenderer | ||
| 26 | PerformanceEntryAddresses performance_entry_address{}; | ||
| 27 | /// Is this detail aspect initialized? | ||
| 28 | bool initialized{}; | ||
| 29 | /// Node id of this aspect | ||
| 30 | s32 node_id; | ||
| 31 | }; | ||
| 32 | |||
| 33 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/performance/entry_aspect.cpp b/src/audio_core/renderer/performance/entry_aspect.cpp new file mode 100644 index 000000000..dd4165803 --- /dev/null +++ b/src/audio_core/renderer/performance/entry_aspect.cpp | |||
| @@ -0,0 +1,23 @@ | |||
| 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_buffer.h" | ||
| 5 | #include "audio_core/renderer/command/command_generator.h" | ||
| 6 | #include "audio_core/renderer/performance/entry_aspect.h" | ||
| 7 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | EntryAspect::EntryAspect(CommandGenerator& command_generator_, const PerformanceEntryType type, | ||
| 11 | const s32 node_id_) | ||
| 12 | : command_generator{command_generator_}, node_id{node_id_} { | ||
| 13 | auto perf_manager{command_generator.GetPerformanceManager()}; | ||
| 14 | if (perf_manager != nullptr && perf_manager->IsInitialized() && | ||
| 15 | perf_manager->GetNextEntry(performance_entry_address, type, node_id)) { | ||
| 16 | command_generator.GeneratePerformanceCommand(node_id, PerformanceState::Start, | ||
| 17 | performance_entry_address); | ||
| 18 | |||
| 19 | initialized = true; | ||
| 20 | } | ||
| 21 | } | ||
| 22 | |||
| 23 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/performance/entry_aspect.h b/src/audio_core/renderer/performance/entry_aspect.h new file mode 100644 index 000000000..01c1eb3f1 --- /dev/null +++ b/src/audio_core/renderer/performance/entry_aspect.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/performance/performance_entry_addresses.h" | ||
| 7 | #include "audio_core/renderer/performance/performance_manager.h" | ||
| 8 | #include "common/common_types.h" | ||
| 9 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | class CommandGenerator; | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Holds entry information about performance metrics, filled in by the AudioRenderer during | ||
| 15 | * Performance commands. | ||
| 16 | */ | ||
| 17 | class EntryAspect { | ||
| 18 | public: | ||
| 19 | EntryAspect() = default; | ||
| 20 | EntryAspect(CommandGenerator& command_generator, PerformanceEntryType type, s32 node_id); | ||
| 21 | |||
| 22 | /// Command generator the command will be generated into | ||
| 23 | CommandGenerator& command_generator; | ||
| 24 | /// Addresses to be filled by the AudioRenderer | ||
| 25 | PerformanceEntryAddresses performance_entry_address{}; | ||
| 26 | /// Is this detail aspect initialized? | ||
| 27 | bool initialized{}; | ||
| 28 | /// Node id of this aspect | ||
| 29 | s32 node_id; | ||
| 30 | }; | ||
| 31 | |||
| 32 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/performance/performance_detail.h b/src/audio_core/renderer/performance/performance_detail.h new file mode 100644 index 000000000..3a4897e60 --- /dev/null +++ b/src/audio_core/renderer/performance/performance_detail.h | |||
| @@ -0,0 +1,50 @@ | |||
| 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/performance/performance_entry.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | enum class PerformanceDetailType : u8 { | ||
| 12 | Invalid, | ||
| 13 | Unk1, | ||
| 14 | Unk2, | ||
| 15 | Unk3, | ||
| 16 | Unk4, | ||
| 17 | Unk5, | ||
| 18 | Unk6, | ||
| 19 | Unk7, | ||
| 20 | Unk8, | ||
| 21 | Unk9, | ||
| 22 | Unk10, | ||
| 23 | Unk11, | ||
| 24 | Unk12, | ||
| 25 | Unk13, | ||
| 26 | }; | ||
| 27 | |||
| 28 | struct PerformanceDetailVersion1 { | ||
| 29 | /* 0x00 */ u32 node_id; | ||
| 30 | /* 0x04 */ u32 start_time; | ||
| 31 | /* 0x08 */ u32 processed_time; | ||
| 32 | /* 0x0C */ PerformanceDetailType detail_type; | ||
| 33 | /* 0x0D */ PerformanceEntryType entry_type; | ||
| 34 | }; | ||
| 35 | static_assert(sizeof(PerformanceDetailVersion1) == 0x10, | ||
| 36 | "PerformanceDetailVersion1 has the worng size!"); | ||
| 37 | |||
| 38 | struct PerformanceDetailVersion2 { | ||
| 39 | /* 0x00 */ u32 node_id; | ||
| 40 | /* 0x04 */ u32 start_time; | ||
| 41 | /* 0x08 */ u32 processed_time; | ||
| 42 | /* 0x0C */ PerformanceDetailType detail_type; | ||
| 43 | /* 0x0D */ PerformanceEntryType entry_type; | ||
| 44 | /* 0x10 */ u32 unk_10; | ||
| 45 | /* 0x14 */ char unk14[0x4]; | ||
| 46 | }; | ||
| 47 | static_assert(sizeof(PerformanceDetailVersion2) == 0x18, | ||
| 48 | "PerformanceDetailVersion2 has the worng size!"); | ||
| 49 | |||
| 50 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/performance/performance_entry.h b/src/audio_core/renderer/performance/performance_entry.h new file mode 100644 index 000000000..d1b21406b --- /dev/null +++ b/src/audio_core/renderer/performance/performance_entry.h | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_types.h" | ||
| 7 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | enum class PerformanceEntryType : u8 { | ||
| 11 | Invalid, | ||
| 12 | Voice, | ||
| 13 | SubMix, | ||
| 14 | FinalMix, | ||
| 15 | Sink, | ||
| 16 | }; | ||
| 17 | |||
| 18 | struct PerformanceEntryVersion1 { | ||
| 19 | /* 0x00 */ u32 node_id; | ||
| 20 | /* 0x04 */ u32 start_time; | ||
| 21 | /* 0x08 */ u32 processed_time; | ||
| 22 | /* 0x0C */ PerformanceEntryType entry_type; | ||
| 23 | }; | ||
| 24 | static_assert(sizeof(PerformanceEntryVersion1) == 0x10, | ||
| 25 | "PerformanceEntryVersion1 has the worng size!"); | ||
| 26 | |||
| 27 | struct PerformanceEntryVersion2 { | ||
| 28 | /* 0x00 */ u32 node_id; | ||
| 29 | /* 0x04 */ u32 start_time; | ||
| 30 | /* 0x08 */ u32 processed_time; | ||
| 31 | /* 0x0C */ PerformanceEntryType entry_type; | ||
| 32 | /* 0x0D */ char unk0D[0xB]; | ||
| 33 | }; | ||
| 34 | static_assert(sizeof(PerformanceEntryVersion2) == 0x18, | ||
| 35 | "PerformanceEntryVersion2 has the worng size!"); | ||
| 36 | |||
| 37 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/performance/performance_entry_addresses.h b/src/audio_core/renderer/performance/performance_entry_addresses.h new file mode 100644 index 000000000..e381d765c --- /dev/null +++ b/src/audio_core/renderer/performance/performance_entry_addresses.h | |||
| @@ -0,0 +1,17 @@ | |||
| 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 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | struct PerformanceEntryAddresses { | ||
| 11 | CpuAddr translated_address; | ||
| 12 | CpuAddr entry_start_time_offset; | ||
| 13 | CpuAddr header_entry_count_offset; | ||
| 14 | CpuAddr entry_processed_time_offset; | ||
| 15 | }; | ||
| 16 | |||
| 17 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/performance/performance_frame_header.h b/src/audio_core/renderer/performance/performance_frame_header.h new file mode 100644 index 000000000..707cc0afb --- /dev/null +++ b/src/audio_core/renderer/performance/performance_frame_header.h | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_types.h" | ||
| 7 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | struct PerformanceFrameHeaderVersion1 { | ||
| 11 | /* 0x00 */ u32 magic; // "PERF" | ||
| 12 | /* 0x04 */ u32 entry_count; | ||
| 13 | /* 0x08 */ u32 detail_count; | ||
| 14 | /* 0x0C */ u32 next_offset; | ||
| 15 | /* 0x10 */ u32 total_processing_time; | ||
| 16 | /* 0x14 */ u32 frame_index; | ||
| 17 | }; | ||
| 18 | static_assert(sizeof(PerformanceFrameHeaderVersion1) == 0x18, | ||
| 19 | "PerformanceFrameHeaderVersion1 has the worng size!"); | ||
| 20 | |||
| 21 | struct PerformanceFrameHeaderVersion2 { | ||
| 22 | /* 0x00 */ u32 magic; // "PERF" | ||
| 23 | /* 0x04 */ u32 entry_count; | ||
| 24 | /* 0x08 */ u32 detail_count; | ||
| 25 | /* 0x0C */ u32 next_offset; | ||
| 26 | /* 0x10 */ u32 total_processing_time; | ||
| 27 | /* 0x14 */ u32 voices_dropped; | ||
| 28 | /* 0x18 */ u64 start_time; | ||
| 29 | /* 0x20 */ u32 frame_index; | ||
| 30 | /* 0x24 */ bool render_time_exceeded; | ||
| 31 | /* 0x25 */ char unk25[0xB]; | ||
| 32 | }; | ||
| 33 | static_assert(sizeof(PerformanceFrameHeaderVersion2) == 0x30, | ||
| 34 | "PerformanceFrameHeaderVersion2 has the worng size!"); | ||
| 35 | |||
| 36 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/performance/performance_manager.cpp b/src/audio_core/renderer/performance/performance_manager.cpp new file mode 100644 index 000000000..fd5873e1e --- /dev/null +++ b/src/audio_core/renderer/performance/performance_manager.cpp | |||
| @@ -0,0 +1,645 @@ | |||
| 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/memory/memory_pool_info.h" | ||
| 6 | #include "audio_core/renderer/performance/performance_manager.h" | ||
| 7 | #include "common/common_funcs.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | void PerformanceManager::CreateImpl(const size_t version) { | ||
| 12 | switch (version) { | ||
| 13 | case 1: | ||
| 14 | impl = std::make_unique< | ||
| 15 | PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, | ||
| 16 | PerformanceEntryVersion1, PerformanceDetailVersion1>>(); | ||
| 17 | break; | ||
| 18 | case 2: | ||
| 19 | impl = std::make_unique< | ||
| 20 | PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, | ||
| 21 | PerformanceEntryVersion2, PerformanceDetailVersion2>>(); | ||
| 22 | break; | ||
| 23 | default: | ||
| 24 | LOG_WARNING(Service_Audio, "Invalid PerformanceMetricsDataFormat {}, creating version 1", | ||
| 25 | static_cast<u32>(version)); | ||
| 26 | impl = std::make_unique< | ||
| 27 | PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, | ||
| 28 | PerformanceEntryVersion1, PerformanceDetailVersion1>>(); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | void PerformanceManager::Initialize(std::span<u8> workbuffer, const u64 workbuffer_size, | ||
| 33 | const AudioRendererParameterInternal& params, | ||
| 34 | const BehaviorInfo& behavior, | ||
| 35 | const MemoryPoolInfo& memory_pool) { | ||
| 36 | CreateImpl(behavior.GetPerformanceMetricsDataFormat()); | ||
| 37 | impl->Initialize(workbuffer, workbuffer_size, params, behavior, memory_pool); | ||
| 38 | } | ||
| 39 | |||
| 40 | bool PerformanceManager::IsInitialized() const { | ||
| 41 | if (impl) { | ||
| 42 | return impl->IsInitialized(); | ||
| 43 | } | ||
| 44 | return false; | ||
| 45 | } | ||
| 46 | |||
| 47 | u32 PerformanceManager::CopyHistories(u8* out_buffer, u64 out_size) { | ||
| 48 | if (impl) { | ||
| 49 | return impl->CopyHistories(out_buffer, out_size); | ||
| 50 | } | ||
| 51 | return 0; | ||
| 52 | } | ||
| 53 | |||
| 54 | bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk, | ||
| 55 | const PerformanceSysDetailType sys_detail_type, | ||
| 56 | const s32 node_id) { | ||
| 57 | if (impl) { | ||
| 58 | return impl->GetNextEntry(addresses, unk, sys_detail_type, node_id); | ||
| 59 | } | ||
| 60 | return false; | ||
| 61 | } | ||
| 62 | |||
| 63 | bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses, | ||
| 64 | const PerformanceEntryType entry_type, const s32 node_id) { | ||
| 65 | if (impl) { | ||
| 66 | return impl->GetNextEntry(addresses, entry_type, node_id); | ||
| 67 | } | ||
| 68 | return false; | ||
| 69 | } | ||
| 70 | |||
| 71 | bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses, | ||
| 72 | const PerformanceDetailType detail_type, | ||
| 73 | const PerformanceEntryType entry_type, const s32 node_id) { | ||
| 74 | if (impl) { | ||
| 75 | return impl->GetNextEntry(addresses, detail_type, entry_type, node_id); | ||
| 76 | } | ||
| 77 | return false; | ||
| 78 | } | ||
| 79 | |||
| 80 | void PerformanceManager::TapFrame(const bool dsp_behind, const u32 voices_dropped, | ||
| 81 | const u64 rendering_start_tick) { | ||
| 82 | if (impl) { | ||
| 83 | impl->TapFrame(dsp_behind, voices_dropped, rendering_start_tick); | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | bool PerformanceManager::IsDetailTarget(const u32 target_node_id) const { | ||
| 88 | if (impl) { | ||
| 89 | return impl->IsDetailTarget(target_node_id); | ||
| 90 | } | ||
| 91 | return false; | ||
| 92 | } | ||
| 93 | |||
| 94 | void PerformanceManager::SetDetailTarget(const u32 target_node_id) { | ||
| 95 | if (impl) { | ||
| 96 | impl->SetDetailTarget(target_node_id); | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | template <> | ||
| 101 | void PerformanceManagerImpl< | ||
| 102 | PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, | ||
| 103 | PerformanceDetailVersion1>::Initialize(std::span<u8> workbuffer_, const u64 workbuffer_size, | ||
| 104 | const AudioRendererParameterInternal& params, | ||
| 105 | const BehaviorInfo& behavior, | ||
| 106 | const MemoryPoolInfo& memory_pool) { | ||
| 107 | workbuffer = workbuffer_; | ||
| 108 | entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1; | ||
| 109 | max_detail_count = MaxDetailEntries; | ||
| 110 | frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params); | ||
| 111 | const auto frame_count{static_cast<u32>(workbuffer_size / frame_size)}; | ||
| 112 | max_frames = frame_count - 1; | ||
| 113 | translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size); | ||
| 114 | |||
| 115 | // The first frame is the "current" frame we're writing to. | ||
| 116 | auto buffer_offset{workbuffer.data()}; | ||
| 117 | frame_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(buffer_offset); | ||
| 118 | buffer_offset += sizeof(PerformanceFrameHeaderVersion1); | ||
| 119 | entry_buffer = {reinterpret_cast<PerformanceEntryVersion1*>(buffer_offset), entries_per_frame}; | ||
| 120 | buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1); | ||
| 121 | detail_buffer = {reinterpret_cast<PerformanceDetailVersion1*>(buffer_offset), max_detail_count}; | ||
| 122 | |||
| 123 | // After the current, is a ringbuffer of history frames, the current frame will be copied here | ||
| 124 | // before a new frame is written. | ||
| 125 | frame_history = std::span<u8>(workbuffer.data() + frame_size, workbuffer_size - frame_size); | ||
| 126 | |||
| 127 | // If there's room for any history frames. | ||
| 128 | if (frame_count >= 2) { | ||
| 129 | buffer_offset = frame_history.data(); | ||
| 130 | frame_history_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(buffer_offset); | ||
| 131 | buffer_offset += sizeof(PerformanceFrameHeaderVersion1); | ||
| 132 | frame_history_entries = {reinterpret_cast<PerformanceEntryVersion1*>(buffer_offset), | ||
| 133 | entries_per_frame}; | ||
| 134 | buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1); | ||
| 135 | frame_history_details = {reinterpret_cast<PerformanceDetailVersion1*>(buffer_offset), | ||
| 136 | max_detail_count}; | ||
| 137 | } else { | ||
| 138 | frame_history_header = {}; | ||
| 139 | frame_history_entries = {}; | ||
| 140 | frame_history_details = {}; | ||
| 141 | } | ||
| 142 | |||
| 143 | target_node_id = 0; | ||
| 144 | version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat()); | ||
| 145 | entry_count = 0; | ||
| 146 | detail_count = 0; | ||
| 147 | frame_header->entry_count = 0; | ||
| 148 | frame_header->detail_count = 0; | ||
| 149 | output_frame_index = 0; | ||
| 150 | last_output_frame_index = 0; | ||
| 151 | is_initialized = true; | ||
| 152 | } | ||
| 153 | |||
| 154 | template <> | ||
| 155 | bool PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, | ||
| 156 | PerformanceEntryVersion1, PerformanceDetailVersion1>::IsInitialized() | ||
| 157 | const { | ||
| 158 | return is_initialized; | ||
| 159 | } | ||
| 160 | |||
| 161 | template <> | ||
| 162 | u32 PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, | ||
| 163 | PerformanceEntryVersion1, | ||
| 164 | PerformanceDetailVersion1>::CopyHistories(u8* out_buffer, u64 out_size) { | ||
| 165 | if (out_buffer == nullptr || out_size == 0 || !is_initialized) { | ||
| 166 | return 0; | ||
| 167 | } | ||
| 168 | |||
| 169 | // Are there any new frames waiting to be output? | ||
| 170 | if (last_output_frame_index == output_frame_index) { | ||
| 171 | return 0; | ||
| 172 | } | ||
| 173 | |||
| 174 | PerformanceFrameHeaderVersion1* out_header{nullptr}; | ||
| 175 | u32 out_history_size{0}; | ||
| 176 | |||
| 177 | while (last_output_frame_index != output_frame_index) { | ||
| 178 | PerformanceFrameHeaderVersion1* history_header{nullptr}; | ||
| 179 | std::span<PerformanceEntryVersion1> history_entries{}; | ||
| 180 | std::span<PerformanceDetailVersion1> history_details{}; | ||
| 181 | |||
| 182 | if (max_frames > 0) { | ||
| 183 | auto frame_offset{&frame_history[last_output_frame_index * frame_size]}; | ||
| 184 | history_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(frame_offset); | ||
| 185 | frame_offset += sizeof(PerformanceFrameHeaderVersion1); | ||
| 186 | history_entries = {reinterpret_cast<PerformanceEntryVersion1*>(frame_offset), | ||
| 187 | history_header->entry_count}; | ||
| 188 | frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion1); | ||
| 189 | history_details = {reinterpret_cast<PerformanceDetailVersion1*>(frame_offset), | ||
| 190 | history_header->detail_count}; | ||
| 191 | } else { | ||
| 192 | // Original code does not break here, but will crash when trying to dereference the | ||
| 193 | // header in the next if, so let's just skip this frame and continue... | ||
| 194 | // Hopefully this will not happen. | ||
| 195 | LOG_WARNING(Service_Audio, | ||
| 196 | "max_frames should not be 0! Skipping frame to avoid a crash"); | ||
| 197 | last_output_frame_index++; | ||
| 198 | continue; | ||
| 199 | } | ||
| 200 | |||
| 201 | if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion1) + | ||
| 202 | history_header->detail_count * sizeof(PerformanceDetailVersion1) + | ||
| 203 | 2 * sizeof(PerformanceFrameHeaderVersion1)) { | ||
| 204 | break; | ||
| 205 | } | ||
| 206 | |||
| 207 | u32 out_offset{sizeof(PerformanceFrameHeaderVersion1)}; | ||
| 208 | auto out_entries{std::span<PerformanceEntryVersion1>( | ||
| 209 | reinterpret_cast<PerformanceEntryVersion1*>(out_buffer + out_offset), | ||
| 210 | history_header->entry_count)}; | ||
| 211 | u32 out_entry_count{0}; | ||
| 212 | u32 total_processing_time{0}; | ||
| 213 | for (auto& history_entry : history_entries) { | ||
| 214 | if (history_entry.processed_time > 0 || history_entry.start_time > 0) { | ||
| 215 | out_entries[out_entry_count++] = history_entry; | ||
| 216 | total_processing_time += history_entry.processed_time; | ||
| 217 | } | ||
| 218 | } | ||
| 219 | |||
| 220 | out_offset += static_cast<u32>(out_entry_count * sizeof(PerformanceEntryVersion1)); | ||
| 221 | auto out_details{std::span<PerformanceDetailVersion1>( | ||
| 222 | reinterpret_cast<PerformanceDetailVersion1*>(out_buffer + out_offset), | ||
| 223 | history_header->detail_count)}; | ||
| 224 | u32 out_detail_count{0}; | ||
| 225 | for (auto& history_detail : history_details) { | ||
| 226 | if (history_detail.processed_time > 0 || history_detail.start_time > 0) { | ||
| 227 | out_details[out_detail_count++] = history_detail; | ||
| 228 | } | ||
| 229 | } | ||
| 230 | |||
| 231 | out_offset += static_cast<u32>(out_detail_count * sizeof(PerformanceDetailVersion1)); | ||
| 232 | out_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(out_buffer); | ||
| 233 | out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F'); | ||
| 234 | out_header->entry_count = out_entry_count; | ||
| 235 | out_header->detail_count = out_detail_count; | ||
| 236 | out_header->next_offset = out_offset; | ||
| 237 | out_header->total_processing_time = total_processing_time; | ||
| 238 | out_header->frame_index = history_header->frame_index; | ||
| 239 | |||
| 240 | out_history_size += out_offset; | ||
| 241 | |||
| 242 | out_buffer += out_offset; | ||
| 243 | out_size -= out_offset; | ||
| 244 | last_output_frame_index = (last_output_frame_index + 1) % max_frames; | ||
| 245 | } | ||
| 246 | |||
| 247 | // We're out of frames to output, so if there's enough left in the output buffer for another | ||
| 248 | // header, and we output at least 1 frame, set the next header to null. | ||
| 249 | if (out_size > sizeof(PerformanceFrameHeaderVersion1) && out_header != nullptr) { | ||
| 250 | std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion1)); | ||
| 251 | } | ||
| 252 | |||
| 253 | return out_history_size; | ||
| 254 | } | ||
| 255 | |||
| 256 | template <> | ||
| 257 | bool PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, | ||
| 258 | PerformanceEntryVersion1, PerformanceDetailVersion1>:: | ||
| 259 | GetNextEntry([[maybe_unused]] PerformanceEntryAddresses& addresses, [[maybe_unused]] u32** unk, | ||
| 260 | [[maybe_unused]] PerformanceSysDetailType sys_detail_type, | ||
| 261 | [[maybe_unused]] s32 node_id) { | ||
| 262 | return false; | ||
| 263 | } | ||
| 264 | |||
| 265 | template <> | ||
| 266 | bool PerformanceManagerImpl< | ||
| 267 | PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, | ||
| 268 | PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses, | ||
| 269 | const PerformanceEntryType entry_type, | ||
| 270 | const s32 node_id) { | ||
| 271 | if (!is_initialized) { | ||
| 272 | return false; | ||
| 273 | } | ||
| 274 | |||
| 275 | addresses.translated_address = translated_buffer; | ||
| 276 | addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) + | ||
| 277 | offsetof(PerformanceFrameHeaderVersion1, entry_count); | ||
| 278 | |||
| 279 | auto entry{&entry_buffer[entry_count++]}; | ||
| 280 | addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) + | ||
| 281 | offsetof(PerformanceEntryVersion1, start_time); | ||
| 282 | addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) + | ||
| 283 | offsetof(PerformanceEntryVersion1, processed_time); | ||
| 284 | |||
| 285 | std::memset(entry, 0, sizeof(PerformanceEntryVersion1)); | ||
| 286 | entry->node_id = node_id; | ||
| 287 | entry->entry_type = entry_type; | ||
| 288 | return true; | ||
| 289 | } | ||
| 290 | |||
| 291 | template <> | ||
| 292 | bool PerformanceManagerImpl< | ||
| 293 | PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, | ||
| 294 | PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses, | ||
| 295 | const PerformanceDetailType detail_type, | ||
| 296 | const PerformanceEntryType entry_type, | ||
| 297 | const s32 node_id) { | ||
| 298 | if (!is_initialized || detail_count > MaxDetailEntries) { | ||
| 299 | return false; | ||
| 300 | } | ||
| 301 | |||
| 302 | auto detail{&detail_buffer[detail_count++]}; | ||
| 303 | |||
| 304 | addresses.translated_address = translated_buffer; | ||
| 305 | addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) + | ||
| 306 | offsetof(PerformanceFrameHeaderVersion1, detail_count); | ||
| 307 | addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + | ||
| 308 | offsetof(PerformanceDetailVersion1, start_time); | ||
| 309 | addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + | ||
| 310 | offsetof(PerformanceDetailVersion1, processed_time); | ||
| 311 | |||
| 312 | std::memset(detail, 0, sizeof(PerformanceDetailVersion1)); | ||
| 313 | detail->node_id = node_id; | ||
| 314 | detail->entry_type = entry_type; | ||
| 315 | detail->detail_type = detail_type; | ||
| 316 | return true; | ||
| 317 | } | ||
| 318 | |||
| 319 | template <> | ||
| 320 | void PerformanceManagerImpl< | ||
| 321 | PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, | ||
| 322 | PerformanceDetailVersion1>::TapFrame([[maybe_unused]] bool dsp_behind, | ||
| 323 | [[maybe_unused]] u32 voices_dropped, | ||
| 324 | [[maybe_unused]] u64 rendering_start_tick) { | ||
| 325 | if (!is_initialized) { | ||
| 326 | return; | ||
| 327 | } | ||
| 328 | |||
| 329 | if (max_frames > 0) { | ||
| 330 | if (!frame_history.empty() && !workbuffer.empty()) { | ||
| 331 | auto history_frame = reinterpret_cast<PerformanceFrameHeaderVersion1*>( | ||
| 332 | &frame_history[output_frame_index * frame_size]); | ||
| 333 | std::memcpy(history_frame, workbuffer.data(), frame_size); | ||
| 334 | history_frame->frame_index = history_frame_index++; | ||
| 335 | } | ||
| 336 | output_frame_index = (output_frame_index + 1) % max_frames; | ||
| 337 | } | ||
| 338 | |||
| 339 | entry_count = 0; | ||
| 340 | detail_count = 0; | ||
| 341 | frame_header->entry_count = 0; | ||
| 342 | frame_header->detail_count = 0; | ||
| 343 | } | ||
| 344 | |||
| 345 | template <> | ||
| 346 | bool PerformanceManagerImpl< | ||
| 347 | PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, | ||
| 348 | PerformanceDetailVersion1>::IsDetailTarget(const u32 target_node_id_) const { | ||
| 349 | return target_node_id == target_node_id_; | ||
| 350 | } | ||
| 351 | |||
| 352 | template <> | ||
| 353 | void PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, | ||
| 354 | PerformanceEntryVersion1, | ||
| 355 | PerformanceDetailVersion1>::SetDetailTarget(const u32 target_node_id_) { | ||
| 356 | target_node_id = target_node_id_; | ||
| 357 | } | ||
| 358 | |||
| 359 | template <> | ||
| 360 | void PerformanceManagerImpl< | ||
| 361 | PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, | ||
| 362 | PerformanceDetailVersion2>::Initialize(std::span<u8> workbuffer_, const u64 workbuffer_size, | ||
| 363 | const AudioRendererParameterInternal& params, | ||
| 364 | const BehaviorInfo& behavior, | ||
| 365 | const MemoryPoolInfo& memory_pool) { | ||
| 366 | workbuffer = workbuffer_; | ||
| 367 | entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1; | ||
| 368 | max_detail_count = MaxDetailEntries; | ||
| 369 | frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params); | ||
| 370 | const auto frame_count{static_cast<u32>(workbuffer_size / frame_size)}; | ||
| 371 | max_frames = frame_count - 1; | ||
| 372 | translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size); | ||
| 373 | |||
| 374 | // The first frame is the "current" frame we're writing to. | ||
| 375 | auto buffer_offset{workbuffer.data()}; | ||
| 376 | frame_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(buffer_offset); | ||
| 377 | buffer_offset += sizeof(PerformanceFrameHeaderVersion2); | ||
| 378 | entry_buffer = {reinterpret_cast<PerformanceEntryVersion2*>(buffer_offset), entries_per_frame}; | ||
| 379 | buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2); | ||
| 380 | detail_buffer = {reinterpret_cast<PerformanceDetailVersion2*>(buffer_offset), max_detail_count}; | ||
| 381 | |||
| 382 | // After the current, is a ringbuffer of history frames, the current frame will be copied here | ||
| 383 | // before a new frame is written. | ||
| 384 | frame_history = std::span<u8>(workbuffer.data() + frame_size, workbuffer_size - frame_size); | ||
| 385 | |||
| 386 | // If there's room for any history frames. | ||
| 387 | if (frame_count >= 2) { | ||
| 388 | buffer_offset = frame_history.data(); | ||
| 389 | frame_history_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(buffer_offset); | ||
| 390 | buffer_offset += sizeof(PerformanceFrameHeaderVersion2); | ||
| 391 | frame_history_entries = {reinterpret_cast<PerformanceEntryVersion2*>(buffer_offset), | ||
| 392 | entries_per_frame}; | ||
| 393 | buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2); | ||
| 394 | frame_history_details = {reinterpret_cast<PerformanceDetailVersion2*>(buffer_offset), | ||
| 395 | max_detail_count}; | ||
| 396 | } else { | ||
| 397 | frame_history_header = {}; | ||
| 398 | frame_history_entries = {}; | ||
| 399 | frame_history_details = {}; | ||
| 400 | } | ||
| 401 | |||
| 402 | target_node_id = 0; | ||
| 403 | version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat()); | ||
| 404 | entry_count = 0; | ||
| 405 | detail_count = 0; | ||
| 406 | frame_header->entry_count = 0; | ||
| 407 | frame_header->detail_count = 0; | ||
| 408 | output_frame_index = 0; | ||
| 409 | last_output_frame_index = 0; | ||
| 410 | is_initialized = true; | ||
| 411 | } | ||
| 412 | |||
| 413 | template <> | ||
| 414 | bool PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, | ||
| 415 | PerformanceEntryVersion2, PerformanceDetailVersion2>::IsInitialized() | ||
| 416 | const { | ||
| 417 | return is_initialized; | ||
| 418 | } | ||
| 419 | |||
| 420 | template <> | ||
| 421 | u32 PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, | ||
| 422 | PerformanceEntryVersion2, | ||
| 423 | PerformanceDetailVersion2>::CopyHistories(u8* out_buffer, u64 out_size) { | ||
| 424 | if (out_buffer == nullptr || out_size == 0 || !is_initialized) { | ||
| 425 | return 0; | ||
| 426 | } | ||
| 427 | |||
| 428 | // Are there any new frames waiting to be output? | ||
| 429 | if (last_output_frame_index == output_frame_index) { | ||
| 430 | return 0; | ||
| 431 | } | ||
| 432 | |||
| 433 | PerformanceFrameHeaderVersion2* out_header{nullptr}; | ||
| 434 | u32 out_history_size{0}; | ||
| 435 | |||
| 436 | while (last_output_frame_index != output_frame_index) { | ||
| 437 | PerformanceFrameHeaderVersion2* history_header{nullptr}; | ||
| 438 | std::span<PerformanceEntryVersion2> history_entries{}; | ||
| 439 | std::span<PerformanceDetailVersion2> history_details{}; | ||
| 440 | |||
| 441 | if (max_frames > 0) { | ||
| 442 | auto frame_offset{&frame_history[last_output_frame_index * frame_size]}; | ||
| 443 | history_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(frame_offset); | ||
| 444 | frame_offset += sizeof(PerformanceFrameHeaderVersion2); | ||
| 445 | history_entries = {reinterpret_cast<PerformanceEntryVersion2*>(frame_offset), | ||
| 446 | history_header->entry_count}; | ||
| 447 | frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion2); | ||
| 448 | history_details = {reinterpret_cast<PerformanceDetailVersion2*>(frame_offset), | ||
| 449 | history_header->detail_count}; | ||
| 450 | } else { | ||
| 451 | // Original code does not break here, but will crash when trying to dereference the | ||
| 452 | // header in the next if, so let's just skip this frame and continue... | ||
| 453 | // Hopefully this will not happen. | ||
| 454 | LOG_WARNING(Service_Audio, | ||
| 455 | "max_frames should not be 0! Skipping frame to avoid a crash"); | ||
| 456 | last_output_frame_index++; | ||
| 457 | continue; | ||
| 458 | } | ||
| 459 | |||
| 460 | if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion2) + | ||
| 461 | history_header->detail_count * sizeof(PerformanceDetailVersion2) + | ||
| 462 | 2 * sizeof(PerformanceFrameHeaderVersion2)) { | ||
| 463 | break; | ||
| 464 | } | ||
| 465 | |||
| 466 | u32 out_offset{sizeof(PerformanceFrameHeaderVersion2)}; | ||
| 467 | auto out_entries{std::span<PerformanceEntryVersion2>( | ||
| 468 | reinterpret_cast<PerformanceEntryVersion2*>(out_buffer + out_offset), | ||
| 469 | history_header->entry_count)}; | ||
| 470 | u32 out_entry_count{0}; | ||
| 471 | u32 total_processing_time{0}; | ||
| 472 | for (auto& history_entry : history_entries) { | ||
| 473 | if (history_entry.processed_time > 0 || history_entry.start_time > 0) { | ||
| 474 | out_entries[out_entry_count++] = history_entry; | ||
| 475 | total_processing_time += history_entry.processed_time; | ||
| 476 | } | ||
| 477 | } | ||
| 478 | |||
| 479 | out_offset += static_cast<u32>(out_entry_count * sizeof(PerformanceEntryVersion2)); | ||
| 480 | auto out_details{std::span<PerformanceDetailVersion2>( | ||
| 481 | reinterpret_cast<PerformanceDetailVersion2*>(out_buffer + out_offset), | ||
| 482 | history_header->detail_count)}; | ||
| 483 | u32 out_detail_count{0}; | ||
| 484 | for (auto& history_detail : history_details) { | ||
| 485 | if (history_detail.processed_time > 0 || history_detail.start_time > 0) { | ||
| 486 | out_details[out_detail_count++] = history_detail; | ||
| 487 | } | ||
| 488 | } | ||
| 489 | |||
| 490 | out_offset += static_cast<u32>(out_detail_count * sizeof(PerformanceDetailVersion2)); | ||
| 491 | out_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(out_buffer); | ||
| 492 | out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F'); | ||
| 493 | out_header->entry_count = out_entry_count; | ||
| 494 | out_header->detail_count = out_detail_count; | ||
| 495 | out_header->next_offset = out_offset; | ||
| 496 | out_header->total_processing_time = total_processing_time; | ||
| 497 | out_header->voices_dropped = history_header->voices_dropped; | ||
| 498 | out_header->start_time = history_header->start_time; | ||
| 499 | out_header->frame_index = history_header->frame_index; | ||
| 500 | out_header->render_time_exceeded = history_header->render_time_exceeded; | ||
| 501 | |||
| 502 | out_history_size += out_offset; | ||
| 503 | |||
| 504 | out_buffer += out_offset; | ||
| 505 | out_size -= out_offset; | ||
| 506 | last_output_frame_index = (last_output_frame_index + 1) % max_frames; | ||
| 507 | } | ||
| 508 | |||
| 509 | // We're out of frames to output, so if there's enough left in the output buffer for another | ||
| 510 | // header, and we output at least 1 frame, set the next header to null. | ||
| 511 | if (out_size > sizeof(PerformanceFrameHeaderVersion2) && out_header != nullptr) { | ||
| 512 | std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion2)); | ||
| 513 | } | ||
| 514 | |||
| 515 | return out_history_size; | ||
| 516 | } | ||
| 517 | |||
| 518 | template <> | ||
| 519 | bool PerformanceManagerImpl< | ||
| 520 | PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, | ||
| 521 | PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk, | ||
| 522 | const PerformanceSysDetailType sys_detail_type, | ||
| 523 | const s32 node_id) { | ||
| 524 | if (!is_initialized || detail_count > MaxDetailEntries) { | ||
| 525 | return false; | ||
| 526 | } | ||
| 527 | |||
| 528 | auto detail{&detail_buffer[detail_count++]}; | ||
| 529 | |||
| 530 | addresses.translated_address = translated_buffer; | ||
| 531 | addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) + | ||
| 532 | offsetof(PerformanceFrameHeaderVersion2, detail_count); | ||
| 533 | addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + | ||
| 534 | offsetof(PerformanceDetailVersion2, start_time); | ||
| 535 | addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + | ||
| 536 | offsetof(PerformanceDetailVersion2, processed_time); | ||
| 537 | |||
| 538 | std::memset(detail, 0, sizeof(PerformanceDetailVersion2)); | ||
| 539 | detail->node_id = node_id; | ||
| 540 | detail->detail_type = static_cast<PerformanceDetailType>(sys_detail_type); | ||
| 541 | |||
| 542 | if (unk) { | ||
| 543 | *unk = &detail->unk_10; | ||
| 544 | } | ||
| 545 | return true; | ||
| 546 | } | ||
| 547 | |||
| 548 | template <> | ||
| 549 | bool PerformanceManagerImpl< | ||
| 550 | PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, | ||
| 551 | PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses, | ||
| 552 | const PerformanceEntryType entry_type, | ||
| 553 | const s32 node_id) { | ||
| 554 | if (!is_initialized) { | ||
| 555 | return false; | ||
| 556 | } | ||
| 557 | |||
| 558 | auto entry{&entry_buffer[entry_count++]}; | ||
| 559 | |||
| 560 | addresses.translated_address = translated_buffer; | ||
| 561 | addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) + | ||
| 562 | offsetof(PerformanceFrameHeaderVersion2, entry_count); | ||
| 563 | addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) + | ||
| 564 | offsetof(PerformanceEntryVersion2, start_time); | ||
| 565 | addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) + | ||
| 566 | offsetof(PerformanceEntryVersion2, processed_time); | ||
| 567 | |||
| 568 | std::memset(entry, 0, sizeof(PerformanceEntryVersion2)); | ||
| 569 | entry->node_id = node_id; | ||
| 570 | entry->entry_type = entry_type; | ||
| 571 | return true; | ||
| 572 | } | ||
| 573 | |||
| 574 | template <> | ||
| 575 | bool PerformanceManagerImpl< | ||
| 576 | PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, | ||
| 577 | PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses, | ||
| 578 | const PerformanceDetailType detail_type, | ||
| 579 | const PerformanceEntryType entry_type, | ||
| 580 | const s32 node_id) { | ||
| 581 | if (!is_initialized || detail_count > MaxDetailEntries) { | ||
| 582 | return false; | ||
| 583 | } | ||
| 584 | |||
| 585 | auto detail{&detail_buffer[detail_count++]}; | ||
| 586 | |||
| 587 | addresses.translated_address = translated_buffer; | ||
| 588 | addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) + | ||
| 589 | offsetof(PerformanceFrameHeaderVersion2, detail_count); | ||
| 590 | addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + | ||
| 591 | offsetof(PerformanceDetailVersion2, start_time); | ||
| 592 | addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + | ||
| 593 | offsetof(PerformanceDetailVersion2, processed_time); | ||
| 594 | |||
| 595 | std::memset(detail, 0, sizeof(PerformanceDetailVersion2)); | ||
| 596 | detail->node_id = node_id; | ||
| 597 | detail->entry_type = entry_type; | ||
| 598 | detail->detail_type = detail_type; | ||
| 599 | return true; | ||
| 600 | } | ||
| 601 | |||
| 602 | template <> | ||
| 603 | void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, | ||
| 604 | PerformanceEntryVersion2, | ||
| 605 | PerformanceDetailVersion2>::TapFrame(const bool dsp_behind, | ||
| 606 | const u32 voices_dropped, | ||
| 607 | const u64 rendering_start_tick) { | ||
| 608 | if (!is_initialized) { | ||
| 609 | return; | ||
| 610 | } | ||
| 611 | |||
| 612 | if (max_frames > 0) { | ||
| 613 | if (!frame_history.empty() && !workbuffer.empty()) { | ||
| 614 | auto history_frame{reinterpret_cast<PerformanceFrameHeaderVersion2*>( | ||
| 615 | &frame_history[output_frame_index * frame_size])}; | ||
| 616 | std::memcpy(history_frame, workbuffer.data(), frame_size); | ||
| 617 | history_frame->render_time_exceeded = dsp_behind; | ||
| 618 | history_frame->voices_dropped = voices_dropped; | ||
| 619 | history_frame->start_time = rendering_start_tick; | ||
| 620 | history_frame->frame_index = history_frame_index++; | ||
| 621 | } | ||
| 622 | output_frame_index = (output_frame_index + 1) % max_frames; | ||
| 623 | } | ||
| 624 | |||
| 625 | entry_count = 0; | ||
| 626 | detail_count = 0; | ||
| 627 | frame_header->entry_count = 0; | ||
| 628 | frame_header->detail_count = 0; | ||
| 629 | } | ||
| 630 | |||
| 631 | template <> | ||
| 632 | bool PerformanceManagerImpl< | ||
| 633 | PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, | ||
| 634 | PerformanceDetailVersion2>::IsDetailTarget(const u32 target_node_id_) const { | ||
| 635 | return target_node_id == target_node_id_; | ||
| 636 | } | ||
| 637 | |||
| 638 | template <> | ||
| 639 | void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, | ||
| 640 | PerformanceEntryVersion2, | ||
| 641 | PerformanceDetailVersion2>::SetDetailTarget(const u32 target_node_id_) { | ||
| 642 | target_node_id = target_node_id_; | ||
| 643 | } | ||
| 644 | |||
| 645 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/performance/performance_manager.h b/src/audio_core/renderer/performance/performance_manager.h new file mode 100644 index 000000000..b82176bef --- /dev/null +++ b/src/audio_core/renderer/performance/performance_manager.h | |||
| @@ -0,0 +1,273 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <chrono> | ||
| 7 | #include <memory> | ||
| 8 | #include <span> | ||
| 9 | |||
| 10 | #include "audio_core/common/audio_renderer_parameter.h" | ||
| 11 | #include "audio_core/renderer/performance/performance_detail.h" | ||
| 12 | #include "audio_core/renderer/performance/performance_entry.h" | ||
| 13 | #include "audio_core/renderer/performance/performance_entry_addresses.h" | ||
| 14 | #include "audio_core/renderer/performance/performance_frame_header.h" | ||
| 15 | #include "common/common_types.h" | ||
| 16 | |||
| 17 | namespace AudioCore::AudioRenderer { | ||
| 18 | class BehaviorInfo; | ||
| 19 | class MemoryPoolInfo; | ||
| 20 | |||
| 21 | enum class PerformanceVersion { | ||
| 22 | Version1, | ||
| 23 | Version2, | ||
| 24 | }; | ||
| 25 | |||
| 26 | enum class PerformanceSysDetailType { | ||
| 27 | PcmInt16 = 15, | ||
| 28 | PcmFloat = 16, | ||
| 29 | Adpcm = 17, | ||
| 30 | LightLimiter = 37, | ||
| 31 | }; | ||
| 32 | |||
| 33 | enum class PerformanceState { | ||
| 34 | Invalid, | ||
| 35 | Start, | ||
| 36 | Stop, | ||
| 37 | }; | ||
| 38 | |||
| 39 | /** | ||
| 40 | * Manages performance information. | ||
| 41 | * | ||
| 42 | * The performance buffer is split into frames, each comprised of: | ||
| 43 | * Frame header - Information about the number of entries/details and some others | ||
| 44 | * Entries - Created when starting to generate types of commands, such as voice | ||
| 45 | * commands, mix commands, sink commands etc. Details - Created for specific commands | ||
| 46 | * within each group. Up to MaxDetailEntries per frame. | ||
| 47 | * | ||
| 48 | * A current frame is written to by the AudioRenderer, and before it processes the next command | ||
| 49 | * list, the current frame is copied to a ringbuffer of history frames. These frames are then | ||
| 50 | * output back to the game if it supplies a performance buffer to RequestUpdate. | ||
| 51 | * | ||
| 52 | * Two versions currently exist, version 2 adds a few extra fields to the header, and a new | ||
| 53 | * SysDetail type which is seemingly unused. | ||
| 54 | */ | ||
| 55 | class PerformanceManager { | ||
| 56 | public: | ||
| 57 | static constexpr size_t MaxDetailEntries = 100; | ||
| 58 | |||
| 59 | struct InParameter { | ||
| 60 | /* 0x00 */ s32 target_node_id; | ||
| 61 | /* 0x04 */ char unk04[0xC]; | ||
| 62 | }; | ||
| 63 | static_assert(sizeof(InParameter) == 0x10, | ||
| 64 | "PerformanceManager::InParameter has the wrong size!"); | ||
| 65 | |||
| 66 | struct OutStatus { | ||
| 67 | /* 0x00 */ s32 history_size; | ||
| 68 | /* 0x04 */ char unk04[0xC]; | ||
| 69 | }; | ||
| 70 | static_assert(sizeof(OutStatus) == 0x10, "PerformanceManager::OutStatus has the wrong size!"); | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Calculate the required size for the performance workbuffer. | ||
| 74 | * | ||
| 75 | * @param behavior - Check which version is supported. | ||
| 76 | * @param params - Input parameters. | ||
| 77 | * @return Required workbuffer size. | ||
| 78 | */ | ||
| 79 | static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame( | ||
| 80 | const BehaviorInfo& behavior, const AudioRendererParameterInternal& params) { | ||
| 81 | u64 entry_count{params.voices + params.effects + params.sub_mixes + params.sinks + 1}; | ||
| 82 | switch (behavior.GetPerformanceMetricsDataFormat()) { | ||
| 83 | case 1: | ||
| 84 | return sizeof(PerformanceFrameHeaderVersion1) + | ||
| 85 | PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion1) + | ||
| 86 | entry_count * sizeof(PerformanceEntryVersion1); | ||
| 87 | case 2: | ||
| 88 | return sizeof(PerformanceFrameHeaderVersion2) + | ||
| 89 | PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion2) + | ||
| 90 | entry_count * sizeof(PerformanceEntryVersion2); | ||
| 91 | } | ||
| 92 | |||
| 93 | LOG_WARNING(Service_Audio, "Invalid PerformanceMetrics version, assuming version 1"); | ||
| 94 | return sizeof(PerformanceFrameHeaderVersion1) + | ||
| 95 | PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion1) + | ||
| 96 | entry_count * sizeof(PerformanceEntryVersion1); | ||
| 97 | } | ||
| 98 | |||
| 99 | virtual ~PerformanceManager() = default; | ||
| 100 | |||
| 101 | /** | ||
| 102 | * Initialize the performance manager. | ||
| 103 | * | ||
| 104 | * @param workbuffer - Workbuffer to use for performance frames. | ||
| 105 | * @param workbuffer_size - Size of the workbuffer. | ||
| 106 | * @param params - Input parameters. | ||
| 107 | * @param behavior - Behaviour to check version and data format. | ||
| 108 | * @param memory_pool - Used to translate the workbuffer address for the DSP. | ||
| 109 | */ | ||
| 110 | virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size, | ||
| 111 | const AudioRendererParameterInternal& params, | ||
| 112 | const BehaviorInfo& behavior, const MemoryPoolInfo& memory_pool); | ||
| 113 | |||
| 114 | /** | ||
| 115 | * Check if the manager is initialized. | ||
| 116 | * | ||
| 117 | * @return True if initialized, otherwise false. | ||
| 118 | */ | ||
| 119 | virtual bool IsInitialized() const; | ||
| 120 | |||
| 121 | /** | ||
| 122 | * Copy the waiting performance frames to the output buffer. | ||
| 123 | * | ||
| 124 | * @param out_buffer - Output buffer to store performance frames. | ||
| 125 | * @param out_size - Size of the output buffer. | ||
| 126 | * @return Size in bytes that were written to the buffer. | ||
| 127 | */ | ||
| 128 | virtual u32 CopyHistories(u8* out_buffer, u64 out_size); | ||
| 129 | |||
| 130 | /** | ||
| 131 | * Setup a new sys detail in the current frame, filling in addresses with offsets to the | ||
| 132 | * current workbuffer, to be written by the AudioRenderer. Note: This version is | ||
| 133 | * unused/incomplete. | ||
| 134 | * | ||
| 135 | * @param addresses - Filled with pointers to the new entry, which should be passed to | ||
| 136 | * the AudioRenderer with Performance commands to be written. | ||
| 137 | * @param unk - Unknown. | ||
| 138 | * @param sys_detail_type - Sys detail type. | ||
| 139 | * @param node_id - Node id for this entry. | ||
| 140 | * @return True if a new entry was created and the offsets are valid, otherwise false. | ||
| 141 | */ | ||
| 142 | virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk, | ||
| 143 | PerformanceSysDetailType sys_detail_type, s32 node_id); | ||
| 144 | |||
| 145 | /** | ||
| 146 | * Setup a new entry in the current frame, filling in addresses with offsets to the current | ||
| 147 | * workbuffer, to be written by the AudioRenderer. | ||
| 148 | * | ||
| 149 | * @param addresses - Filled with pointers to the new entry, which should be passed to | ||
| 150 | * the AudioRenderer with Performance commands to be written. | ||
| 151 | * @param entry_type - The type of this entry. See PerformanceEntryType | ||
| 152 | * @param node_id - Node id for this entry. | ||
| 153 | * @return True if a new entry was created and the offsets are valid, otherwise false. | ||
| 154 | */ | ||
| 155 | virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceEntryType entry_type, | ||
| 156 | s32 node_id); | ||
| 157 | |||
| 158 | /** | ||
| 159 | * Setup a new detail in the current frame, filling in addresses with offsets to the current | ||
| 160 | * workbuffer, to be written by the AudioRenderer. | ||
| 161 | * | ||
| 162 | * @param addresses - Filled with pointers to the new detail, which should be passed | ||
| 163 | * to the AudioRenderer with Performance commands to be written. | ||
| 164 | * @param entry_type - The type of this detail. See PerformanceEntryType | ||
| 165 | * @param node_id - Node id for this detail. | ||
| 166 | * @return True if a new detail was created and the offsets are valid, otherwise false. | ||
| 167 | */ | ||
| 168 | virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, | ||
| 169 | PerformanceDetailType detail_type, PerformanceEntryType entry_type, | ||
| 170 | s32 node_id); | ||
| 171 | |||
| 172 | /** | ||
| 173 | * Save the current frame to the ring buffer. | ||
| 174 | * | ||
| 175 | * @param dsp_behind - Did the AudioRenderer fall behind and not | ||
| 176 | * finish processing the command list? | ||
| 177 | * @param voices_dropped - The number of voices that were dropped. | ||
| 178 | * @param rendering_start_tick - The tick rendering started. | ||
| 179 | */ | ||
| 180 | virtual void TapFrame(bool dsp_behind, u32 voices_dropped, u64 rendering_start_tick); | ||
| 181 | |||
| 182 | /** | ||
| 183 | * Check if the node id is a detail type. | ||
| 184 | * | ||
| 185 | * @return True if the node is a detail type, otherwise false. | ||
| 186 | */ | ||
| 187 | virtual bool IsDetailTarget(u32 target_node_id) const; | ||
| 188 | |||
| 189 | /** | ||
| 190 | * Set the given node to be a detail type. | ||
| 191 | * | ||
| 192 | * @param target_node_id - Node to set. | ||
| 193 | */ | ||
| 194 | virtual void SetDetailTarget(u32 target_node_id); | ||
| 195 | |||
| 196 | private: | ||
| 197 | /** | ||
| 198 | * Create the performance manager. | ||
| 199 | * | ||
| 200 | * @param version - Performance version to create. | ||
| 201 | */ | ||
| 202 | void CreateImpl(size_t version); | ||
| 203 | |||
| 204 | std::unique_ptr<PerformanceManager> | ||
| 205 | /// Impl for the performance manager, may be version 1 or 2. | ||
| 206 | impl; | ||
| 207 | }; | ||
| 208 | |||
| 209 | template <PerformanceVersion Version, typename FrameHeaderVersion, typename EntryVersion, | ||
| 210 | typename DetailVersion> | ||
| 211 | class PerformanceManagerImpl : public PerformanceManager { | ||
| 212 | public: | ||
| 213 | void Initialize(std::span<u8> workbuffer, u64 workbuffer_size, | ||
| 214 | const AudioRendererParameterInternal& params, const BehaviorInfo& behavior, | ||
| 215 | const MemoryPoolInfo& memory_pool) override; | ||
| 216 | bool IsInitialized() const override; | ||
| 217 | u32 CopyHistories(u8* out_buffer, u64 out_size) override; | ||
| 218 | bool GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk, | ||
| 219 | PerformanceSysDetailType sys_detail_type, s32 node_id) override; | ||
| 220 | bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceEntryType entry_type, | ||
| 221 | s32 node_id) override; | ||
| 222 | bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceDetailType detail_type, | ||
| 223 | PerformanceEntryType entry_type, s32 node_id) override; | ||
| 224 | void TapFrame(bool dsp_behind, u32 voices_dropped, u64 rendering_start_tick) override; | ||
| 225 | bool IsDetailTarget(u32 target_node_id) const override; | ||
| 226 | void SetDetailTarget(u32 target_node_id) override; | ||
| 227 | |||
| 228 | private: | ||
| 229 | /// Workbuffer used to store the current performance frame | ||
| 230 | std::span<u8> workbuffer{}; | ||
| 231 | /// DSP address of the workbuffer, used by the AudioRenderer | ||
| 232 | CpuAddr translated_buffer{}; | ||
| 233 | /// Current frame index | ||
| 234 | u32 history_frame_index{}; | ||
| 235 | /// Current frame header | ||
| 236 | FrameHeaderVersion* frame_header{}; | ||
| 237 | /// Current frame entry buffer | ||
| 238 | std::span<EntryVersion> entry_buffer{}; | ||
| 239 | /// Current frame detail buffer | ||
| 240 | std::span<DetailVersion> detail_buffer{}; | ||
| 241 | /// Current frame entry count | ||
| 242 | u32 entry_count{}; | ||
| 243 | /// Current frame detail count | ||
| 244 | u32 detail_count{}; | ||
| 245 | /// Ringbuffer of previous frames | ||
| 246 | std::span<u8> frame_history{}; | ||
| 247 | /// Current history frame header | ||
| 248 | FrameHeaderVersion* frame_history_header{}; | ||
| 249 | /// Current history entry buffer | ||
| 250 | std::span<EntryVersion> frame_history_entries{}; | ||
| 251 | /// Current history detail buffer | ||
| 252 | std::span<DetailVersion> frame_history_details{}; | ||
| 253 | /// Current history ringbuffer write index | ||
| 254 | u32 output_frame_index{}; | ||
| 255 | /// Last history frame index that was written back to the game | ||
| 256 | u32 last_output_frame_index{}; | ||
| 257 | /// Maximum number of history frames in the ringbuffer | ||
| 258 | u32 max_frames{}; | ||
| 259 | /// Number of entries per frame | ||
| 260 | u32 entries_per_frame{}; | ||
| 261 | /// Maximum number of details per frame | ||
| 262 | u32 max_detail_count{}; | ||
| 263 | /// Frame size in bytes | ||
| 264 | u64 frame_size{}; | ||
| 265 | /// Is the performance manager initialized? | ||
| 266 | bool is_initialized{}; | ||
| 267 | /// Target node id | ||
| 268 | u32 target_node_id{}; | ||
| 269 | /// Performance version in use | ||
| 270 | PerformanceVersion version{}; | ||
| 271 | }; | ||
| 272 | |||
| 273 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp b/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp new file mode 100644 index 000000000..d91f10402 --- /dev/null +++ b/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp | |||
| @@ -0,0 +1,76 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/memory/pool_mapper.h" | ||
| 5 | #include "audio_core/renderer/sink/circular_buffer_sink_info.h" | ||
| 6 | #include "audio_core/renderer/upsampler/upsampler_manager.h" | ||
| 7 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | CircularBufferSinkInfo::CircularBufferSinkInfo() { | ||
| 11 | state.fill(0); | ||
| 12 | parameter.fill(0); | ||
| 13 | type = Type::CircularBufferSink; | ||
| 14 | |||
| 15 | auto state_{reinterpret_cast<CircularBufferState*>(state.data())}; | ||
| 16 | state_->address_info.Setup(0, 0); | ||
| 17 | } | ||
| 18 | |||
| 19 | void CircularBufferSinkInfo::CleanUp() { | ||
| 20 | auto state_{reinterpret_cast<DeviceState*>(state.data())}; | ||
| 21 | |||
| 22 | if (state_->upsampler_info) { | ||
| 23 | state_->upsampler_info->manager->Free(state_->upsampler_info); | ||
| 24 | state_->upsampler_info = nullptr; | ||
| 25 | } | ||
| 26 | |||
| 27 | parameter.fill(0); | ||
| 28 | type = Type::Invalid; | ||
| 29 | } | ||
| 30 | |||
| 31 | void CircularBufferSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, | ||
| 32 | const InParameter& in_params, const PoolMapper& pool_mapper) { | ||
| 33 | const auto buffer_params{ | ||
| 34 | reinterpret_cast<const CircularBufferInParameter*>(&in_params.circular_buffer)}; | ||
| 35 | auto current_params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())}; | ||
| 36 | auto current_state{reinterpret_cast<CircularBufferState*>(state.data())}; | ||
| 37 | |||
| 38 | if (in_use == buffer_params->in_use && !buffer_unmapped) { | ||
| 39 | error_info.error_code = ResultSuccess; | ||
| 40 | error_info.address = CpuAddr(0); | ||
| 41 | out_status.writeOffset = current_state->last_pos2; | ||
| 42 | return; | ||
| 43 | } | ||
| 44 | |||
| 45 | node_id = in_params.node_id; | ||
| 46 | in_use = in_params.in_use; | ||
| 47 | |||
| 48 | if (in_use) { | ||
| 49 | buffer_unmapped = | ||
| 50 | !pool_mapper.TryAttachBuffer(error_info, current_state->address_info, | ||
| 51 | buffer_params->cpu_address, buffer_params->size); | ||
| 52 | *current_params = *buffer_params; | ||
| 53 | } else { | ||
| 54 | *current_params = *buffer_params; | ||
| 55 | } | ||
| 56 | out_status.writeOffset = current_state->last_pos2; | ||
| 57 | } | ||
| 58 | |||
| 59 | void CircularBufferSinkInfo::UpdateForCommandGeneration() { | ||
| 60 | if (in_use) { | ||
| 61 | auto params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())}; | ||
| 62 | auto state_{reinterpret_cast<CircularBufferState*>(state.data())}; | ||
| 63 | |||
| 64 | const auto pos{state_->current_pos}; | ||
| 65 | state_->last_pos2 = state_->last_pos; | ||
| 66 | state_->last_pos = pos; | ||
| 67 | |||
| 68 | state_->current_pos += static_cast<s32>(params->input_count * params->sample_count * | ||
| 69 | GetSampleFormatByteSize(SampleFormat::PcmInt16)); | ||
| 70 | if (params->size > 0) { | ||
| 71 | state_->current_pos %= params->size; | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/sink/circular_buffer_sink_info.h b/src/audio_core/renderer/sink/circular_buffer_sink_info.h new file mode 100644 index 000000000..3356213ea --- /dev/null +++ b/src/audio_core/renderer/sink/circular_buffer_sink_info.h | |||
| @@ -0,0 +1,41 @@ | |||
| 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/sink/sink_info_base.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | /** | ||
| 11 | * Info for a circular buffer sink. | ||
| 12 | */ | ||
| 13 | class CircularBufferSinkInfo : public SinkInfoBase { | ||
| 14 | public: | ||
| 15 | CircularBufferSinkInfo(); | ||
| 16 | |||
| 17 | /** | ||
| 18 | * Clean up for info, resetting it to a default state. | ||
| 19 | */ | ||
| 20 | void CleanUp() override; | ||
| 21 | |||
| 22 | /** | ||
| 23 | * Update the info according to parameters, and write the current state to out_status. | ||
| 24 | * | ||
| 25 | * @param error_info - Output error code. | ||
| 26 | * @param out_status - Output status. | ||
| 27 | * @param in_params - Input parameters. | ||
| 28 | * @param pool_mapper - Used to map the circular buffer. | ||
| 29 | */ | ||
| 30 | void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, | ||
| 31 | const InParameter& in_params, const PoolMapper& pool_mapper) override; | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Update the circular buffer on command generation, incrementing its current offsets. | ||
| 35 | */ | ||
| 36 | void UpdateForCommandGeneration() override; | ||
| 37 | }; | ||
| 38 | static_assert(sizeof(CircularBufferSinkInfo) <= sizeof(SinkInfoBase), | ||
| 39 | "CircularBufferSinkInfo is too large!"); | ||
| 40 | |||
| 41 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/sink/device_sink_info.cpp b/src/audio_core/renderer/sink/device_sink_info.cpp new file mode 100644 index 000000000..b7b3d6f1d --- /dev/null +++ b/src/audio_core/renderer/sink/device_sink_info.cpp | |||
| @@ -0,0 +1,57 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/sink/device_sink_info.h" | ||
| 5 | #include "audio_core/renderer/upsampler/upsampler_manager.h" | ||
| 6 | |||
| 7 | namespace AudioCore::AudioRenderer { | ||
| 8 | |||
| 9 | DeviceSinkInfo::DeviceSinkInfo() { | ||
| 10 | state.fill(0); | ||
| 11 | parameter.fill(0); | ||
| 12 | type = Type::DeviceSink; | ||
| 13 | } | ||
| 14 | |||
| 15 | void DeviceSinkInfo::CleanUp() { | ||
| 16 | auto state_{reinterpret_cast<DeviceState*>(state.data())}; | ||
| 17 | |||
| 18 | if (state_->upsampler_info) { | ||
| 19 | state_->upsampler_info->manager->Free(state_->upsampler_info); | ||
| 20 | state_->upsampler_info = nullptr; | ||
| 21 | } | ||
| 22 | |||
| 23 | parameter.fill(0); | ||
| 24 | type = Type::Invalid; | ||
| 25 | } | ||
| 26 | |||
| 27 | void DeviceSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, | ||
| 28 | const InParameter& in_params, | ||
| 29 | [[maybe_unused]] const PoolMapper& pool_mapper) { | ||
| 30 | |||
| 31 | const auto device_params{reinterpret_cast<const DeviceInParameter*>(&in_params.device)}; | ||
| 32 | auto current_params{reinterpret_cast<DeviceInParameter*>(parameter.data())}; | ||
| 33 | |||
| 34 | if (in_use == in_params.in_use) { | ||
| 35 | current_params->downmix_enabled = device_params->downmix_enabled; | ||
| 36 | current_params->downmix_coeff = device_params->downmix_coeff; | ||
| 37 | } else { | ||
| 38 | type = in_params.type; | ||
| 39 | in_use = in_params.in_use; | ||
| 40 | node_id = in_params.node_id; | ||
| 41 | *current_params = *device_params; | ||
| 42 | } | ||
| 43 | |||
| 44 | auto current_state{reinterpret_cast<DeviceState*>(state.data())}; | ||
| 45 | |||
| 46 | for (size_t i = 0; i < current_state->downmix_coeff.size(); i++) { | ||
| 47 | current_state->downmix_coeff[i] = current_params->downmix_coeff[i]; | ||
| 48 | } | ||
| 49 | |||
| 50 | std::memset(&out_status, 0, sizeof(OutStatus)); | ||
| 51 | error_info.error_code = ResultSuccess; | ||
| 52 | error_info.address = CpuAddr(0); | ||
| 53 | } | ||
| 54 | |||
| 55 | void DeviceSinkInfo::UpdateForCommandGeneration() {} | ||
| 56 | |||
| 57 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/sink/device_sink_info.h b/src/audio_core/renderer/sink/device_sink_info.h new file mode 100644 index 000000000..a1c441454 --- /dev/null +++ b/src/audio_core/renderer/sink/device_sink_info.h | |||
| @@ -0,0 +1,40 @@ | |||
| 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/sink/sink_info_base.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | /** | ||
| 11 | * Info for a device sink. | ||
| 12 | */ | ||
| 13 | class DeviceSinkInfo : public SinkInfoBase { | ||
| 14 | public: | ||
| 15 | DeviceSinkInfo(); | ||
| 16 | |||
| 17 | /** | ||
| 18 | * Clean up for info, resetting it to a default state. | ||
| 19 | */ | ||
| 20 | void CleanUp() override; | ||
| 21 | |||
| 22 | /** | ||
| 23 | * Update the info according to parameters, and write the current state to out_status. | ||
| 24 | * | ||
| 25 | * @param error_info - Output error code. | ||
| 26 | * @param out_status - Output status. | ||
| 27 | * @param in_params - Input parameters. | ||
| 28 | * @param pool_mapper - Unused. | ||
| 29 | */ | ||
| 30 | void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, | ||
| 31 | const InParameter& in_params, const PoolMapper& pool_mapper) override; | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Update the device sink on command generation, unused. | ||
| 35 | */ | ||
| 36 | void UpdateForCommandGeneration() override; | ||
| 37 | }; | ||
| 38 | static_assert(sizeof(DeviceSinkInfo) <= sizeof(SinkInfoBase), "DeviceSinkInfo is too large!"); | ||
| 39 | |||
| 40 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/sink/sink_context.cpp b/src/audio_core/renderer/sink/sink_context.cpp new file mode 100644 index 000000000..634bc1cf9 --- /dev/null +++ b/src/audio_core/renderer/sink/sink_context.cpp | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/sink/sink_context.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void SinkContext::Initialize(std::span<SinkInfoBase> sink_infos_, const u32 sink_count_) { | ||
| 9 | sink_infos = sink_infos_; | ||
| 10 | sink_count = sink_count_; | ||
| 11 | } | ||
| 12 | |||
| 13 | SinkInfoBase* SinkContext::GetInfo(const u32 index) { | ||
| 14 | return &sink_infos[index]; | ||
| 15 | } | ||
| 16 | |||
| 17 | u32 SinkContext::GetCount() const { | ||
| 18 | return sink_count; | ||
| 19 | } | ||
| 20 | |||
| 21 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/sink/sink_context.h b/src/audio_core/renderer/sink/sink_context.h new file mode 100644 index 000000000..185572e29 --- /dev/null +++ b/src/audio_core/renderer/sink/sink_context.h | |||
| @@ -0,0 +1,47 @@ | |||
| 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/sink/sink_info_base.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | /** | ||
| 13 | * Manages output sinks. | ||
| 14 | */ | ||
| 15 | class SinkContext { | ||
| 16 | public: | ||
| 17 | /** | ||
| 18 | * Initialize the sink context. | ||
| 19 | * | ||
| 20 | * @param sink_infos - Workbuffer for the sinks. | ||
| 21 | * @param sink_count - Number of sinks in the buffer. | ||
| 22 | */ | ||
| 23 | void Initialize(std::span<SinkInfoBase> sink_infos, u32 sink_count); | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Get a given index's info. | ||
| 27 | * | ||
| 28 | * @param index - Sink index to get. | ||
| 29 | * @return The sink info base for the given index. | ||
| 30 | */ | ||
| 31 | SinkInfoBase* GetInfo(u32 index); | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Get the current number of sinks. | ||
| 35 | * | ||
| 36 | * @return The number of sinks. | ||
| 37 | */ | ||
| 38 | u32 GetCount() const; | ||
| 39 | |||
| 40 | private: | ||
| 41 | /// Buffer of sink infos | ||
| 42 | std::span<SinkInfoBase> sink_infos{}; | ||
| 43 | /// Number of sinks in the buffer | ||
| 44 | u32 sink_count{}; | ||
| 45 | }; | ||
| 46 | |||
| 47 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/sink/sink_info_base.cpp b/src/audio_core/renderer/sink/sink_info_base.cpp new file mode 100644 index 000000000..4279beaa0 --- /dev/null +++ b/src/audio_core/renderer/sink/sink_info_base.cpp | |||
| @@ -0,0 +1,51 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/memory/pool_mapper.h" | ||
| 5 | #include "audio_core/renderer/sink/sink_info_base.h" | ||
| 6 | |||
| 7 | namespace AudioCore::AudioRenderer { | ||
| 8 | |||
| 9 | void SinkInfoBase::CleanUp() { | ||
| 10 | type = Type::Invalid; | ||
| 11 | } | ||
| 12 | |||
| 13 | void SinkInfoBase::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, | ||
| 14 | [[maybe_unused]] const InParameter& in_params, | ||
| 15 | [[maybe_unused]] const PoolMapper& pool_mapper) { | ||
| 16 | std::memset(&out_status, 0, sizeof(OutStatus)); | ||
| 17 | error_info.error_code = ResultSuccess; | ||
| 18 | error_info.address = CpuAddr(0); | ||
| 19 | } | ||
| 20 | |||
| 21 | void SinkInfoBase::UpdateForCommandGeneration() {} | ||
| 22 | |||
| 23 | SinkInfoBase::DeviceState* SinkInfoBase::GetDeviceState() { | ||
| 24 | return reinterpret_cast<DeviceState*>(state.data()); | ||
| 25 | } | ||
| 26 | |||
| 27 | SinkInfoBase::Type SinkInfoBase::GetType() const { | ||
| 28 | return type; | ||
| 29 | } | ||
| 30 | |||
| 31 | bool SinkInfoBase::IsUsed() const { | ||
| 32 | return in_use; | ||
| 33 | } | ||
| 34 | |||
| 35 | bool SinkInfoBase::ShouldSkip() const { | ||
| 36 | return buffer_unmapped; | ||
| 37 | } | ||
| 38 | |||
| 39 | u32 SinkInfoBase::GetNodeId() const { | ||
| 40 | return node_id; | ||
| 41 | } | ||
| 42 | |||
| 43 | u8* SinkInfoBase::GetState() { | ||
| 44 | return state.data(); | ||
| 45 | } | ||
| 46 | |||
| 47 | u8* SinkInfoBase::GetParameter() { | ||
| 48 | return parameter.data(); | ||
| 49 | } | ||
| 50 | |||
| 51 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/sink/sink_info_base.h b/src/audio_core/renderer/sink/sink_info_base.h new file mode 100644 index 000000000..a1b855f20 --- /dev/null +++ b/src/audio_core/renderer/sink/sink_info_base.h | |||
| @@ -0,0 +1,177 @@ | |||
| 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 | |||
| 8 | #include "audio_core/common/common.h" | ||
| 9 | #include "audio_core/renderer/behavior/behavior_info.h" | ||
| 10 | #include "audio_core/renderer/memory/address_info.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/fixed_point.h" | ||
| 13 | |||
| 14 | namespace AudioCore::AudioRenderer { | ||
| 15 | struct UpsamplerInfo; | ||
| 16 | class PoolMapper; | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Base for the circular buffer and device sinks, holding their states for the AudioRenderer and | ||
| 20 | * their parametetrs for generating sink commands. | ||
| 21 | */ | ||
| 22 | class SinkInfoBase { | ||
| 23 | public: | ||
| 24 | enum class Type : u8 { | ||
| 25 | Invalid, | ||
| 26 | DeviceSink, | ||
| 27 | CircularBufferSink, | ||
| 28 | }; | ||
| 29 | |||
| 30 | struct DeviceInParameter { | ||
| 31 | /* 0x000 */ char name[0x100]; | ||
| 32 | /* 0x100 */ u32 input_count; | ||
| 33 | /* 0x104 */ std::array<s8, MaxChannels> inputs; | ||
| 34 | /* 0x10A */ char unk10A[0x1]; | ||
| 35 | /* 0x10B */ bool downmix_enabled; | ||
| 36 | /* 0x10C */ std::array<f32, 4> downmix_coeff; | ||
| 37 | }; | ||
| 38 | static_assert(sizeof(DeviceInParameter) == 0x11C, "DeviceInParameter has the wrong size!"); | ||
| 39 | |||
| 40 | struct DeviceState { | ||
| 41 | /* 0x00 */ UpsamplerInfo* upsampler_info; | ||
| 42 | /* 0x08 */ std::array<Common::FixedPoint<16, 16>, 4> downmix_coeff; | ||
| 43 | /* 0x18 */ char unk18[0x18]; | ||
| 44 | }; | ||
| 45 | static_assert(sizeof(DeviceState) == 0x30, "DeviceState has the wrong size!"); | ||
| 46 | |||
| 47 | struct CircularBufferInParameter { | ||
| 48 | /* 0x00 */ u64 cpu_address; | ||
| 49 | /* 0x08 */ u32 size; | ||
| 50 | /* 0x0C */ u32 input_count; | ||
| 51 | /* 0x10 */ u32 sample_count; | ||
| 52 | /* 0x14 */ u32 previous_pos; | ||
| 53 | /* 0x18 */ SampleFormat format; | ||
| 54 | /* 0x1C */ std::array<s8, MaxChannels> inputs; | ||
| 55 | /* 0x22 */ bool in_use; | ||
| 56 | /* 0x23 */ char unk23[0x5]; | ||
| 57 | }; | ||
| 58 | static_assert(sizeof(CircularBufferInParameter) == 0x28, | ||
| 59 | "CircularBufferInParameter has the wrong size!"); | ||
| 60 | |||
| 61 | struct CircularBufferState { | ||
| 62 | /* 0x00 */ u32 last_pos2; | ||
| 63 | /* 0x04 */ s32 current_pos; | ||
| 64 | /* 0x08 */ u32 last_pos; | ||
| 65 | /* 0x0C */ char unk0C[0x4]; | ||
| 66 | /* 0x10 */ AddressInfo address_info; | ||
| 67 | }; | ||
| 68 | static_assert(sizeof(CircularBufferState) == 0x30, "CircularBufferState has the wrong size!"); | ||
| 69 | |||
| 70 | struct InParameter { | ||
| 71 | /* 0x000 */ Type type; | ||
| 72 | /* 0x001 */ bool in_use; | ||
| 73 | /* 0x004 */ u32 node_id; | ||
| 74 | /* 0x008 */ char unk08[0x18]; | ||
| 75 | union { | ||
| 76 | /* 0x020 */ DeviceInParameter device; | ||
| 77 | /* 0x020 */ CircularBufferInParameter circular_buffer; | ||
| 78 | }; | ||
| 79 | }; | ||
| 80 | static_assert(sizeof(InParameter) == 0x140, "SinkInfoBase::InParameter has the wrong size!"); | ||
| 81 | |||
| 82 | struct OutStatus { | ||
| 83 | /* 0x00 */ u32 writeOffset; | ||
| 84 | /* 0x04 */ char unk04[0x1C]; | ||
| 85 | }; // size == 0x20 | ||
| 86 | static_assert(sizeof(OutStatus) == 0x20, "SinkInfoBase::OutStatus has the wrong size!"); | ||
| 87 | |||
| 88 | virtual ~SinkInfoBase() = default; | ||
| 89 | |||
| 90 | /** | ||
| 91 | * Clean up for info, resetting it to a default state. | ||
| 92 | */ | ||
| 93 | virtual void CleanUp(); | ||
| 94 | |||
| 95 | /** | ||
| 96 | * Update the info according to parameters, and write the current state to out_status. | ||
| 97 | * | ||
| 98 | * @param error_info - Output error code. | ||
| 99 | * @param out_status - Output status. | ||
| 100 | * @param in_params - Input parameters. | ||
| 101 | * @param pool_mapper - Used to map the circular buffer. | ||
| 102 | */ | ||
| 103 | virtual void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, | ||
| 104 | [[maybe_unused]] const InParameter& in_params, | ||
| 105 | [[maybe_unused]] const PoolMapper& pool_mapper); | ||
| 106 | |||
| 107 | /** | ||
| 108 | * Update the circular buffer on command generation, incrementing its current offsets. | ||
| 109 | */ | ||
| 110 | virtual void UpdateForCommandGeneration(); | ||
| 111 | |||
| 112 | /** | ||
| 113 | * Get the state as a device sink. | ||
| 114 | * | ||
| 115 | * @return Device state. | ||
| 116 | */ | ||
| 117 | DeviceState* GetDeviceState(); | ||
| 118 | |||
| 119 | /** | ||
| 120 | * Get the type of this sink. | ||
| 121 | * | ||
| 122 | * @return Either Device, Circular, or Invalid. | ||
| 123 | */ | ||
| 124 | Type GetType() const; | ||
| 125 | |||
| 126 | /** | ||
| 127 | * Check if this sink is in use. | ||
| 128 | * | ||
| 129 | * @return True if used, otherwise false. | ||
| 130 | */ | ||
| 131 | bool IsUsed() const; | ||
| 132 | |||
| 133 | /** | ||
| 134 | * Check if this sink should be skipped for updates. | ||
| 135 | * | ||
| 136 | * @return True if it should be skipped, otherwise false. | ||
| 137 | */ | ||
| 138 | bool ShouldSkip() const; | ||
| 139 | |||
| 140 | /** | ||
| 141 | * Get the node if of this sink. | ||
| 142 | * | ||
| 143 | * @return Node id for this sink. | ||
| 144 | */ | ||
| 145 | u32 GetNodeId() const; | ||
| 146 | |||
| 147 | /** | ||
| 148 | * Get the state of this sink. | ||
| 149 | * | ||
| 150 | * @return Pointer to the state, must be cast to the correct type. | ||
| 151 | */ | ||
| 152 | u8* GetState(); | ||
| 153 | |||
| 154 | /** | ||
| 155 | * Get the parameters of this sink. | ||
| 156 | * | ||
| 157 | * @return Pointer to the parameters, must be cast to the correct type. | ||
| 158 | */ | ||
| 159 | u8* GetParameter(); | ||
| 160 | |||
| 161 | protected: | ||
| 162 | /// Type of this sink | ||
| 163 | Type type{Type::Invalid}; | ||
| 164 | /// Is this sink in use? | ||
| 165 | bool in_use{}; | ||
| 166 | /// Is this sink's buffer unmapped? Circular only | ||
| 167 | bool buffer_unmapped{}; | ||
| 168 | /// Node id for this sink | ||
| 169 | u32 node_id{}; | ||
| 170 | /// State buffer for this sink | ||
| 171 | std::array<u8, std::max(sizeof(DeviceState), sizeof(CircularBufferState))> state{}; | ||
| 172 | /// Parameter buffer for this sink | ||
| 173 | std::array<u8, std::max(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))> | ||
| 174 | parameter{}; | ||
| 175 | }; | ||
| 176 | |||
| 177 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/splitter/splitter_context.cpp b/src/audio_core/renderer/splitter/splitter_context.cpp new file mode 100644 index 000000000..7a23ba43f --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_context.cpp | |||
| @@ -0,0 +1,217 @@ | |||
| 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/common/workbuffer_allocator.h" | ||
| 6 | #include "audio_core/renderer/behavior/behavior_info.h" | ||
| 7 | #include "audio_core/renderer/splitter/splitter_context.h" | ||
| 8 | #include "common/alignment.h" | ||
| 9 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | SplitterDestinationData* SplitterContext::GetDesintationData(const s32 splitter_id, | ||
| 13 | const s32 destination_id) { | ||
| 14 | return splitter_infos[splitter_id].GetData(destination_id); | ||
| 15 | } | ||
| 16 | |||
| 17 | SplitterInfo& SplitterContext::GetInfo(const s32 splitter_id) { | ||
| 18 | return splitter_infos[splitter_id]; | ||
| 19 | } | ||
| 20 | |||
| 21 | u32 SplitterContext::GetDataCount() const { | ||
| 22 | return destinations_count; | ||
| 23 | } | ||
| 24 | |||
| 25 | u32 SplitterContext::GetInfoCount() const { | ||
| 26 | return info_count; | ||
| 27 | } | ||
| 28 | |||
| 29 | SplitterDestinationData& SplitterContext::GetData(const u32 index) { | ||
| 30 | return splitter_destinations[index]; | ||
| 31 | } | ||
| 32 | |||
| 33 | void SplitterContext::Setup(std::span<SplitterInfo> splitter_infos_, const u32 splitter_info_count_, | ||
| 34 | SplitterDestinationData* splitter_destinations_, | ||
| 35 | const u32 destination_count_, const bool splitter_bug_fixed_) { | ||
| 36 | splitter_infos = splitter_infos_; | ||
| 37 | info_count = splitter_info_count_; | ||
| 38 | splitter_destinations = splitter_destinations_; | ||
| 39 | destinations_count = destination_count_; | ||
| 40 | splitter_bug_fixed = splitter_bug_fixed_; | ||
| 41 | } | ||
| 42 | |||
| 43 | bool SplitterContext::UsingSplitter() const { | ||
| 44 | return splitter_infos.size() > 0 && info_count > 0 && splitter_destinations != nullptr && | ||
| 45 | destinations_count > 0; | ||
| 46 | } | ||
| 47 | |||
| 48 | void SplitterContext::ClearAllNewConnectionFlag() { | ||
| 49 | for (s32 i = 0; i < info_count; i++) { | ||
| 50 | splitter_infos[i].SetNewConnectionFlag(); | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | bool SplitterContext::Initialize(const BehaviorInfo& behavior, | ||
| 55 | const AudioRendererParameterInternal& params, | ||
| 56 | WorkbufferAllocator& allocator) { | ||
| 57 | if (behavior.IsSplitterSupported() && params.splitter_infos > 0 && | ||
| 58 | params.splitter_destinations > 0) { | ||
| 59 | splitter_infos = allocator.Allocate<SplitterInfo>(params.splitter_infos, 0x10); | ||
| 60 | |||
| 61 | for (u32 i = 0; i < params.splitter_infos; i++) { | ||
| 62 | std::construct_at<SplitterInfo>(&splitter_infos[i], static_cast<s32>(i)); | ||
| 63 | } | ||
| 64 | |||
| 65 | if (splitter_infos.size() == 0) { | ||
| 66 | splitter_infos = {}; | ||
| 67 | return false; | ||
| 68 | } | ||
| 69 | |||
| 70 | splitter_destinations = | ||
| 71 | allocator.Allocate<SplitterDestinationData>(params.splitter_destinations, 0x10).data(); | ||
| 72 | |||
| 73 | for (s32 i = 0; i < params.splitter_destinations; i++) { | ||
| 74 | std::construct_at<SplitterDestinationData>(&splitter_destinations[i], i); | ||
| 75 | } | ||
| 76 | |||
| 77 | if (params.splitter_destinations <= 0) { | ||
| 78 | splitter_infos = {}; | ||
| 79 | splitter_destinations = nullptr; | ||
| 80 | return false; | ||
| 81 | } | ||
| 82 | |||
| 83 | Setup(splitter_infos, params.splitter_infos, splitter_destinations, | ||
| 84 | params.splitter_destinations, behavior.IsSplitterBugFixed()); | ||
| 85 | } | ||
| 86 | return true; | ||
| 87 | } | ||
| 88 | |||
| 89 | bool SplitterContext::Update(const u8* input, u32& consumed_size) { | ||
| 90 | auto in_params{reinterpret_cast<const InParameterHeader*>(input)}; | ||
| 91 | |||
| 92 | if (destinations_count == 0 || info_count == 0) { | ||
| 93 | consumed_size = 0; | ||
| 94 | return true; | ||
| 95 | } | ||
| 96 | |||
| 97 | if (in_params->magic != GetSplitterInParamHeaderMagic()) { | ||
| 98 | consumed_size = 0; | ||
| 99 | return false; | ||
| 100 | } | ||
| 101 | |||
| 102 | for (auto& splitter_info : splitter_infos) { | ||
| 103 | splitter_info.ClearNewConnectionFlag(); | ||
| 104 | } | ||
| 105 | |||
| 106 | u32 offset{sizeof(InParameterHeader)}; | ||
| 107 | offset = UpdateInfo(input, offset, in_params->info_count); | ||
| 108 | offset = UpdateData(input, offset, in_params->destination_count); | ||
| 109 | |||
| 110 | consumed_size = Common::AlignUp(offset, 0x10); | ||
| 111 | return true; | ||
| 112 | } | ||
| 113 | |||
| 114 | u32 SplitterContext::UpdateInfo(const u8* input, u32 offset, const u32 splitter_count) { | ||
| 115 | for (u32 i = 0; i < splitter_count; i++) { | ||
| 116 | auto info_header{reinterpret_cast<const SplitterInfo::InParameter*>(input + offset)}; | ||
| 117 | |||
| 118 | if (info_header->magic != GetSplitterInfoMagic()) { | ||
| 119 | continue; | ||
| 120 | } | ||
| 121 | |||
| 122 | if (info_header->id < 0 || info_header->id > info_count) { | ||
| 123 | break; | ||
| 124 | } | ||
| 125 | |||
| 126 | auto& info{splitter_infos[info_header->id]}; | ||
| 127 | RecomposeDestination(info, info_header); | ||
| 128 | |||
| 129 | offset += info.Update(info_header); | ||
| 130 | } | ||
| 131 | |||
| 132 | return offset; | ||
| 133 | } | ||
| 134 | |||
| 135 | u32 SplitterContext::UpdateData(const u8* input, u32 offset, const u32 count) { | ||
| 136 | for (u32 i = 0; i < count; i++) { | ||
| 137 | auto data_header{ | ||
| 138 | reinterpret_cast<const SplitterDestinationData::InParameter*>(input + offset)}; | ||
| 139 | |||
| 140 | if (data_header->magic != GetSplitterSendDataMagic()) { | ||
| 141 | continue; | ||
| 142 | } | ||
| 143 | |||
| 144 | if (data_header->id < 0 || data_header->id > destinations_count) { | ||
| 145 | continue; | ||
| 146 | } | ||
| 147 | |||
| 148 | splitter_destinations[data_header->id].Update(*data_header); | ||
| 149 | offset += sizeof(SplitterDestinationData::InParameter); | ||
| 150 | } | ||
| 151 | |||
| 152 | return offset; | ||
| 153 | } | ||
| 154 | |||
| 155 | void SplitterContext::UpdateInternalState() { | ||
| 156 | for (s32 i = 0; i < info_count; i++) { | ||
| 157 | splitter_infos[i].UpdateInternalState(); | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | void SplitterContext::RecomposeDestination(SplitterInfo& out_info, | ||
| 162 | const SplitterInfo::InParameter* info_header) { | ||
| 163 | auto destination{out_info.GetData(0)}; | ||
| 164 | while (destination != nullptr) { | ||
| 165 | auto dest{destination->GetNext()}; | ||
| 166 | destination->SetNext(nullptr); | ||
| 167 | destination = dest; | ||
| 168 | } | ||
| 169 | out_info.SetDestinations(nullptr); | ||
| 170 | |||
| 171 | auto dest_count{info_header->destination_count}; | ||
| 172 | if (!splitter_bug_fixed) { | ||
| 173 | dest_count = std::min(dest_count, GetDestCountPerInfoForCompat()); | ||
| 174 | } | ||
| 175 | |||
| 176 | if (dest_count == 0) { | ||
| 177 | return; | ||
| 178 | } | ||
| 179 | |||
| 180 | std::span<const u32> destination_ids{reinterpret_cast<const u32*>(&info_header[1]), dest_count}; | ||
| 181 | |||
| 182 | auto head{&splitter_destinations[destination_ids[0]]}; | ||
| 183 | auto current_destination{head}; | ||
| 184 | for (u32 i = 1; i < dest_count; i++) { | ||
| 185 | auto next_destination{&splitter_destinations[destination_ids[i]]}; | ||
| 186 | current_destination->SetNext(next_destination); | ||
| 187 | current_destination = next_destination; | ||
| 188 | } | ||
| 189 | |||
| 190 | out_info.SetDestinations(head); | ||
| 191 | out_info.SetDestinationCount(dest_count); | ||
| 192 | } | ||
| 193 | |||
| 194 | u32 SplitterContext::GetDestCountPerInfoForCompat() const { | ||
| 195 | if (info_count <= 0) { | ||
| 196 | return 0; | ||
| 197 | } | ||
| 198 | return static_cast<u32>(destinations_count / info_count); | ||
| 199 | } | ||
| 200 | |||
| 201 | u64 SplitterContext::CalcWorkBufferSize(const BehaviorInfo& behavior, | ||
| 202 | const AudioRendererParameterInternal& params) { | ||
| 203 | u64 size{0}; | ||
| 204 | if (!behavior.IsSplitterSupported()) { | ||
| 205 | return size; | ||
| 206 | } | ||
| 207 | |||
| 208 | size += params.splitter_destinations * sizeof(SplitterDestinationData) + | ||
| 209 | params.splitter_infos * sizeof(SplitterInfo); | ||
| 210 | |||
| 211 | if (behavior.IsSplitterBugFixed()) { | ||
| 212 | size += Common::AlignUp(params.splitter_destinations * sizeof(u32), 0x10); | ||
| 213 | } | ||
| 214 | return size; | ||
| 215 | } | ||
| 216 | |||
| 217 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/splitter/splitter_context.h b/src/audio_core/renderer/splitter/splitter_context.h new file mode 100644 index 000000000..cfd092b4f --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_context.h | |||
| @@ -0,0 +1,189 @@ | |||
| 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/splitter/splitter_destinations_data.h" | ||
| 9 | #include "audio_core/renderer/splitter/splitter_info.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore { | ||
| 13 | struct AudioRendererParameterInternal; | ||
| 14 | class WorkbufferAllocator; | ||
| 15 | |||
| 16 | namespace AudioRenderer { | ||
| 17 | class BehaviorInfo; | ||
| 18 | |||
| 19 | /** | ||
| 20 | * The splitter allows much more control over how sound is mixed together. | ||
| 21 | * Previously, one mix can only connect to one other, and you may need | ||
| 22 | * more mixes (and duplicate processing) to achieve the same result. | ||
| 23 | * With the splitter, many-to-one and one-to-many mixing is possible. | ||
| 24 | * This was added in revision 2. | ||
| 25 | * Had a bug with incorrect numbers of destinations, fixed in revision 5. | ||
| 26 | */ | ||
| 27 | class SplitterContext { | ||
| 28 | struct InParameterHeader { | ||
| 29 | /* 0x00 */ u32 magic; // 'SNDH' | ||
| 30 | /* 0x04 */ s32 info_count; | ||
| 31 | /* 0x08 */ s32 destination_count; | ||
| 32 | /* 0x0C */ char unk0C[0x14]; | ||
| 33 | }; | ||
| 34 | static_assert(sizeof(InParameterHeader) == 0x20, | ||
| 35 | "SplitterContext::InParameterHeader has the wrong size!"); | ||
| 36 | |||
| 37 | public: | ||
| 38 | /** | ||
| 39 | * Get a destination mix from the given splitter and destination index. | ||
| 40 | * | ||
| 41 | * @param splitter_id - Splitter index to get from. | ||
| 42 | * @param destination_id - Destination index within the splitter. | ||
| 43 | * @return Pointer to the found destination. May be nullptr. | ||
| 44 | */ | ||
| 45 | SplitterDestinationData* GetDesintationData(s32 splitter_id, s32 destination_id); | ||
| 46 | |||
| 47 | /** | ||
| 48 | * Get a splitter from the given index. | ||
| 49 | * | ||
| 50 | * @param index - Index of the desired splitter. | ||
| 51 | * @return Splitter requested. | ||
| 52 | */ | ||
| 53 | SplitterInfo& GetInfo(s32 index); | ||
| 54 | |||
| 55 | /** | ||
| 56 | * Get the total number of splitter destinations. | ||
| 57 | * | ||
| 58 | * @return Number of destiantions. | ||
| 59 | */ | ||
| 60 | u32 GetDataCount() const; | ||
| 61 | |||
| 62 | /** | ||
| 63 | * Get the total number of splitters. | ||
| 64 | * | ||
| 65 | * @return Number of splitters. | ||
| 66 | */ | ||
| 67 | u32 GetInfoCount() const; | ||
| 68 | |||
| 69 | /** | ||
| 70 | * Get a specific global destination. | ||
| 71 | * | ||
| 72 | * @param index - Index of the desired destination. | ||
| 73 | * @return The requested destination. | ||
| 74 | */ | ||
| 75 | SplitterDestinationData& GetData(u32 index); | ||
| 76 | |||
| 77 | /** | ||
| 78 | * Check if the splitter is in use. | ||
| 79 | * | ||
| 80 | * @return True if any splitter or destination is in use, otherwise false. | ||
| 81 | */ | ||
| 82 | bool UsingSplitter() const; | ||
| 83 | |||
| 84 | /** | ||
| 85 | * Mark all splitters as having new connections. | ||
| 86 | */ | ||
| 87 | void ClearAllNewConnectionFlag(); | ||
| 88 | |||
| 89 | /** | ||
| 90 | * Initialize the context. | ||
| 91 | * | ||
| 92 | * @param behavior - Used to check for splitter support. | ||
| 93 | * @param params - Input parameters. | ||
| 94 | * @param allocator - Allocator used to allocate workbuffer memory. | ||
| 95 | */ | ||
| 96 | bool Initialize(const BehaviorInfo& behavior, const AudioRendererParameterInternal& params, | ||
| 97 | WorkbufferAllocator& allocator); | ||
| 98 | |||
| 99 | /** | ||
| 100 | * Update the context. | ||
| 101 | * | ||
| 102 | * @param input - Input buffer with the new info, | ||
| 103 | * expected to point to a InParameterHeader. | ||
| 104 | * @param consumed_size - Output with the number of bytes consumed from input. | ||
| 105 | */ | ||
| 106 | bool Update(const u8* input, u32& consumed_size); | ||
| 107 | |||
| 108 | /** | ||
| 109 | * Update the splitters. | ||
| 110 | * | ||
| 111 | * @param input - Input buffer with the new info. | ||
| 112 | * @param offset - Current offset within the input buffer, | ||
| 113 | * input + offset should point to a SplitterInfo::InParameter. | ||
| 114 | * @param splitter_count - Number of splitters in the input buffer. | ||
| 115 | * @return Number of bytes consumed in input. | ||
| 116 | */ | ||
| 117 | u32 UpdateInfo(const u8* input, u32 offset, u32 splitter_count); | ||
| 118 | |||
| 119 | /** | ||
| 120 | * Update the splitters. | ||
| 121 | * | ||
| 122 | * @param input - Input buffer with the new info. | ||
| 123 | * @param offset - Current offset within the input buffer, | ||
| 124 | * input + offset should point to a | ||
| 125 | * SplitterDestinationData::InParameter. | ||
| 126 | * @param destination_count - Number of destinations in the input buffer. | ||
| 127 | * @return Number of bytes consumed in input. | ||
| 128 | */ | ||
| 129 | u32 UpdateData(const u8* input, u32 offset, u32 destination_count); | ||
| 130 | |||
| 131 | /** | ||
| 132 | * Update the state of all destinations in all splitters. | ||
| 133 | */ | ||
| 134 | void UpdateInternalState(); | ||
| 135 | |||
| 136 | /** | ||
| 137 | * Replace the given splitter's destinations with new ones. | ||
| 138 | * | ||
| 139 | * @param out_info - Splitter to recompose. | ||
| 140 | * @param info_header - Input parameters containing new destination ids. | ||
| 141 | */ | ||
| 142 | void RecomposeDestination(SplitterInfo& out_info, const SplitterInfo::InParameter* info_header); | ||
| 143 | |||
| 144 | /** | ||
| 145 | * Old calculation for destinations, this is the thing the splitter bug fixes. | ||
| 146 | * Left for compatibility, and now min'd with the actual count to not bug. | ||
| 147 | * | ||
| 148 | * @return Number of splitter destinations. | ||
| 149 | */ | ||
| 150 | u32 GetDestCountPerInfoForCompat() const; | ||
| 151 | |||
| 152 | /** | ||
| 153 | * Calculate the size of the required workbuffer for splitters and destinations. | ||
| 154 | * | ||
| 155 | * @param behavior - Used to check splitter features. | ||
| 156 | * @param params - Input parameters with splitter/destination counts. | ||
| 157 | * @return Required buffer size. | ||
| 158 | */ | ||
| 159 | static u64 CalcWorkBufferSize(const BehaviorInfo& behavior, | ||
| 160 | const AudioRendererParameterInternal& params); | ||
| 161 | |||
| 162 | private: | ||
| 163 | /** | ||
| 164 | * Setup the context. | ||
| 165 | * | ||
| 166 | * @param splitter_infos - Workbuffer for splitters. | ||
| 167 | * @param splitter_info_count - Number of splitters in the workbuffer. | ||
| 168 | * @param splitter_destinations - Workbuffer for splitter destinations. | ||
| 169 | * @param destination_count - Number of destinations in the workbuffer. | ||
| 170 | * @param splitter_bug_fixed - Is the splitter bug fixed? | ||
| 171 | */ | ||
| 172 | void Setup(std::span<SplitterInfo> splitter_infos, u32 splitter_info_count, | ||
| 173 | SplitterDestinationData* splitter_destinations, u32 destination_count, | ||
| 174 | bool splitter_bug_fixed); | ||
| 175 | |||
| 176 | /// Workbuffer for splitters | ||
| 177 | std::span<SplitterInfo> splitter_infos{}; | ||
| 178 | /// Number of splitters in buffer | ||
| 179 | s32 info_count{}; | ||
| 180 | /// Workbuffer for destinations | ||
| 181 | SplitterDestinationData* splitter_destinations{}; | ||
| 182 | /// Number of destinations in buffer | ||
| 183 | s32 destinations_count{}; | ||
| 184 | /// Is the splitter bug fixed? | ||
| 185 | bool splitter_bug_fixed{}; | ||
| 186 | }; | ||
| 187 | |||
| 188 | } // namespace AudioRenderer | ||
| 189 | } // namespace AudioCore | ||
diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp new file mode 100644 index 000000000..b27d44896 --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.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 "audio_core/renderer/splitter/splitter_destinations_data.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | SplitterDestinationData::SplitterDestinationData(const s32 id_) : id{id_} {} | ||
| 9 | |||
| 10 | void SplitterDestinationData::ClearMixVolume() { | ||
| 11 | mix_volumes.fill(0.0f); | ||
| 12 | prev_mix_volumes.fill(0.0f); | ||
| 13 | } | ||
| 14 | |||
| 15 | s32 SplitterDestinationData::GetId() const { | ||
| 16 | return id; | ||
| 17 | } | ||
| 18 | |||
| 19 | bool SplitterDestinationData::IsConfigured() const { | ||
| 20 | return in_use && destination_id != UnusedMixId; | ||
| 21 | } | ||
| 22 | |||
| 23 | s32 SplitterDestinationData::GetMixId() const { | ||
| 24 | return destination_id; | ||
| 25 | } | ||
| 26 | |||
| 27 | f32 SplitterDestinationData::GetMixVolume(const u32 index) const { | ||
| 28 | if (index >= mix_volumes.size()) { | ||
| 29 | LOG_ERROR(Service_Audio, "SplitterDestinationData::GetMixVolume Invalid index {}", index); | ||
| 30 | return 0.0f; | ||
| 31 | } | ||
| 32 | return mix_volumes[index]; | ||
| 33 | } | ||
| 34 | |||
| 35 | std::span<f32> SplitterDestinationData::GetMixVolume() { | ||
| 36 | return mix_volumes; | ||
| 37 | } | ||
| 38 | |||
| 39 | f32 SplitterDestinationData::GetMixVolumePrev(const u32 index) const { | ||
| 40 | if (index >= prev_mix_volumes.size()) { | ||
| 41 | LOG_ERROR(Service_Audio, "SplitterDestinationData::GetMixVolumePrev Invalid index {}", | ||
| 42 | index); | ||
| 43 | return 0.0f; | ||
| 44 | } | ||
| 45 | return prev_mix_volumes[index]; | ||
| 46 | } | ||
| 47 | |||
| 48 | std::span<f32> SplitterDestinationData::GetMixVolumePrev() { | ||
| 49 | return prev_mix_volumes; | ||
| 50 | } | ||
| 51 | |||
| 52 | void SplitterDestinationData::Update(const InParameter& params) { | ||
| 53 | if (params.id != id || params.magic != GetSplitterSendDataMagic()) { | ||
| 54 | return; | ||
| 55 | } | ||
| 56 | |||
| 57 | destination_id = params.mix_id; | ||
| 58 | mix_volumes = params.mix_volumes; | ||
| 59 | |||
| 60 | if (!in_use && params.in_use) { | ||
| 61 | prev_mix_volumes = mix_volumes; | ||
| 62 | need_update = false; | ||
| 63 | } | ||
| 64 | |||
| 65 | in_use = params.in_use; | ||
| 66 | } | ||
| 67 | |||
| 68 | void SplitterDestinationData::MarkAsNeedToUpdateInternalState() { | ||
| 69 | need_update = true; | ||
| 70 | } | ||
| 71 | |||
| 72 | void SplitterDestinationData::UpdateInternalState() { | ||
| 73 | if (in_use && need_update) { | ||
| 74 | prev_mix_volumes = mix_volumes; | ||
| 75 | } | ||
| 76 | need_update = false; | ||
| 77 | } | ||
| 78 | |||
| 79 | SplitterDestinationData* SplitterDestinationData::GetNext() const { | ||
| 80 | return next; | ||
| 81 | } | ||
| 82 | |||
| 83 | void SplitterDestinationData::SetNext(SplitterDestinationData* next_) { | ||
| 84 | next = next_; | ||
| 85 | } | ||
| 86 | |||
| 87 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.h b/src/audio_core/renderer/splitter/splitter_destinations_data.h new file mode 100644 index 000000000..bd3d55748 --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.h | |||
| @@ -0,0 +1,135 @@ | |||
| 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 "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | /** | ||
| 14 | * Represents a mixing node, can be connected to a previous and next destination forming a chain | ||
| 15 | * that a certain mix buffer will pass through to output. | ||
| 16 | */ | ||
| 17 | class SplitterDestinationData { | ||
| 18 | public: | ||
| 19 | struct InParameter { | ||
| 20 | /* 0x00 */ u32 magic; // 'SNDD' | ||
| 21 | /* 0x04 */ s32 id; | ||
| 22 | /* 0x08 */ std::array<f32, MaxMixBuffers> mix_volumes; | ||
| 23 | /* 0x68 */ u32 mix_id; | ||
| 24 | /* 0x6C */ bool in_use; | ||
| 25 | }; | ||
| 26 | static_assert(sizeof(InParameter) == 0x70, | ||
| 27 | "SplitterDestinationData::InParameter has the wrong size!"); | ||
| 28 | |||
| 29 | SplitterDestinationData(s32 id); | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Reset the mix volumes for this destination. | ||
| 33 | */ | ||
| 34 | void ClearMixVolume(); | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Get the id of this destination. | ||
| 38 | * | ||
| 39 | * @return Id for this destination. | ||
| 40 | */ | ||
| 41 | s32 GetId() const; | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Check if this destination is correctly configured. | ||
| 45 | * | ||
| 46 | * @return True if configured, otherwise false. | ||
| 47 | */ | ||
| 48 | bool IsConfigured() const; | ||
| 49 | |||
| 50 | /** | ||
| 51 | * Get the mix id for this destination. | ||
| 52 | * | ||
| 53 | * @return Mix id for this destination. | ||
| 54 | */ | ||
| 55 | s32 GetMixId() const; | ||
| 56 | |||
| 57 | /** | ||
| 58 | * Get the current mix volume of a given index in this destination. | ||
| 59 | * | ||
| 60 | * @param index - Mix buffer index to get the volume for. | ||
| 61 | * @return Current volume of the specified mix. | ||
| 62 | */ | ||
| 63 | f32 GetMixVolume(u32 index) const; | ||
| 64 | |||
| 65 | /** | ||
| 66 | * Get the current mix volumes for all mix buffers in this destination. | ||
| 67 | * | ||
| 68 | * @return Span of current mix buffer volumes. | ||
| 69 | */ | ||
| 70 | std::span<f32> GetMixVolume(); | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Get the previous mix volume of a given index in this destination. | ||
| 74 | * | ||
| 75 | * @param index - Mix buffer index to get the volume for. | ||
| 76 | * @return Previous volume of the specified mix. | ||
| 77 | */ | ||
| 78 | f32 GetMixVolumePrev(u32 index) const; | ||
| 79 | |||
| 80 | /** | ||
| 81 | * Get the previous mix volumes for all mix buffers in this destination. | ||
| 82 | * | ||
| 83 | * @return Span of previous mix buffer volumes. | ||
| 84 | */ | ||
| 85 | std::span<f32> GetMixVolumePrev(); | ||
| 86 | |||
| 87 | /** | ||
| 88 | * Update this destination. | ||
| 89 | * | ||
| 90 | * @param params - Inpout parameters to update the destination. | ||
| 91 | */ | ||
| 92 | void Update(const InParameter& params); | ||
| 93 | |||
| 94 | /** | ||
| 95 | * Mark this destination as needing its volumes updated. | ||
| 96 | */ | ||
| 97 | void MarkAsNeedToUpdateInternalState(); | ||
| 98 | |||
| 99 | /** | ||
| 100 | * Copy current volumes to previous if an update is required. | ||
| 101 | */ | ||
| 102 | void UpdateInternalState(); | ||
| 103 | |||
| 104 | /** | ||
| 105 | * Get the next destination in the mix chain. | ||
| 106 | * | ||
| 107 | * @return The next splitter destination, may be nullptr if this is the last in the chain. | ||
| 108 | */ | ||
| 109 | SplitterDestinationData* GetNext() const; | ||
| 110 | |||
| 111 | /** | ||
| 112 | * Set the next destination in the mix chain. | ||
| 113 | * | ||
| 114 | * @param next - Destination this one is to be connected to. | ||
| 115 | */ | ||
| 116 | void SetNext(SplitterDestinationData* next); | ||
| 117 | |||
| 118 | private: | ||
| 119 | /// Id of this destination | ||
| 120 | const s32 id; | ||
| 121 | /// Mix id this destination represents | ||
| 122 | s32 destination_id{UnusedMixId}; | ||
| 123 | /// Current mix volumes | ||
| 124 | std::array<f32, MaxMixBuffers> mix_volumes{0.0f}; | ||
| 125 | /// Previous mix volumes | ||
| 126 | std::array<f32, MaxMixBuffers> prev_mix_volumes{0.0f}; | ||
| 127 | /// Next destination in the mix chain | ||
| 128 | SplitterDestinationData* next{}; | ||
| 129 | /// Is this destiantion in use? | ||
| 130 | bool in_use{}; | ||
| 131 | /// Does this destiantion need its volumes updated? | ||
| 132 | bool need_update{}; | ||
| 133 | }; | ||
| 134 | |||
| 135 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/splitter/splitter_info.cpp b/src/audio_core/renderer/splitter/splitter_info.cpp new file mode 100644 index 000000000..1aee6720b --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_info.cpp | |||
| @@ -0,0 +1,79 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/splitter/splitter_info.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | SplitterInfo::SplitterInfo(const s32 id_) : id{id_} {} | ||
| 9 | |||
| 10 | void SplitterInfo::InitializeInfos(SplitterInfo* splitters, const u32 count) { | ||
| 11 | if (splitters == nullptr) { | ||
| 12 | return; | ||
| 13 | } | ||
| 14 | |||
| 15 | for (u32 i = 0; i < count; i++) { | ||
| 16 | auto& splitter{splitters[i]}; | ||
| 17 | splitter.destinations = nullptr; | ||
| 18 | splitter.destination_count = 0; | ||
| 19 | splitter.has_new_connection = true; | ||
| 20 | } | ||
| 21 | } | ||
| 22 | |||
| 23 | u32 SplitterInfo::Update(const InParameter* params) { | ||
| 24 | if (params->id != id) { | ||
| 25 | return 0; | ||
| 26 | } | ||
| 27 | sample_rate = params->sample_rate; | ||
| 28 | has_new_connection = true; | ||
| 29 | return static_cast<u32>((sizeof(InParameter) + 3 * sizeof(s32)) + | ||
| 30 | params->destination_count * sizeof(s32)); | ||
| 31 | } | ||
| 32 | |||
| 33 | SplitterDestinationData* SplitterInfo::GetData(const u32 destination_id) { | ||
| 34 | auto out_destination{destinations}; | ||
| 35 | u32 i{0}; | ||
| 36 | while (i < destination_id) { | ||
| 37 | if (out_destination == nullptr) { | ||
| 38 | break; | ||
| 39 | } | ||
| 40 | out_destination = out_destination->GetNext(); | ||
| 41 | i++; | ||
| 42 | } | ||
| 43 | |||
| 44 | return out_destination; | ||
| 45 | } | ||
| 46 | |||
| 47 | u32 SplitterInfo::GetDestinationCount() const { | ||
| 48 | return destination_count; | ||
| 49 | } | ||
| 50 | |||
| 51 | void SplitterInfo::SetDestinationCount(const u32 count) { | ||
| 52 | destination_count = count; | ||
| 53 | } | ||
| 54 | |||
| 55 | bool SplitterInfo::HasNewConnection() const { | ||
| 56 | return has_new_connection; | ||
| 57 | } | ||
| 58 | |||
| 59 | void SplitterInfo::ClearNewConnectionFlag() { | ||
| 60 | has_new_connection = false; | ||
| 61 | } | ||
| 62 | |||
| 63 | void SplitterInfo::SetNewConnectionFlag() { | ||
| 64 | has_new_connection = true; | ||
| 65 | } | ||
| 66 | |||
| 67 | void SplitterInfo::UpdateInternalState() { | ||
| 68 | auto destination{destinations}; | ||
| 69 | while (destination != nullptr) { | ||
| 70 | destination->UpdateInternalState(); | ||
| 71 | destination = destination->GetNext(); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | void SplitterInfo::SetDestinations(SplitterDestinationData* destinations_) { | ||
| 76 | destinations = destinations_; | ||
| 77 | } | ||
| 78 | |||
| 79 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/splitter/splitter_info.h b/src/audio_core/renderer/splitter/splitter_info.h new file mode 100644 index 000000000..d1d75064c --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_info.h | |||
| @@ -0,0 +1,107 @@ | |||
| 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/splitter/splitter_destinations_data.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | /** | ||
| 11 | * Represents a splitter, wraps multiple output destinations to split an input mix into. | ||
| 12 | */ | ||
| 13 | class SplitterInfo { | ||
| 14 | public: | ||
| 15 | struct InParameter { | ||
| 16 | /* 0x00 */ u32 magic; // 'SNDI' | ||
| 17 | /* 0x04 */ s32 id; | ||
| 18 | /* 0x08 */ u32 sample_rate; | ||
| 19 | /* 0x0C */ u32 destination_count; | ||
| 20 | }; | ||
| 21 | static_assert(sizeof(InParameter) == 0x10, "SplitterInfo::InParameter has the wrong size!"); | ||
| 22 | |||
| 23 | explicit SplitterInfo(s32 id); | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Initialize the given splitters. | ||
| 27 | * | ||
| 28 | * @param splitters - Splitters to initialize. | ||
| 29 | * @param count - Number of splitters given. | ||
| 30 | */ | ||
| 31 | static void InitializeInfos(SplitterInfo* splitters, u32 count); | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Update this splitter. | ||
| 35 | * | ||
| 36 | * @param params - Input parameters to update with. | ||
| 37 | * @return The size in bytes of this splitter. | ||
| 38 | */ | ||
| 39 | u32 Update(const InParameter* params); | ||
| 40 | |||
| 41 | /** | ||
| 42 | * Get a destination in this splitter. | ||
| 43 | * | ||
| 44 | * @param id - Destination id to get. | ||
| 45 | * @return Pointer to the destination, may be nullptr. | ||
| 46 | */ | ||
| 47 | SplitterDestinationData* GetData(u32 id); | ||
| 48 | |||
| 49 | /** | ||
| 50 | * Get the number of destinations in this splitter. | ||
| 51 | * | ||
| 52 | * @return The number of destiantions. | ||
| 53 | */ | ||
| 54 | u32 GetDestinationCount() const; | ||
| 55 | |||
| 56 | /** | ||
| 57 | * Set the number of destinations in this splitter. | ||
| 58 | * | ||
| 59 | * @param count - The new number of destiantions. | ||
| 60 | */ | ||
| 61 | void SetDestinationCount(u32 count); | ||
| 62 | |||
| 63 | /** | ||
| 64 | * Check if the splitter has a new connection. | ||
| 65 | * | ||
| 66 | * @return True if there is a new connection, otherwise false. | ||
| 67 | */ | ||
| 68 | bool HasNewConnection() const; | ||
| 69 | |||
| 70 | /** | ||
| 71 | * Reset the new connection flag. | ||
| 72 | */ | ||
| 73 | void ClearNewConnectionFlag(); | ||
| 74 | |||
| 75 | /** | ||
| 76 | * Mark as having a new connection. | ||
| 77 | */ | ||
| 78 | void SetNewConnectionFlag(); | ||
| 79 | |||
| 80 | /** | ||
| 81 | * Update the state of all destinations. | ||
| 82 | */ | ||
| 83 | void UpdateInternalState(); | ||
| 84 | |||
| 85 | /** | ||
| 86 | * Set this splitter's destinations. | ||
| 87 | * | ||
| 88 | * @param destinations - The new destination list for this splitter. | ||
| 89 | */ | ||
| 90 | void SetDestinations(SplitterDestinationData* destinations); | ||
| 91 | |||
| 92 | private: | ||
| 93 | /// Id of this splitter | ||
| 94 | s32 id; | ||
| 95 | /// Sample rate of this splitter | ||
| 96 | u32 sample_rate{}; | ||
| 97 | /// Number of destinations in this splitter | ||
| 98 | u32 destination_count{}; | ||
| 99 | /// Does this splitter have a new connection? | ||
| 100 | bool has_new_connection{true}; | ||
| 101 | /// Pointer to the destinations of this splitter | ||
| 102 | SplitterDestinationData* destinations{}; | ||
| 103 | /// Number of channels this splitter manages | ||
| 104 | u32 channel_count{}; | ||
| 105 | }; | ||
| 106 | |||
| 107 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp new file mode 100644 index 000000000..7a217969e --- /dev/null +++ b/src/audio_core/renderer/system.cpp | |||
| @@ -0,0 +1,802 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <chrono> | ||
| 5 | #include <span> | ||
| 6 | |||
| 7 | #include "audio_core/audio_core.h" | ||
| 8 | #include "audio_core/common/audio_renderer_parameter.h" | ||
| 9 | #include "audio_core/common/common.h" | ||
| 10 | #include "audio_core/common/feature_support.h" | ||
| 11 | #include "audio_core/common/workbuffer_allocator.h" | ||
| 12 | #include "audio_core/renderer/adsp/adsp.h" | ||
| 13 | #include "audio_core/renderer/behavior/info_updater.h" | ||
| 14 | #include "audio_core/renderer/command/command_buffer.h" | ||
| 15 | #include "audio_core/renderer/command/command_generator.h" | ||
| 16 | #include "audio_core/renderer/command/command_list_header.h" | ||
| 17 | #include "audio_core/renderer/effect/effect_info_base.h" | ||
| 18 | #include "audio_core/renderer/effect/effect_result_state.h" | ||
| 19 | #include "audio_core/renderer/memory/memory_pool_info.h" | ||
| 20 | #include "audio_core/renderer/memory/pool_mapper.h" | ||
| 21 | #include "audio_core/renderer/mix/mix_info.h" | ||
| 22 | #include "audio_core/renderer/nodes/edge_matrix.h" | ||
| 23 | #include "audio_core/renderer/nodes/node_states.h" | ||
| 24 | #include "audio_core/renderer/sink/sink_info_base.h" | ||
| 25 | #include "audio_core/renderer/system.h" | ||
| 26 | #include "audio_core/renderer/upsampler/upsampler_info.h" | ||
| 27 | #include "audio_core/renderer/voice/voice_channel_resource.h" | ||
| 28 | #include "audio_core/renderer/voice/voice_info.h" | ||
| 29 | #include "audio_core/renderer/voice/voice_state.h" | ||
| 30 | #include "common/alignment.h" | ||
| 31 | #include "core/core.h" | ||
| 32 | #include "core/core_timing.h" | ||
| 33 | #include "core/hle/kernel/k_event.h" | ||
| 34 | #include "core/hle/kernel/k_transfer_memory.h" | ||
| 35 | #include "core/memory.h" | ||
| 36 | |||
| 37 | namespace AudioCore::AudioRenderer { | ||
| 38 | |||
| 39 | u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) { | ||
| 40 | BehaviorInfo behavior; | ||
| 41 | behavior.SetUserLibRevision(params.revision); | ||
| 42 | |||
| 43 | u64 size{0}; | ||
| 44 | |||
| 45 | size += Common::AlignUp(params.mixes * sizeof(s32), 0x40); | ||
| 46 | size += params.sub_mixes * MaxEffects * sizeof(s32); | ||
| 47 | size += (params.sub_mixes + 1) * sizeof(MixInfo); | ||
| 48 | size += params.voices * (sizeof(VoiceInfo) + sizeof(VoiceChannelResource) + sizeof(VoiceState)); | ||
| 49 | size += Common::AlignUp((params.sub_mixes + 1) * sizeof(MixInfo*), 0x10); | ||
| 50 | size += Common::AlignUp(params.voices * sizeof(VoiceInfo*), 0x10); | ||
| 51 | size += Common::AlignUp(((params.sinks + params.sub_mixes) * TargetSampleCount * sizeof(s32) + | ||
| 52 | params.sample_count * sizeof(s32)) * | ||
| 53 | (params.mixes + MaxChannels), | ||
| 54 | 0x40); | ||
| 55 | |||
| 56 | if (behavior.IsSplitterSupported()) { | ||
| 57 | const auto node_size{NodeStates::GetWorkBufferSize(params.sub_mixes + 1)}; | ||
| 58 | const auto edge_size{EdgeMatrix::GetWorkBufferSize(params.sub_mixes + 1)}; | ||
| 59 | size += Common::AlignUp(node_size + edge_size, 0x10); | ||
| 60 | } | ||
| 61 | |||
| 62 | size += SplitterContext::CalcWorkBufferSize(behavior, params); | ||
| 63 | size += (params.effects + params.voices * MaxWaveBuffers) * sizeof(MemoryPoolInfo); | ||
| 64 | |||
| 65 | if (behavior.IsEffectInfoVersion2Supported()) { | ||
| 66 | size += params.effects * sizeof(EffectResultState); | ||
| 67 | } | ||
| 68 | size += 0x50; | ||
| 69 | |||
| 70 | size = Common::AlignUp(size, 0x40); | ||
| 71 | |||
| 72 | size += (params.sinks + params.sub_mixes) * sizeof(UpsamplerInfo); | ||
| 73 | size += params.effects * sizeof(EffectInfoBase); | ||
| 74 | size += Common::AlignUp(params.voices * sizeof(VoiceState), 0x40); | ||
| 75 | size += params.sinks * sizeof(SinkInfoBase); | ||
| 76 | |||
| 77 | if (behavior.IsEffectInfoVersion2Supported()) { | ||
| 78 | size += params.effects * sizeof(EffectResultState); | ||
| 79 | } | ||
| 80 | |||
| 81 | if (params.perf_frames > 0) { | ||
| 82 | auto perf_size{PerformanceManager::GetRequiredBufferSizeForPerformanceMetricsPerFrame( | ||
| 83 | behavior, params)}; | ||
| 84 | size += Common::AlignUp(perf_size * (params.perf_frames + 1) + 0xC0, 0x100); | ||
| 85 | } | ||
| 86 | |||
| 87 | if (behavior.IsVariadicCommandBufferSizeSupported()) { | ||
| 88 | size += CommandGenerator::CalculateCommandBufferSize(behavior, params) + (0x40 - 1) * 2; | ||
| 89 | } else { | ||
| 90 | size += 0x18000 + (0x40 - 1) * 2; | ||
| 91 | } | ||
| 92 | |||
| 93 | size = Common::AlignUp(size, 0x1000); | ||
| 94 | return size; | ||
| 95 | } | ||
| 96 | |||
| 97 | System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_) | ||
| 98 | : core{core_}, adsp{core.AudioCore().GetADSP()}, adsp_rendered_event{adsp_rendered_event_} {} | ||
| 99 | |||
| 100 | Result System::Initialize(const AudioRendererParameterInternal& params, | ||
| 101 | Kernel::KTransferMemory* transfer_memory, const u64 transfer_memory_size, | ||
| 102 | const u32 process_handle_, const u64 applet_resource_user_id_, | ||
| 103 | const s32 session_id_) { | ||
| 104 | if (!CheckValidRevision(params.revision)) { | ||
| 105 | return Service::Audio::ERR_INVALID_REVISION; | ||
| 106 | } | ||
| 107 | |||
| 108 | if (GetWorkBufferSize(params) > transfer_memory_size) { | ||
| 109 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 110 | } | ||
| 111 | |||
| 112 | if (process_handle_ == 0) { | ||
| 113 | return Service::Audio::ERR_INVALID_PROCESS_HANDLE; | ||
| 114 | } | ||
| 115 | |||
| 116 | behavior.SetUserLibRevision(params.revision); | ||
| 117 | |||
| 118 | process_handle = process_handle_; | ||
| 119 | applet_resource_user_id = applet_resource_user_id_; | ||
| 120 | session_id = session_id_; | ||
| 121 | |||
| 122 | sample_rate = params.sample_rate; | ||
| 123 | sample_count = params.sample_count; | ||
| 124 | mix_buffer_count = static_cast<s16>(params.mixes); | ||
| 125 | voice_channels = MaxChannels; | ||
| 126 | upsampler_count = params.sinks + params.sub_mixes; | ||
| 127 | memory_pool_count = params.effects + params.voices * MaxWaveBuffers; | ||
| 128 | render_device = params.rendering_device; | ||
| 129 | execution_mode = params.execution_mode; | ||
| 130 | |||
| 131 | core.Memory().ZeroBlock(*core.Kernel().CurrentProcess(), transfer_memory->GetSourceAddress(), | ||
| 132 | transfer_memory_size); | ||
| 133 | |||
| 134 | // Note: We're not actually using the transfer memory because it's a pain to code for. | ||
| 135 | // Allocate the memory normally instead and hope the game doesn't try to read anything back | ||
| 136 | workbuffer = std::make_unique<u8[]>(transfer_memory_size); | ||
| 137 | workbuffer_size = transfer_memory_size; | ||
| 138 | |||
| 139 | PoolMapper pool_mapper(process_handle, false); | ||
| 140 | pool_mapper.InitializeSystemPool(memory_pool_info, workbuffer.get(), workbuffer_size); | ||
| 141 | |||
| 142 | WorkbufferAllocator allocator({workbuffer.get(), workbuffer_size}, workbuffer_size); | ||
| 143 | |||
| 144 | samples_workbuffer = | ||
| 145 | allocator.Allocate<s32>((voice_channels + mix_buffer_count) * sample_count, 0x10); | ||
| 146 | if (samples_workbuffer.empty()) { | ||
| 147 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 148 | } | ||
| 149 | |||
| 150 | auto upsampler_workbuffer{allocator.Allocate<s32>( | ||
| 151 | (voice_channels + mix_buffer_count) * TargetSampleCount * upsampler_count, 0x10)}; | ||
| 152 | if (upsampler_workbuffer.empty()) { | ||
| 153 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 154 | } | ||
| 155 | |||
| 156 | depop_buffer = | ||
| 157 | allocator.Allocate<s32>(Common::AlignUp(static_cast<u32>(mix_buffer_count), 0x40), 0x40); | ||
| 158 | if (depop_buffer.empty()) { | ||
| 159 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 160 | } | ||
| 161 | |||
| 162 | // invalidate samples_workbuffer DSP cache | ||
| 163 | |||
| 164 | auto voice_infos{allocator.Allocate<VoiceInfo>(params.voices, 0x10)}; | ||
| 165 | for (auto& voice_info : voice_infos) { | ||
| 166 | std::construct_at<VoiceInfo>(&voice_info); | ||
| 167 | } | ||
| 168 | |||
| 169 | if (voice_infos.empty()) { | ||
| 170 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 171 | } | ||
| 172 | |||
| 173 | auto sorted_voice_infos{allocator.Allocate<VoiceInfo*>(params.voices, 0x10)}; | ||
| 174 | if (sorted_voice_infos.empty()) { | ||
| 175 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 176 | } | ||
| 177 | |||
| 178 | std::memset(sorted_voice_infos.data(), 0, sorted_voice_infos.size_bytes()); | ||
| 179 | |||
| 180 | auto voice_channel_resources{allocator.Allocate<VoiceChannelResource>(params.voices, 0x10)}; | ||
| 181 | u32 i{0}; | ||
| 182 | for (auto& voice_channel_resource : voice_channel_resources) { | ||
| 183 | std::construct_at<VoiceChannelResource>(&voice_channel_resource, i++); | ||
| 184 | } | ||
| 185 | |||
| 186 | if (voice_channel_resources.empty()) { | ||
| 187 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 188 | } | ||
| 189 | |||
| 190 | auto voice_cpu_states{allocator.Allocate<VoiceState>(params.voices, 0x10)}; | ||
| 191 | if (voice_cpu_states.empty()) { | ||
| 192 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 193 | } | ||
| 194 | |||
| 195 | for (auto& voice_state : voice_cpu_states) { | ||
| 196 | voice_state = {}; | ||
| 197 | } | ||
| 198 | |||
| 199 | auto mix_infos{allocator.Allocate<MixInfo>(params.sub_mixes + 1, 0x10)}; | ||
| 200 | |||
| 201 | if (mix_infos.empty()) { | ||
| 202 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 203 | } | ||
| 204 | |||
| 205 | u32 effect_process_order_count{0}; | ||
| 206 | std::span<s32> effect_process_order_buffer{}; | ||
| 207 | |||
| 208 | if (params.effects > 0) { | ||
| 209 | effect_process_order_count = params.effects * (params.sub_mixes + 1); | ||
| 210 | effect_process_order_buffer = allocator.Allocate<s32>(effect_process_order_count, 0x10); | ||
| 211 | if (effect_process_order_buffer.empty()) { | ||
| 212 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | i = 0; | ||
| 217 | for (auto& mix_info : mix_infos) { | ||
| 218 | std::construct_at<MixInfo>( | ||
| 219 | &mix_info, effect_process_order_buffer.subspan(i * params.effects, params.effects), | ||
| 220 | params.effects, this->behavior); | ||
| 221 | i++; | ||
| 222 | } | ||
| 223 | |||
| 224 | auto sorted_mix_infos{allocator.Allocate<MixInfo*>(params.sub_mixes + 1, 0x10)}; | ||
| 225 | if (sorted_mix_infos.empty()) { | ||
| 226 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 227 | } | ||
| 228 | |||
| 229 | std::memset(sorted_mix_infos.data(), 0, sorted_mix_infos.size_bytes()); | ||
| 230 | |||
| 231 | if (behavior.IsSplitterSupported()) { | ||
| 232 | u64 node_state_size{NodeStates::GetWorkBufferSize(params.sub_mixes + 1)}; | ||
| 233 | u64 edge_matrix_size{EdgeMatrix::GetWorkBufferSize(params.sub_mixes + 1)}; | ||
| 234 | |||
| 235 | auto node_states_workbuffer{allocator.Allocate<u8>(node_state_size, 1)}; | ||
| 236 | auto edge_matrix_workbuffer{allocator.Allocate<u8>(edge_matrix_size, 1)}; | ||
| 237 | |||
| 238 | if (node_states_workbuffer.empty() || edge_matrix_workbuffer.size() == 0) { | ||
| 239 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 240 | } | ||
| 241 | |||
| 242 | mix_context.Initialize(sorted_mix_infos, mix_infos, params.sub_mixes + 1, | ||
| 243 | effect_process_order_buffer, effect_process_order_count, | ||
| 244 | node_states_workbuffer, node_state_size, edge_matrix_workbuffer, | ||
| 245 | edge_matrix_size); | ||
| 246 | } else { | ||
| 247 | mix_context.Initialize(sorted_mix_infos, mix_infos, params.sub_mixes + 1, | ||
| 248 | effect_process_order_buffer, effect_process_order_count, {}, 0, {}, | ||
| 249 | 0); | ||
| 250 | } | ||
| 251 | |||
| 252 | upsampler_manager = allocator.Allocate<UpsamplerManager>(1, 0x10).data(); | ||
| 253 | if (upsampler_manager == nullptr) { | ||
| 254 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 255 | } | ||
| 256 | |||
| 257 | memory_pool_workbuffer = allocator.Allocate<MemoryPoolInfo>(memory_pool_count, 0x10); | ||
| 258 | for (auto& memory_pool : memory_pool_workbuffer) { | ||
| 259 | std::construct_at<MemoryPoolInfo>(&memory_pool, MemoryPoolInfo::Location::DSP); | ||
| 260 | } | ||
| 261 | |||
| 262 | if (memory_pool_workbuffer.empty() && memory_pool_count > 0) { | ||
| 263 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 264 | } | ||
| 265 | |||
| 266 | if (!splitter_context.Initialize(behavior, params, allocator)) { | ||
| 267 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 268 | } | ||
| 269 | |||
| 270 | std::span<EffectResultState> effect_result_states_cpu{}; | ||
| 271 | if (behavior.IsEffectInfoVersion2Supported() && params.effects > 0) { | ||
| 272 | effect_result_states_cpu = allocator.Allocate<EffectResultState>(params.effects, 0x10); | ||
| 273 | if (effect_result_states_cpu.empty()) { | ||
| 274 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 275 | } | ||
| 276 | std::memset(effect_result_states_cpu.data(), 0, effect_result_states_cpu.size_bytes()); | ||
| 277 | } | ||
| 278 | |||
| 279 | allocator.Align(0x40); | ||
| 280 | |||
| 281 | unk_2B0 = allocator.GetSize() - allocator.GetCurrentOffset(); | ||
| 282 | unk_2A8 = {&workbuffer[allocator.GetCurrentOffset()], unk_2B0}; | ||
| 283 | |||
| 284 | upsampler_infos = allocator.Allocate<UpsamplerInfo>(upsampler_count, 0x40); | ||
| 285 | for (auto& upsampler_info : upsampler_infos) { | ||
| 286 | std::construct_at<UpsamplerInfo>(&upsampler_info); | ||
| 287 | } | ||
| 288 | |||
| 289 | std::construct_at<UpsamplerManager>(upsampler_manager, upsampler_count, upsampler_infos, | ||
| 290 | upsampler_workbuffer); | ||
| 291 | |||
| 292 | if (upsampler_infos.empty()) { | ||
| 293 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 294 | } | ||
| 295 | |||
| 296 | auto effect_infos{allocator.Allocate<EffectInfoBase>(params.effects, 0x40)}; | ||
| 297 | for (auto& effect_info : effect_infos) { | ||
| 298 | std::construct_at<EffectInfoBase>(&effect_info); | ||
| 299 | } | ||
| 300 | |||
| 301 | if (effect_infos.empty() && params.effects > 0) { | ||
| 302 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 303 | } | ||
| 304 | |||
| 305 | std::span<EffectResultState> effect_result_states_dsp{}; | ||
| 306 | if (behavior.IsEffectInfoVersion2Supported() && params.effects > 0) { | ||
| 307 | effect_result_states_dsp = allocator.Allocate<EffectResultState>(params.effects, 0x40); | ||
| 308 | if (effect_result_states_dsp.empty()) { | ||
| 309 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 310 | } | ||
| 311 | std::memset(effect_result_states_dsp.data(), 0, effect_result_states_dsp.size_bytes()); | ||
| 312 | } | ||
| 313 | |||
| 314 | effect_context.Initialize(effect_infos, params.effects, effect_result_states_cpu, | ||
| 315 | effect_result_states_dsp, effect_result_states_dsp.size()); | ||
| 316 | |||
| 317 | auto sinks{allocator.Allocate<SinkInfoBase>(params.sinks, 0x10)}; | ||
| 318 | for (auto& sink : sinks) { | ||
| 319 | std::construct_at<SinkInfoBase>(&sink); | ||
| 320 | } | ||
| 321 | |||
| 322 | if (sinks.empty()) { | ||
| 323 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 324 | } | ||
| 325 | |||
| 326 | sink_context.Initialize(sinks, params.sinks); | ||
| 327 | |||
| 328 | auto voice_dsp_states{allocator.Allocate<VoiceState>(params.voices, 0x40)}; | ||
| 329 | if (voice_dsp_states.empty()) { | ||
| 330 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 331 | } | ||
| 332 | |||
| 333 | for (auto& voice_state : voice_dsp_states) { | ||
| 334 | voice_state = {}; | ||
| 335 | } | ||
| 336 | |||
| 337 | voice_context.Initialize(sorted_voice_infos, voice_infos, voice_channel_resources, | ||
| 338 | voice_cpu_states, voice_dsp_states, params.voices); | ||
| 339 | |||
| 340 | if (params.perf_frames > 0) { | ||
| 341 | const auto perf_workbuffer_size{ | ||
| 342 | PerformanceManager::GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, | ||
| 343 | params) * | ||
| 344 | (params.perf_frames + 1) + | ||
| 345 | 0xC}; | ||
| 346 | performance_workbuffer = allocator.Allocate<u8>(perf_workbuffer_size, 0x40); | ||
| 347 | if (performance_workbuffer.empty()) { | ||
| 348 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 349 | } | ||
| 350 | std::memset(performance_workbuffer.data(), 0, performance_workbuffer.size_bytes()); | ||
| 351 | performance_manager.Initialize(performance_workbuffer, performance_workbuffer.size_bytes(), | ||
| 352 | params, behavior, memory_pool_info); | ||
| 353 | } | ||
| 354 | |||
| 355 | render_time_limit_percent = 100; | ||
| 356 | drop_voice = params.voice_drop_enabled && params.execution_mode == ExecutionMode::Auto; | ||
| 357 | |||
| 358 | allocator.Align(0x40); | ||
| 359 | command_workbuffer_size = allocator.GetRemainingSize(); | ||
| 360 | command_workbuffer = allocator.Allocate<u8>(command_workbuffer_size, 0x40); | ||
| 361 | if (command_workbuffer.empty()) { | ||
| 362 | return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; | ||
| 363 | } | ||
| 364 | |||
| 365 | command_buffer_size = 0; | ||
| 366 | reset_command_buffers = true; | ||
| 367 | |||
| 368 | // nn::audio::dsp::FlushDataCache(transferMemory, transferMemorySize); | ||
| 369 | |||
| 370 | if (behavior.IsCommandProcessingTimeEstimatorVersion5Supported()) { | ||
| 371 | command_processing_time_estimator = | ||
| 372 | std::make_unique<CommandProcessingTimeEstimatorVersion5>(sample_count, | ||
| 373 | mix_buffer_count); | ||
| 374 | } else if (behavior.IsCommandProcessingTimeEstimatorVersion4Supported()) { | ||
| 375 | command_processing_time_estimator = | ||
| 376 | std::make_unique<CommandProcessingTimeEstimatorVersion4>(sample_count, | ||
| 377 | mix_buffer_count); | ||
| 378 | } else if (behavior.IsCommandProcessingTimeEstimatorVersion3Supported()) { | ||
| 379 | command_processing_time_estimator = | ||
| 380 | std::make_unique<CommandProcessingTimeEstimatorVersion3>(sample_count, | ||
| 381 | mix_buffer_count); | ||
| 382 | } else if (behavior.IsCommandProcessingTimeEstimatorVersion2Supported()) { | ||
| 383 | command_processing_time_estimator = | ||
| 384 | std::make_unique<CommandProcessingTimeEstimatorVersion2>(sample_count, | ||
| 385 | mix_buffer_count); | ||
| 386 | } else { | ||
| 387 | command_processing_time_estimator = | ||
| 388 | std::make_unique<CommandProcessingTimeEstimatorVersion1>(sample_count, | ||
| 389 | mix_buffer_count); | ||
| 390 | } | ||
| 391 | |||
| 392 | initialized = true; | ||
| 393 | return ResultSuccess; | ||
| 394 | } | ||
| 395 | |||
| 396 | void System::Finalize() { | ||
| 397 | if (!initialized) { | ||
| 398 | return; | ||
| 399 | } | ||
| 400 | |||
| 401 | if (active) { | ||
| 402 | Stop(); | ||
| 403 | } | ||
| 404 | |||
| 405 | applet_resource_user_id = 0; | ||
| 406 | |||
| 407 | PoolMapper pool_mapper(process_handle, false); | ||
| 408 | pool_mapper.Unmap(memory_pool_info); | ||
| 409 | |||
| 410 | if (process_handle) { | ||
| 411 | pool_mapper.ClearUseState(memory_pool_workbuffer, memory_pool_count); | ||
| 412 | for (auto& memory_pool : memory_pool_workbuffer) { | ||
| 413 | if (memory_pool.IsMapped()) { | ||
| 414 | pool_mapper.Unmap(memory_pool); | ||
| 415 | } | ||
| 416 | } | ||
| 417 | |||
| 418 | // dsp::ProcessCleanup | ||
| 419 | // close handle | ||
| 420 | } | ||
| 421 | initialized = false; | ||
| 422 | } | ||
| 423 | |||
| 424 | void System::Start() { | ||
| 425 | std::scoped_lock l{lock}; | ||
| 426 | frames_elapsed = 0; | ||
| 427 | state = State::Started; | ||
| 428 | active = true; | ||
| 429 | } | ||
| 430 | |||
| 431 | void System::Stop() { | ||
| 432 | { | ||
| 433 | std::scoped_lock l{lock}; | ||
| 434 | state = State::Stopped; | ||
| 435 | active = false; | ||
| 436 | } | ||
| 437 | |||
| 438 | if (execution_mode == ExecutionMode::Auto) { | ||
| 439 | // Should wait for the system to terminate here, but core timing (should have) already | ||
| 440 | // stopped, so this isn't needed. Find a way to make this definite. | ||
| 441 | |||
| 442 | // terminate_event.Wait(); | ||
| 443 | } | ||
| 444 | } | ||
| 445 | |||
| 446 | Result System::Update(std::span<const u8> input, std::span<u8> performance, std::span<u8> output) { | ||
| 447 | std::scoped_lock l{lock}; | ||
| 448 | |||
| 449 | const auto start_time{core.CoreTiming().GetClockTicks()}; | ||
| 450 | |||
| 451 | InfoUpdater info_updater(input, output, process_handle, behavior); | ||
| 452 | |||
| 453 | auto result{info_updater.UpdateBehaviorInfo(behavior)}; | ||
| 454 | if (result.IsError()) { | ||
| 455 | LOG_ERROR(Service_Audio, "Failed to update BehaviorInfo!"); | ||
| 456 | return result; | ||
| 457 | } | ||
| 458 | |||
| 459 | result = info_updater.UpdateMemoryPools(memory_pool_workbuffer, memory_pool_count); | ||
| 460 | if (result.IsError()) { | ||
| 461 | LOG_ERROR(Service_Audio, "Failed to update MemoryPools!"); | ||
| 462 | return result; | ||
| 463 | } | ||
| 464 | |||
| 465 | result = info_updater.UpdateVoiceChannelResources(voice_context); | ||
| 466 | if (result.IsError()) { | ||
| 467 | LOG_ERROR(Service_Audio, "Failed to update VoiceChannelResources!"); | ||
| 468 | return result; | ||
| 469 | } | ||
| 470 | |||
| 471 | result = info_updater.UpdateVoices(voice_context, memory_pool_workbuffer, memory_pool_count); | ||
| 472 | if (result.IsError()) { | ||
| 473 | LOG_ERROR(Service_Audio, "Failed to update Voices!"); | ||
| 474 | return result; | ||
| 475 | } | ||
| 476 | |||
| 477 | result = info_updater.UpdateEffects(effect_context, active, memory_pool_workbuffer, | ||
| 478 | memory_pool_count); | ||
| 479 | if (result.IsError()) { | ||
| 480 | LOG_ERROR(Service_Audio, "Failed to update Effects!"); | ||
| 481 | return result; | ||
| 482 | } | ||
| 483 | |||
| 484 | if (behavior.IsSplitterSupported()) { | ||
| 485 | result = info_updater.UpdateSplitterInfo(splitter_context); | ||
| 486 | if (result.IsError()) { | ||
| 487 | LOG_ERROR(Service_Audio, "Failed to update SplitterInfo!"); | ||
| 488 | return result; | ||
| 489 | } | ||
| 490 | } | ||
| 491 | |||
| 492 | result = | ||
| 493 | info_updater.UpdateMixes(mix_context, mix_buffer_count, effect_context, splitter_context); | ||
| 494 | if (result.IsError()) { | ||
| 495 | LOG_ERROR(Service_Audio, "Failed to update Mixes!"); | ||
| 496 | return result; | ||
| 497 | } | ||
| 498 | |||
| 499 | result = info_updater.UpdateSinks(sink_context, memory_pool_workbuffer, memory_pool_count); | ||
| 500 | if (result.IsError()) { | ||
| 501 | LOG_ERROR(Service_Audio, "Failed to update Sinks!"); | ||
| 502 | return result; | ||
| 503 | } | ||
| 504 | |||
| 505 | PerformanceManager* perf_manager{nullptr}; | ||
| 506 | if (performance_manager.IsInitialized()) { | ||
| 507 | perf_manager = &performance_manager; | ||
| 508 | } | ||
| 509 | |||
| 510 | result = | ||
| 511 | info_updater.UpdatePerformanceBuffer(performance, performance.size_bytes(), perf_manager); | ||
| 512 | if (result.IsError()) { | ||
| 513 | LOG_ERROR(Service_Audio, "Failed to update PerformanceBuffer!"); | ||
| 514 | return result; | ||
| 515 | } | ||
| 516 | |||
| 517 | result = info_updater.UpdateErrorInfo(behavior); | ||
| 518 | if (result.IsError()) { | ||
| 519 | LOG_ERROR(Service_Audio, "Failed to update ErrorInfo!"); | ||
| 520 | return result; | ||
| 521 | } | ||
| 522 | |||
| 523 | if (behavior.IsElapsedFrameCountSupported()) { | ||
| 524 | result = info_updater.UpdateRendererInfo(frames_elapsed); | ||
| 525 | if (result.IsError()) { | ||
| 526 | LOG_ERROR(Service_Audio, "Failed to update RendererInfo!"); | ||
| 527 | return result; | ||
| 528 | } | ||
| 529 | } | ||
| 530 | |||
| 531 | result = info_updater.CheckConsumedSize(); | ||
| 532 | if (result.IsError()) { | ||
| 533 | LOG_ERROR(Service_Audio, "Invalid consume size!"); | ||
| 534 | return result; | ||
| 535 | } | ||
| 536 | |||
| 537 | adsp_rendered_event->GetWritableEvent().Clear(); | ||
| 538 | num_times_updated++; | ||
| 539 | |||
| 540 | const auto end_time{core.CoreTiming().GetClockTicks()}; | ||
| 541 | ticks_spent_updating += end_time - start_time; | ||
| 542 | |||
| 543 | return ResultSuccess; | ||
| 544 | } | ||
| 545 | |||
| 546 | u32 System::GetRenderingTimeLimit() const { | ||
| 547 | return render_time_limit_percent; | ||
| 548 | } | ||
| 549 | |||
| 550 | void System::SetRenderingTimeLimit(const u32 limit) { | ||
| 551 | render_time_limit_percent = limit; | ||
| 552 | } | ||
| 553 | |||
| 554 | u32 System::GetSessionId() const { | ||
| 555 | return session_id; | ||
| 556 | } | ||
| 557 | |||
| 558 | u32 System::GetSampleRate() const { | ||
| 559 | return sample_rate; | ||
| 560 | } | ||
| 561 | |||
| 562 | u32 System::GetSampleCount() const { | ||
| 563 | return sample_count; | ||
| 564 | } | ||
| 565 | |||
| 566 | u32 System::GetMixBufferCount() const { | ||
| 567 | return mix_buffer_count; | ||
| 568 | } | ||
| 569 | |||
| 570 | ExecutionMode System::GetExecutionMode() const { | ||
| 571 | return execution_mode; | ||
| 572 | } | ||
| 573 | |||
| 574 | u32 System::GetRenderingDevice() const { | ||
| 575 | return render_device; | ||
| 576 | } | ||
| 577 | |||
| 578 | bool System::IsActive() const { | ||
| 579 | return active; | ||
| 580 | } | ||
| 581 | |||
| 582 | void System::SendCommandToDsp() { | ||
| 583 | std::scoped_lock l{lock}; | ||
| 584 | |||
| 585 | if (initialized) { | ||
| 586 | if (active) { | ||
| 587 | terminate_event.Reset(); | ||
| 588 | const auto remaining_command_count{adsp.GetRemainCommandCount(session_id)}; | ||
| 589 | u64 command_size{0}; | ||
| 590 | |||
| 591 | if (remaining_command_count) { | ||
| 592 | adsp_behind = true; | ||
| 593 | command_size = command_buffer_size; | ||
| 594 | } else { | ||
| 595 | command_size = GenerateCommand(command_workbuffer, command_workbuffer_size); | ||
| 596 | } | ||
| 597 | |||
| 598 | auto translated_addr{ | ||
| 599 | memory_pool_info.Translate(CpuAddr(command_workbuffer.data()), command_size)}; | ||
| 600 | |||
| 601 | auto time_limit_percent{70.0f}; | ||
| 602 | if (behavior.IsAudioRendererProcessingTimeLimit80PercentSupported()) { | ||
| 603 | time_limit_percent = 80.0f; | ||
| 604 | } else if (behavior.IsAudioRendererProcessingTimeLimit75PercentSupported()) { | ||
| 605 | time_limit_percent = 75.0f; | ||
| 606 | } else { | ||
| 607 | // result ignored and 70 is used anyway | ||
| 608 | behavior.IsAudioRendererProcessingTimeLimit70PercentSupported(); | ||
| 609 | time_limit_percent = 70.0f; | ||
| 610 | } | ||
| 611 | |||
| 612 | ADSP::CommandBuffer command_buffer{ | ||
| 613 | .buffer{translated_addr}, | ||
| 614 | .size{command_size}, | ||
| 615 | .time_limit{ | ||
| 616 | static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 * | ||
| 617 | (static_cast<f32>(render_time_limit_percent) / 100.0f))}, | ||
| 618 | .remaining_command_count{remaining_command_count}, | ||
| 619 | .reset_buffers{reset_command_buffers}, | ||
| 620 | .applet_resource_user_id{applet_resource_user_id}, | ||
| 621 | .render_time_taken{adsp.GetRenderTimeTaken(session_id)}, | ||
| 622 | }; | ||
| 623 | |||
| 624 | adsp.SendCommandBuffer(session_id, command_buffer); | ||
| 625 | reset_command_buffers = false; | ||
| 626 | command_buffer_size = command_size; | ||
| 627 | if (remaining_command_count == 0) { | ||
| 628 | adsp_rendered_event->GetWritableEvent().Signal(); | ||
| 629 | } | ||
| 630 | } else { | ||
| 631 | adsp.ClearRemainCount(session_id); | ||
| 632 | terminate_event.Set(); | ||
| 633 | } | ||
| 634 | } | ||
| 635 | } | ||
| 636 | |||
| 637 | u64 System::GenerateCommand(std::span<u8> in_command_buffer, | ||
| 638 | [[maybe_unused]] const u64 command_buffer_size_) { | ||
| 639 | PoolMapper::ClearUseState(memory_pool_workbuffer, memory_pool_count); | ||
| 640 | const auto start_time{core.CoreTiming().GetClockTicks()}; | ||
| 641 | |||
| 642 | auto command_list_header{reinterpret_cast<CommandListHeader*>(in_command_buffer.data())}; | ||
| 643 | |||
| 644 | command_list_header->buffer_count = static_cast<s16>(voice_channels + mix_buffer_count); | ||
| 645 | command_list_header->sample_count = sample_count; | ||
| 646 | command_list_header->sample_rate = sample_rate; | ||
| 647 | command_list_header->samples_buffer = samples_workbuffer; | ||
| 648 | |||
| 649 | const auto performance_initialized{performance_manager.IsInitialized()}; | ||
| 650 | if (performance_initialized) { | ||
| 651 | performance_manager.TapFrame(adsp_behind, num_voices_dropped, render_start_tick); | ||
| 652 | adsp_behind = false; | ||
| 653 | num_voices_dropped = 0; | ||
| 654 | render_start_tick = 0; | ||
| 655 | } | ||
| 656 | |||
| 657 | s8 channel_count{2}; | ||
| 658 | if (execution_mode == ExecutionMode::Auto) { | ||
| 659 | const auto& sink{core.AudioCore().GetOutputSink()}; | ||
| 660 | channel_count = static_cast<s8>(sink.GetDeviceChannels()); | ||
| 661 | } | ||
| 662 | |||
| 663 | AudioRendererSystemContext render_context{ | ||
| 664 | .session_id{session_id}, | ||
| 665 | .channels{channel_count}, | ||
| 666 | .mix_buffer_count{mix_buffer_count}, | ||
| 667 | .behavior{&behavior}, | ||
| 668 | .depop_buffer{depop_buffer}, | ||
| 669 | .upsampler_manager{upsampler_manager}, | ||
| 670 | .memory_pool_info{&memory_pool_info}, | ||
| 671 | }; | ||
| 672 | |||
| 673 | CommandBuffer command_buffer{ | ||
| 674 | .command_list{in_command_buffer}, | ||
| 675 | .sample_count{sample_count}, | ||
| 676 | .sample_rate{sample_rate}, | ||
| 677 | .size{sizeof(CommandListHeader)}, | ||
| 678 | .count{0}, | ||
| 679 | .estimated_process_time{0}, | ||
| 680 | .memory_pool{&memory_pool_info}, | ||
| 681 | .time_estimator{command_processing_time_estimator.get()}, | ||
| 682 | .behavior{&behavior}, | ||
| 683 | }; | ||
| 684 | |||
| 685 | PerformanceManager* perf_manager{nullptr}; | ||
| 686 | if (performance_initialized) { | ||
| 687 | perf_manager = &performance_manager; | ||
| 688 | } | ||
| 689 | |||
| 690 | CommandGenerator command_generator{command_buffer, *command_list_header, render_context, | ||
| 691 | voice_context, mix_context, effect_context, | ||
| 692 | sink_context, splitter_context, perf_manager}; | ||
| 693 | |||
| 694 | voice_context.SortInfo(); | ||
| 695 | |||
| 696 | const auto start_estimated_time{command_buffer.estimated_process_time}; | ||
| 697 | |||
| 698 | command_generator.GenerateVoiceCommands(); | ||
| 699 | command_generator.GenerateSubMixCommands(); | ||
| 700 | command_generator.GenerateFinalMixCommands(); | ||
| 701 | command_generator.GenerateSinkCommands(); | ||
| 702 | |||
| 703 | if (drop_voice) { | ||
| 704 | f32 time_limit_percent{70.0f}; | ||
| 705 | if (render_context.behavior->IsAudioRendererProcessingTimeLimit80PercentSupported()) { | ||
| 706 | time_limit_percent = 80.0f; | ||
| 707 | } else if (render_context.behavior | ||
| 708 | ->IsAudioRendererProcessingTimeLimit75PercentSupported()) { | ||
| 709 | time_limit_percent = 75.0f; | ||
| 710 | } else { | ||
| 711 | // result is ignored | ||
| 712 | render_context.behavior->IsAudioRendererProcessingTimeLimit70PercentSupported(); | ||
| 713 | time_limit_percent = 70.0f; | ||
| 714 | } | ||
| 715 | const auto time_limit{static_cast<u32>( | ||
| 716 | static_cast<f32>(start_estimated_time - command_buffer.estimated_process_time) + | ||
| 717 | (((time_limit_percent / 100.0f) * 2'880'000.0) * | ||
| 718 | (static_cast<f32>(render_time_limit_percent) / 100.0f)))}; | ||
| 719 | num_voices_dropped = DropVoices(command_buffer, start_estimated_time, time_limit); | ||
| 720 | } | ||
| 721 | |||
| 722 | command_list_header->buffer_size = command_buffer.size; | ||
| 723 | command_list_header->command_count = command_buffer.count; | ||
| 724 | |||
| 725 | voice_context.UpdateStateByDspShared(); | ||
| 726 | |||
| 727 | if (render_context.behavior->IsEffectInfoVersion2Supported()) { | ||
| 728 | effect_context.UpdateStateByDspShared(); | ||
| 729 | } | ||
| 730 | |||
| 731 | const auto end_time{core.CoreTiming().GetClockTicks()}; | ||
| 732 | total_ticks_elapsed += end_time - start_time; | ||
| 733 | num_command_lists_generated++; | ||
| 734 | render_start_tick = adsp.GetRenderingStartTick(session_id); | ||
| 735 | frames_elapsed++; | ||
| 736 | |||
| 737 | return command_buffer.size; | ||
| 738 | } | ||
| 739 | |||
| 740 | u32 System::DropVoices(CommandBuffer& command_buffer, const u32 estimated_process_time, | ||
| 741 | const u32 time_limit) { | ||
| 742 | u32 i{0}; | ||
| 743 | auto command_list{command_buffer.command_list.data() + sizeof(CommandListHeader)}; | ||
| 744 | ICommand* cmd{}; | ||
| 745 | |||
| 746 | for (; i < command_buffer.count; i++) { | ||
| 747 | cmd = reinterpret_cast<ICommand*>(command_list); | ||
| 748 | if (cmd->type != CommandId::Performance && | ||
| 749 | cmd->type != CommandId::DataSourcePcmInt16Version1 && | ||
| 750 | cmd->type != CommandId::DataSourcePcmInt16Version2 && | ||
| 751 | cmd->type != CommandId::DataSourcePcmFloatVersion1 && | ||
| 752 | cmd->type != CommandId::DataSourcePcmFloatVersion2 && | ||
| 753 | cmd->type != CommandId::DataSourceAdpcmVersion1 && | ||
| 754 | cmd->type != CommandId::DataSourceAdpcmVersion2) { | ||
| 755 | break; | ||
| 756 | } | ||
| 757 | command_list += cmd->size; | ||
| 758 | } | ||
| 759 | |||
| 760 | if (cmd == nullptr || command_buffer.count == 0 || i >= command_buffer.count) { | ||
| 761 | return 0; | ||
| 762 | } | ||
| 763 | |||
| 764 | auto voices_dropped{0}; | ||
| 765 | while (i < command_buffer.count) { | ||
| 766 | const auto node_id{cmd->node_id}; | ||
| 767 | const auto node_id_type{cmd->node_id >> 28}; | ||
| 768 | const auto node_id_base{cmd->node_id & 0xFFF}; | ||
| 769 | |||
| 770 | if (estimated_process_time <= time_limit) { | ||
| 771 | break; | ||
| 772 | } | ||
| 773 | |||
| 774 | if (node_id_type != 1) { | ||
| 775 | break; | ||
| 776 | } | ||
| 777 | |||
| 778 | auto& voice_info{voice_context.GetInfo(node_id_base)}; | ||
| 779 | if (voice_info.priority == HighestVoicePriority) { | ||
| 780 | break; | ||
| 781 | } | ||
| 782 | |||
| 783 | voices_dropped++; | ||
| 784 | voice_info.voice_dropped = true; | ||
| 785 | |||
| 786 | if (i < command_buffer.count) { | ||
| 787 | while (cmd->node_id == node_id) { | ||
| 788 | if (cmd->type == CommandId::DepopPrepare) { | ||
| 789 | cmd->enabled = true; | ||
| 790 | } else if (cmd->type == CommandId::Performance || !cmd->enabled) { | ||
| 791 | cmd->enabled = false; | ||
| 792 | } | ||
| 793 | i++; | ||
| 794 | command_list += cmd->size; | ||
| 795 | cmd = reinterpret_cast<ICommand*>(command_list); | ||
| 796 | } | ||
| 797 | } | ||
| 798 | } | ||
| 799 | return voices_dropped; | ||
| 800 | } | ||
| 801 | |||
| 802 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/system.h b/src/audio_core/renderer/system.h new file mode 100644 index 000000000..bcbe65b07 --- /dev/null +++ b/src/audio_core/renderer/system.h | |||
| @@ -0,0 +1,307 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <memory> | ||
| 7 | #include <mutex> | ||
| 8 | #include <span> | ||
| 9 | |||
| 10 | #include "audio_core/renderer/behavior/behavior_info.h" | ||
| 11 | #include "audio_core/renderer/command/command_processing_time_estimator.h" | ||
| 12 | #include "audio_core/renderer/effect/effect_context.h" | ||
| 13 | #include "audio_core/renderer/memory/memory_pool_info.h" | ||
| 14 | #include "audio_core/renderer/mix/mix_context.h" | ||
| 15 | #include "audio_core/renderer/performance/performance_manager.h" | ||
| 16 | #include "audio_core/renderer/sink/sink_context.h" | ||
| 17 | #include "audio_core/renderer/splitter/splitter_context.h" | ||
| 18 | #include "audio_core/renderer/upsampler/upsampler_manager.h" | ||
| 19 | #include "audio_core/renderer/voice/voice_context.h" | ||
| 20 | #include "common/thread.h" | ||
| 21 | #include "core/hle/service/audio/errors.h" | ||
| 22 | |||
| 23 | namespace Core { | ||
| 24 | namespace Memory { | ||
| 25 | class Memory; | ||
| 26 | } | ||
| 27 | class System; | ||
| 28 | } // namespace Core | ||
| 29 | |||
| 30 | namespace Kernel { | ||
| 31 | class KEvent; | ||
| 32 | class KTransferMemory; | ||
| 33 | } // namespace Kernel | ||
| 34 | |||
| 35 | namespace AudioCore { | ||
| 36 | struct AudioRendererParameterInternal; | ||
| 37 | |||
| 38 | namespace AudioRenderer { | ||
| 39 | class CommandBuffer; | ||
| 40 | namespace ADSP { | ||
| 41 | class ADSP; | ||
| 42 | } | ||
| 43 | |||
| 44 | /** | ||
| 45 | * Audio Renderer System, the main worker for audio rendering. | ||
| 46 | */ | ||
| 47 | class System { | ||
| 48 | enum class State { | ||
| 49 | Started = 0, | ||
| 50 | Stopped = 2, | ||
| 51 | }; | ||
| 52 | |||
| 53 | public: | ||
| 54 | explicit System(Core::System& core, Kernel::KEvent* adsp_rendered_event); | ||
| 55 | |||
| 56 | /** | ||
| 57 | * Calculate the total size required for all audio render workbuffers. | ||
| 58 | * | ||
| 59 | * @param params - Input parameters with the numbers of voices/mixes/sinks/etc. | ||
| 60 | * @return Size (in bytes) required for the audio renderer. | ||
| 61 | */ | ||
| 62 | static u64 GetWorkBufferSize(const AudioRendererParameterInternal& params); | ||
| 63 | |||
| 64 | /** | ||
| 65 | * Initialize the renderer system. | ||
| 66 | * Allocates workbuffers and initializes everything to a default state, ready to receive a | ||
| 67 | * RequestUpdate. | ||
| 68 | * | ||
| 69 | * @param params - Input parameters to initialize the system with. | ||
| 70 | * @param transfer_memory - Game-supplied memory for all workbuffers. Unused. | ||
| 71 | * @param transfer_memory_size - Size of the transfer memory. Unused. | ||
| 72 | * @param process_handle - Process handle, also used for memory. Unused. | ||
| 73 | * @param applet_resource_user_id - Applet id for this renderer. Unused. | ||
| 74 | * @param session_id - Session id of this renderer. | ||
| 75 | * @return Result code. | ||
| 76 | */ | ||
| 77 | Result Initialize(const AudioRendererParameterInternal& params, | ||
| 78 | Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size, | ||
| 79 | u32 process_handle, u64 applet_resource_user_id, s32 session_id); | ||
| 80 | |||
| 81 | /** | ||
| 82 | * Finalize the system. | ||
| 83 | */ | ||
| 84 | void Finalize(); | ||
| 85 | |||
| 86 | /** | ||
| 87 | * Start the system. | ||
| 88 | */ | ||
| 89 | void Start(); | ||
| 90 | |||
| 91 | /** | ||
| 92 | * Stop the system. | ||
| 93 | */ | ||
| 94 | void Stop(); | ||
| 95 | |||
| 96 | /** | ||
| 97 | * Update the system. | ||
| 98 | * | ||
| 99 | * @param input - Inout buffer containing the update data. | ||
| 100 | * @param performance - Optional buffer for writing back performance metrics. | ||
| 101 | * @param output - Output information from rendering. | ||
| 102 | * @return Result code. | ||
| 103 | */ | ||
| 104 | Result Update(std::span<const u8> input, std::span<u8> performance, std::span<u8> output); | ||
| 105 | |||
| 106 | /** | ||
| 107 | * Get the time limit (percent) for rendering | ||
| 108 | * | ||
| 109 | * @return Time limit as a percent. | ||
| 110 | */ | ||
| 111 | u32 GetRenderingTimeLimit() const; | ||
| 112 | |||
| 113 | /** | ||
| 114 | * Set the time limit (percent) for rendering | ||
| 115 | * | ||
| 116 | * @param limit - New time limit. | ||
| 117 | */ | ||
| 118 | void SetRenderingTimeLimit(u32 limit); | ||
| 119 | |||
| 120 | /** | ||
| 121 | * Get the session id for this system. | ||
| 122 | * | ||
| 123 | * @return Session id of this system. | ||
| 124 | */ | ||
| 125 | u32 GetSessionId() const; | ||
| 126 | |||
| 127 | /** | ||
| 128 | * Get the sample rate of this system. | ||
| 129 | * | ||
| 130 | * @return Sample rate of this system. | ||
| 131 | */ | ||
| 132 | u32 GetSampleRate() const; | ||
| 133 | |||
| 134 | /** | ||
| 135 | * Get the sample count of this system. | ||
| 136 | * | ||
| 137 | * @return Sample count of this system. | ||
| 138 | */ | ||
| 139 | u32 GetSampleCount() const; | ||
| 140 | |||
| 141 | /** | ||
| 142 | * Get the number of mix buffers for this system. | ||
| 143 | * | ||
| 144 | * @return Number of mix buffers in the system. | ||
| 145 | */ | ||
| 146 | u32 GetMixBufferCount() const; | ||
| 147 | |||
| 148 | /** | ||
| 149 | * Get the execution mode of this system. | ||
| 150 | * Note: Only Auto is implemented. | ||
| 151 | * | ||
| 152 | * @return Execution mode for this system. | ||
| 153 | */ | ||
| 154 | ExecutionMode GetExecutionMode() const; | ||
| 155 | |||
| 156 | /** | ||
| 157 | * Get the rendering deivce for this system. | ||
| 158 | * This is unused. | ||
| 159 | * | ||
| 160 | * @return Rendering device for this system. | ||
| 161 | */ | ||
| 162 | u32 GetRenderingDevice() const; | ||
| 163 | |||
| 164 | /** | ||
| 165 | * Check if this system is currently active. | ||
| 166 | * | ||
| 167 | * @return True if active, otherwise false. | ||
| 168 | */ | ||
| 169 | bool IsActive() const; | ||
| 170 | |||
| 171 | /** | ||
| 172 | * Prepare and generate a list of commands for the AudioRenderer based on current state, | ||
| 173 | * signalling the buffer event when all processed. | ||
| 174 | */ | ||
| 175 | void SendCommandToDsp(); | ||
| 176 | |||
| 177 | /** | ||
| 178 | * Generate a list of commands for the AudioRenderer based on current state. | ||
| 179 | * | ||
| 180 | * @param command_buffer - Buffer for commands to be written to. | ||
| 181 | * @param command_buffer_size - Size of the command_buffer. | ||
| 182 | * | ||
| 183 | * @return Number of bytes written. | ||
| 184 | */ | ||
| 185 | u64 GenerateCommand(std::span<u8> command_buffer, u64 command_buffer_size); | ||
| 186 | |||
| 187 | /** | ||
| 188 | * Try to drop some voices if the AudioRenderer fell behind. | ||
| 189 | * | ||
| 190 | * @param command_buffer - Command buffer to drop voices from. | ||
| 191 | * @param estimated_process_time - Current estimated processing time of all commands. | ||
| 192 | * @param time_limit - Time limit for rendering, voices are dropped if estimated | ||
| 193 | * exceeds this. | ||
| 194 | * | ||
| 195 | * @return Number of voices dropped. | ||
| 196 | */ | ||
| 197 | u32 DropVoices(CommandBuffer& command_buffer, u32 estimated_process_time, u32 time_limit); | ||
| 198 | |||
| 199 | private: | ||
| 200 | /// Core system | ||
| 201 | Core::System& core; | ||
| 202 | /// Reference to the ADSP for communication | ||
| 203 | ADSP::ADSP& adsp; | ||
| 204 | /// Is this system initialized? | ||
| 205 | bool initialized{}; | ||
| 206 | /// Is this system currently active? | ||
| 207 | std::atomic<bool> active{}; | ||
| 208 | /// State of the system | ||
| 209 | State state{State::Stopped}; | ||
| 210 | /// Sample rate for the system | ||
| 211 | u32 sample_rate{}; | ||
| 212 | /// Sample count of the system | ||
| 213 | u32 sample_count{}; | ||
| 214 | /// Number of mix buffers in use by the system | ||
| 215 | s16 mix_buffer_count{}; | ||
| 216 | /// Workbuffer for mix buffers, used by the AudioRenderer | ||
| 217 | std::span<s32> samples_workbuffer{}; | ||
| 218 | /// Depop samples for depopping commands | ||
| 219 | std::span<s32> depop_buffer{}; | ||
| 220 | /// Number of memory pools in the buffer | ||
| 221 | u32 memory_pool_count{}; | ||
| 222 | /// Workbuffer for memory pools | ||
| 223 | std::span<MemoryPoolInfo> memory_pool_workbuffer{}; | ||
| 224 | /// System memory pool info | ||
| 225 | MemoryPoolInfo memory_pool_info{}; | ||
| 226 | /// Workbuffer that commands will be generated into | ||
| 227 | std::span<u8> command_workbuffer{}; | ||
| 228 | /// Size of command workbuffer | ||
| 229 | u64 command_workbuffer_size{}; | ||
| 230 | /// Numebr of commands in the workbuffer | ||
| 231 | u64 command_buffer_size{}; | ||
| 232 | /// Manager for upsamplers | ||
| 233 | UpsamplerManager* upsampler_manager{}; | ||
| 234 | /// Upsampler workbuffer | ||
| 235 | std::span<UpsamplerInfo> upsampler_infos{}; | ||
| 236 | /// Number of upsamplers in the workbuffer | ||
| 237 | u32 upsampler_count{}; | ||
| 238 | /// Holds and controls all voices | ||
| 239 | VoiceContext voice_context{}; | ||
| 240 | /// Holds and controls all mixes | ||
| 241 | MixContext mix_context{}; | ||
| 242 | /// Holds and controls all effects | ||
| 243 | EffectContext effect_context{}; | ||
| 244 | /// Holds and controls all sinks | ||
| 245 | SinkContext sink_context{}; | ||
| 246 | /// Holds and controls all splitters | ||
| 247 | SplitterContext splitter_context{}; | ||
| 248 | /// Estimates the time taken for each command | ||
| 249 | std::unique_ptr<ICommandProcessingTimeEstimator> command_processing_time_estimator{}; | ||
| 250 | /// Session id of this system | ||
| 251 | s32 session_id{}; | ||
| 252 | /// Number of channels in use by voices | ||
| 253 | s32 voice_channels{}; | ||
| 254 | /// Event to be called when the AudioRenderer processes a command list | ||
| 255 | Kernel::KEvent* adsp_rendered_event{}; | ||
| 256 | /// Event signalled on system terminate | ||
| 257 | Common::Event terminate_event{}; | ||
| 258 | /// Does what locks do | ||
| 259 | std::mutex lock{}; | ||
| 260 | /// Handle for the process for this system, unused | ||
| 261 | u32 process_handle{}; | ||
| 262 | /// Applet resource id for this system, unused | ||
| 263 | u64 applet_resource_user_id{}; | ||
| 264 | /// Controls performance input and output | ||
| 265 | PerformanceManager performance_manager{}; | ||
| 266 | /// Workbuffer for performance metrics | ||
| 267 | std::span<u8> performance_workbuffer{}; | ||
| 268 | /// Main workbuffer, from which all other workbuffers here allocate into | ||
| 269 | std::unique_ptr<u8[]> workbuffer{}; | ||
| 270 | /// Size of the main workbuffer | ||
| 271 | u64 workbuffer_size{}; | ||
| 272 | /// Unknown buffer/marker | ||
| 273 | std::span<u8> unk_2A8{}; | ||
| 274 | /// Size of the above unknown buffer/marker | ||
| 275 | u64 unk_2B0{}; | ||
| 276 | /// Rendering time limit (percent) | ||
| 277 | u32 render_time_limit_percent{}; | ||
| 278 | /// Should any voices be dropped? | ||
| 279 | bool drop_voice{}; | ||
| 280 | /// Should the backend stream have its buffers flushed? | ||
| 281 | bool reset_command_buffers{}; | ||
| 282 | /// Execution mode of this system, only Auto is supported | ||
| 283 | ExecutionMode execution_mode{ExecutionMode::Auto}; | ||
| 284 | /// Render device, unused | ||
| 285 | u32 render_device{}; | ||
| 286 | /// Behaviour to check which features are supported by the user revision | ||
| 287 | BehaviorInfo behavior{}; | ||
| 288 | /// Total ticks the audio system has been running | ||
| 289 | u64 total_ticks_elapsed{}; | ||
| 290 | /// Ticks the system has spent in updates | ||
| 291 | u64 ticks_spent_updating{}; | ||
| 292 | /// Number of times a command list was generated | ||
| 293 | u64 num_command_lists_generated{}; | ||
| 294 | /// Number of times the system has updated | ||
| 295 | u64 num_times_updated{}; | ||
| 296 | /// Number of frames generated, written back to the game | ||
| 297 | std::atomic<u64> frames_elapsed{}; | ||
| 298 | /// Is the AudioRenderer running too slow? | ||
| 299 | bool adsp_behind{}; | ||
| 300 | /// Number of voices dropped | ||
| 301 | u32 num_voices_dropped{}; | ||
| 302 | /// Tick that rendering started | ||
| 303 | u64 render_start_tick{}; | ||
| 304 | }; | ||
| 305 | |||
| 306 | } // namespace AudioRenderer | ||
| 307 | } // namespace AudioCore | ||
diff --git a/src/audio_core/renderer/system_manager.cpp b/src/audio_core/renderer/system_manager.cpp new file mode 100644 index 000000000..b326819ed --- /dev/null +++ b/src/audio_core/renderer/system_manager.cpp | |||
| @@ -0,0 +1,162 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <chrono> | ||
| 5 | |||
| 6 | #include "audio_core/audio_core.h" | ||
| 7 | #include "audio_core/renderer/adsp/adsp.h" | ||
| 8 | #include "audio_core/renderer/system_manager.h" | ||
| 9 | #include "common/microprofile.h" | ||
| 10 | #include "common/thread.h" | ||
| 11 | #include "core/core.h" | ||
| 12 | #include "core/core_timing.h" | ||
| 13 | |||
| 14 | MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager", | ||
| 15 | MP_RGB(60, 19, 97)); | ||
| 16 | |||
| 17 | namespace AudioCore::AudioRenderer { | ||
| 18 | constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL}; | ||
| 19 | constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL}; | ||
| 20 | |||
| 21 | SystemManager::SystemManager(Core::System& core_) | ||
| 22 | : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()}, | ||
| 23 | thread_event{Core::Timing::CreateEvent( | ||
| 24 | "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) { | ||
| 25 | return ThreadFunc2(time); | ||
| 26 | })} { | ||
| 27 | core.CoreTiming().RegisterPauseCallback([this](bool paused) { PauseCallback(paused); }); | ||
| 28 | } | ||
| 29 | |||
| 30 | SystemManager::~SystemManager() { | ||
| 31 | Stop(); | ||
| 32 | } | ||
| 33 | |||
| 34 | bool SystemManager::InitializeUnsafe() { | ||
| 35 | if (!active) { | ||
| 36 | if (adsp.Start()) { | ||
| 37 | active = true; | ||
| 38 | thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); }); | ||
| 39 | core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), | ||
| 40 | BaseRenderTime - RenderTimeOffset, thread_event); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | return adsp.GetState() == ADSP::State::Started; | ||
| 45 | } | ||
| 46 | |||
| 47 | void SystemManager::Stop() { | ||
| 48 | if (!active) { | ||
| 49 | return; | ||
| 50 | } | ||
| 51 | core.CoreTiming().UnscheduleEvent(thread_event, {}); | ||
| 52 | active = false; | ||
| 53 | update.store(true); | ||
| 54 | update.notify_all(); | ||
| 55 | thread.join(); | ||
| 56 | adsp.Stop(); | ||
| 57 | } | ||
| 58 | |||
| 59 | bool SystemManager::Add(System& system_) { | ||
| 60 | std::scoped_lock l2{mutex2}; | ||
| 61 | |||
| 62 | if (systems.size() + 1 > MaxRendererSessions) { | ||
| 63 | LOG_ERROR(Service_Audio, "Maximum AudioRenderer Systems active, cannot add more!"); | ||
| 64 | return false; | ||
| 65 | } | ||
| 66 | |||
| 67 | { | ||
| 68 | std::scoped_lock l{mutex1}; | ||
| 69 | if (systems.empty()) { | ||
| 70 | if (!InitializeUnsafe()) { | ||
| 71 | LOG_ERROR(Service_Audio, "Failed to start the AudioRenderer SystemManager"); | ||
| 72 | return false; | ||
| 73 | } | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | systems.push_back(&system_); | ||
| 78 | return true; | ||
| 79 | } | ||
| 80 | |||
| 81 | bool SystemManager::Remove(System& system_) { | ||
| 82 | std::scoped_lock l2{mutex2}; | ||
| 83 | |||
| 84 | { | ||
| 85 | std::scoped_lock l{mutex1}; | ||
| 86 | if (systems.remove(&system_) == 0) { | ||
| 87 | LOG_ERROR(Service_Audio, | ||
| 88 | "Failed to remove a render system, it was not found in the list!"); | ||
| 89 | return false; | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | if (systems.empty()) { | ||
| 94 | Stop(); | ||
| 95 | } | ||
| 96 | return true; | ||
| 97 | } | ||
| 98 | |||
| 99 | void SystemManager::ThreadFunc() { | ||
| 100 | constexpr char name[]{"yuzu:AudioRenderSystemManager"}; | ||
| 101 | MicroProfileOnThreadCreate(name); | ||
| 102 | Common::SetCurrentThreadName(name); | ||
| 103 | Common::SetCurrentThreadPriority(Common::ThreadPriority::High); | ||
| 104 | while (active) { | ||
| 105 | { | ||
| 106 | std::scoped_lock l{mutex1}; | ||
| 107 | |||
| 108 | MICROPROFILE_SCOPE(Audio_RenderSystemManager); | ||
| 109 | |||
| 110 | for (auto system : systems) { | ||
| 111 | system->SendCommandToDsp(); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | adsp.Signal(); | ||
| 116 | adsp.Wait(); | ||
| 117 | |||
| 118 | update.wait(false); | ||
| 119 | update.store(false); | ||
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 123 | std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) { | ||
| 124 | std::optional<std::chrono::nanoseconds> new_schedule_time{std::nullopt}; | ||
| 125 | const auto queue_size{core.AudioCore().GetStreamQueue()}; | ||
| 126 | switch (state) { | ||
| 127 | case StreamState::Filling: | ||
| 128 | if (queue_size >= 5) { | ||
| 129 | new_schedule_time = BaseRenderTime; | ||
| 130 | state = StreamState::Steady; | ||
| 131 | } | ||
| 132 | break; | ||
| 133 | case StreamState::Steady: | ||
| 134 | if (queue_size <= 2) { | ||
| 135 | new_schedule_time = BaseRenderTime - RenderTimeOffset; | ||
| 136 | state = StreamState::Filling; | ||
| 137 | } else if (queue_size > 5) { | ||
| 138 | new_schedule_time = BaseRenderTime + RenderTimeOffset; | ||
| 139 | state = StreamState::Draining; | ||
| 140 | } | ||
| 141 | break; | ||
| 142 | case StreamState::Draining: | ||
| 143 | if (queue_size <= 5) { | ||
| 144 | new_schedule_time = BaseRenderTime; | ||
| 145 | state = StreamState::Steady; | ||
| 146 | } | ||
| 147 | break; | ||
| 148 | } | ||
| 149 | |||
| 150 | update.store(true); | ||
| 151 | update.notify_all(); | ||
| 152 | return new_schedule_time; | ||
| 153 | } | ||
| 154 | |||
| 155 | void SystemManager::PauseCallback(bool paused) { | ||
| 156 | if (paused && core.IsPoweredOn() && core.IsShuttingDown()) { | ||
| 157 | update.store(true); | ||
| 158 | update.notify_all(); | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/system_manager.h b/src/audio_core/renderer/system_manager.h new file mode 100644 index 000000000..1291e9e0e --- /dev/null +++ b/src/audio_core/renderer/system_manager.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 <list> | ||
| 7 | #include <memory> | ||
| 8 | #include <mutex> | ||
| 9 | #include <optional> | ||
| 10 | #include <thread> | ||
| 11 | |||
| 12 | #include "audio_core/renderer/system.h" | ||
| 13 | |||
| 14 | namespace Core { | ||
| 15 | namespace Timing { | ||
| 16 | struct EventType; | ||
| 17 | } | ||
| 18 | class System; | ||
| 19 | } // namespace Core | ||
| 20 | |||
| 21 | namespace AudioCore::AudioRenderer { | ||
| 22 | namespace ADSP { | ||
| 23 | class ADSP; | ||
| 24 | class AudioRenderer_Mailbox; | ||
| 25 | } // namespace ADSP | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Manages all audio renderers, responsible for triggering command list generation and signalling | ||
| 29 | * the ADSP. | ||
| 30 | */ | ||
| 31 | class SystemManager { | ||
| 32 | public: | ||
| 33 | explicit SystemManager(Core::System& core); | ||
| 34 | ~SystemManager(); | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Initialize the system manager, called when any system is registered. | ||
| 38 | * | ||
| 39 | * @return True if sucessfully initialized, otherwise false. | ||
| 40 | */ | ||
| 41 | bool InitializeUnsafe(); | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Stop the system manager. | ||
| 45 | */ | ||
| 46 | void Stop(); | ||
| 47 | |||
| 48 | /** | ||
| 49 | * Add an audio render system to the manager. | ||
| 50 | * The manager does not own the system, so do not free it without calling Remove. | ||
| 51 | * | ||
| 52 | * @param system - The system to add. | ||
| 53 | * @return True if succesfully added, otherwise false. | ||
| 54 | */ | ||
| 55 | bool Add(System& system); | ||
| 56 | |||
| 57 | /** | ||
| 58 | * Remove an audio render system from the manager. | ||
| 59 | * | ||
| 60 | * @param system - The system to remove. | ||
| 61 | * @return True if succesfully removed, otherwise false. | ||
| 62 | */ | ||
| 63 | bool Remove(System& system); | ||
| 64 | |||
| 65 | private: | ||
| 66 | /** | ||
| 67 | * Main thread responsible for command generation. | ||
| 68 | */ | ||
| 69 | void ThreadFunc(); | ||
| 70 | |||
| 71 | /** | ||
| 72 | * Signalling core timing thread to run ThreadFunc. | ||
| 73 | */ | ||
| 74 | std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time); | ||
| 75 | |||
| 76 | /** | ||
| 77 | * Callback from core timing when pausing, used to detect shutdowns and stop ThreadFunc. | ||
| 78 | * | ||
| 79 | * @param paused - Are we pausing or resuming? | ||
| 80 | */ | ||
| 81 | void PauseCallback(bool paused); | ||
| 82 | |||
| 83 | enum class StreamState { | ||
| 84 | Filling, | ||
| 85 | Steady, | ||
| 86 | Draining, | ||
| 87 | }; | ||
| 88 | |||
| 89 | /// Core system | ||
| 90 | Core::System& core; | ||
| 91 | /// List of pointers to managed systems | ||
| 92 | std::list<System*> systems{}; | ||
| 93 | /// Main worker thread for generating command lists | ||
| 94 | std::jthread thread; | ||
| 95 | /// Mutex for the systems | ||
| 96 | std::mutex mutex1{}; | ||
| 97 | /// Mutex for adding/removing systems | ||
| 98 | std::mutex mutex2{}; | ||
| 99 | /// Is the system manager thread active? | ||
| 100 | std::atomic<bool> active{}; | ||
| 101 | /// Reference to the ADSP for communication | ||
| 102 | ADSP::ADSP& adsp; | ||
| 103 | /// AudioRenderer mailbox for communication | ||
| 104 | ADSP::AudioRenderer_Mailbox* mailbox{}; | ||
| 105 | /// Core timing event to signal main thread | ||
| 106 | std::shared_ptr<Core::Timing::EventType> thread_event; | ||
| 107 | /// Atomic for main thread to wait on | ||
| 108 | std::atomic<bool> update{}; | ||
| 109 | /// Current state of the streams | ||
| 110 | StreamState state{StreamState::Filling}; | ||
| 111 | }; | ||
| 112 | |||
| 113 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/upsampler/upsampler_info.cpp b/src/audio_core/renderer/upsampler/upsampler_info.cpp new file mode 100644 index 000000000..e3d2f7db0 --- /dev/null +++ b/src/audio_core/renderer/upsampler/upsampler_info.cpp | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/upsampler/upsampler_info.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer {} // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/upsampler/upsampler_info.h b/src/audio_core/renderer/upsampler/upsampler_info.h new file mode 100644 index 000000000..a43c15af3 --- /dev/null +++ b/src/audio_core/renderer/upsampler/upsampler_info.h | |||
| @@ -0,0 +1,35 @@ | |||
| 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 | |||
| 8 | #include "audio_core/common/common.h" | ||
| 9 | #include "audio_core/renderer/upsampler/upsampler_state.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | class UpsamplerManager; | ||
| 14 | |||
| 15 | /** | ||
| 16 | * Manages information needed to upsample a mix buffer. | ||
| 17 | */ | ||
| 18 | struct UpsamplerInfo { | ||
| 19 | /// States used by the AudioRenderer across calls. | ||
| 20 | std::array<UpsamplerState, MaxChannels> states{}; | ||
| 21 | /// Pointer to the manager | ||
| 22 | UpsamplerManager* manager{}; | ||
| 23 | /// Pointer to the samples to be upsampled | ||
| 24 | CpuAddr samples_pos{}; | ||
| 25 | /// Target number of samples to upsample to | ||
| 26 | u32 sample_count{}; | ||
| 27 | /// Number of channels to upsample | ||
| 28 | u32 input_count{}; | ||
| 29 | /// Is this upsampler enabled? | ||
| 30 | bool enabled{}; | ||
| 31 | /// Mix buffer indexes to be upsampled | ||
| 32 | std::array<s16, MaxChannels> inputs{}; | ||
| 33 | }; | ||
| 34 | |||
| 35 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.cpp b/src/audio_core/renderer/upsampler/upsampler_manager.cpp new file mode 100644 index 000000000..4c76a5066 --- /dev/null +++ b/src/audio_core/renderer/upsampler/upsampler_manager.cpp | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/upsampler/upsampler_manager.h" | ||
| 5 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | UpsamplerManager::UpsamplerManager(const u32 count_, std::span<UpsamplerInfo> infos_, | ||
| 9 | std::span<s32> workbuffer_) | ||
| 10 | : count{count_}, upsampler_infos{infos_}, workbuffer{workbuffer_} {} | ||
| 11 | |||
| 12 | UpsamplerInfo* UpsamplerManager::Allocate() { | ||
| 13 | std::scoped_lock l{lock}; | ||
| 14 | |||
| 15 | if (count == 0) { | ||
| 16 | return nullptr; | ||
| 17 | } | ||
| 18 | |||
| 19 | u32 free_index{0}; | ||
| 20 | for (auto& upsampler : upsampler_infos) { | ||
| 21 | if (!upsampler.enabled) { | ||
| 22 | break; | ||
| 23 | } | ||
| 24 | free_index++; | ||
| 25 | } | ||
| 26 | |||
| 27 | if (free_index >= count) { | ||
| 28 | return nullptr; | ||
| 29 | } | ||
| 30 | |||
| 31 | auto& upsampler{upsampler_infos[free_index]}; | ||
| 32 | upsampler.manager = this; | ||
| 33 | upsampler.sample_count = TargetSampleCount; | ||
| 34 | upsampler.samples_pos = CpuAddr(&workbuffer[upsampler.sample_count * MaxChannels]); | ||
| 35 | upsampler.enabled = true; | ||
| 36 | return &upsampler; | ||
| 37 | } | ||
| 38 | |||
| 39 | void UpsamplerManager::Free(UpsamplerInfo* info) { | ||
| 40 | std::scoped_lock l{lock}; | ||
| 41 | info->enabled = false; | ||
| 42 | } | ||
| 43 | |||
| 44 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.h b/src/audio_core/renderer/upsampler/upsampler_manager.h new file mode 100644 index 000000000..70cd42b08 --- /dev/null +++ b/src/audio_core/renderer/upsampler/upsampler_manager.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 <mutex> | ||
| 7 | #include <span> | ||
| 8 | |||
| 9 | #include "audio_core/renderer/upsampler/upsampler_info.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | /** | ||
| 14 | * Manages and has utility functions for upsampler infos. | ||
| 15 | */ | ||
| 16 | class UpsamplerManager { | ||
| 17 | public: | ||
| 18 | UpsamplerManager(u32 count, std::span<UpsamplerInfo> infos, std::span<s32> workbuffer); | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Allocate a new UpsamplerInfo. | ||
| 22 | * | ||
| 23 | * @return The allocated upsampler, may be nullptr if alloc failed. | ||
| 24 | */ | ||
| 25 | UpsamplerInfo* Allocate(); | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Free the given upsampler. | ||
| 29 | * | ||
| 30 | * @param The upsampler to be freed. | ||
| 31 | */ | ||
| 32 | void Free(UpsamplerInfo* info); | ||
| 33 | |||
| 34 | private: | ||
| 35 | /// Maximum number of upsamplers in the buffer | ||
| 36 | const u32 count; | ||
| 37 | /// Upsamplers buffer | ||
| 38 | std::span<UpsamplerInfo> upsampler_infos; | ||
| 39 | /// Workbuffer for upsampling samples | ||
| 40 | std::span<s32> workbuffer; | ||
| 41 | /// Lock for allocate/free | ||
| 42 | std::mutex lock{}; | ||
| 43 | }; | ||
| 44 | |||
| 45 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/upsampler/upsampler_state.h b/src/audio_core/renderer/upsampler/upsampler_state.h new file mode 100644 index 000000000..28cebe200 --- /dev/null +++ b/src/audio_core/renderer/upsampler/upsampler_state.h | |||
| @@ -0,0 +1,40 @@ | |||
| 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 | |||
| 8 | #include "common/common_types.h" | ||
| 9 | #include "common/fixed_point.h" | ||
| 10 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | /** | ||
| 13 | * Upsampling state used by the AudioRenderer across calls. | ||
| 14 | */ | ||
| 15 | struct UpsamplerState { | ||
| 16 | static constexpr u16 HistorySize = 20; | ||
| 17 | |||
| 18 | /// Source data to target data ratio. E.g 48'000/32'000 = 1.5 | ||
| 19 | Common::FixedPoint<16, 16> ratio; | ||
| 20 | /// Sample history | ||
| 21 | std::array<Common::FixedPoint<24, 8>, HistorySize> history; | ||
| 22 | /// Size of the sinc coefficient window | ||
| 23 | u16 window_size; | ||
| 24 | /// Read index for the history | ||
| 25 | u16 history_output_index; | ||
| 26 | /// Write index for the history | ||
| 27 | u16 history_input_index; | ||
| 28 | /// Start offset within the history, fixed to 0 | ||
| 29 | u16 history_start_index; | ||
| 30 | /// Ebd offset within the history, fixed to HistorySize | ||
| 31 | u16 history_end_index; | ||
| 32 | /// Is this state initialized? | ||
| 33 | bool initialized; | ||
| 34 | /// Index of the current sample. | ||
| 35 | /// E.g 16K -> 48K has a ratio of 3, so this will be 0-2. | ||
| 36 | /// See the Upsample command in the AudioRenderer for more information. | ||
| 37 | u8 sample_index; | ||
| 38 | }; | ||
| 39 | |||
| 40 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/voice/voice_channel_resource.h b/src/audio_core/renderer/voice/voice_channel_resource.h new file mode 100644 index 000000000..26ab4ccce --- /dev/null +++ b/src/audio_core/renderer/voice/voice_channel_resource.h | |||
| @@ -0,0 +1,38 @@ | |||
| 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 | |||
| 8 | #include "audio_core/common/common.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | /** | ||
| 13 | * Represents one channel for mixing a voice. | ||
| 14 | */ | ||
| 15 | class VoiceChannelResource { | ||
| 16 | public: | ||
| 17 | struct InParameter { | ||
| 18 | /* 0x00 */ u32 id; | ||
| 19 | /* 0x04 */ std::array<f32, MaxMixBuffers> mix_volumes; | ||
| 20 | /* 0x64 */ bool in_use; | ||
| 21 | /* 0x65 */ char unk65[0xB]; | ||
| 22 | }; | ||
| 23 | static_assert(sizeof(InParameter) == 0x70, | ||
| 24 | "VoiceChannelResource::InParameter has the wrong size!"); | ||
| 25 | |||
| 26 | explicit VoiceChannelResource(u32 id_) : id{id_} {} | ||
| 27 | |||
| 28 | /// Current volume for each mix buffer | ||
| 29 | std::array<f32, MaxMixBuffers> mix_volumes{}; | ||
| 30 | /// Previous volume for each mix buffer | ||
| 31 | std::array<f32, MaxMixBuffers> prev_mix_volumes{}; | ||
| 32 | /// Id of this resource | ||
| 33 | const u32 id; | ||
| 34 | /// Is this resource in use? | ||
| 35 | bool in_use{}; | ||
| 36 | }; | ||
| 37 | |||
| 38 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/voice/voice_context.cpp b/src/audio_core/renderer/voice/voice_context.cpp new file mode 100644 index 000000000..eafb51b01 --- /dev/null +++ b/src/audio_core/renderer/voice/voice_context.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 <ranges> | ||
| 5 | |||
| 6 | #include "audio_core/renderer/voice/voice_context.h" | ||
| 7 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | VoiceState& VoiceContext::GetDspSharedState(const u32 index) { | ||
| 11 | if (index >= dsp_states.size()) { | ||
| 12 | LOG_ERROR(Service_Audio, "Invalid voice dsp state index {:04X}", index); | ||
| 13 | } | ||
| 14 | return dsp_states[index]; | ||
| 15 | } | ||
| 16 | |||
| 17 | VoiceChannelResource& VoiceContext::GetChannelResource(const u32 index) { | ||
| 18 | if (index >= channel_resources.size()) { | ||
| 19 | LOG_ERROR(Service_Audio, "Invalid voice channel resource index {:04X}", index); | ||
| 20 | } | ||
| 21 | return channel_resources[index]; | ||
| 22 | } | ||
| 23 | |||
| 24 | void VoiceContext::Initialize(std::span<VoiceInfo*> sorted_voice_infos_, | ||
| 25 | std::span<VoiceInfo> voice_infos_, | ||
| 26 | std::span<VoiceChannelResource> voice_channel_resources_, | ||
| 27 | std::span<VoiceState> cpu_states_, std::span<VoiceState> dsp_states_, | ||
| 28 | const u32 voice_count_) { | ||
| 29 | sorted_voice_info = sorted_voice_infos_; | ||
| 30 | voices = voice_infos_; | ||
| 31 | channel_resources = voice_channel_resources_; | ||
| 32 | cpu_states = cpu_states_; | ||
| 33 | dsp_states = dsp_states_; | ||
| 34 | voice_count = voice_count_; | ||
| 35 | active_count = 0; | ||
| 36 | } | ||
| 37 | |||
| 38 | VoiceInfo* VoiceContext::GetSortedInfo(const u32 index) { | ||
| 39 | if (index >= sorted_voice_info.size()) { | ||
| 40 | LOG_ERROR(Service_Audio, "Invalid voice sorted info index {:04X}", index); | ||
| 41 | } | ||
| 42 | return sorted_voice_info[index]; | ||
| 43 | } | ||
| 44 | |||
| 45 | VoiceInfo& VoiceContext::GetInfo(const u32 index) { | ||
| 46 | if (index >= voices.size()) { | ||
| 47 | LOG_ERROR(Service_Audio, "Invalid voice info index {:04X}", index); | ||
| 48 | } | ||
| 49 | return voices[index]; | ||
| 50 | } | ||
| 51 | |||
| 52 | VoiceState& VoiceContext::GetState(const u32 index) { | ||
| 53 | if (index >= cpu_states.size()) { | ||
| 54 | LOG_ERROR(Service_Audio, "Invalid voice cpu state index {:04X}", index); | ||
| 55 | } | ||
| 56 | return cpu_states[index]; | ||
| 57 | } | ||
| 58 | |||
| 59 | u32 VoiceContext::GetCount() const { | ||
| 60 | return voice_count; | ||
| 61 | } | ||
| 62 | |||
| 63 | u32 VoiceContext::GetActiveCount() const { | ||
| 64 | return active_count; | ||
| 65 | } | ||
| 66 | |||
| 67 | void VoiceContext::SetActiveCount(const u32 active_count_) { | ||
| 68 | active_count = active_count_; | ||
| 69 | } | ||
| 70 | |||
| 71 | void VoiceContext::SortInfo() { | ||
| 72 | for (u32 i = 0; i < voice_count; i++) { | ||
| 73 | sorted_voice_info[i] = &voices[i]; | ||
| 74 | } | ||
| 75 | |||
| 76 | std::ranges::sort(sorted_voice_info, [](const VoiceInfo* a, const VoiceInfo* b) { | ||
| 77 | return a->priority != b->priority ? a->priority < b->priority | ||
| 78 | : a->sort_order < b->sort_order; | ||
| 79 | }); | ||
| 80 | } | ||
| 81 | |||
| 82 | void VoiceContext::UpdateStateByDspShared() { | ||
| 83 | std::memcpy(cpu_states.data(), dsp_states.data(), voice_count * sizeof(VoiceState)); | ||
| 84 | } | ||
| 85 | |||
| 86 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/voice/voice_context.h b/src/audio_core/renderer/voice/voice_context.h new file mode 100644 index 000000000..43b677154 --- /dev/null +++ b/src/audio_core/renderer/voice/voice_context.h | |||
| @@ -0,0 +1,126 @@ | |||
| 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/voice/voice_channel_resource.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 | /** | ||
| 15 | * Contains all voices, with utility functions for managing them. | ||
| 16 | */ | ||
| 17 | class VoiceContext { | ||
| 18 | public: | ||
| 19 | /** | ||
| 20 | * Get the AudioRenderer state for a given index | ||
| 21 | * | ||
| 22 | * @param index - State index to get. | ||
| 23 | * @return The requested voice state. | ||
| 24 | */ | ||
| 25 | VoiceState& GetDspSharedState(u32 index); | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Get the channel resource for a given index | ||
| 29 | * | ||
| 30 | * @param index - Resource index to get. | ||
| 31 | * @return The requested voice resource. | ||
| 32 | */ | ||
| 33 | VoiceChannelResource& GetChannelResource(u32 index); | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Initialize the voice context. | ||
| 37 | * | ||
| 38 | * @param sorted_voice_infos - Workbuffer for the sorted voices. | ||
| 39 | * @param voice_infos - Workbuffer for the voices. | ||
| 40 | * @param voice_channel_resources - Workbuffer for the voice channel resources. | ||
| 41 | * @param cpu_states - Workbuffer for the host-side voice states. | ||
| 42 | * @param dsp_states - Workbuffer for the AudioRenderer-side voice states. | ||
| 43 | * @param voice_count - The number of voices in each workbuffer. | ||
| 44 | */ | ||
| 45 | void Initialize(std::span<VoiceInfo*> sorted_voice_infos, std::span<VoiceInfo> voice_infos, | ||
| 46 | std::span<VoiceChannelResource> voice_channel_resources, | ||
| 47 | std::span<VoiceState> cpu_states, std::span<VoiceState> dsp_states, | ||
| 48 | u32 voice_count); | ||
| 49 | |||
| 50 | /** | ||
| 51 | * Get a sorted voice with the given index. | ||
| 52 | * | ||
| 53 | * @param index - The sorted voice index to get. | ||
| 54 | * @return The sorted voice. | ||
| 55 | */ | ||
| 56 | VoiceInfo* GetSortedInfo(u32 index); | ||
| 57 | |||
| 58 | /** | ||
| 59 | * Get a voice with the given index. | ||
| 60 | * | ||
| 61 | * @param index - The voice index to get. | ||
| 62 | * @return The voice. | ||
| 63 | */ | ||
| 64 | VoiceInfo& GetInfo(u32 index); | ||
| 65 | |||
| 66 | /** | ||
| 67 | * Get a host voice state with the given index. | ||
| 68 | * | ||
| 69 | * @param index - The host voice state index to get. | ||
| 70 | * @return The voice state. | ||
| 71 | */ | ||
| 72 | VoiceState& GetState(u32 index); | ||
| 73 | |||
| 74 | /** | ||
| 75 | * Get the maximum number of voices. | ||
| 76 | * Not all voices in the buffers may be in use, see GetActiveCount. | ||
| 77 | * | ||
| 78 | * @return The maximum number of voices. | ||
| 79 | */ | ||
| 80 | u32 GetCount() const; | ||
| 81 | |||
| 82 | /** | ||
| 83 | * Get the number of active voices. | ||
| 84 | * Can be less than or equal to the maximum number of voices. | ||
| 85 | * | ||
| 86 | * @return The number of active voices. | ||
| 87 | */ | ||
| 88 | u32 GetActiveCount() const; | ||
| 89 | |||
| 90 | /** | ||
| 91 | * Set the number of active voices. | ||
| 92 | * Can be less than or equal to the maximum number of voices. | ||
| 93 | * | ||
| 94 | * @param active_count - The new number of active voices. | ||
| 95 | */ | ||
| 96 | void SetActiveCount(u32 active_count); | ||
| 97 | |||
| 98 | /** | ||
| 99 | * Sort all voices. Results are available via GetSortedInfo. | ||
| 100 | * Voices are sorted descendingly, according to priority, and then sort order. | ||
| 101 | */ | ||
| 102 | void SortInfo(); | ||
| 103 | |||
| 104 | /** | ||
| 105 | * Update all voice states, copying AudioRenderer-side states to host-side states. | ||
| 106 | */ | ||
| 107 | void UpdateStateByDspShared(); | ||
| 108 | |||
| 109 | private: | ||
| 110 | /// Sorted voices | ||
| 111 | std::span<VoiceInfo*> sorted_voice_info{}; | ||
| 112 | /// Voices | ||
| 113 | std::span<VoiceInfo> voices{}; | ||
| 114 | /// Channel resources | ||
| 115 | std::span<VoiceChannelResource> channel_resources{}; | ||
| 116 | /// Host-side voice states | ||
| 117 | std::span<VoiceState> cpu_states{}; | ||
| 118 | /// AudioRenderer-side voice states | ||
| 119 | std::span<VoiceState> dsp_states{}; | ||
| 120 | /// Maximum number of voices | ||
| 121 | u32 voice_count{}; | ||
| 122 | /// Number of active voices | ||
| 123 | u32 active_count{}; | ||
| 124 | }; | ||
| 125 | |||
| 126 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/voice/voice_info.cpp b/src/audio_core/renderer/voice/voice_info.cpp new file mode 100644 index 000000000..1849eeb57 --- /dev/null +++ b/src/audio_core/renderer/voice/voice_info.cpp | |||
| @@ -0,0 +1,408 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/renderer/memory/pool_mapper.h" | ||
| 5 | #include "audio_core/renderer/voice/voice_context.h" | ||
| 6 | #include "audio_core/renderer/voice/voice_info.h" | ||
| 7 | #include "audio_core/renderer/voice/voice_state.h" | ||
| 8 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | VoiceInfo::VoiceInfo() { | ||
| 12 | Initialize(); | ||
| 13 | } | ||
| 14 | |||
| 15 | void VoiceInfo::Initialize() { | ||
| 16 | in_use = false; | ||
| 17 | is_new = false; | ||
| 18 | id = 0; | ||
| 19 | node_id = 0; | ||
| 20 | current_play_state = ServerPlayState::Stopped; | ||
| 21 | src_quality = SrcQuality::Medium; | ||
| 22 | priority = LowestVoicePriority; | ||
| 23 | sample_format = SampleFormat::Invalid; | ||
| 24 | sample_rate = 0; | ||
| 25 | channel_count = 0; | ||
| 26 | wave_buffer_count = 0; | ||
| 27 | wave_buffer_index = 0; | ||
| 28 | pitch = 0.0f; | ||
| 29 | volume = 0.0f; | ||
| 30 | prev_volume = 0.0f; | ||
| 31 | mix_id = UnusedMixId; | ||
| 32 | splitter_id = UnusedSplitterId; | ||
| 33 | biquads = {}; | ||
| 34 | biquad_initialized = {}; | ||
| 35 | voice_dropped = false; | ||
| 36 | data_unmapped = false; | ||
| 37 | buffer_unmapped = false; | ||
| 38 | flush_buffer_count = 0; | ||
| 39 | |||
| 40 | data_address.Setup(0, 0); | ||
| 41 | for (auto& wavebuffer : wavebuffers) { | ||
| 42 | wavebuffer.Initialize(); | ||
| 43 | } | ||
| 44 | } | ||
| 45 | |||
| 46 | bool VoiceInfo::ShouldUpdateParameters(const InParameter& params) const { | ||
| 47 | return data_address.GetCpuAddr() != params.src_data_address || | ||
| 48 | data_address.GetSize() != params.src_data_size || data_unmapped; | ||
| 49 | } | ||
| 50 | |||
| 51 | void VoiceInfo::UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params, | ||
| 52 | const PoolMapper& pool_mapper, const BehaviorInfo& behavior) { | ||
| 53 | in_use = params.in_use; | ||
| 54 | id = params.id; | ||
| 55 | node_id = params.node_id; | ||
| 56 | UpdatePlayState(params.play_state); | ||
| 57 | UpdateSrcQuality(params.src_quality); | ||
| 58 | priority = params.priority; | ||
| 59 | sort_order = params.sort_order; | ||
| 60 | sample_rate = params.sample_rate; | ||
| 61 | sample_format = params.sample_format; | ||
| 62 | channel_count = static_cast<s8>(params.channel_count); | ||
| 63 | pitch = params.pitch; | ||
| 64 | volume = params.volume; | ||
| 65 | biquads = params.biquads; | ||
| 66 | wave_buffer_count = params.wave_buffer_count; | ||
| 67 | wave_buffer_index = params.wave_buffer_index; | ||
| 68 | |||
| 69 | if (behavior.IsFlushVoiceWaveBuffersSupported()) { | ||
| 70 | flush_buffer_count += params.flush_buffer_count; | ||
| 71 | } | ||
| 72 | |||
| 73 | mix_id = params.mix_id; | ||
| 74 | |||
| 75 | if (behavior.IsSplitterSupported()) { | ||
| 76 | splitter_id = params.splitter_id; | ||
| 77 | } else { | ||
| 78 | splitter_id = UnusedSplitterId; | ||
| 79 | } | ||
| 80 | |||
| 81 | channel_resource_ids = params.channel_resource_ids; | ||
| 82 | |||
| 83 | flags &= u16(~0b11); | ||
| 84 | if (behavior.IsVoicePlayedSampleCountResetAtLoopPointSupported()) { | ||
| 85 | flags |= u16(params.flags.IsVoicePlayedSampleCountResetAtLoopPointSupported); | ||
| 86 | } | ||
| 87 | |||
| 88 | if (behavior.IsVoicePitchAndSrcSkippedSupported()) { | ||
| 89 | flags |= u16(params.flags.IsVoicePitchAndSrcSkippedSupported); | ||
| 90 | } | ||
| 91 | |||
| 92 | if (params.clear_voice_drop) { | ||
| 93 | voice_dropped = false; | ||
| 94 | } | ||
| 95 | |||
| 96 | if (ShouldUpdateParameters(params)) { | ||
| 97 | data_unmapped = !pool_mapper.TryAttachBuffer(error_info, data_address, | ||
| 98 | params.src_data_address, params.src_data_size); | ||
| 99 | } else { | ||
| 100 | error_info.error_code = ResultSuccess; | ||
| 101 | error_info.address = CpuAddr(0); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | void VoiceInfo::UpdatePlayState(const PlayState state) { | ||
| 106 | last_play_state = current_play_state; | ||
| 107 | |||
| 108 | switch (state) { | ||
| 109 | case PlayState::Started: | ||
| 110 | current_play_state = ServerPlayState::Started; | ||
| 111 | break; | ||
| 112 | case PlayState::Stopped: | ||
| 113 | if (current_play_state != ServerPlayState::Stopped) { | ||
| 114 | current_play_state = ServerPlayState::RequestStop; | ||
| 115 | } | ||
| 116 | break; | ||
| 117 | case PlayState::Paused: | ||
| 118 | current_play_state = ServerPlayState::Paused; | ||
| 119 | break; | ||
| 120 | default: | ||
| 121 | LOG_ERROR(Service_Audio, "Invalid input play state {}", static_cast<u32>(state)); | ||
| 122 | break; | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | void VoiceInfo::UpdateSrcQuality(const SrcQuality quality) { | ||
| 127 | switch (quality) { | ||
| 128 | case SrcQuality::Medium: | ||
| 129 | src_quality = quality; | ||
| 130 | break; | ||
| 131 | case SrcQuality::High: | ||
| 132 | src_quality = quality; | ||
| 133 | break; | ||
| 134 | case SrcQuality::Low: | ||
| 135 | src_quality = quality; | ||
| 136 | break; | ||
| 137 | default: | ||
| 138 | LOG_ERROR(Service_Audio, "Invalid input src quality {}", static_cast<u32>(quality)); | ||
| 139 | break; | ||
| 140 | } | ||
| 141 | } | ||
| 142 | |||
| 143 | void VoiceInfo::UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos, | ||
| 144 | [[maybe_unused]] u32 error_count, const InParameter& params, | ||
| 145 | std::span<VoiceState*> voice_states, | ||
| 146 | const PoolMapper& pool_mapper, const BehaviorInfo& behavior) { | ||
| 147 | if (params.is_new) { | ||
| 148 | for (size_t i = 0; i < wavebuffers.size(); i++) { | ||
| 149 | wavebuffers[i].Initialize(); | ||
| 150 | } | ||
| 151 | |||
| 152 | for (s8 channel = 0; channel < static_cast<s8>(params.channel_count); channel++) { | ||
| 153 | voice_states[channel]->wave_buffer_valid.fill(false); | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | for (u32 i = 0; i < MaxWaveBuffers; i++) { | ||
| 158 | UpdateWaveBuffer(error_infos[i], wavebuffers[i], params.wave_buffer_internal[i], | ||
| 159 | params.sample_format, voice_states[0]->wave_buffer_valid[i], pool_mapper, | ||
| 160 | behavior); | ||
| 161 | } | ||
| 162 | } | ||
| 163 | |||
| 164 | void VoiceInfo::UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, | ||
| 165 | WaveBuffer& wave_buffer, | ||
| 166 | const WaveBufferInternal& wave_buffer_internal, | ||
| 167 | const SampleFormat sample_format_, const bool valid, | ||
| 168 | const PoolMapper& pool_mapper, const BehaviorInfo& behavior) { | ||
| 169 | if (!valid && wave_buffer.sent_to_DSP && wave_buffer.buffer_address.GetCpuAddr() != 0) { | ||
| 170 | pool_mapper.ForceUnmapPointer(wave_buffer.buffer_address); | ||
| 171 | wave_buffer.buffer_address.Setup(0, 0); | ||
| 172 | } | ||
| 173 | |||
| 174 | if (!ShouldUpdateWaveBuffer(wave_buffer_internal)) { | ||
| 175 | return; | ||
| 176 | } | ||
| 177 | |||
| 178 | switch (sample_format_) { | ||
| 179 | case SampleFormat::PcmInt16: { | ||
| 180 | constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmInt16)}; | ||
| 181 | if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size || | ||
| 182 | wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) { | ||
| 183 | LOG_ERROR(Service_Audio, "Invalid PCM16 start/end wavebuffer sizes!"); | ||
| 184 | error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 185 | error_info[0].address = wave_buffer_internal.address; | ||
| 186 | return; | ||
| 187 | } | ||
| 188 | } break; | ||
| 189 | |||
| 190 | case SampleFormat::PcmFloat: { | ||
| 191 | constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmFloat)}; | ||
| 192 | if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size || | ||
| 193 | wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) { | ||
| 194 | LOG_ERROR(Service_Audio, "Invalid PCMFloat start/end wavebuffer sizes!"); | ||
| 195 | error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 196 | error_info[0].address = wave_buffer_internal.address; | ||
| 197 | return; | ||
| 198 | } | ||
| 199 | } break; | ||
| 200 | |||
| 201 | case SampleFormat::Adpcm: { | ||
| 202 | const auto start_frame{wave_buffer_internal.start_offset / 14}; | ||
| 203 | auto start_extra{wave_buffer_internal.start_offset % 14 == 0 | ||
| 204 | ? 0 | ||
| 205 | : (wave_buffer_internal.start_offset % 14) / 2 + 1 + | ||
| 206 | ((wave_buffer_internal.start_offset % 14) % 2)}; | ||
| 207 | const auto start{start_frame * 8 + start_extra}; | ||
| 208 | |||
| 209 | const auto end_frame{wave_buffer_internal.end_offset / 14}; | ||
| 210 | const auto end_extra{wave_buffer_internal.end_offset % 14 == 0 | ||
| 211 | ? 0 | ||
| 212 | : (wave_buffer_internal.end_offset % 14) / 2 + 1 + | ||
| 213 | ((wave_buffer_internal.end_offset % 14) % 2)}; | ||
| 214 | const auto end{end_frame * 8 + end_extra}; | ||
| 215 | |||
| 216 | if (start > static_cast<s64>(wave_buffer_internal.size) || | ||
| 217 | end > static_cast<s64>(wave_buffer_internal.size)) { | ||
| 218 | LOG_ERROR(Service_Audio, "Invalid ADPCM start/end wavebuffer sizes!"); | ||
| 219 | error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 220 | error_info[0].address = wave_buffer_internal.address; | ||
| 221 | return; | ||
| 222 | } | ||
| 223 | } break; | ||
| 224 | |||
| 225 | default: | ||
| 226 | break; | ||
| 227 | } | ||
| 228 | |||
| 229 | if (wave_buffer_internal.start_offset < 0 || wave_buffer_internal.end_offset < 0) { | ||
| 230 | LOG_ERROR(Service_Audio, "Invalid input start/end wavebuffer sizes!"); | ||
| 231 | error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA; | ||
| 232 | error_info[0].address = wave_buffer_internal.address; | ||
| 233 | return; | ||
| 234 | } | ||
| 235 | |||
| 236 | wave_buffer.start_offset = wave_buffer_internal.start_offset; | ||
| 237 | wave_buffer.end_offset = wave_buffer_internal.end_offset; | ||
| 238 | wave_buffer.loop = wave_buffer_internal.loop; | ||
| 239 | wave_buffer.stream_ended = wave_buffer_internal.stream_ended; | ||
| 240 | wave_buffer.sent_to_DSP = false; | ||
| 241 | wave_buffer.loop_start_offset = wave_buffer_internal.loop_start; | ||
| 242 | wave_buffer.loop_end_offset = wave_buffer_internal.loop_end; | ||
| 243 | wave_buffer.loop_count = wave_buffer_internal.loop_count; | ||
| 244 | |||
| 245 | buffer_unmapped = | ||
| 246 | !pool_mapper.TryAttachBuffer(error_info[0], wave_buffer.buffer_address, | ||
| 247 | wave_buffer_internal.address, wave_buffer_internal.size); | ||
| 248 | |||
| 249 | if (sample_format_ == SampleFormat::Adpcm && behavior.IsAdpcmLoopContextBugFixed() && | ||
| 250 | wave_buffer_internal.context_address != 0) { | ||
| 251 | buffer_unmapped = !pool_mapper.TryAttachBuffer(error_info[1], wave_buffer.context_address, | ||
| 252 | wave_buffer_internal.context_address, | ||
| 253 | wave_buffer_internal.context_size) || | ||
| 254 | data_unmapped; | ||
| 255 | } else { | ||
| 256 | wave_buffer.context_address.Setup(0, 0); | ||
| 257 | } | ||
| 258 | } | ||
| 259 | |||
| 260 | bool VoiceInfo::ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const { | ||
| 261 | return !wave_buffer_internal.sent_to_DSP || buffer_unmapped; | ||
| 262 | } | ||
| 263 | |||
| 264 | void VoiceInfo::WriteOutStatus(OutStatus& out_status, const InParameter& params, | ||
| 265 | std::span<VoiceState*> voice_states) { | ||
| 266 | if (params.is_new) { | ||
| 267 | is_new = true; | ||
| 268 | } | ||
| 269 | |||
| 270 | if (params.is_new || is_new) { | ||
| 271 | out_status.played_sample_count = 0; | ||
| 272 | out_status.wave_buffers_consumed = 0; | ||
| 273 | out_status.voice_dropped = false; | ||
| 274 | } else { | ||
| 275 | out_status.played_sample_count = voice_states[0]->played_sample_count; | ||
| 276 | out_status.wave_buffers_consumed = voice_states[0]->wave_buffers_consumed; | ||
| 277 | out_status.voice_dropped = voice_dropped; | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | bool VoiceInfo::ShouldSkip() const { | ||
| 282 | return !in_use || wave_buffer_count == 0 || data_unmapped || buffer_unmapped || voice_dropped; | ||
| 283 | } | ||
| 284 | |||
| 285 | bool VoiceInfo::HasAnyConnection() const { | ||
| 286 | return mix_id != UnusedMixId || splitter_id != UnusedSplitterId; | ||
| 287 | } | ||
| 288 | |||
| 289 | void VoiceInfo::FlushWaveBuffers(const u32 flush_count, std::span<VoiceState*> voice_states, | ||
| 290 | const s8 channel_count_) { | ||
| 291 | auto wave_index{wave_buffer_index}; | ||
| 292 | |||
| 293 | for (size_t i = 0; i < flush_count; i++) { | ||
| 294 | wavebuffers[wave_index].sent_to_DSP = true; | ||
| 295 | |||
| 296 | for (s8 j = 0; j < channel_count_; j++) { | ||
| 297 | auto voice_state{voice_states[j]}; | ||
| 298 | if (voice_state->wave_buffer_index == wave_index) { | ||
| 299 | voice_state->wave_buffer_index = | ||
| 300 | (voice_state->wave_buffer_index + 1) % MaxWaveBuffers; | ||
| 301 | voice_state->wave_buffers_consumed++; | ||
| 302 | } | ||
| 303 | voice_state->wave_buffer_valid[wave_index] = false; | ||
| 304 | } | ||
| 305 | |||
| 306 | wave_index = (wave_index + 1) % MaxWaveBuffers; | ||
| 307 | } | ||
| 308 | } | ||
| 309 | |||
| 310 | bool VoiceInfo::UpdateParametersForCommandGeneration(std::span<VoiceState*> voice_states) { | ||
| 311 | if (flush_buffer_count > 0) { | ||
| 312 | FlushWaveBuffers(flush_buffer_count, voice_states, channel_count); | ||
| 313 | flush_buffer_count = 0; | ||
| 314 | } | ||
| 315 | |||
| 316 | switch (current_play_state) { | ||
| 317 | case ServerPlayState::Started: | ||
| 318 | for (u32 i = 0; i < MaxWaveBuffers; i++) { | ||
| 319 | if (!wavebuffers[i].sent_to_DSP) { | ||
| 320 | for (s8 channel = 0; channel < channel_count; channel++) { | ||
| 321 | voice_states[channel]->wave_buffer_valid[i] = true; | ||
| 322 | } | ||
| 323 | wavebuffers[i].sent_to_DSP = true; | ||
| 324 | } | ||
| 325 | } | ||
| 326 | |||
| 327 | was_playing = false; | ||
| 328 | |||
| 329 | for (u32 i = 0; i < MaxWaveBuffers; i++) { | ||
| 330 | if (voice_states[0]->wave_buffer_valid[i]) { | ||
| 331 | return true; | ||
| 332 | } | ||
| 333 | } | ||
| 334 | break; | ||
| 335 | |||
| 336 | case ServerPlayState::Stopped: | ||
| 337 | case ServerPlayState::Paused: | ||
| 338 | for (auto& wavebuffer : wavebuffers) { | ||
| 339 | if (!wavebuffer.sent_to_DSP) { | ||
| 340 | wavebuffer.buffer_address.GetReference(true); | ||
| 341 | wavebuffer.context_address.GetReference(true); | ||
| 342 | } | ||
| 343 | } | ||
| 344 | |||
| 345 | if (sample_format == SampleFormat::Adpcm && data_address.GetCpuAddr() != 0) { | ||
| 346 | data_address.GetReference(true); | ||
| 347 | } | ||
| 348 | |||
| 349 | was_playing = last_play_state == ServerPlayState::Started; | ||
| 350 | break; | ||
| 351 | |||
| 352 | case ServerPlayState::RequestStop: | ||
| 353 | for (u32 i = 0; i < MaxWaveBuffers; i++) { | ||
| 354 | wavebuffers[i].sent_to_DSP = true; | ||
| 355 | |||
| 356 | for (s8 channel = 0; channel < channel_count; channel++) { | ||
| 357 | if (voice_states[channel]->wave_buffer_valid[i]) { | ||
| 358 | voice_states[channel]->wave_buffer_index = | ||
| 359 | (voice_states[channel]->wave_buffer_index + 1) % MaxWaveBuffers; | ||
| 360 | voice_states[channel]->wave_buffers_consumed++; | ||
| 361 | } | ||
| 362 | voice_states[channel]->wave_buffer_valid[i] = false; | ||
| 363 | } | ||
| 364 | } | ||
| 365 | |||
| 366 | for (s8 channel = 0; channel < channel_count; channel++) { | ||
| 367 | voice_states[channel]->offset = 0; | ||
| 368 | voice_states[channel]->played_sample_count = 0; | ||
| 369 | voice_states[channel]->adpcm_context = {}; | ||
| 370 | voice_states[channel]->sample_history.fill(0); | ||
| 371 | voice_states[channel]->fraction = 0; | ||
| 372 | } | ||
| 373 | |||
| 374 | current_play_state = ServerPlayState::Stopped; | ||
| 375 | was_playing = last_play_state == ServerPlayState::Started; | ||
| 376 | break; | ||
| 377 | } | ||
| 378 | |||
| 379 | return was_playing; | ||
| 380 | } | ||
| 381 | |||
| 382 | bool VoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) { | ||
| 383 | std::array<VoiceState*, MaxChannels> voice_states{}; | ||
| 384 | |||
| 385 | if (is_new) { | ||
| 386 | ResetResources(voice_context); | ||
| 387 | prev_volume = volume; | ||
| 388 | is_new = false; | ||
| 389 | } | ||
| 390 | |||
| 391 | for (s8 channel = 0; channel < channel_count; channel++) { | ||
| 392 | voice_states[channel] = &voice_context.GetDspSharedState(channel_resource_ids[channel]); | ||
| 393 | } | ||
| 394 | |||
| 395 | return UpdateParametersForCommandGeneration(voice_states); | ||
| 396 | } | ||
| 397 | |||
| 398 | void VoiceInfo::ResetResources(VoiceContext& voice_context) const { | ||
| 399 | for (s8 channel = 0; channel < channel_count; channel++) { | ||
| 400 | auto& state{voice_context.GetDspSharedState(channel_resource_ids[channel])}; | ||
| 401 | state = {}; | ||
| 402 | |||
| 403 | auto& channel_resource{voice_context.GetChannelResource(channel_resource_ids[channel])}; | ||
| 404 | channel_resource.prev_mix_volumes = channel_resource.mix_volumes; | ||
| 405 | } | ||
| 406 | } | ||
| 407 | |||
| 408 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h new file mode 100644 index 000000000..896723e0c --- /dev/null +++ b/src/audio_core/renderer/voice/voice_info.h | |||
| @@ -0,0 +1,378 @@ | |||
| 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 <bitset> | ||
| 8 | |||
| 9 | #include "audio_core/common/common.h" | ||
| 10 | #include "audio_core/common/wave_buffer.h" | ||
| 11 | #include "audio_core/renderer/behavior/behavior_info.h" | ||
| 12 | #include "audio_core/renderer/memory/address_info.h" | ||
| 13 | #include "common/common_types.h" | ||
| 14 | |||
| 15 | namespace AudioCore::AudioRenderer { | ||
| 16 | class PoolMapper; | ||
| 17 | class VoiceContext; | ||
| 18 | struct VoiceState; | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Represents one voice. Voices are essentially noises, and they can be further mixed and have | ||
| 22 | * effects applied to them, but voices are the basis of all sounds. | ||
| 23 | */ | ||
| 24 | class VoiceInfo { | ||
| 25 | public: | ||
| 26 | enum class ServerPlayState { | ||
| 27 | Started, | ||
| 28 | Stopped, | ||
| 29 | RequestStop, | ||
| 30 | Paused, | ||
| 31 | }; | ||
| 32 | |||
| 33 | struct Flags { | ||
| 34 | u8 IsVoicePlayedSampleCountResetAtLoopPointSupported : 1; | ||
| 35 | u8 IsVoicePitchAndSrcSkippedSupported : 1; | ||
| 36 | }; | ||
| 37 | |||
| 38 | /** | ||
| 39 | * A wavebuffer contains information on the data source buffers. | ||
| 40 | */ | ||
| 41 | struct WaveBuffer { | ||
| 42 | void Copy(WaveBufferVersion1& other) { | ||
| 43 | other.buffer = buffer_address.GetReference(true); | ||
| 44 | other.buffer_size = buffer_address.GetSize(); | ||
| 45 | other.start_offset = start_offset; | ||
| 46 | other.end_offset = end_offset; | ||
| 47 | other.loop = loop; | ||
| 48 | other.stream_ended = stream_ended; | ||
| 49 | |||
| 50 | if (context_address.GetCpuAddr()) { | ||
| 51 | other.context = context_address.GetReference(true); | ||
| 52 | other.context_size = context_address.GetSize(); | ||
| 53 | } else { | ||
| 54 | other.context = CpuAddr(0); | ||
| 55 | other.context_size = 0; | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | void Copy(WaveBufferVersion2& other) { | ||
| 60 | other.buffer = buffer_address.GetReference(true); | ||
| 61 | other.buffer_size = buffer_address.GetSize(); | ||
| 62 | other.start_offset = start_offset; | ||
| 63 | other.end_offset = end_offset; | ||
| 64 | other.loop_start_offset = loop_start_offset; | ||
| 65 | other.loop_end_offset = loop_end_offset; | ||
| 66 | other.loop = loop; | ||
| 67 | other.loop_count = loop_count; | ||
| 68 | other.stream_ended = stream_ended; | ||
| 69 | |||
| 70 | if (context_address.GetCpuAddr()) { | ||
| 71 | other.context = context_address.GetReference(true); | ||
| 72 | other.context_size = context_address.GetSize(); | ||
| 73 | } else { | ||
| 74 | other.context = CpuAddr(0); | ||
| 75 | other.context_size = 0; | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | void Initialize() { | ||
| 80 | buffer_address.Setup(0, 0); | ||
| 81 | context_address.Setup(0, 0); | ||
| 82 | start_offset = 0; | ||
| 83 | end_offset = 0; | ||
| 84 | loop = false; | ||
| 85 | stream_ended = false; | ||
| 86 | sent_to_DSP = true; | ||
| 87 | loop_start_offset = 0; | ||
| 88 | loop_end_offset = 0; | ||
| 89 | loop_count = 0; | ||
| 90 | } | ||
| 91 | /// Game memory address of the wavebuffer data | ||
| 92 | AddressInfo buffer_address{0, 0}; | ||
| 93 | /// Context for decoding, used for ADPCM | ||
| 94 | AddressInfo context_address{0, 0}; | ||
| 95 | /// Starting offset for the wavebuffer | ||
| 96 | u32 start_offset{}; | ||
| 97 | /// Ending offset the wavebuffer | ||
| 98 | u32 end_offset{}; | ||
| 99 | /// Should this wavebuffer loop? | ||
| 100 | bool loop{}; | ||
| 101 | /// Has this wavebuffer ended? | ||
| 102 | bool stream_ended{}; | ||
| 103 | /// Has this wavebuffer been sent to the AudioRenderer? | ||
| 104 | bool sent_to_DSP{true}; | ||
| 105 | /// Starting offset when looping, can differ from start_offset | ||
| 106 | u32 loop_start_offset{}; | ||
| 107 | /// Ending offset when looping, can differ from end_offset | ||
| 108 | u32 loop_end_offset{}; | ||
| 109 | /// Number of times to loop this wavebuffer | ||
| 110 | s32 loop_count{}; | ||
| 111 | }; | ||
| 112 | |||
| 113 | struct WaveBufferInternal { | ||
| 114 | /* 0x00 */ CpuAddr address; | ||
| 115 | /* 0x08 */ u64 size; | ||
| 116 | /* 0x10 */ s32 start_offset; | ||
| 117 | /* 0x14 */ s32 end_offset; | ||
| 118 | /* 0x18 */ bool loop; | ||
| 119 | /* 0x19 */ bool stream_ended; | ||
| 120 | /* 0x1A */ bool sent_to_DSP; | ||
| 121 | /* 0x1C */ s32 loop_count; | ||
| 122 | /* 0x20 */ CpuAddr context_address; | ||
| 123 | /* 0x28 */ u64 context_size; | ||
| 124 | /* 0x30 */ u32 loop_start; | ||
| 125 | /* 0x34 */ u32 loop_end; | ||
| 126 | }; | ||
| 127 | static_assert(sizeof(WaveBufferInternal) == 0x38, | ||
| 128 | "VoiceInfo::WaveBufferInternal has the wrong size!"); | ||
| 129 | |||
| 130 | struct BiquadFilterParameter { | ||
| 131 | /* 0x00 */ bool enabled; | ||
| 132 | /* 0x02 */ std::array<s16, 3> b; | ||
| 133 | /* 0x08 */ std::array<s16, 2> a; | ||
| 134 | }; | ||
| 135 | static_assert(sizeof(BiquadFilterParameter) == 0xC, | ||
| 136 | "VoiceInfo::BiquadFilterParameter has the wrong size!"); | ||
| 137 | |||
| 138 | struct InParameter { | ||
| 139 | /* 0x000 */ u32 id; | ||
| 140 | /* 0x004 */ u32 node_id; | ||
| 141 | /* 0x008 */ bool is_new; | ||
| 142 | /* 0x009 */ bool in_use; | ||
| 143 | /* 0x00A */ PlayState play_state; | ||
| 144 | /* 0x00B */ SampleFormat sample_format; | ||
| 145 | /* 0x00C */ u32 sample_rate; | ||
| 146 | /* 0x010 */ s32 priority; | ||
| 147 | /* 0x014 */ s32 sort_order; | ||
| 148 | /* 0x018 */ u32 channel_count; | ||
| 149 | /* 0x01C */ f32 pitch; | ||
| 150 | /* 0x020 */ f32 volume; | ||
| 151 | /* 0x024 */ std::array<BiquadFilterParameter, MaxBiquadFilters> biquads; | ||
| 152 | /* 0x03C */ u32 wave_buffer_count; | ||
| 153 | /* 0x040 */ u16 wave_buffer_index; | ||
| 154 | /* 0x042 */ char unk042[0x6]; | ||
| 155 | /* 0x048 */ CpuAddr src_data_address; | ||
| 156 | /* 0x050 */ u64 src_data_size; | ||
| 157 | /* 0x058 */ u32 mix_id; | ||
| 158 | /* 0x05C */ u32 splitter_id; | ||
| 159 | /* 0x060 */ std::array<WaveBufferInternal, MaxWaveBuffers> wave_buffer_internal; | ||
| 160 | /* 0x140 */ std::array<u32, MaxChannels> channel_resource_ids; | ||
| 161 | /* 0x158 */ bool clear_voice_drop; | ||
| 162 | /* 0x159 */ u8 flush_buffer_count; | ||
| 163 | /* 0x15A */ char unk15A[0x2]; | ||
| 164 | /* 0x15C */ Flags flags; | ||
| 165 | /* 0x15D */ char unk15D[0x1]; | ||
| 166 | /* 0x15E */ SrcQuality src_quality; | ||
| 167 | /* 0x15F */ char unk15F[0x11]; | ||
| 168 | }; | ||
| 169 | static_assert(sizeof(InParameter) == 0x170, "VoiceInfo::InParameter has the wrong size!"); | ||
| 170 | |||
| 171 | struct OutStatus { | ||
| 172 | /* 0x00 */ u64 played_sample_count; | ||
| 173 | /* 0x08 */ u32 wave_buffers_consumed; | ||
| 174 | /* 0x0C */ bool voice_dropped; | ||
| 175 | }; | ||
| 176 | static_assert(sizeof(OutStatus) == 0x10, "OutStatus::InParameter has the wrong size!"); | ||
| 177 | |||
| 178 | VoiceInfo(); | ||
| 179 | |||
| 180 | /** | ||
| 181 | * Initialize this voice. | ||
| 182 | */ | ||
| 183 | void Initialize(); | ||
| 184 | |||
| 185 | /** | ||
| 186 | * Does this voice ned an update? | ||
| 187 | * | ||
| 188 | * @param params - Input parametetrs to check matching. | ||
| 189 | * @return True if this voice needs an update, otherwise false. | ||
| 190 | */ | ||
| 191 | bool ShouldUpdateParameters(const InParameter& params) const; | ||
| 192 | |||
| 193 | /** | ||
| 194 | * Update the parameters of this voice. | ||
| 195 | * | ||
| 196 | * @param error_info - Output error code. | ||
| 197 | * @param params - Input parametters to udpate from. | ||
| 198 | * @param pool_mapper - Used to map buffers. | ||
| 199 | * @param behavior - behavior to check supported features. | ||
| 200 | */ | ||
| 201 | void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params, | ||
| 202 | const PoolMapper& pool_mapper, const BehaviorInfo& behavior); | ||
| 203 | |||
| 204 | /** | ||
| 205 | * Update the current play state. | ||
| 206 | * | ||
| 207 | * @param state - New play state for this voice. | ||
| 208 | */ | ||
| 209 | void UpdatePlayState(PlayState state); | ||
| 210 | |||
| 211 | /** | ||
| 212 | * Update the current sample rate conversion quality. | ||
| 213 | * | ||
| 214 | * @param quality - New quality. | ||
| 215 | */ | ||
| 216 | void UpdateSrcQuality(SrcQuality quality); | ||
| 217 | |||
| 218 | /** | ||
| 219 | * Update all wavebuffers. | ||
| 220 | * | ||
| 221 | * @param error_infos - Output 2D array of errors, 2 per wavebuffer. | ||
| 222 | * @param error_count - Number of errors provided. Unused. | ||
| 223 | * @param params - Input parametters to be used for the update. | ||
| 224 | * @param voice_states - The voice states for each channel in this voice to be updated. | ||
| 225 | * @param pool_mapper - Used to map the wavebuffers. | ||
| 226 | * @param behavior - Used to check for supported features. | ||
| 227 | */ | ||
| 228 | void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos, | ||
| 229 | u32 error_count, const InParameter& params, | ||
| 230 | std::span<VoiceState*> voice_states, const PoolMapper& pool_mapper, | ||
| 231 | const BehaviorInfo& behavior); | ||
| 232 | |||
| 233 | /** | ||
| 234 | * Update a wavebuffer. | ||
| 235 | * | ||
| 236 | * @param error_infos - Output array of errors. | ||
| 237 | * @param wave_buffer - The wavebuffer to be updated. | ||
| 238 | * @param wave_buffer_internal - Input parametters to be used for the update. | ||
| 239 | * @param sample_format - Sample format of the wavebuffer. | ||
| 240 | * @param valid - Is this wavebuffer valid? | ||
| 241 | * @param pool_mapper - Used to map the wavebuffers. | ||
| 242 | * @param behavior - Used to check for supported features. | ||
| 243 | */ | ||
| 244 | void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer, | ||
| 245 | const WaveBufferInternal& wave_buffer_internal, | ||
| 246 | SampleFormat sample_format, bool valid, const PoolMapper& pool_mapper, | ||
| 247 | const BehaviorInfo& behavior); | ||
| 248 | |||
| 249 | /** | ||
| 250 | * Check if the input wavebuffer needs an update. | ||
| 251 | * | ||
| 252 | * @param wave_buffer_internal - Input wavebuffer parameters to check. | ||
| 253 | * @return True if the given wavebuffer needs an update, otherwise false. | ||
| 254 | */ | ||
| 255 | bool ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const; | ||
| 256 | |||
| 257 | /** | ||
| 258 | * Write the number of played samples, number of consumed wavebuffers and if this voice was | ||
| 259 | * dropped, to the given out_status. | ||
| 260 | * | ||
| 261 | * @param out_status - Output status to be written to. | ||
| 262 | * @param in_params - Input parameters to check if the wavebuffer is new. | ||
| 263 | * @param voice_states - Current host voice states for this voice, source of the output. | ||
| 264 | */ | ||
| 265 | void WriteOutStatus(OutStatus& out_status, const InParameter& in_params, | ||
| 266 | std::span<VoiceState*> voice_states); | ||
| 267 | |||
| 268 | /** | ||
| 269 | * Check if this voice should be skipped for command generation. | ||
| 270 | * Checks various things such as usage state, whether data is mapped etc. | ||
| 271 | * | ||
| 272 | * @return True if this voice should not be generated, otherwise false. | ||
| 273 | */ | ||
| 274 | bool ShouldSkip() const; | ||
| 275 | |||
| 276 | /** | ||
| 277 | * Check if this voice has any mixing connections. | ||
| 278 | * | ||
| 279 | * @return True if this voice participes in mixing, otherwise false. | ||
| 280 | */ | ||
| 281 | bool HasAnyConnection() const; | ||
| 282 | |||
| 283 | /** | ||
| 284 | * Flush flush_count wavebuffers, marking them as consumed. | ||
| 285 | * | ||
| 286 | * @param flush_count - Number of wavebuffers to flush. | ||
| 287 | * @param voice_states - Voice states for these wavebuffers. | ||
| 288 | * @param channel_count - Number of active channels. | ||
| 289 | */ | ||
| 290 | void FlushWaveBuffers(u32 flush_count, std::span<VoiceState*> voice_states, s8 channel_count); | ||
| 291 | |||
| 292 | /** | ||
| 293 | * Update this voice's parameters on command generation, | ||
| 294 | * updating voice states and flushing if needed. | ||
| 295 | * | ||
| 296 | * @param voice_states - Voice states for these wavebuffers. | ||
| 297 | * @return True if this voice should be generated, otherwise false. | ||
| 298 | */ | ||
| 299 | bool UpdateParametersForCommandGeneration(std::span<VoiceState*> voice_states); | ||
| 300 | |||
| 301 | /** | ||
| 302 | * Update this voice on command generation. | ||
| 303 | * | ||
| 304 | * @param voice_states - Voice states for these wavebuffers. | ||
| 305 | * @return True if this voice should be generated, otherwise false. | ||
| 306 | */ | ||
| 307 | bool UpdateForCommandGeneration(VoiceContext& voice_context); | ||
| 308 | |||
| 309 | /** | ||
| 310 | * Reset the AudioRenderer-side voice states, and the channel resources for this voice. | ||
| 311 | * | ||
| 312 | * @param voice_context - Context from which to get the resources. | ||
| 313 | */ | ||
| 314 | void ResetResources(VoiceContext& voice_context) const; | ||
| 315 | |||
| 316 | /// Is this voice in use? | ||
| 317 | bool in_use{}; | ||
| 318 | /// Is this voice new? | ||
| 319 | bool is_new{}; | ||
| 320 | /// Was this voice last playing? Used for depopping | ||
| 321 | bool was_playing{}; | ||
| 322 | /// Sample format of the wavebuffers in this voice | ||
| 323 | SampleFormat sample_format{}; | ||
| 324 | /// Sample rate of the wavebuffers in this voice | ||
| 325 | u32 sample_rate{}; | ||
| 326 | /// Number of channels in this voice | ||
| 327 | s8 channel_count{}; | ||
| 328 | /// Id of this voice | ||
| 329 | u32 id{}; | ||
| 330 | /// Node id of this voice | ||
| 331 | u32 node_id{}; | ||
| 332 | /// Mix id this voice is mixed to | ||
| 333 | u32 mix_id{}; | ||
| 334 | /// Play state of this voice | ||
| 335 | ServerPlayState current_play_state{ServerPlayState::Stopped}; | ||
| 336 | /// Last play state of this voice | ||
| 337 | ServerPlayState last_play_state{ServerPlayState::Started}; | ||
| 338 | /// Priority of this voice, lower is higher | ||
| 339 | s32 priority{}; | ||
| 340 | /// Sort order of this voice, used when same priority | ||
| 341 | s32 sort_order{}; | ||
| 342 | /// Pitch of this voice (for sample rate conversion) | ||
| 343 | f32 pitch{}; | ||
| 344 | /// Current volume of this voice | ||
| 345 | f32 volume{}; | ||
| 346 | /// Previous volume of this voice | ||
| 347 | f32 prev_volume{}; | ||
| 348 | /// Biquad filters for generating filter commands on this voice | ||
| 349 | std::array<BiquadFilterParameter, MaxBiquadFilters> biquads{}; | ||
| 350 | /// Number of active wavebuffers | ||
| 351 | u32 wave_buffer_count{}; | ||
| 352 | /// Current playing wavebuffer index | ||
| 353 | u16 wave_buffer_index{}; | ||
| 354 | /// Flags controlling decode behavior | ||
| 355 | u16 flags{}; | ||
| 356 | /// Game memory for ADPCM coefficients | ||
| 357 | AddressInfo data_address{0, 0}; | ||
| 358 | /// Wavebuffers | ||
| 359 | std::array<WaveBuffer, MaxWaveBuffers> wavebuffers{}; | ||
| 360 | /// Channel resources for this voice | ||
| 361 | std::array<u32, MaxChannels> channel_resource_ids{}; | ||
| 362 | /// Splitter id this voice is connected with | ||
| 363 | s32 splitter_id{UnusedSplitterId}; | ||
| 364 | /// Sample rate conversion quality | ||
| 365 | SrcQuality src_quality{SrcQuality::Medium}; | ||
| 366 | /// Was this voice dropped due to limited time? | ||
| 367 | bool voice_dropped{}; | ||
| 368 | /// Is this voice's coefficient (data_address) unmapped? | ||
| 369 | bool data_unmapped{}; | ||
| 370 | /// Is this voice's buffers (wavebuffer data and ADPCM context) unmapped? | ||
| 371 | bool buffer_unmapped{}; | ||
| 372 | /// Initialisation state of the biquads | ||
| 373 | std::array<bool, MaxBiquadFilters> biquad_initialized{}; | ||
| 374 | /// Number of wavebuffers to flush | ||
| 375 | u8 flush_buffer_count{}; | ||
| 376 | }; | ||
| 377 | |||
| 378 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/voice/voice_state.h b/src/audio_core/renderer/voice/voice_state.h new file mode 100644 index 000000000..d5497e2fb --- /dev/null +++ b/src/audio_core/renderer/voice/voice_state.h | |||
| @@ -0,0 +1,70 @@ | |||
| 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 | |||
| 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 | * Holds a state for a voice. One is kept host-side, and one is used by the AudioRenderer, | ||
| 15 | * host-side is updated on the next iteration. | ||
| 16 | */ | ||
| 17 | struct VoiceState { | ||
| 18 | /** | ||
| 19 | * State of the voice's biquad filter. | ||
| 20 | */ | ||
| 21 | struct BiquadFilterState { | ||
| 22 | Common::FixedPoint<50, 14> s0; | ||
| 23 | Common::FixedPoint<50, 14> s1; | ||
| 24 | Common::FixedPoint<50, 14> s2; | ||
| 25 | Common::FixedPoint<50, 14> s3; | ||
| 26 | }; | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Context for ADPCM decoding. | ||
| 30 | */ | ||
| 31 | struct AdpcmContext { | ||
| 32 | u16 header; | ||
| 33 | s16 yn0; | ||
| 34 | s16 yn1; | ||
| 35 | }; | ||
| 36 | |||
| 37 | /// Number of samples played | ||
| 38 | u64 played_sample_count; | ||
| 39 | /// Current offset from the starting offset | ||
| 40 | u32 offset; | ||
| 41 | /// Currently active wavebuffer index | ||
| 42 | u32 wave_buffer_index; | ||
| 43 | /// Array of which wavebuffers are currently valid | ||
| 44 | |||
| 45 | std::array<bool, MaxWaveBuffers> wave_buffer_valid; | ||
| 46 | /// Number of wavebuffers consumed, given back to the game | ||
| 47 | u32 wave_buffers_consumed; | ||
| 48 | /// History of samples, used for rate conversion | ||
| 49 | |||
| 50 | std::array<s16, MaxWaveBuffers * 2> sample_history; | ||
| 51 | /// Current read fraction, used for resampling | ||
| 52 | Common::FixedPoint<49, 15> fraction; | ||
| 53 | /// Current adpcm context | ||
| 54 | AdpcmContext adpcm_context; | ||
| 55 | /// Current biquad states, used when filtering | ||
| 56 | |||
| 57 | std::array<std::array<BiquadFilterState, MaxBiquadFilters>, MaxBiquadFilters> biquad_states; | ||
| 58 | /// Previous samples | ||
| 59 | std::array<s32, MaxMixBuffers> previous_samples; | ||
| 60 | /// Unused | ||
| 61 | u32 external_context_size; | ||
| 62 | /// Unused | ||
| 63 | bool external_context_enabled; | ||
| 64 | /// Was this voice dropped? | ||
| 65 | bool voice_dropped; | ||
| 66 | /// Number of times the wavebuffer has looped | ||
| 67 | s32 loop_count; | ||
| 68 | }; | ||
| 69 | |||
| 70 | } // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp deleted file mode 100644 index a10ba4044..000000000 --- a/src/audio_core/sdl2_sink.cpp +++ /dev/null | |||
| @@ -1,160 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <atomic> | ||
| 6 | #include <cstring> | ||
| 7 | #include "audio_core/sdl2_sink.h" | ||
| 8 | #include "audio_core/stream.h" | ||
| 9 | #include "common/assert.h" | ||
| 10 | #include "common/logging/log.h" | ||
| 11 | //#include "common/settings.h" | ||
| 12 | |||
| 13 | // Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 | ||
| 14 | #ifdef __clang__ | ||
| 15 | #pragma clang diagnostic push | ||
| 16 | #pragma clang diagnostic ignored "-Wimplicit-fallthrough" | ||
| 17 | #endif | ||
| 18 | #include <SDL.h> | ||
| 19 | #ifdef __clang__ | ||
| 20 | #pragma clang diagnostic pop | ||
| 21 | #endif | ||
| 22 | |||
| 23 | namespace AudioCore { | ||
| 24 | |||
| 25 | class SDLSinkStream final : public SinkStream { | ||
| 26 | public: | ||
| 27 | SDLSinkStream(u32 sample_rate, u32 num_channels_, const std::string& output_device) | ||
| 28 | : num_channels{std::min(num_channels_, 6u)} { | ||
| 29 | |||
| 30 | SDL_AudioSpec spec; | ||
| 31 | spec.freq = sample_rate; | ||
| 32 | spec.channels = static_cast<u8>(num_channels); | ||
| 33 | spec.format = AUDIO_S16SYS; | ||
| 34 | spec.samples = 4096; | ||
| 35 | spec.callback = nullptr; | ||
| 36 | |||
| 37 | SDL_AudioSpec obtained; | ||
| 38 | if (output_device.empty()) { | ||
| 39 | dev = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained, 0); | ||
| 40 | } else { | ||
| 41 | dev = SDL_OpenAudioDevice(output_device.c_str(), 0, &spec, &obtained, 0); | ||
| 42 | } | ||
| 43 | |||
| 44 | if (dev == 0) { | ||
| 45 | LOG_CRITICAL(Audio_Sink, "Error opening sdl audio device: {}", SDL_GetError()); | ||
| 46 | return; | ||
| 47 | } | ||
| 48 | |||
| 49 | SDL_PauseAudioDevice(dev, 0); | ||
| 50 | } | ||
| 51 | |||
| 52 | ~SDLSinkStream() override { | ||
| 53 | if (dev == 0) { | ||
| 54 | return; | ||
| 55 | } | ||
| 56 | |||
| 57 | SDL_CloseAudioDevice(dev); | ||
| 58 | } | ||
| 59 | |||
| 60 | void EnqueueSamples(u32 source_num_channels, const std::vector<s16>& samples) override { | ||
| 61 | if (source_num_channels > num_channels) { | ||
| 62 | // Downsample 6 channels to 2 | ||
| 63 | ASSERT_MSG(source_num_channels == 6, "Channel count must be 6"); | ||
| 64 | |||
| 65 | std::vector<s16> buf; | ||
| 66 | buf.reserve(samples.size() * num_channels / source_num_channels); | ||
| 67 | for (std::size_t i = 0; i < samples.size(); i += source_num_channels) { | ||
| 68 | // Downmixing implementation taken from the ATSC standard | ||
| 69 | const s16 left{samples[i + 0]}; | ||
| 70 | const s16 right{samples[i + 1]}; | ||
| 71 | const s16 center{samples[i + 2]}; | ||
| 72 | const s16 surround_left{samples[i + 4]}; | ||
| 73 | const s16 surround_right{samples[i + 5]}; | ||
| 74 | // Not used in the ATSC reference implementation | ||
| 75 | [[maybe_unused]] const s16 low_frequency_effects{samples[i + 3]}; | ||
| 76 | |||
| 77 | constexpr s32 clev{707}; // center mixing level coefficient | ||
| 78 | constexpr s32 slev{707}; // surround mixing level coefficient | ||
| 79 | |||
| 80 | buf.push_back(static_cast<s16>(left + (clev * center / 1000) + | ||
| 81 | (slev * surround_left / 1000))); | ||
| 82 | buf.push_back(static_cast<s16>(right + (clev * center / 1000) + | ||
| 83 | (slev * surround_right / 1000))); | ||
| 84 | } | ||
| 85 | int ret = SDL_QueueAudio(dev, static_cast<const void*>(buf.data()), | ||
| 86 | static_cast<u32>(buf.size() * sizeof(s16))); | ||
| 87 | if (ret < 0) | ||
| 88 | LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError()); | ||
| 89 | return; | ||
| 90 | } | ||
| 91 | |||
| 92 | int ret = SDL_QueueAudio(dev, static_cast<const void*>(samples.data()), | ||
| 93 | static_cast<u32>(samples.size() * sizeof(s16))); | ||
| 94 | if (ret < 0) | ||
| 95 | LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError()); | ||
| 96 | } | ||
| 97 | |||
| 98 | std::size_t SamplesInQueue(u32 channel_count) const override { | ||
| 99 | if (dev == 0) | ||
| 100 | return 0; | ||
| 101 | |||
| 102 | return SDL_GetQueuedAudioSize(dev) / (channel_count * sizeof(s16)); | ||
| 103 | } | ||
| 104 | |||
| 105 | void Flush() override { | ||
| 106 | should_flush = true; | ||
| 107 | } | ||
| 108 | |||
| 109 | u32 GetNumChannels() const { | ||
| 110 | return num_channels; | ||
| 111 | } | ||
| 112 | |||
| 113 | private: | ||
| 114 | SDL_AudioDeviceID dev = 0; | ||
| 115 | u32 num_channels{}; | ||
| 116 | std::atomic<bool> should_flush{}; | ||
| 117 | }; | ||
| 118 | |||
| 119 | SDLSink::SDLSink(std::string_view target_device_name) { | ||
| 120 | if (!SDL_WasInit(SDL_INIT_AUDIO)) { | ||
| 121 | if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { | ||
| 122 | LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); | ||
| 123 | return; | ||
| 124 | } | ||
| 125 | } | ||
| 126 | |||
| 127 | if (target_device_name != auto_device_name && !target_device_name.empty()) { | ||
| 128 | output_device = target_device_name; | ||
| 129 | } else { | ||
| 130 | output_device.clear(); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | SDLSink::~SDLSink() = default; | ||
| 135 | |||
| 136 | SinkStream& SDLSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, const std::string&) { | ||
| 137 | sink_streams.push_back( | ||
| 138 | std::make_unique<SDLSinkStream>(sample_rate, num_channels, output_device)); | ||
| 139 | return *sink_streams.back(); | ||
| 140 | } | ||
| 141 | |||
| 142 | std::vector<std::string> ListSDLSinkDevices() { | ||
| 143 | std::vector<std::string> device_list; | ||
| 144 | |||
| 145 | if (!SDL_WasInit(SDL_INIT_AUDIO)) { | ||
| 146 | if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { | ||
| 147 | LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); | ||
| 148 | return {}; | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | const int device_count = SDL_GetNumAudioDevices(0); | ||
| 153 | for (int i = 0; i < device_count; ++i) { | ||
| 154 | device_list.emplace_back(SDL_GetAudioDeviceName(i, 0)); | ||
| 155 | } | ||
| 156 | |||
| 157 | return device_list; | ||
| 158 | } | ||
| 159 | |||
| 160 | } // namespace AudioCore | ||
diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h deleted file mode 100644 index f1dd1d677..000000000 --- a/src/audio_core/sdl2_sink.h +++ /dev/null | |||
| @@ -1,28 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | #include <vector> | ||
| 8 | |||
| 9 | #include "audio_core/sink.h" | ||
| 10 | |||
| 11 | namespace AudioCore { | ||
| 12 | |||
| 13 | class SDLSink final : public Sink { | ||
| 14 | public: | ||
| 15 | explicit SDLSink(std::string_view device_id); | ||
| 16 | ~SDLSink() override; | ||
| 17 | |||
| 18 | SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels, | ||
| 19 | const std::string& name) override; | ||
| 20 | |||
| 21 | private: | ||
| 22 | std::string output_device; | ||
| 23 | std::vector<SinkStreamPtr> sink_streams; | ||
| 24 | }; | ||
| 25 | |||
| 26 | std::vector<std::string> ListSDLSinkDevices(); | ||
| 27 | |||
| 28 | } // namespace AudioCore | ||
diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h deleted file mode 100644 index 3c03554fa..000000000 --- a/src/audio_core/sink.h +++ /dev/null | |||
| @@ -1,30 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <memory> | ||
| 7 | #include <string> | ||
| 8 | |||
| 9 | #include "audio_core/sink_stream.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace AudioCore { | ||
| 13 | |||
| 14 | constexpr char auto_device_name[] = "auto"; | ||
| 15 | |||
| 16 | /** | ||
| 17 | * This class is an interface for an audio sink. An audio sink accepts samples in stereo signed | ||
| 18 | * PCM16 format to be output. Sinks *do not* handle resampling and expect the correct sample rate. | ||
| 19 | * They are dumb outputs. | ||
| 20 | */ | ||
| 21 | class Sink { | ||
| 22 | public: | ||
| 23 | virtual ~Sink() = default; | ||
| 24 | virtual SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels, | ||
| 25 | const std::string& name) = 0; | ||
| 26 | }; | ||
| 27 | |||
| 28 | using SinkPtr = std::unique_ptr<Sink>; | ||
| 29 | |||
| 30 | } // namespace AudioCore | ||
diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp new file mode 100644 index 000000000..a4e28de6d --- /dev/null +++ b/src/audio_core/sink/cubeb_sink.cpp | |||
| @@ -0,0 +1,651 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <atomic> | ||
| 6 | #include <span> | ||
| 7 | |||
| 8 | #include "audio_core/audio_core.h" | ||
| 9 | #include "audio_core/audio_event.h" | ||
| 10 | #include "audio_core/audio_manager.h" | ||
| 11 | #include "audio_core/sink/cubeb_sink.h" | ||
| 12 | #include "audio_core/sink/sink_stream.h" | ||
| 13 | #include "common/assert.h" | ||
| 14 | #include "common/fixed_point.h" | ||
| 15 | #include "common/logging/log.h" | ||
| 16 | #include "common/reader_writer_queue.h" | ||
| 17 | #include "common/ring_buffer.h" | ||
| 18 | #include "common/settings.h" | ||
| 19 | #include "core/core.h" | ||
| 20 | |||
| 21 | #ifdef _WIN32 | ||
| 22 | #include <objbase.h> | ||
| 23 | #undef CreateEvent | ||
| 24 | #endif | ||
| 25 | |||
| 26 | namespace AudioCore::Sink { | ||
| 27 | /** | ||
| 28 | * Cubeb sink stream, responsible for sinking samples to hardware. | ||
| 29 | */ | ||
| 30 | class CubebSinkStream final : public SinkStream { | ||
| 31 | public: | ||
| 32 | /** | ||
| 33 | * Create a new sink stream. | ||
| 34 | * | ||
| 35 | * @param ctx_ - Cubeb context to create this stream with. | ||
| 36 | * @param device_channels_ - Number of channels supported by the hardware. | ||
| 37 | * @param system_channels_ - Number of channels the audio systems expect. | ||
| 38 | * @param output_device - Cubeb output device id. | ||
| 39 | * @param input_device - Cubeb input device id. | ||
| 40 | * @param name_ - Name of this stream. | ||
| 41 | * @param type_ - Type of this stream. | ||
| 42 | * @param system_ - Core system. | ||
| 43 | * @param event - Event used only for audio renderer, signalled on buffer consume. | ||
| 44 | */ | ||
| 45 | CubebSinkStream(cubeb* ctx_, const u32 device_channels_, const u32 system_channels_, | ||
| 46 | cubeb_devid output_device, cubeb_devid input_device, const std::string& name_, | ||
| 47 | const StreamType type_, Core::System& system_) | ||
| 48 | : ctx{ctx_}, type{type_}, system{system_} { | ||
| 49 | #ifdef _WIN32 | ||
| 50 | CoInitializeEx(nullptr, COINIT_MULTITHREADED); | ||
| 51 | #endif | ||
| 52 | name = name_; | ||
| 53 | device_channels = device_channels_; | ||
| 54 | system_channels = system_channels_; | ||
| 55 | |||
| 56 | cubeb_stream_params params{}; | ||
| 57 | params.rate = TargetSampleRate; | ||
| 58 | params.channels = device_channels; | ||
| 59 | params.format = CUBEB_SAMPLE_S16LE; | ||
| 60 | params.prefs = CUBEB_STREAM_PREF_NONE; | ||
| 61 | switch (params.channels) { | ||
| 62 | case 1: | ||
| 63 | params.layout = CUBEB_LAYOUT_MONO; | ||
| 64 | break; | ||
| 65 | case 2: | ||
| 66 | params.layout = CUBEB_LAYOUT_STEREO; | ||
| 67 | break; | ||
| 68 | case 6: | ||
| 69 | params.layout = CUBEB_LAYOUT_3F2_LFE; | ||
| 70 | break; | ||
| 71 | } | ||
| 72 | |||
| 73 | u32 minimum_latency{0}; | ||
| 74 | const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &minimum_latency); | ||
| 75 | if (latency_error != CUBEB_OK) { | ||
| 76 | LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error); | ||
| 77 | minimum_latency = 256U; | ||
| 78 | } | ||
| 79 | |||
| 80 | minimum_latency = std::max(minimum_latency, 256u); | ||
| 81 | |||
| 82 | playing_buffer.consumed = true; | ||
| 83 | |||
| 84 | LOG_DEBUG(Service_Audio, | ||
| 85 | "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) " | ||
| 86 | "latency {}", | ||
| 87 | name, type, params.rate, params.channels, system_channels, minimum_latency); | ||
| 88 | |||
| 89 | auto init_error{0}; | ||
| 90 | if (type == StreamType::In) { | ||
| 91 | init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device, | ||
| 92 | ¶ms, output_device, nullptr, minimum_latency, | ||
| 93 | &CubebSinkStream::DataCallback, | ||
| 94 | &CubebSinkStream::StateCallback, this); | ||
| 95 | } else { | ||
| 96 | init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device, | ||
| 97 | nullptr, output_device, ¶ms, minimum_latency, | ||
| 98 | &CubebSinkStream::DataCallback, | ||
| 99 | &CubebSinkStream::StateCallback, this); | ||
| 100 | } | ||
| 101 | |||
| 102 | if (init_error != CUBEB_OK) { | ||
| 103 | LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream, error: {}", init_error); | ||
| 104 | return; | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | /** | ||
| 109 | * Destroy the sink stream. | ||
| 110 | */ | ||
| 111 | ~CubebSinkStream() override { | ||
| 112 | LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name); | ||
| 113 | |||
| 114 | if (!ctx) { | ||
| 115 | return; | ||
| 116 | } | ||
| 117 | |||
| 118 | Finalize(); | ||
| 119 | |||
| 120 | #ifdef _WIN32 | ||
| 121 | CoUninitialize(); | ||
| 122 | #endif | ||
| 123 | } | ||
| 124 | |||
| 125 | /** | ||
| 126 | * Finalize the sink stream. | ||
| 127 | */ | ||
| 128 | void Finalize() override { | ||
| 129 | Stop(); | ||
| 130 | cubeb_stream_destroy(stream_backend); | ||
| 131 | } | ||
| 132 | |||
| 133 | /** | ||
| 134 | * Start the sink stream. | ||
| 135 | * | ||
| 136 | * @param resume - Set to true if this is resuming the stream a previously-active stream. | ||
| 137 | * Default false. | ||
| 138 | */ | ||
| 139 | void Start(const bool resume = false) override { | ||
| 140 | if (!ctx) { | ||
| 141 | return; | ||
| 142 | } | ||
| 143 | |||
| 144 | if (resume && was_playing) { | ||
| 145 | if (cubeb_stream_start(stream_backend) != CUBEB_OK) { | ||
| 146 | LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); | ||
| 147 | } | ||
| 148 | paused = false; | ||
| 149 | } else if (!resume) { | ||
| 150 | if (cubeb_stream_start(stream_backend) != CUBEB_OK) { | ||
| 151 | LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); | ||
| 152 | } | ||
| 153 | paused = false; | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | /** | ||
| 158 | * Stop the sink stream. | ||
| 159 | */ | ||
| 160 | void Stop() override { | ||
| 161 | if (!ctx) { | ||
| 162 | return; | ||
| 163 | } | ||
| 164 | |||
| 165 | if (cubeb_stream_stop(stream_backend) != CUBEB_OK) { | ||
| 166 | LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); | ||
| 167 | } | ||
| 168 | |||
| 169 | was_playing.store(!paused); | ||
| 170 | paused = true; | ||
| 171 | } | ||
| 172 | |||
| 173 | /** | ||
| 174 | * Append a new buffer and its samples to a waiting queue to play. | ||
| 175 | * | ||
| 176 | * @param buffer - Audio buffer information to be queued. | ||
| 177 | * @param samples - The s16 samples to be queue for playback. | ||
| 178 | */ | ||
| 179 | void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override { | ||
| 180 | if (type == StreamType::In) { | ||
| 181 | queue.enqueue(buffer); | ||
| 182 | queued_buffers++; | ||
| 183 | } else { | ||
| 184 | constexpr s32 min{std::numeric_limits<s16>::min()}; | ||
| 185 | constexpr s32 max{std::numeric_limits<s16>::max()}; | ||
| 186 | |||
| 187 | auto yuzu_volume{Settings::Volume()}; | ||
| 188 | auto volume{system_volume * device_volume * yuzu_volume}; | ||
| 189 | |||
| 190 | if (system_channels == 6 && device_channels == 2) { | ||
| 191 | // We're given 6 channels, but our device only outputs 2, so downmix. | ||
| 192 | constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; | ||
| 193 | |||
| 194 | for (u32 read_index = 0, write_index = 0; read_index < samples.size(); | ||
| 195 | read_index += system_channels, write_index += device_channels) { | ||
| 196 | const auto left_sample{ | ||
| 197 | ((Common::FixedPoint<49, 15>( | ||
| 198 | samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * | ||
| 199 | down_mix_coeff[0] + | ||
| 200 | samples[read_index + static_cast<u32>(Channels::Center)] * | ||
| 201 | down_mix_coeff[1] + | ||
| 202 | samples[read_index + static_cast<u32>(Channels::LFE)] * | ||
| 203 | down_mix_coeff[2] + | ||
| 204 | samples[read_index + static_cast<u32>(Channels::BackLeft)] * | ||
| 205 | down_mix_coeff[3]) * | ||
| 206 | volume) | ||
| 207 | .to_int()}; | ||
| 208 | |||
| 209 | const auto right_sample{ | ||
| 210 | ((Common::FixedPoint<49, 15>( | ||
| 211 | samples[read_index + static_cast<u32>(Channels::FrontRight)]) * | ||
| 212 | down_mix_coeff[0] + | ||
| 213 | samples[read_index + static_cast<u32>(Channels::Center)] * | ||
| 214 | down_mix_coeff[1] + | ||
| 215 | samples[read_index + static_cast<u32>(Channels::LFE)] * | ||
| 216 | down_mix_coeff[2] + | ||
| 217 | samples[read_index + static_cast<u32>(Channels::BackRight)] * | ||
| 218 | down_mix_coeff[3]) * | ||
| 219 | volume) | ||
| 220 | .to_int()}; | ||
| 221 | |||
| 222 | samples[write_index + static_cast<u32>(Channels::FrontLeft)] = | ||
| 223 | static_cast<s16>(std::clamp(left_sample, min, max)); | ||
| 224 | samples[write_index + static_cast<u32>(Channels::FrontRight)] = | ||
| 225 | static_cast<s16>(std::clamp(right_sample, min, max)); | ||
| 226 | } | ||
| 227 | |||
| 228 | samples.resize(samples.size() / system_channels * device_channels); | ||
| 229 | |||
| 230 | } else if (system_channels == 2 && device_channels == 6) { | ||
| 231 | // We need moar samples! Not all games will provide 6 channel audio. | ||
| 232 | // TODO: Implement some upmixing here. Currently just passthrough, with other | ||
| 233 | // channels left as silence. | ||
| 234 | std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0); | ||
| 235 | |||
| 236 | for (u32 read_index = 0, write_index = 0; read_index < samples.size(); | ||
| 237 | read_index += system_channels, write_index += device_channels) { | ||
| 238 | const auto left_sample{static_cast<s16>(std::clamp( | ||
| 239 | static_cast<s32>( | ||
| 240 | static_cast<f32>( | ||
| 241 | samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * | ||
| 242 | volume), | ||
| 243 | min, max))}; | ||
| 244 | |||
| 245 | new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample; | ||
| 246 | |||
| 247 | const auto right_sample{static_cast<s16>(std::clamp( | ||
| 248 | static_cast<s32>( | ||
| 249 | static_cast<f32>( | ||
| 250 | samples[read_index + static_cast<u32>(Channels::FrontRight)]) * | ||
| 251 | volume), | ||
| 252 | min, max))}; | ||
| 253 | |||
| 254 | new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = | ||
| 255 | right_sample; | ||
| 256 | } | ||
| 257 | samples = std::move(new_samples); | ||
| 258 | |||
| 259 | } else if (volume != 1.0f) { | ||
| 260 | for (u32 i = 0; i < samples.size(); i++) { | ||
| 261 | samples[i] = static_cast<s16>(std::clamp( | ||
| 262 | static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); | ||
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | samples_buffer.Push(samples); | ||
| 267 | queue.enqueue(buffer); | ||
| 268 | queued_buffers++; | ||
| 269 | } | ||
| 270 | } | ||
| 271 | |||
| 272 | /** | ||
| 273 | * Release a buffer. Audio In only, will fill a buffer with recorded samples. | ||
| 274 | * | ||
| 275 | * @param num_samples - Maximum number of samples to receive. | ||
| 276 | * @return Vector of recorded samples. May have fewer than num_samples. | ||
| 277 | */ | ||
| 278 | std::vector<s16> ReleaseBuffer(const u64 num_samples) override { | ||
| 279 | static constexpr s32 min = std::numeric_limits<s16>::min(); | ||
| 280 | static constexpr s32 max = std::numeric_limits<s16>::max(); | ||
| 281 | |||
| 282 | auto samples{samples_buffer.Pop(num_samples)}; | ||
| 283 | |||
| 284 | // TODO: Up-mix to 6 channels if the game expects it. | ||
| 285 | // For audio input this is unlikely to ever be the case though. | ||
| 286 | |||
| 287 | // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here. | ||
| 288 | // TODO: Play with this and find something that works better. | ||
| 289 | auto volume{system_volume * device_volume * 8}; | ||
| 290 | for (u32 i = 0; i < samples.size(); i++) { | ||
| 291 | samples[i] = static_cast<s16>( | ||
| 292 | std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); | ||
| 293 | } | ||
| 294 | |||
| 295 | if (samples.size() < num_samples) { | ||
| 296 | samples.resize(num_samples, 0); | ||
| 297 | } | ||
| 298 | return samples; | ||
| 299 | } | ||
| 300 | |||
| 301 | /** | ||
| 302 | * Check if a certain buffer has been consumed (fully played). | ||
| 303 | * | ||
| 304 | * @param tag - Unique tag of a buffer to check for. | ||
| 305 | * @return True if the buffer has been played, otherwise false. | ||
| 306 | */ | ||
| 307 | bool IsBufferConsumed(const u64 tag) override { | ||
| 308 | if (released_buffer.tag == 0) { | ||
| 309 | if (!released_buffers.try_dequeue(released_buffer)) { | ||
| 310 | return false; | ||
| 311 | } | ||
| 312 | } | ||
| 313 | |||
| 314 | if (released_buffer.tag == tag) { | ||
| 315 | released_buffer.tag = 0; | ||
| 316 | return true; | ||
| 317 | } | ||
| 318 | return false; | ||
| 319 | } | ||
| 320 | |||
| 321 | /** | ||
| 322 | * Empty out the buffer queue. | ||
| 323 | */ | ||
| 324 | void ClearQueue() override { | ||
| 325 | samples_buffer.Pop(); | ||
| 326 | while (queue.pop()) { | ||
| 327 | } | ||
| 328 | while (released_buffers.pop()) { | ||
| 329 | } | ||
| 330 | queued_buffers = 0; | ||
| 331 | released_buffer = {}; | ||
| 332 | playing_buffer = {}; | ||
| 333 | playing_buffer.consumed = true; | ||
| 334 | } | ||
| 335 | |||
| 336 | private: | ||
| 337 | /** | ||
| 338 | * Signal events back to the audio system that a buffer was played/can be filled. | ||
| 339 | * | ||
| 340 | * @param buffer - Consumed audio buffer to be released. | ||
| 341 | */ | ||
| 342 | void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) { | ||
| 343 | auto& manager{system.AudioCore().GetAudioManager()}; | ||
| 344 | switch (type) { | ||
| 345 | case StreamType::Out: | ||
| 346 | released_buffers.enqueue(buffer); | ||
| 347 | manager.SetEvent(Event::Type::AudioOutManager, true); | ||
| 348 | break; | ||
| 349 | case StreamType::In: | ||
| 350 | released_buffers.enqueue(buffer); | ||
| 351 | manager.SetEvent(Event::Type::AudioInManager, true); | ||
| 352 | break; | ||
| 353 | case StreamType::Render: | ||
| 354 | break; | ||
| 355 | } | ||
| 356 | } | ||
| 357 | |||
| 358 | /** | ||
| 359 | * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will | ||
| 360 | * provide samples to be copied (audio in). | ||
| 361 | * | ||
| 362 | * @param stream - Cubeb-specific data about the stream. | ||
| 363 | * @param user_data - Custom data pointer passed along, points to a CubebSinkStream. | ||
| 364 | * @param in_buff - Input buffer to be used if the stream is an input type. | ||
| 365 | * @param out_buff - Output buffer to be used if the stream is an output type. | ||
| 366 | * @param num_frames_ - Number of frames of audio in the buffers. Note: Not number of samples. | ||
| 367 | */ | ||
| 368 | static long DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data, | ||
| 369 | [[maybe_unused]] const void* in_buff, void* out_buff, | ||
| 370 | long num_frames_) { | ||
| 371 | auto* impl = static_cast<CubebSinkStream*>(user_data); | ||
| 372 | if (!impl) { | ||
| 373 | return -1; | ||
| 374 | } | ||
| 375 | |||
| 376 | const std::size_t num_channels = impl->GetDeviceChannels(); | ||
| 377 | const std::size_t frame_size = num_channels; | ||
| 378 | const std::size_t frame_size_bytes = frame_size * sizeof(s16); | ||
| 379 | const std::size_t num_frames{static_cast<size_t>(num_frames_)}; | ||
| 380 | size_t frames_written{0}; | ||
| 381 | [[maybe_unused]] bool underrun{false}; | ||
| 382 | |||
| 383 | if (impl->type == StreamType::In) { | ||
| 384 | // INPUT | ||
| 385 | std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff), | ||
| 386 | num_frames * frame_size}; | ||
| 387 | |||
| 388 | while (frames_written < num_frames) { | ||
| 389 | auto& playing_buffer{impl->playing_buffer}; | ||
| 390 | |||
| 391 | // If the playing buffer has been consumed or has no frames, we need a new one | ||
| 392 | if (playing_buffer.consumed || playing_buffer.frames == 0) { | ||
| 393 | if (!impl->queue.try_dequeue(impl->playing_buffer)) { | ||
| 394 | // If no buffer was available we've underrun, just push the samples and | ||
| 395 | // continue. | ||
| 396 | underrun = true; | ||
| 397 | impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], | ||
| 398 | (num_frames - frames_written) * frame_size); | ||
| 399 | frames_written = num_frames; | ||
| 400 | continue; | ||
| 401 | } else { | ||
| 402 | // Successfully got a new buffer, mark the old one as consumed and signal. | ||
| 403 | impl->queued_buffers--; | ||
| 404 | impl->SignalEvent(impl->playing_buffer); | ||
| 405 | } | ||
| 406 | } | ||
| 407 | |||
| 408 | // Get the minimum frames available between the currently playing buffer, and the | ||
| 409 | // amount we have left to fill | ||
| 410 | size_t frames_available{ | ||
| 411 | std::min(playing_buffer.frames - playing_buffer.frames_played, | ||
| 412 | num_frames - frames_written)}; | ||
| 413 | |||
| 414 | impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], | ||
| 415 | frames_available * frame_size); | ||
| 416 | |||
| 417 | frames_written += frames_available; | ||
| 418 | playing_buffer.frames_played += frames_available; | ||
| 419 | |||
| 420 | // If that's all the frames in the current buffer, add its samples and mark it as | ||
| 421 | // consumed | ||
| 422 | if (playing_buffer.frames_played >= playing_buffer.frames) { | ||
| 423 | impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); | ||
| 424 | impl->playing_buffer.consumed = true; | ||
| 425 | } | ||
| 426 | } | ||
| 427 | |||
| 428 | std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size], | ||
| 429 | frame_size_bytes); | ||
| 430 | } else { | ||
| 431 | // OUTPUT | ||
| 432 | std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size}; | ||
| 433 | |||
| 434 | while (frames_written < num_frames) { | ||
| 435 | auto& playing_buffer{impl->playing_buffer}; | ||
| 436 | |||
| 437 | // If the playing buffer has been consumed or has no frames, we need a new one | ||
| 438 | if (playing_buffer.consumed || playing_buffer.frames == 0) { | ||
| 439 | if (!impl->queue.try_dequeue(impl->playing_buffer)) { | ||
| 440 | // If no buffer was available we've underrun, fill the remaining buffer with | ||
| 441 | // the last written frame and continue. | ||
| 442 | underrun = true; | ||
| 443 | for (size_t i = frames_written; i < num_frames; i++) { | ||
| 444 | std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0], | ||
| 445 | frame_size_bytes); | ||
| 446 | } | ||
| 447 | frames_written = num_frames; | ||
| 448 | continue; | ||
| 449 | } else { | ||
| 450 | // Successfully got a new buffer, mark the old one as consumed and signal. | ||
| 451 | impl->queued_buffers--; | ||
| 452 | impl->SignalEvent(impl->playing_buffer); | ||
| 453 | } | ||
| 454 | } | ||
| 455 | |||
| 456 | // Get the minimum frames available between the currently playing buffer, and the | ||
| 457 | // amount we have left to fill | ||
| 458 | size_t frames_available{ | ||
| 459 | std::min(playing_buffer.frames - playing_buffer.frames_played, | ||
| 460 | num_frames - frames_written)}; | ||
| 461 | |||
| 462 | impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size], | ||
| 463 | frames_available * frame_size); | ||
| 464 | |||
| 465 | frames_written += frames_available; | ||
| 466 | playing_buffer.frames_played += frames_available; | ||
| 467 | |||
| 468 | // If that's all the frames in the current buffer, add its samples and mark it as | ||
| 469 | // consumed | ||
| 470 | if (playing_buffer.frames_played >= playing_buffer.frames) { | ||
| 471 | impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); | ||
| 472 | impl->playing_buffer.consumed = true; | ||
| 473 | } | ||
| 474 | } | ||
| 475 | |||
| 476 | std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size], | ||
| 477 | frame_size_bytes); | ||
| 478 | } | ||
| 479 | |||
| 480 | return num_frames_; | ||
| 481 | } | ||
| 482 | |||
| 483 | /** | ||
| 484 | * Cubeb callback for if a device state changes. Unused currently. | ||
| 485 | * | ||
| 486 | * @param stream - Cubeb-specific data about the stream. | ||
| 487 | * @param user_data - Custom data pointer passed along, points to a CubebSinkStream. | ||
| 488 | * @param state - New state of the device. | ||
| 489 | */ | ||
| 490 | static void StateCallback([[maybe_unused]] cubeb_stream* stream, | ||
| 491 | [[maybe_unused]] void* user_data, | ||
| 492 | [[maybe_unused]] cubeb_state state) {} | ||
| 493 | |||
| 494 | /// Main Cubeb context | ||
| 495 | cubeb* ctx{}; | ||
| 496 | /// Cubeb stream backend | ||
| 497 | cubeb_stream* stream_backend{}; | ||
| 498 | /// Name of this stream | ||
| 499 | std::string name{}; | ||
| 500 | /// Type of this stream | ||
| 501 | StreamType type; | ||
| 502 | /// Core system | ||
| 503 | Core::System& system; | ||
| 504 | /// Ring buffer of the samples waiting to be played or consumed | ||
| 505 | Common::RingBuffer<s16, 0x10000> samples_buffer; | ||
| 506 | /// Audio buffers queued and waiting to play | ||
| 507 | Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue; | ||
| 508 | /// The currently-playing audio buffer | ||
| 509 | ::AudioCore::Sink::SinkBuffer playing_buffer{}; | ||
| 510 | /// Audio buffers which have been played and are in queue to be released by the audio system | ||
| 511 | Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{}; | ||
| 512 | /// Currently released buffer waiting to be taken by the audio system | ||
| 513 | ::AudioCore::Sink::SinkBuffer released_buffer{}; | ||
| 514 | /// The last played (or received) frame of audio, used when the callback underruns | ||
| 515 | std::array<s16, MaxChannels> last_frame{}; | ||
| 516 | }; | ||
| 517 | |||
| 518 | CubebSink::CubebSink(std::string_view target_device_name) { | ||
| 519 | // Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows | ||
| 520 | #ifdef _WIN32 | ||
| 521 | com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); | ||
| 522 | #endif | ||
| 523 | |||
| 524 | if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) { | ||
| 525 | LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); | ||
| 526 | return; | ||
| 527 | } | ||
| 528 | |||
| 529 | if (target_device_name != auto_device_name && !target_device_name.empty()) { | ||
| 530 | cubeb_device_collection collection; | ||
| 531 | if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) { | ||
| 532 | LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported"); | ||
| 533 | } else { | ||
| 534 | const auto collection_end{collection.device + collection.count}; | ||
| 535 | const auto device{ | ||
| 536 | std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) { | ||
| 537 | return info.friendly_name != nullptr && | ||
| 538 | target_device_name == std::string(info.friendly_name); | ||
| 539 | })}; | ||
| 540 | if (device != collection_end) { | ||
| 541 | output_device = device->devid; | ||
| 542 | } | ||
| 543 | cubeb_device_collection_destroy(ctx, &collection); | ||
| 544 | } | ||
| 545 | } | ||
| 546 | |||
| 547 | cubeb_get_max_channel_count(ctx, &device_channels); | ||
| 548 | device_channels = device_channels >= 6U ? 6U : 2U; | ||
| 549 | } | ||
| 550 | |||
| 551 | CubebSink::~CubebSink() { | ||
| 552 | if (!ctx) { | ||
| 553 | return; | ||
| 554 | } | ||
| 555 | |||
| 556 | for (auto& sink_stream : sink_streams) { | ||
| 557 | sink_stream.reset(); | ||
| 558 | } | ||
| 559 | |||
| 560 | cubeb_destroy(ctx); | ||
| 561 | |||
| 562 | #ifdef _WIN32 | ||
| 563 | if (SUCCEEDED(com_init_result)) { | ||
| 564 | CoUninitialize(); | ||
| 565 | } | ||
| 566 | #endif | ||
| 567 | } | ||
| 568 | |||
| 569 | SinkStream* CubebSink::AcquireSinkStream(Core::System& system, const u32 system_channels, | ||
| 570 | const std::string& name, const StreamType type) { | ||
| 571 | SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>( | ||
| 572 | ctx, device_channels, system_channels, output_device, input_device, name, type, system)); | ||
| 573 | |||
| 574 | return stream.get(); | ||
| 575 | } | ||
| 576 | |||
| 577 | void CubebSink::CloseStream(const SinkStream* stream) { | ||
| 578 | for (size_t i = 0; i < sink_streams.size(); i++) { | ||
| 579 | if (sink_streams[i].get() == stream) { | ||
| 580 | sink_streams[i].reset(); | ||
| 581 | sink_streams.erase(sink_streams.begin() + i); | ||
| 582 | break; | ||
| 583 | } | ||
| 584 | } | ||
| 585 | } | ||
| 586 | |||
| 587 | void CubebSink::CloseStreams() { | ||
| 588 | sink_streams.clear(); | ||
| 589 | } | ||
| 590 | |||
| 591 | void CubebSink::PauseStreams() { | ||
| 592 | for (auto& stream : sink_streams) { | ||
| 593 | stream->Stop(); | ||
| 594 | } | ||
| 595 | } | ||
| 596 | |||
| 597 | void CubebSink::UnpauseStreams() { | ||
| 598 | for (auto& stream : sink_streams) { | ||
| 599 | stream->Start(true); | ||
| 600 | } | ||
| 601 | } | ||
| 602 | |||
| 603 | f32 CubebSink::GetDeviceVolume() const { | ||
| 604 | if (sink_streams.empty()) { | ||
| 605 | return 1.0f; | ||
| 606 | } | ||
| 607 | |||
| 608 | return sink_streams[0]->GetDeviceVolume(); | ||
| 609 | } | ||
| 610 | |||
| 611 | void CubebSink::SetDeviceVolume(const f32 volume) { | ||
| 612 | for (auto& stream : sink_streams) { | ||
| 613 | stream->SetDeviceVolume(volume); | ||
| 614 | } | ||
| 615 | } | ||
| 616 | |||
| 617 | void CubebSink::SetSystemVolume(const f32 volume) { | ||
| 618 | for (auto& stream : sink_streams) { | ||
| 619 | stream->SetSystemVolume(volume); | ||
| 620 | } | ||
| 621 | } | ||
| 622 | |||
| 623 | std::vector<std::string> ListCubebSinkDevices(const bool capture) { | ||
| 624 | std::vector<std::string> device_list; | ||
| 625 | cubeb* ctx; | ||
| 626 | |||
| 627 | if (cubeb_init(&ctx, "yuzu Device Enumerator", nullptr) != CUBEB_OK) { | ||
| 628 | LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); | ||
| 629 | return {}; | ||
| 630 | } | ||
| 631 | |||
| 632 | auto type{capture ? CUBEB_DEVICE_TYPE_INPUT : CUBEB_DEVICE_TYPE_OUTPUT}; | ||
| 633 | cubeb_device_collection collection; | ||
| 634 | if (cubeb_enumerate_devices(ctx, type, &collection) != CUBEB_OK) { | ||
| 635 | LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported"); | ||
| 636 | } else { | ||
| 637 | for (std::size_t i = 0; i < collection.count; i++) { | ||
| 638 | const cubeb_device_info& device = collection.device[i]; | ||
| 639 | if (device.friendly_name && device.friendly_name[0] != '\0' && | ||
| 640 | device.state == CUBEB_DEVICE_STATE_ENABLED) { | ||
| 641 | device_list.emplace_back(device.friendly_name); | ||
| 642 | } | ||
| 643 | } | ||
| 644 | cubeb_device_collection_destroy(ctx, &collection); | ||
| 645 | } | ||
| 646 | |||
| 647 | cubeb_destroy(ctx); | ||
| 648 | return device_list; | ||
| 649 | } | ||
| 650 | |||
| 651 | } // namespace AudioCore::Sink | ||
diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h new file mode 100644 index 000000000..f0f43dfa1 --- /dev/null +++ b/src/audio_core/sink/cubeb_sink.h | |||
| @@ -0,0 +1,110 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | #include <vector> | ||
| 8 | |||
| 9 | #include <cubeb/cubeb.h> | ||
| 10 | |||
| 11 | #include "audio_core/sink/sink.h" | ||
| 12 | |||
| 13 | namespace Core { | ||
| 14 | class System; | ||
| 15 | } | ||
| 16 | |||
| 17 | namespace AudioCore::Sink { | ||
| 18 | class SinkStream; | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Cubeb backend sink, holds multiple output streams and is responsible for sinking samples to | ||
| 22 | * hardware. Used by Audio Render, Audio In and Audio Out. | ||
| 23 | */ | ||
| 24 | class CubebSink final : public Sink { | ||
| 25 | public: | ||
| 26 | explicit CubebSink(std::string_view device_id); | ||
| 27 | ~CubebSink() override; | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Create a new sink stream. | ||
| 31 | * | ||
| 32 | * @param system - Core system. | ||
| 33 | * @param system_channels - Number of channels the audio system expects. | ||
| 34 | * May differ from the device's channel count. | ||
| 35 | * @param name - Name of this stream. | ||
| 36 | * @param type - Type of this stream, render/in/out. | ||
| 37 | * @param event - Audio render only, a signal used to prevent the renderer running too | ||
| 38 | * fast. | ||
| 39 | * @return A pointer to the created SinkStream | ||
| 40 | */ | ||
| 41 | SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, | ||
| 42 | const std::string& name, StreamType type) override; | ||
| 43 | |||
| 44 | /** | ||
| 45 | * Close a given stream. | ||
| 46 | * | ||
| 47 | * @param stream - The stream to close. | ||
| 48 | */ | ||
| 49 | void CloseStream(const SinkStream* stream) override; | ||
| 50 | |||
| 51 | /** | ||
| 52 | * Close all streams. | ||
| 53 | */ | ||
| 54 | void CloseStreams() override; | ||
| 55 | |||
| 56 | /** | ||
| 57 | * Pause all streams. | ||
| 58 | */ | ||
| 59 | void PauseStreams() override; | ||
| 60 | |||
| 61 | /** | ||
| 62 | * Unpause all streams. | ||
| 63 | */ | ||
| 64 | void UnpauseStreams() override; | ||
| 65 | |||
| 66 | /** | ||
| 67 | * Get the device volume. Set from calls to the IAudioDevice service. | ||
| 68 | * | ||
| 69 | * @return Volume of the device. | ||
| 70 | */ | ||
| 71 | f32 GetDeviceVolume() const override; | ||
| 72 | |||
| 73 | /** | ||
| 74 | * Set the device volume. Set from calls to the IAudioDevice service. | ||
| 75 | * | ||
| 76 | * @param volume - New volume of the device. | ||
| 77 | */ | ||
| 78 | void SetDeviceVolume(f32 volume) override; | ||
| 79 | |||
| 80 | /** | ||
| 81 | * Set the system volume. Comes from the audio system using this stream. | ||
| 82 | * | ||
| 83 | * @param volume - New volume of the system. | ||
| 84 | */ | ||
| 85 | void SetSystemVolume(f32 volume) override; | ||
| 86 | |||
| 87 | private: | ||
| 88 | /// Backend Cubeb context | ||
| 89 | cubeb* ctx{}; | ||
| 90 | /// Cubeb id of the actual hardware output device | ||
| 91 | cubeb_devid output_device{}; | ||
| 92 | /// Cubeb id of the actual hardware input device | ||
| 93 | cubeb_devid input_device{}; | ||
| 94 | /// Vector of streams managed by this sink | ||
| 95 | std::vector<SinkStreamPtr> sink_streams{}; | ||
| 96 | |||
| 97 | #ifdef _WIN32 | ||
| 98 | /// Cubeb required COM to be initialized multi-threaded on Windows | ||
| 99 | u32 com_init_result = 0; | ||
| 100 | #endif | ||
| 101 | }; | ||
| 102 | |||
| 103 | /** | ||
| 104 | * Get a list of conencted devices from Cubeb. | ||
| 105 | * | ||
| 106 | * @param capture - Return input (capture) devices if true, otherwise output devices. | ||
| 107 | */ | ||
| 108 | std::vector<std::string> ListCubebSinkDevices(bool capture); | ||
| 109 | |||
| 110 | } // namespace AudioCore::Sink | ||
diff --git a/src/audio_core/sink/null_sink.h b/src/audio_core/sink/null_sink.h new file mode 100644 index 000000000..47a342171 --- /dev/null +++ b/src/audio_core/sink/null_sink.h | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "audio_core/sink/sink.h" | ||
| 7 | #include "audio_core/sink/sink_stream.h" | ||
| 8 | |||
| 9 | namespace AudioCore::Sink { | ||
| 10 | /** | ||
| 11 | * A no-op sink for when no audio out is wanted. | ||
| 12 | */ | ||
| 13 | class NullSink final : public Sink { | ||
| 14 | public: | ||
| 15 | explicit NullSink(std::string_view) {} | ||
| 16 | ~NullSink() override = default; | ||
| 17 | |||
| 18 | SinkStream* AcquireSinkStream([[maybe_unused]] Core::System& system, | ||
| 19 | [[maybe_unused]] u32 system_channels, | ||
| 20 | [[maybe_unused]] const std::string& name, | ||
| 21 | [[maybe_unused]] StreamType type) override { | ||
| 22 | return &null_sink_stream; | ||
| 23 | } | ||
| 24 | |||
| 25 | void CloseStream([[maybe_unused]] const SinkStream* stream) override {} | ||
| 26 | void CloseStreams() override {} | ||
| 27 | void PauseStreams() override {} | ||
| 28 | void UnpauseStreams() override {} | ||
| 29 | f32 GetDeviceVolume() const override { | ||
| 30 | return 1.0f; | ||
| 31 | } | ||
| 32 | void SetDeviceVolume(f32 volume) override {} | ||
| 33 | void SetSystemVolume(f32 volume) override {} | ||
| 34 | |||
| 35 | private: | ||
| 36 | struct NullSinkStreamImpl final : SinkStream { | ||
| 37 | void Finalize() override {} | ||
| 38 | void Start(bool resume = false) override {} | ||
| 39 | void Stop() override {} | ||
| 40 | void AppendBuffer([[maybe_unused]] ::AudioCore::Sink::SinkBuffer& buffer, | ||
| 41 | [[maybe_unused]] std::vector<s16>& samples) override {} | ||
| 42 | std::vector<s16> ReleaseBuffer([[maybe_unused]] u64 num_samples) override { | ||
| 43 | return {}; | ||
| 44 | } | ||
| 45 | bool IsBufferConsumed([[maybe_unused]] const u64 tag) { | ||
| 46 | return true; | ||
| 47 | } | ||
| 48 | void ClearQueue() override {} | ||
| 49 | } null_sink_stream; | ||
| 50 | }; | ||
| 51 | |||
| 52 | } // namespace AudioCore::Sink | ||
diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp new file mode 100644 index 000000000..d6c9ec90d --- /dev/null +++ b/src/audio_core/sink/sdl2_sink.cpp | |||
| @@ -0,0 +1,556 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <atomic> | ||
| 6 | |||
| 7 | #include "audio_core/audio_core.h" | ||
| 8 | #include "audio_core/audio_event.h" | ||
| 9 | #include "audio_core/audio_manager.h" | ||
| 10 | #include "audio_core/sink/sdl2_sink.h" | ||
| 11 | #include "audio_core/sink/sink_stream.h" | ||
| 12 | #include "common/assert.h" | ||
| 13 | #include "common/fixed_point.h" | ||
| 14 | #include "common/logging/log.h" | ||
| 15 | #include "common/reader_writer_queue.h" | ||
| 16 | #include "common/ring_buffer.h" | ||
| 17 | #include "common/settings.h" | ||
| 18 | #include "core/core.h" | ||
| 19 | |||
| 20 | // Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 | ||
| 21 | #ifdef __clang__ | ||
| 22 | #pragma clang diagnostic push | ||
| 23 | #pragma clang diagnostic ignored "-Wimplicit-fallthrough" | ||
| 24 | #endif | ||
| 25 | #include <SDL.h> | ||
| 26 | #ifdef __clang__ | ||
| 27 | #pragma clang diagnostic pop | ||
| 28 | #endif | ||
| 29 | |||
| 30 | namespace AudioCore::Sink { | ||
| 31 | /** | ||
| 32 | * SDL sink stream, responsible for sinking samples to hardware. | ||
| 33 | */ | ||
| 34 | class SDLSinkStream final : public SinkStream { | ||
| 35 | public: | ||
| 36 | /** | ||
| 37 | * Create a new sink stream. | ||
| 38 | * | ||
| 39 | * @param device_channels_ - Number of channels supported by the hardware. | ||
| 40 | * @param system_channels_ - Number of channels the audio systems expect. | ||
| 41 | * @param output_device - Name of the output device to use for this stream. | ||
| 42 | * @param input_device - Name of the input device to use for this stream. | ||
| 43 | * @param type_ - Type of this stream. | ||
| 44 | * @param system_ - Core system. | ||
| 45 | * @param event - Event used only for audio renderer, signalled on buffer consume. | ||
| 46 | */ | ||
| 47 | SDLSinkStream(u32 device_channels_, const u32 system_channels_, | ||
| 48 | const std::string& output_device, const std::string& input_device, | ||
| 49 | const StreamType type_, Core::System& system_) | ||
| 50 | : type{type_}, system{system_} { | ||
| 51 | system_channels = system_channels_; | ||
| 52 | device_channels = device_channels_; | ||
| 53 | |||
| 54 | SDL_AudioSpec spec; | ||
| 55 | spec.freq = TargetSampleRate; | ||
| 56 | spec.channels = static_cast<u8>(device_channels); | ||
| 57 | spec.format = AUDIO_S16SYS; | ||
| 58 | if (type == StreamType::Render) { | ||
| 59 | spec.samples = TargetSampleCount; | ||
| 60 | } else { | ||
| 61 | spec.samples = 1024; | ||
| 62 | } | ||
| 63 | spec.callback = &SDLSinkStream::DataCallback; | ||
| 64 | spec.userdata = this; | ||
| 65 | |||
| 66 | playing_buffer.consumed = true; | ||
| 67 | |||
| 68 | std::string device_name{output_device}; | ||
| 69 | bool capture{false}; | ||
| 70 | if (type == StreamType::In) { | ||
| 71 | device_name = input_device; | ||
| 72 | capture = true; | ||
| 73 | } | ||
| 74 | |||
| 75 | SDL_AudioSpec obtained; | ||
| 76 | if (device_name.empty()) { | ||
| 77 | device = SDL_OpenAudioDevice(nullptr, capture, &spec, &obtained, false); | ||
| 78 | } else { | ||
| 79 | device = SDL_OpenAudioDevice(device_name.c_str(), capture, &spec, &obtained, false); | ||
| 80 | } | ||
| 81 | |||
| 82 | if (device == 0) { | ||
| 83 | LOG_CRITICAL(Audio_Sink, "Error opening SDL audio device: {}", SDL_GetError()); | ||
| 84 | return; | ||
| 85 | } | ||
| 86 | |||
| 87 | LOG_DEBUG(Service_Audio, | ||
| 88 | "Opening sdl stream {} with: rate {} channels {} (system channels {}) " | ||
| 89 | " samples {}", | ||
| 90 | device, obtained.freq, obtained.channels, system_channels, obtained.samples); | ||
| 91 | } | ||
| 92 | |||
| 93 | /** | ||
| 94 | * Destroy the sink stream. | ||
| 95 | */ | ||
| 96 | ~SDLSinkStream() override { | ||
| 97 | if (device == 0) { | ||
| 98 | return; | ||
| 99 | } | ||
| 100 | |||
| 101 | SDL_CloseAudioDevice(device); | ||
| 102 | } | ||
| 103 | |||
| 104 | /** | ||
| 105 | * Finalize the sink stream. | ||
| 106 | */ | ||
| 107 | void Finalize() override { | ||
| 108 | if (device == 0) { | ||
| 109 | return; | ||
| 110 | } | ||
| 111 | |||
| 112 | SDL_CloseAudioDevice(device); | ||
| 113 | } | ||
| 114 | |||
| 115 | /** | ||
| 116 | * Start the sink stream. | ||
| 117 | * | ||
| 118 | * @param resume - Set to true if this is resuming the stream a previously-active stream. | ||
| 119 | * Default false. | ||
| 120 | */ | ||
| 121 | void Start(const bool resume = false) override { | ||
| 122 | if (device == 0) { | ||
| 123 | return; | ||
| 124 | } | ||
| 125 | |||
| 126 | if (resume && was_playing) { | ||
| 127 | SDL_PauseAudioDevice(device, 0); | ||
| 128 | paused = false; | ||
| 129 | } else if (!resume) { | ||
| 130 | SDL_PauseAudioDevice(device, 0); | ||
| 131 | paused = false; | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | /** | ||
| 136 | * Stop the sink stream. | ||
| 137 | */ | ||
| 138 | void Stop() { | ||
| 139 | if (device == 0) { | ||
| 140 | return; | ||
| 141 | } | ||
| 142 | SDL_PauseAudioDevice(device, 1); | ||
| 143 | paused = true; | ||
| 144 | } | ||
| 145 | |||
| 146 | /** | ||
| 147 | * Append a new buffer and its samples to a waiting queue to play. | ||
| 148 | * | ||
| 149 | * @param buffer - Audio buffer information to be queued. | ||
| 150 | * @param samples - The s16 samples to be queue for playback. | ||
| 151 | */ | ||
| 152 | void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override { | ||
| 153 | if (type == StreamType::In) { | ||
| 154 | queue.enqueue(buffer); | ||
| 155 | queued_buffers++; | ||
| 156 | } else { | ||
| 157 | constexpr s32 min = std::numeric_limits<s16>::min(); | ||
| 158 | constexpr s32 max = std::numeric_limits<s16>::max(); | ||
| 159 | |||
| 160 | auto yuzu_volume{Settings::Volume()}; | ||
| 161 | auto volume{system_volume * device_volume * yuzu_volume}; | ||
| 162 | |||
| 163 | if (system_channels == 6 && device_channels == 2) { | ||
| 164 | // We're given 6 channels, but our device only outputs 2, so downmix. | ||
| 165 | constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; | ||
| 166 | |||
| 167 | for (u32 read_index = 0, write_index = 0; read_index < samples.size(); | ||
| 168 | read_index += system_channels, write_index += device_channels) { | ||
| 169 | const auto left_sample{ | ||
| 170 | ((Common::FixedPoint<49, 15>( | ||
| 171 | samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * | ||
| 172 | down_mix_coeff[0] + | ||
| 173 | samples[read_index + static_cast<u32>(Channels::Center)] * | ||
| 174 | down_mix_coeff[1] + | ||
| 175 | samples[read_index + static_cast<u32>(Channels::LFE)] * | ||
| 176 | down_mix_coeff[2] + | ||
| 177 | samples[read_index + static_cast<u32>(Channels::BackLeft)] * | ||
| 178 | down_mix_coeff[3]) * | ||
| 179 | volume) | ||
| 180 | .to_int()}; | ||
| 181 | |||
| 182 | const auto right_sample{ | ||
| 183 | ((Common::FixedPoint<49, 15>( | ||
| 184 | samples[read_index + static_cast<u32>(Channels::FrontRight)]) * | ||
| 185 | down_mix_coeff[0] + | ||
| 186 | samples[read_index + static_cast<u32>(Channels::Center)] * | ||
| 187 | down_mix_coeff[1] + | ||
| 188 | samples[read_index + static_cast<u32>(Channels::LFE)] * | ||
| 189 | down_mix_coeff[2] + | ||
| 190 | samples[read_index + static_cast<u32>(Channels::BackRight)] * | ||
| 191 | down_mix_coeff[3]) * | ||
| 192 | volume) | ||
| 193 | .to_int()}; | ||
| 194 | |||
| 195 | samples[write_index + static_cast<u32>(Channels::FrontLeft)] = | ||
| 196 | static_cast<s16>(std::clamp(left_sample, min, max)); | ||
| 197 | samples[write_index + static_cast<u32>(Channels::FrontRight)] = | ||
| 198 | static_cast<s16>(std::clamp(right_sample, min, max)); | ||
| 199 | } | ||
| 200 | |||
| 201 | samples.resize(samples.size() / system_channels * device_channels); | ||
| 202 | |||
| 203 | } else if (system_channels == 2 && device_channels == 6) { | ||
| 204 | // We need moar samples! Not all games will provide 6 channel audio. | ||
| 205 | // TODO: Implement some upmixing here. Currently just passthrough, with other | ||
| 206 | // channels left as silence. | ||
| 207 | std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0); | ||
| 208 | |||
| 209 | for (u32 read_index = 0, write_index = 0; read_index < samples.size(); | ||
| 210 | read_index += system_channels, write_index += device_channels) { | ||
| 211 | const auto left_sample{static_cast<s16>(std::clamp( | ||
| 212 | static_cast<s32>( | ||
| 213 | static_cast<f32>( | ||
| 214 | samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * | ||
| 215 | volume), | ||
| 216 | min, max))}; | ||
| 217 | |||
| 218 | new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample; | ||
| 219 | |||
| 220 | const auto right_sample{static_cast<s16>(std::clamp( | ||
| 221 | static_cast<s32>( | ||
| 222 | static_cast<f32>( | ||
| 223 | samples[read_index + static_cast<u32>(Channels::FrontRight)]) * | ||
| 224 | volume), | ||
| 225 | min, max))}; | ||
| 226 | |||
| 227 | new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = | ||
| 228 | right_sample; | ||
| 229 | } | ||
| 230 | samples = std::move(new_samples); | ||
| 231 | |||
| 232 | } else if (volume != 1.0f) { | ||
| 233 | for (u32 i = 0; i < samples.size(); i++) { | ||
| 234 | samples[i] = static_cast<s16>(std::clamp( | ||
| 235 | static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); | ||
| 236 | } | ||
| 237 | } | ||
| 238 | |||
| 239 | samples_buffer.Push(samples); | ||
| 240 | queue.enqueue(buffer); | ||
| 241 | queued_buffers++; | ||
| 242 | } | ||
| 243 | } | ||
| 244 | |||
| 245 | /** | ||
| 246 | * Release a buffer. Audio In only, will fill a buffer with recorded samples. | ||
| 247 | * | ||
| 248 | * @param num_samples - Maximum number of samples to receive. | ||
| 249 | * @return Vector of recorded samples. May have fewer than num_samples. | ||
| 250 | */ | ||
| 251 | std::vector<s16> ReleaseBuffer(const u64 num_samples) override { | ||
| 252 | static constexpr s32 min = std::numeric_limits<s16>::min(); | ||
| 253 | static constexpr s32 max = std::numeric_limits<s16>::max(); | ||
| 254 | |||
| 255 | auto samples{samples_buffer.Pop(num_samples)}; | ||
| 256 | |||
| 257 | // TODO: Up-mix to 6 channels if the game expects it. | ||
| 258 | // For audio input this is unlikely to ever be the case though. | ||
| 259 | |||
| 260 | // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here. | ||
| 261 | // TODO: Play with this and find something that works better. | ||
| 262 | auto volume{system_volume * device_volume * 8}; | ||
| 263 | for (u32 i = 0; i < samples.size(); i++) { | ||
| 264 | samples[i] = static_cast<s16>( | ||
| 265 | std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); | ||
| 266 | } | ||
| 267 | |||
| 268 | if (samples.size() < num_samples) { | ||
| 269 | samples.resize(num_samples, 0); | ||
| 270 | } | ||
| 271 | return samples; | ||
| 272 | } | ||
| 273 | |||
| 274 | /** | ||
| 275 | * Check if a certain buffer has been consumed (fully played). | ||
| 276 | * | ||
| 277 | * @param tag - Unique tag of a buffer to check for. | ||
| 278 | * @return True if the buffer has been played, otherwise false. | ||
| 279 | */ | ||
| 280 | bool IsBufferConsumed(const u64 tag) override { | ||
| 281 | if (released_buffer.tag == 0) { | ||
| 282 | if (!released_buffers.try_dequeue(released_buffer)) { | ||
| 283 | return false; | ||
| 284 | } | ||
| 285 | } | ||
| 286 | |||
| 287 | if (released_buffer.tag == tag) { | ||
| 288 | released_buffer.tag = 0; | ||
| 289 | return true; | ||
| 290 | } | ||
| 291 | return false; | ||
| 292 | } | ||
| 293 | |||
| 294 | /** | ||
| 295 | * Empty out the buffer queue. | ||
| 296 | */ | ||
| 297 | void ClearQueue() override { | ||
| 298 | samples_buffer.Pop(); | ||
| 299 | while (queue.pop()) { | ||
| 300 | } | ||
| 301 | while (released_buffers.pop()) { | ||
| 302 | } | ||
| 303 | released_buffer = {}; | ||
| 304 | playing_buffer = {}; | ||
| 305 | playing_buffer.consumed = true; | ||
| 306 | queued_buffers = 0; | ||
| 307 | } | ||
| 308 | |||
| 309 | private: | ||
| 310 | /** | ||
| 311 | * Signal events back to the audio system that a buffer was played/can be filled. | ||
| 312 | * | ||
| 313 | * @param buffer - Consumed audio buffer to be released. | ||
| 314 | */ | ||
| 315 | void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) { | ||
| 316 | auto& manager{system.AudioCore().GetAudioManager()}; | ||
| 317 | switch (type) { | ||
| 318 | case StreamType::Out: | ||
| 319 | released_buffers.enqueue(buffer); | ||
| 320 | manager.SetEvent(Event::Type::AudioOutManager, true); | ||
| 321 | break; | ||
| 322 | case StreamType::In: | ||
| 323 | released_buffers.enqueue(buffer); | ||
| 324 | manager.SetEvent(Event::Type::AudioInManager, true); | ||
| 325 | break; | ||
| 326 | case StreamType::Render: | ||
| 327 | break; | ||
| 328 | } | ||
| 329 | } | ||
| 330 | |||
| 331 | /** | ||
| 332 | * Main callback from SDL. Either expects samples from us (audio render/audio out), or will | ||
| 333 | * provide samples to be copied (audio in). | ||
| 334 | * | ||
| 335 | * @param userdata - Custom data pointer passed along, points to a SDLSinkStream. | ||
| 336 | * @param stream - Buffer of samples to be filled or read. | ||
| 337 | * @param len - Length of the stream in bytes. | ||
| 338 | */ | ||
| 339 | static void DataCallback(void* userdata, Uint8* stream, int len) { | ||
| 340 | auto* impl = static_cast<SDLSinkStream*>(userdata); | ||
| 341 | |||
| 342 | if (!impl) { | ||
| 343 | return; | ||
| 344 | } | ||
| 345 | |||
| 346 | const std::size_t num_channels = impl->GetDeviceChannels(); | ||
| 347 | const std::size_t frame_size = num_channels; | ||
| 348 | const std::size_t frame_size_bytes = frame_size * sizeof(s16); | ||
| 349 | const std::size_t num_frames{len / num_channels / sizeof(s16)}; | ||
| 350 | size_t frames_written{0}; | ||
| 351 | [[maybe_unused]] bool underrun{false}; | ||
| 352 | |||
| 353 | if (impl->type == StreamType::In) { | ||
| 354 | std::span<s16> input_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; | ||
| 355 | |||
| 356 | while (frames_written < num_frames) { | ||
| 357 | auto& playing_buffer{impl->playing_buffer}; | ||
| 358 | |||
| 359 | // If the playing buffer has been consumed or has no frames, we need a new one | ||
| 360 | if (playing_buffer.consumed || playing_buffer.frames == 0) { | ||
| 361 | if (!impl->queue.try_dequeue(impl->playing_buffer)) { | ||
| 362 | // If no buffer was available we've underrun, just push the samples and | ||
| 363 | // continue. | ||
| 364 | underrun = true; | ||
| 365 | impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], | ||
| 366 | (num_frames - frames_written) * frame_size); | ||
| 367 | frames_written = num_frames; | ||
| 368 | continue; | ||
| 369 | } else { | ||
| 370 | impl->queued_buffers--; | ||
| 371 | impl->SignalEvent(impl->playing_buffer); | ||
| 372 | } | ||
| 373 | } | ||
| 374 | |||
| 375 | // Get the minimum frames available between the currently playing buffer, and the | ||
| 376 | // amount we have left to fill | ||
| 377 | size_t frames_available{ | ||
| 378 | std::min(playing_buffer.frames - playing_buffer.frames_played, | ||
| 379 | num_frames - frames_written)}; | ||
| 380 | |||
| 381 | impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], | ||
| 382 | frames_available * frame_size); | ||
| 383 | |||
| 384 | frames_written += frames_available; | ||
| 385 | playing_buffer.frames_played += frames_available; | ||
| 386 | |||
| 387 | // If that's all the frames in the current buffer, add its samples and mark it as | ||
| 388 | // consumed | ||
| 389 | if (playing_buffer.frames_played >= playing_buffer.frames) { | ||
| 390 | impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); | ||
| 391 | impl->playing_buffer.consumed = true; | ||
| 392 | } | ||
| 393 | } | ||
| 394 | |||
| 395 | std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size], | ||
| 396 | frame_size_bytes); | ||
| 397 | } else { | ||
| 398 | std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; | ||
| 399 | |||
| 400 | while (frames_written < num_frames) { | ||
| 401 | auto& playing_buffer{impl->playing_buffer}; | ||
| 402 | |||
| 403 | // If the playing buffer has been consumed or has no frames, we need a new one | ||
| 404 | if (playing_buffer.consumed || playing_buffer.frames == 0) { | ||
| 405 | if (!impl->queue.try_dequeue(impl->playing_buffer)) { | ||
| 406 | // If no buffer was available we've underrun, fill the remaining buffer with | ||
| 407 | // the last written frame and continue. | ||
| 408 | underrun = true; | ||
| 409 | for (size_t i = frames_written; i < num_frames; i++) { | ||
| 410 | std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0], | ||
| 411 | frame_size_bytes); | ||
| 412 | } | ||
| 413 | frames_written = num_frames; | ||
| 414 | continue; | ||
| 415 | } else { | ||
| 416 | impl->queued_buffers--; | ||
| 417 | impl->SignalEvent(impl->playing_buffer); | ||
| 418 | } | ||
| 419 | } | ||
| 420 | |||
| 421 | // Get the minimum frames available between the currently playing buffer, and the | ||
| 422 | // amount we have left to fill | ||
| 423 | size_t frames_available{ | ||
| 424 | std::min(playing_buffer.frames - playing_buffer.frames_played, | ||
| 425 | num_frames - frames_written)}; | ||
| 426 | |||
| 427 | impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size], | ||
| 428 | frames_available * frame_size); | ||
| 429 | |||
| 430 | frames_written += frames_available; | ||
| 431 | playing_buffer.frames_played += frames_available; | ||
| 432 | |||
| 433 | // If that's all the frames in the current buffer, add its samples and mark it as | ||
| 434 | // consumed | ||
| 435 | if (playing_buffer.frames_played >= playing_buffer.frames) { | ||
| 436 | impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); | ||
| 437 | impl->playing_buffer.consumed = true; | ||
| 438 | } | ||
| 439 | } | ||
| 440 | |||
| 441 | std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size], | ||
| 442 | frame_size_bytes); | ||
| 443 | } | ||
| 444 | } | ||
| 445 | |||
| 446 | /// SDL device id of the opened input/output device | ||
| 447 | SDL_AudioDeviceID device{}; | ||
| 448 | /// Type of this stream | ||
| 449 | StreamType type; | ||
| 450 | /// Core system | ||
| 451 | Core::System& system; | ||
| 452 | /// Ring buffer of the samples waiting to be played or consumed | ||
| 453 | Common::RingBuffer<s16, 0x10000> samples_buffer; | ||
| 454 | /// Audio buffers queued and waiting to play | ||
| 455 | Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue; | ||
| 456 | /// The currently-playing audio buffer | ||
| 457 | ::AudioCore::Sink::SinkBuffer playing_buffer{}; | ||
| 458 | /// Audio buffers which have been played and are in queue to be released by the audio system | ||
| 459 | Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{}; | ||
| 460 | /// Currently released buffer waiting to be taken by the audio system | ||
| 461 | ::AudioCore::Sink::SinkBuffer released_buffer{}; | ||
| 462 | /// The last played (or received) frame of audio, used when the callback underruns | ||
| 463 | std::array<s16, MaxChannels> last_frame{}; | ||
| 464 | }; | ||
| 465 | |||
| 466 | SDLSink::SDLSink(std::string_view target_device_name) { | ||
| 467 | if (!SDL_WasInit(SDL_INIT_AUDIO)) { | ||
| 468 | if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { | ||
| 469 | LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); | ||
| 470 | return; | ||
| 471 | } | ||
| 472 | } | ||
| 473 | |||
| 474 | if (target_device_name != auto_device_name && !target_device_name.empty()) { | ||
| 475 | output_device = target_device_name; | ||
| 476 | } else { | ||
| 477 | output_device.clear(); | ||
| 478 | } | ||
| 479 | |||
| 480 | device_channels = 2; | ||
| 481 | } | ||
| 482 | |||
| 483 | SDLSink::~SDLSink() = default; | ||
| 484 | |||
| 485 | SinkStream* SDLSink::AcquireSinkStream(Core::System& system, const u32 system_channels, | ||
| 486 | const std::string&, const StreamType type) { | ||
| 487 | SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>( | ||
| 488 | device_channels, system_channels, output_device, input_device, type, system)); | ||
| 489 | return stream.get(); | ||
| 490 | } | ||
| 491 | |||
| 492 | void SDLSink::CloseStream(const SinkStream* stream) { | ||
| 493 | for (size_t i = 0; i < sink_streams.size(); i++) { | ||
| 494 | if (sink_streams[i].get() == stream) { | ||
| 495 | sink_streams[i].reset(); | ||
| 496 | sink_streams.erase(sink_streams.begin() + i); | ||
| 497 | break; | ||
| 498 | } | ||
| 499 | } | ||
| 500 | } | ||
| 501 | |||
| 502 | void SDLSink::CloseStreams() { | ||
| 503 | sink_streams.clear(); | ||
| 504 | } | ||
| 505 | |||
| 506 | void SDLSink::PauseStreams() { | ||
| 507 | for (auto& stream : sink_streams) { | ||
| 508 | stream->Stop(); | ||
| 509 | } | ||
| 510 | } | ||
| 511 | |||
| 512 | void SDLSink::UnpauseStreams() { | ||
| 513 | for (auto& stream : sink_streams) { | ||
| 514 | stream->Start(); | ||
| 515 | } | ||
| 516 | } | ||
| 517 | |||
| 518 | f32 SDLSink::GetDeviceVolume() const { | ||
| 519 | if (sink_streams.empty()) { | ||
| 520 | return 1.0f; | ||
| 521 | } | ||
| 522 | |||
| 523 | return sink_streams[0]->GetDeviceVolume(); | ||
| 524 | } | ||
| 525 | |||
| 526 | void SDLSink::SetDeviceVolume(const f32 volume) { | ||
| 527 | for (auto& stream : sink_streams) { | ||
| 528 | stream->SetDeviceVolume(volume); | ||
| 529 | } | ||
| 530 | } | ||
| 531 | |||
| 532 | void SDLSink::SetSystemVolume(const f32 volume) { | ||
| 533 | for (auto& stream : sink_streams) { | ||
| 534 | stream->SetSystemVolume(volume); | ||
| 535 | } | ||
| 536 | } | ||
| 537 | |||
| 538 | std::vector<std::string> ListSDLSinkDevices(const bool capture) { | ||
| 539 | std::vector<std::string> device_list; | ||
| 540 | |||
| 541 | if (!SDL_WasInit(SDL_INIT_AUDIO)) { | ||
| 542 | if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { | ||
| 543 | LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); | ||
| 544 | return {}; | ||
| 545 | } | ||
| 546 | } | ||
| 547 | |||
| 548 | const int device_count = SDL_GetNumAudioDevices(capture); | ||
| 549 | for (int i = 0; i < device_count; ++i) { | ||
| 550 | device_list.emplace_back(SDL_GetAudioDeviceName(i, 0)); | ||
| 551 | } | ||
| 552 | |||
| 553 | return device_list; | ||
| 554 | } | ||
| 555 | |||
| 556 | } // namespace AudioCore::Sink | ||
diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h new file mode 100644 index 000000000..186bc2fa3 --- /dev/null +++ b/src/audio_core/sink/sdl2_sink.h | |||
| @@ -0,0 +1,101 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | #include <vector> | ||
| 8 | |||
| 9 | #include "audio_core/sink/sink.h" | ||
| 10 | |||
| 11 | namespace Core { | ||
| 12 | class System; | ||
| 13 | } | ||
| 14 | |||
| 15 | namespace AudioCore::Sink { | ||
| 16 | class SinkStream; | ||
| 17 | |||
| 18 | /** | ||
| 19 | * SDL backend sink, holds multiple output streams and is responsible for sinking samples to | ||
| 20 | * hardware. Used by Audio Render, Audio In and Audio Out. | ||
| 21 | */ | ||
| 22 | class SDLSink final : public Sink { | ||
| 23 | public: | ||
| 24 | explicit SDLSink(std::string_view device_id); | ||
| 25 | ~SDLSink() override; | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Create a new sink stream. | ||
| 29 | * | ||
| 30 | * @param system - Core system. | ||
| 31 | * @param system_channels - Number of channels the audio system expects. | ||
| 32 | * May differ from the device's channel count. | ||
| 33 | * @param name - Name of this stream. | ||
| 34 | * @param type - Type of this stream, render/in/out. | ||
| 35 | * @param event - Audio render only, a signal used to prevent the renderer running too | ||
| 36 | * fast. | ||
| 37 | * @return A pointer to the created SinkStream | ||
| 38 | */ | ||
| 39 | SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, | ||
| 40 | const std::string& name, StreamType type) override; | ||
| 41 | |||
| 42 | /** | ||
| 43 | * Close a given stream. | ||
| 44 | * | ||
| 45 | * @param stream - The stream to close. | ||
| 46 | */ | ||
| 47 | void CloseStream(const SinkStream* stream) override; | ||
| 48 | |||
| 49 | /** | ||
| 50 | * Close all streams. | ||
| 51 | */ | ||
| 52 | void CloseStreams() override; | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Pause all streams. | ||
| 56 | */ | ||
| 57 | void PauseStreams() override; | ||
| 58 | |||
| 59 | /** | ||
| 60 | * Unpause all streams. | ||
| 61 | */ | ||
| 62 | void UnpauseStreams() override; | ||
| 63 | |||
| 64 | /** | ||
| 65 | * Get the device volume. Set from calls to the IAudioDevice service. | ||
| 66 | * | ||
| 67 | * @return Volume of the device. | ||
| 68 | */ | ||
| 69 | f32 GetDeviceVolume() const override; | ||
| 70 | |||
| 71 | /** | ||
| 72 | * Set the device volume. Set from calls to the IAudioDevice service. | ||
| 73 | * | ||
| 74 | * @param volume - New volume of the device. | ||
| 75 | */ | ||
| 76 | void SetDeviceVolume(f32 volume) override; | ||
| 77 | |||
| 78 | /** | ||
| 79 | * Set the system volume. Comes from the audio system using this stream. | ||
| 80 | * | ||
| 81 | * @param volume - New volume of the system. | ||
| 82 | */ | ||
| 83 | void SetSystemVolume(f32 volume) override; | ||
| 84 | |||
| 85 | private: | ||
| 86 | /// Name of the output device used by streams | ||
| 87 | std::string output_device; | ||
| 88 | /// Name of the input device used by streams | ||
| 89 | std::string input_device; | ||
| 90 | /// Vector of streams managed by this sink | ||
| 91 | std::vector<SinkStreamPtr> sink_streams; | ||
| 92 | }; | ||
| 93 | |||
| 94 | /** | ||
| 95 | * Get a list of conencted devices from Cubeb. | ||
| 96 | * | ||
| 97 | * @param capture - Return input (capture) devices if true, otherwise output devices. | ||
| 98 | */ | ||
| 99 | std::vector<std::string> ListSDLSinkDevices(bool capture); | ||
| 100 | |||
| 101 | } // namespace AudioCore::Sink | ||
diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h new file mode 100644 index 000000000..91fe455e4 --- /dev/null +++ b/src/audio_core/sink/sink.h | |||
| @@ -0,0 +1,106 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <memory> | ||
| 7 | #include <string> | ||
| 8 | |||
| 9 | #include "audio_core/sink/sink_stream.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace Common { | ||
| 13 | class Event; | ||
| 14 | } | ||
| 15 | namespace Core { | ||
| 16 | class System; | ||
| 17 | } | ||
| 18 | |||
| 19 | namespace AudioCore::Sink { | ||
| 20 | |||
| 21 | constexpr char auto_device_name[] = "auto"; | ||
| 22 | |||
| 23 | /** | ||
| 24 | * This class is an interface for an audio sink, holds multiple output streams and is responsible | ||
| 25 | * for sinking samples to hardware. Used by Audio Render, Audio In and Audio Out. | ||
| 26 | */ | ||
| 27 | class Sink { | ||
| 28 | public: | ||
| 29 | virtual ~Sink() = default; | ||
| 30 | /** | ||
| 31 | * Close a given stream. | ||
| 32 | * | ||
| 33 | * @param stream - The stream to close. | ||
| 34 | */ | ||
| 35 | virtual void CloseStream(const SinkStream* stream) = 0; | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Close all streams. | ||
| 39 | */ | ||
| 40 | virtual void CloseStreams() = 0; | ||
| 41 | |||
| 42 | /** | ||
| 43 | * Pause all streams. | ||
| 44 | */ | ||
| 45 | virtual void PauseStreams() = 0; | ||
| 46 | |||
| 47 | /** | ||
| 48 | * Unpause all streams. | ||
| 49 | */ | ||
| 50 | virtual void UnpauseStreams() = 0; | ||
| 51 | |||
| 52 | /** | ||
| 53 | * Create a new sink stream, kept within this sink, with a pointer returned for use. | ||
| 54 | * Do not free the returned pointer. When done with the stream, call CloseStream on the sink. | ||
| 55 | * | ||
| 56 | * @param system - Core system. | ||
| 57 | * @param system_channels - Number of channels the audio system expects. | ||
| 58 | * May differ from the device's channel count. | ||
| 59 | * @param name - Name of this stream. | ||
| 60 | * @param type - Type of this stream, render/in/out. | ||
| 61 | * @param event - Audio render only, a signal used to prevent the renderer running too | ||
| 62 | * fast. | ||
| 63 | * @return A pointer to the created SinkStream | ||
| 64 | */ | ||
| 65 | virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, | ||
| 66 | const std::string& name, StreamType type) = 0; | ||
| 67 | |||
| 68 | /** | ||
| 69 | * Get the number of channels the hardware device supports. | ||
| 70 | * Either 2 or 6. | ||
| 71 | * | ||
| 72 | * @return Number of device channels. | ||
| 73 | */ | ||
| 74 | u32 GetDeviceChannels() const { | ||
| 75 | return device_channels; | ||
| 76 | } | ||
| 77 | |||
| 78 | /** | ||
| 79 | * Get the device volume. Set from calls to the IAudioDevice service. | ||
| 80 | * | ||
| 81 | * @return Volume of the device. | ||
| 82 | */ | ||
| 83 | virtual f32 GetDeviceVolume() const = 0; | ||
| 84 | |||
| 85 | /** | ||
| 86 | * Set the device volume. Set from calls to the IAudioDevice service. | ||
| 87 | * | ||
| 88 | * @param volume - New volume of the device. | ||
| 89 | */ | ||
| 90 | virtual void SetDeviceVolume(f32 volume) = 0; | ||
| 91 | |||
| 92 | /** | ||
| 93 | * Set the system volume. Comes from the audio system using this stream. | ||
| 94 | * | ||
| 95 | * @param volume - New volume of the system. | ||
| 96 | */ | ||
| 97 | virtual void SetSystemVolume(f32 volume) = 0; | ||
| 98 | |||
| 99 | protected: | ||
| 100 | /// Number of device channels supported by the hardware | ||
| 101 | u32 device_channels{2}; | ||
| 102 | }; | ||
| 103 | |||
| 104 | using SinkPtr = std::unique_ptr<Sink>; | ||
| 105 | |||
| 106 | } // namespace AudioCore::Sink | ||
diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink/sink_details.cpp index c4cc66111..253c0fd1e 100644 --- a/src/audio_core/sink_details.cpp +++ b/src/audio_core/sink/sink_details.cpp | |||
| @@ -5,21 +5,21 @@ | |||
| 5 | #include <memory> | 5 | #include <memory> |
| 6 | #include <string> | 6 | #include <string> |
| 7 | #include <vector> | 7 | #include <vector> |
| 8 | #include "audio_core/null_sink.h" | 8 | #include "audio_core/sink/null_sink.h" |
| 9 | #include "audio_core/sink_details.h" | 9 | #include "audio_core/sink/sink_details.h" |
| 10 | #ifdef HAVE_CUBEB | 10 | #ifdef HAVE_CUBEB |
| 11 | #include "audio_core/cubeb_sink.h" | 11 | #include "audio_core/sink/cubeb_sink.h" |
| 12 | #endif | 12 | #endif |
| 13 | #ifdef HAVE_SDL2 | 13 | #ifdef HAVE_SDL2 |
| 14 | #include "audio_core/sdl2_sink.h" | 14 | #include "audio_core/sink/sdl2_sink.h" |
| 15 | #endif | 15 | #endif |
| 16 | #include "common/logging/log.h" | 16 | #include "common/logging/log.h" |
| 17 | 17 | ||
| 18 | namespace AudioCore { | 18 | namespace AudioCore::Sink { |
| 19 | namespace { | 19 | namespace { |
| 20 | struct SinkDetails { | 20 | struct SinkDetails { |
| 21 | using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); | 21 | using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); |
| 22 | using ListDevicesFn = std::vector<std::string> (*)(); | 22 | using ListDevicesFn = std::vector<std::string> (*)(bool); |
| 23 | 23 | ||
| 24 | /// Name for this sink. | 24 | /// Name for this sink. |
| 25 | const char* id; | 25 | const char* id; |
| @@ -49,17 +49,18 @@ constexpr SinkDetails sink_details[] = { | |||
| 49 | [](std::string_view device_id) -> std::unique_ptr<Sink> { | 49 | [](std::string_view device_id) -> std::unique_ptr<Sink> { |
| 50 | return std::make_unique<NullSink>(device_id); | 50 | return std::make_unique<NullSink>(device_id); |
| 51 | }, | 51 | }, |
| 52 | [] { return std::vector<std::string>{"null"}; }}, | 52 | [](bool capture) { return std::vector<std::string>{"null"}; }}, |
| 53 | }; | 53 | }; |
| 54 | 54 | ||
| 55 | const SinkDetails& GetSinkDetails(std::string_view sink_id) { | 55 | const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) { |
| 56 | auto iter = | 56 | auto iter = |
| 57 | std::find_if(std::begin(sink_details), std::end(sink_details), | 57 | std::find_if(std::begin(sink_details), std::end(sink_details), |
| 58 | [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); | 58 | [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); |
| 59 | 59 | ||
| 60 | if (sink_id == "auto" || iter == std::end(sink_details)) { | 60 | if (sink_id == "auto" || iter == std::end(sink_details)) { |
| 61 | if (sink_id != "auto") { | 61 | if (sink_id != "auto") { |
| 62 | LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id); | 62 | LOG_ERROR(Audio, "AudioCore::Sink::GetOutputSinkDetails given invalid sink_id {}", |
| 63 | sink_id); | ||
| 63 | } | 64 | } |
| 64 | // Auto-select. | 65 | // Auto-select. |
| 65 | // sink_details is ordered in terms of desirability, with the best choice at the front. | 66 | // sink_details is ordered in terms of desirability, with the best choice at the front. |
| @@ -79,12 +80,12 @@ std::vector<const char*> GetSinkIDs() { | |||
| 79 | return sink_ids; | 80 | return sink_ids; |
| 80 | } | 81 | } |
| 81 | 82 | ||
| 82 | std::vector<std::string> GetDeviceListForSink(std::string_view sink_id) { | 83 | std::vector<std::string> GetDeviceListForSink(std::string_view sink_id, bool capture) { |
| 83 | return GetSinkDetails(sink_id).list_devices(); | 84 | return GetOutputSinkDetails(sink_id).list_devices(capture); |
| 84 | } | 85 | } |
| 85 | 86 | ||
| 86 | std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) { | 87 | std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) { |
| 87 | return GetSinkDetails(sink_id).factory(device_id); | 88 | return GetOutputSinkDetails(sink_id).factory(device_id); |
| 88 | } | 89 | } |
| 89 | 90 | ||
| 90 | } // namespace AudioCore | 91 | } // namespace AudioCore::Sink |
diff --git a/src/audio_core/sink/sink_details.h b/src/audio_core/sink/sink_details.h new file mode 100644 index 000000000..3ebdb1e30 --- /dev/null +++ b/src/audio_core/sink/sink_details.h | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | #include <string_view> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | namespace AudioCore { | ||
| 11 | class AudioManager; | ||
| 12 | |||
| 13 | namespace Sink { | ||
| 14 | |||
| 15 | class Sink; | ||
| 16 | |||
| 17 | /** | ||
| 18 | * Retrieves the IDs for all available audio sinks. | ||
| 19 | * | ||
| 20 | * @return Vector of available sink names. | ||
| 21 | */ | ||
| 22 | std::vector<const char*> GetSinkIDs(); | ||
| 23 | |||
| 24 | /** | ||
| 25 | * Gets the list of devices for a particular sink identified by the given ID. | ||
| 26 | * | ||
| 27 | * @param sink_id - Id of the sink to get devices from. | ||
| 28 | * @param capture - Get capture (input) devices, or output devices? | ||
| 29 | * @return Vector of device names. | ||
| 30 | */ | ||
| 31 | std::vector<std::string> GetDeviceListForSink(std::string_view sink_id, bool capture); | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Creates an audio sink identified by the given device ID. | ||
| 35 | * | ||
| 36 | * @param sink_id - Id of the sink to create. | ||
| 37 | * @param device_id - Name of the device to create. | ||
| 38 | * @return Pointer to the created sink. | ||
| 39 | */ | ||
| 40 | std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id); | ||
| 41 | |||
| 42 | } // namespace Sink | ||
| 43 | } // namespace AudioCore | ||
diff --git a/src/audio_core/sink/sink_stream.h b/src/audio_core/sink/sink_stream.h new file mode 100644 index 000000000..17ed6593f --- /dev/null +++ b/src/audio_core/sink/sink_stream.h | |||
| @@ -0,0 +1,224 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <atomic> | ||
| 7 | #include <memory> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include "audio_core/common/common.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | |||
| 13 | namespace AudioCore::Sink { | ||
| 14 | |||
| 15 | enum class StreamType { | ||
| 16 | Render, | ||
| 17 | Out, | ||
| 18 | In, | ||
| 19 | }; | ||
| 20 | |||
| 21 | struct SinkBuffer { | ||
| 22 | u64 frames; | ||
| 23 | u64 frames_played; | ||
| 24 | u64 tag; | ||
| 25 | bool consumed; | ||
| 26 | }; | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Contains a real backend stream for outputting samples to hardware, | ||
| 30 | * created only via a Sink (See Sink::AcquireSinkStream). | ||
| 31 | * | ||
| 32 | * Accepts a SinkBuffer and samples in PCM16 format to be output (see AppendBuffer). | ||
| 33 | * Appended buffers act as a FIFO queue, and will be held until played. | ||
| 34 | * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer | ||
| 35 | * has been consumed. | ||
| 36 | * | ||
| 37 | * Since these are a FIFO queue, always check IsBufferConsumed in the same order you appended the | ||
| 38 | * buffers, skipping a buffer will result in all following buffers to never release. | ||
| 39 | * | ||
| 40 | * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this | ||
| 41 | * is what games do), or call ClearQueue to flush all of the buffers without a full restart. | ||
| 42 | */ | ||
| 43 | class SinkStream { | ||
| 44 | public: | ||
| 45 | virtual ~SinkStream() = default; | ||
| 46 | |||
| 47 | /** | ||
| 48 | * Finalize the sink stream. | ||
| 49 | */ | ||
| 50 | virtual void Finalize() = 0; | ||
| 51 | |||
| 52 | /** | ||
| 53 | * Start the sink stream. | ||
| 54 | * | ||
| 55 | * @param resume - Set to true if this is resuming the stream a previously-active stream. | ||
| 56 | * Default false. | ||
| 57 | */ | ||
| 58 | virtual void Start(bool resume = false) = 0; | ||
| 59 | |||
| 60 | /** | ||
| 61 | * Stop the sink stream. | ||
| 62 | */ | ||
| 63 | virtual void Stop() = 0; | ||
| 64 | |||
| 65 | /** | ||
| 66 | * Append a new buffer and its samples to a waiting queue to play. | ||
| 67 | * | ||
| 68 | * @param buffer - Audio buffer information to be queued. | ||
| 69 | * @param samples - The s16 samples to be queue for playback. | ||
| 70 | */ | ||
| 71 | virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) = 0; | ||
| 72 | |||
| 73 | /** | ||
| 74 | * Release a buffer. Audio In only, will fill a buffer with recorded samples. | ||
| 75 | * | ||
| 76 | * @param num_samples - Maximum number of samples to receive. | ||
| 77 | * @return Vector of recorded samples. May have fewer than num_samples. | ||
| 78 | */ | ||
| 79 | virtual std::vector<s16> ReleaseBuffer(u64 num_samples) = 0; | ||
| 80 | |||
| 81 | /** | ||
| 82 | * Check if a certain buffer has been consumed (fully played). | ||
| 83 | * | ||
| 84 | * @param tag - Unique tag of a buffer to check for. | ||
| 85 | * @return True if the buffer has been played, otherwise false. | ||
| 86 | */ | ||
| 87 | virtual bool IsBufferConsumed(u64 tag) = 0; | ||
| 88 | |||
| 89 | /** | ||
| 90 | * Empty out the buffer queue. | ||
| 91 | */ | ||
| 92 | virtual void ClearQueue() = 0; | ||
| 93 | |||
| 94 | /** | ||
| 95 | * Check if the stream is paused. | ||
| 96 | * | ||
| 97 | * @return True if paused, otherwise false. | ||
| 98 | */ | ||
| 99 | bool IsPaused() { | ||
| 100 | return paused; | ||
| 101 | } | ||
| 102 | |||
| 103 | /** | ||
| 104 | * Get the number of system channels in this stream. | ||
| 105 | * | ||
| 106 | * @return Number of system channels. | ||
| 107 | */ | ||
| 108 | u32 GetSystemChannels() const { | ||
| 109 | return system_channels; | ||
| 110 | } | ||
| 111 | |||
| 112 | /** | ||
| 113 | * Set the number of channels the system expects. | ||
| 114 | * | ||
| 115 | * @param channels - New number of system channels. | ||
| 116 | */ | ||
| 117 | void SetSystemChannels(u32 channels) { | ||
| 118 | system_channels = channels; | ||
| 119 | } | ||
| 120 | |||
| 121 | /** | ||
| 122 | * Get the number of channels the hardware supports. | ||
| 123 | * | ||
| 124 | * @return Number of channels supported. | ||
| 125 | */ | ||
| 126 | u32 GetDeviceChannels() const { | ||
| 127 | return device_channels; | ||
| 128 | } | ||
| 129 | |||
| 130 | /** | ||
| 131 | * Get the total number of samples played by this stream. | ||
| 132 | * | ||
| 133 | * @return Number of samples played. | ||
| 134 | */ | ||
| 135 | u64 GetPlayedSampleCount() const { | ||
| 136 | return played_sample_count; | ||
| 137 | } | ||
| 138 | |||
| 139 | /** | ||
| 140 | * Set the number of samples played. | ||
| 141 | * This is started and stopped on system start/stop. | ||
| 142 | * | ||
| 143 | * @param played_sample_count_ - Number of samples to set. | ||
| 144 | */ | ||
| 145 | void SetPlayedSampleCount(u64 played_sample_count_) { | ||
| 146 | played_sample_count = played_sample_count_; | ||
| 147 | } | ||
| 148 | |||
| 149 | /** | ||
| 150 | * Add to the played sample count. | ||
| 151 | * | ||
| 152 | * @param num_samples - Number of samples to add. | ||
| 153 | */ | ||
| 154 | void AddPlayedSampleCount(u64 num_samples) { | ||
| 155 | played_sample_count += num_samples; | ||
| 156 | } | ||
| 157 | |||
| 158 | /** | ||
| 159 | * Get the system volume. | ||
| 160 | * | ||
| 161 | * @return The current system volume. | ||
| 162 | */ | ||
| 163 | f32 GetSystemVolume() const { | ||
| 164 | return system_volume; | ||
| 165 | } | ||
| 166 | |||
| 167 | /** | ||
| 168 | * Get the device volume. | ||
| 169 | * | ||
| 170 | * @return The current device volume. | ||
| 171 | */ | ||
| 172 | f32 GetDeviceVolume() const { | ||
| 173 | return device_volume; | ||
| 174 | } | ||
| 175 | |||
| 176 | /** | ||
| 177 | * Set the system volume. | ||
| 178 | * | ||
| 179 | * @param volume_ - The new system volume. | ||
| 180 | */ | ||
| 181 | void SetSystemVolume(f32 volume_) { | ||
| 182 | system_volume = volume_; | ||
| 183 | } | ||
| 184 | |||
| 185 | /** | ||
| 186 | * Set the device volume. | ||
| 187 | * | ||
| 188 | * @param volume_ - The new device volume. | ||
| 189 | */ | ||
| 190 | void SetDeviceVolume(f32 volume_) { | ||
| 191 | device_volume = volume_; | ||
| 192 | } | ||
| 193 | |||
| 194 | /** | ||
| 195 | * Get the number of queued audio buffers. | ||
| 196 | * | ||
| 197 | * @return The number of queued buffers. | ||
| 198 | */ | ||
| 199 | u32 GetQueueSize() { | ||
| 200 | return queued_buffers.load(); | ||
| 201 | } | ||
| 202 | |||
| 203 | protected: | ||
| 204 | /// Number of buffers waiting to be played | ||
| 205 | std::atomic<u32> queued_buffers{}; | ||
| 206 | /// Total samples played by this stream | ||
| 207 | std::atomic<u64> played_sample_count{}; | ||
| 208 | /// Set by the audio render/in/out system which uses this stream | ||
| 209 | f32 system_volume{1.0f}; | ||
| 210 | /// Set via IAudioDevice service calls | ||
| 211 | f32 device_volume{1.0f}; | ||
| 212 | /// Set by the audio render/in/out systen which uses this stream | ||
| 213 | u32 system_channels{2}; | ||
| 214 | /// Channels supported by hardware | ||
| 215 | u32 device_channels{2}; | ||
| 216 | /// Is this stream currently paused? | ||
| 217 | std::atomic<bool> paused{true}; | ||
| 218 | /// Was this stream previously playing? | ||
| 219 | std::atomic<bool> was_playing{false}; | ||
| 220 | }; | ||
| 221 | |||
| 222 | using SinkStreamPtr = std::unique_ptr<SinkStream>; | ||
| 223 | |||
| 224 | } // namespace AudioCore::Sink | ||
diff --git a/src/audio_core/sink_context.cpp b/src/audio_core/sink_context.cpp deleted file mode 100644 index 835e12f67..000000000 --- a/src/audio_core/sink_context.cpp +++ /dev/null | |||
| @@ -1,47 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/sink_context.h" | ||
| 5 | |||
| 6 | namespace AudioCore { | ||
| 7 | SinkContext::SinkContext(std::size_t sink_count_) : sink_count{sink_count_} {} | ||
| 8 | SinkContext::~SinkContext() = default; | ||
| 9 | |||
| 10 | std::size_t SinkContext::GetCount() const { | ||
| 11 | return sink_count; | ||
| 12 | } | ||
| 13 | |||
| 14 | void SinkContext::UpdateMainSink(const SinkInfo::InParams& in) { | ||
| 15 | ASSERT(in.type == SinkTypes::Device); | ||
| 16 | |||
| 17 | if (in.device.down_matrix_enabled) { | ||
| 18 | downmix_coefficients = in.device.down_matrix_coef; | ||
| 19 | } else { | ||
| 20 | downmix_coefficients = { | ||
| 21 | 1.0f, // front | ||
| 22 | 0.707f, // center | ||
| 23 | 0.0f, // lfe | ||
| 24 | 0.707f, // back | ||
| 25 | }; | ||
| 26 | } | ||
| 27 | |||
| 28 | in_use = in.in_use; | ||
| 29 | use_count = in.device.input_count; | ||
| 30 | buffers = in.device.input; | ||
| 31 | } | ||
| 32 | |||
| 33 | bool SinkContext::InUse() const { | ||
| 34 | return in_use; | ||
| 35 | } | ||
| 36 | |||
| 37 | std::vector<u8> SinkContext::OutputBuffers() const { | ||
| 38 | std::vector<u8> buffer_ret(use_count); | ||
| 39 | std::memcpy(buffer_ret.data(), buffers.data(), use_count); | ||
| 40 | return buffer_ret; | ||
| 41 | } | ||
| 42 | |||
| 43 | const DownmixCoefficients& SinkContext::GetDownmixCoefficients() const { | ||
| 44 | return downmix_coefficients; | ||
| 45 | } | ||
| 46 | |||
| 47 | } // namespace AudioCore | ||
diff --git a/src/audio_core/sink_context.h b/src/audio_core/sink_context.h deleted file mode 100644 index cc5a90d80..000000000 --- a/src/audio_core/sink_context.h +++ /dev/null | |||
| @@ -1,95 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <vector> | ||
| 8 | #include "audio_core/common.h" | ||
| 9 | #include "common/common_funcs.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | #include "common/swap.h" | ||
| 12 | |||
| 13 | namespace AudioCore { | ||
| 14 | |||
| 15 | using DownmixCoefficients = std::array<float_le, 4>; | ||
| 16 | |||
| 17 | enum class SinkTypes : u8 { | ||
| 18 | Invalid = 0, | ||
| 19 | Device = 1, | ||
| 20 | Circular = 2, | ||
| 21 | }; | ||
| 22 | |||
| 23 | enum class SinkSampleFormat : u32_le { | ||
| 24 | None = 0, | ||
| 25 | Pcm8 = 1, | ||
| 26 | Pcm16 = 2, | ||
| 27 | Pcm24 = 3, | ||
| 28 | Pcm32 = 4, | ||
| 29 | PcmFloat = 5, | ||
| 30 | Adpcm = 6, | ||
| 31 | }; | ||
| 32 | |||
| 33 | class SinkInfo { | ||
| 34 | public: | ||
| 35 | struct CircularBufferIn { | ||
| 36 | u64_le address; | ||
| 37 | u32_le size; | ||
| 38 | u32_le input_count; | ||
| 39 | u32_le sample_count; | ||
| 40 | u32_le previous_position; | ||
| 41 | SinkSampleFormat sample_format; | ||
| 42 | std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input; | ||
| 43 | bool in_use; | ||
| 44 | INSERT_PADDING_BYTES_NOINIT(5); | ||
| 45 | }; | ||
| 46 | static_assert(sizeof(CircularBufferIn) == 0x28, | ||
| 47 | "SinkInfo::CircularBufferIn is in invalid size"); | ||
| 48 | |||
| 49 | struct DeviceIn { | ||
| 50 | std::array<u8, 255> device_name; | ||
| 51 | INSERT_PADDING_BYTES_NOINIT(1); | ||
| 52 | s32_le input_count; | ||
| 53 | std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input; | ||
| 54 | INSERT_PADDING_BYTES_NOINIT(1); | ||
| 55 | bool down_matrix_enabled; | ||
| 56 | DownmixCoefficients down_matrix_coef; | ||
| 57 | }; | ||
| 58 | static_assert(sizeof(DeviceIn) == 0x11c, "SinkInfo::DeviceIn is an invalid size"); | ||
| 59 | |||
| 60 | struct InParams { | ||
| 61 | SinkTypes type{}; | ||
| 62 | bool in_use{}; | ||
| 63 | INSERT_PADDING_BYTES(2); | ||
| 64 | u32_le node_id{}; | ||
| 65 | INSERT_PADDING_WORDS(6); | ||
| 66 | union { | ||
| 67 | // std::array<u8, 0x120> raw{}; | ||
| 68 | DeviceIn device; | ||
| 69 | CircularBufferIn circular_buffer; | ||
| 70 | }; | ||
| 71 | }; | ||
| 72 | static_assert(sizeof(InParams) == 0x140, "SinkInfo::InParams are an invalid size!"); | ||
| 73 | }; | ||
| 74 | |||
| 75 | class SinkContext { | ||
| 76 | public: | ||
| 77 | explicit SinkContext(std::size_t sink_count_); | ||
| 78 | ~SinkContext(); | ||
| 79 | |||
| 80 | [[nodiscard]] std::size_t GetCount() const; | ||
| 81 | |||
| 82 | void UpdateMainSink(const SinkInfo::InParams& in); | ||
| 83 | [[nodiscard]] bool InUse() const; | ||
| 84 | [[nodiscard]] std::vector<u8> OutputBuffers() const; | ||
| 85 | |||
| 86 | [[nodiscard]] const DownmixCoefficients& GetDownmixCoefficients() const; | ||
| 87 | |||
| 88 | private: | ||
| 89 | bool in_use{false}; | ||
| 90 | s32 use_count{}; | ||
| 91 | std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> buffers{}; | ||
| 92 | std::size_t sink_count{}; | ||
| 93 | DownmixCoefficients downmix_coefficients{}; | ||
| 94 | }; | ||
| 95 | } // namespace AudioCore | ||
diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h deleted file mode 100644 index 042766358..000000000 --- a/src/audio_core/sink_details.h +++ /dev/null | |||
| @@ -1,23 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string> | ||
| 7 | #include <string_view> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | namespace AudioCore { | ||
| 11 | |||
| 12 | class Sink; | ||
| 13 | |||
| 14 | /// Retrieves the IDs for all available audio sinks. | ||
| 15 | std::vector<const char*> GetSinkIDs(); | ||
| 16 | |||
| 17 | /// Gets the list of devices for a particular sink identified by the given ID. | ||
| 18 | std::vector<std::string> GetDeviceListForSink(std::string_view sink_id); | ||
| 19 | |||
| 20 | /// Creates an audio sink identified by the given device ID. | ||
| 21 | std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id); | ||
| 22 | |||
| 23 | } // namespace AudioCore | ||
diff --git a/src/audio_core/sink_stream.h b/src/audio_core/sink_stream.h deleted file mode 100644 index 0449b90af..000000000 --- a/src/audio_core/sink_stream.h +++ /dev/null | |||
| @@ -1,35 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <memory> | ||
| 7 | #include <vector> | ||
| 8 | |||
| 9 | #include "common/common_types.h" | ||
| 10 | |||
| 11 | namespace AudioCore { | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Accepts samples in stereo signed PCM16 format to be output. Sinks *do not* handle resampling and | ||
| 15 | * expect the correct sample rate. They are dumb outputs. | ||
| 16 | */ | ||
| 17 | class SinkStream { | ||
| 18 | public: | ||
| 19 | virtual ~SinkStream() = default; | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Feed stereo samples to sink. | ||
| 23 | * @param num_channels Number of channels used. | ||
| 24 | * @param samples Samples in interleaved stereo PCM16 format. | ||
| 25 | */ | ||
| 26 | virtual void EnqueueSamples(u32 num_channels, const std::vector<s16>& samples) = 0; | ||
| 27 | |||
| 28 | virtual std::size_t SamplesInQueue(u32 num_channels) const = 0; | ||
| 29 | |||
| 30 | virtual void Flush() = 0; | ||
| 31 | }; | ||
| 32 | |||
| 33 | using SinkStreamPtr = std::unique_ptr<SinkStream>; | ||
| 34 | |||
| 35 | } // namespace AudioCore | ||
diff --git a/src/audio_core/splitter_context.cpp b/src/audio_core/splitter_context.cpp deleted file mode 100644 index 10646dc05..000000000 --- a/src/audio_core/splitter_context.cpp +++ /dev/null | |||
| @@ -1,616 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "audio_core/behavior_info.h" | ||
| 5 | #include "audio_core/splitter_context.h" | ||
| 6 | #include "common/alignment.h" | ||
| 7 | #include "common/assert.h" | ||
| 8 | #include "common/logging/log.h" | ||
| 9 | |||
| 10 | namespace AudioCore { | ||
| 11 | |||
| 12 | ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id_) : id{id_} {} | ||
| 13 | ServerSplitterDestinationData::~ServerSplitterDestinationData() = default; | ||
| 14 | |||
| 15 | void ServerSplitterDestinationData::Update(SplitterInfo::InDestinationParams& header) { | ||
| 16 | // Log error as these are not actually failure states | ||
| 17 | if (header.magic != SplitterMagic::DataHeader) { | ||
| 18 | LOG_ERROR(Audio, "Splitter destination header is invalid!"); | ||
| 19 | return; | ||
| 20 | } | ||
| 21 | |||
| 22 | // Incorrect splitter id | ||
| 23 | if (header.splitter_id != id) { | ||
| 24 | LOG_ERROR(Audio, "Splitter destination ids do not match!"); | ||
| 25 | return; | ||
| 26 | } | ||
| 27 | |||
| 28 | mix_id = header.mix_id; | ||
| 29 | // Copy our mix volumes | ||
| 30 | std::copy(header.mix_volumes.begin(), header.mix_volumes.end(), current_mix_volumes.begin()); | ||
| 31 | if (!in_use && header.in_use) { | ||
| 32 | // Update mix volumes | ||
| 33 | std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin()); | ||
| 34 | needs_update = false; | ||
| 35 | } | ||
| 36 | in_use = header.in_use; | ||
| 37 | } | ||
| 38 | |||
| 39 | ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() { | ||
| 40 | return next; | ||
| 41 | } | ||
| 42 | |||
| 43 | const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const { | ||
| 44 | return next; | ||
| 45 | } | ||
| 46 | |||
| 47 | void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) { | ||
| 48 | next = dest; | ||
| 49 | } | ||
| 50 | |||
| 51 | bool ServerSplitterDestinationData::ValidMixId() const { | ||
| 52 | return GetMixId() != AudioCommon::NO_MIX; | ||
| 53 | } | ||
| 54 | |||
| 55 | s32 ServerSplitterDestinationData::GetMixId() const { | ||
| 56 | return mix_id; | ||
| 57 | } | ||
| 58 | |||
| 59 | bool ServerSplitterDestinationData::IsConfigured() const { | ||
| 60 | return in_use && ValidMixId(); | ||
| 61 | } | ||
| 62 | |||
| 63 | float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const { | ||
| 64 | ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); | ||
| 65 | return current_mix_volumes.at(i); | ||
| 66 | } | ||
| 67 | |||
| 68 | const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& | ||
| 69 | ServerSplitterDestinationData::CurrentMixVolumes() const { | ||
| 70 | return current_mix_volumes; | ||
| 71 | } | ||
| 72 | |||
| 73 | const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& | ||
| 74 | ServerSplitterDestinationData::LastMixVolumes() const { | ||
| 75 | return last_mix_volumes; | ||
| 76 | } | ||
| 77 | |||
| 78 | void ServerSplitterDestinationData::MarkDirty() { | ||
| 79 | needs_update = true; | ||
| 80 | } | ||
| 81 | |||
| 82 | void ServerSplitterDestinationData::UpdateInternalState() { | ||
| 83 | if (in_use && needs_update) { | ||
| 84 | std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin()); | ||
| 85 | } | ||
| 86 | needs_update = false; | ||
| 87 | } | ||
| 88 | |||
| 89 | ServerSplitterInfo::ServerSplitterInfo(s32 id_) : id(id_) {} | ||
| 90 | ServerSplitterInfo::~ServerSplitterInfo() = default; | ||
| 91 | |||
| 92 | void ServerSplitterInfo::InitializeInfos() { | ||
| 93 | send_length = 0; | ||
| 94 | head = nullptr; | ||
| 95 | new_connection = true; | ||
| 96 | } | ||
| 97 | |||
| 98 | void ServerSplitterInfo::ClearNewConnectionFlag() { | ||
| 99 | new_connection = false; | ||
| 100 | } | ||
| 101 | |||
| 102 | std::size_t ServerSplitterInfo::Update(SplitterInfo::InInfoPrams& header) { | ||
| 103 | if (header.send_id != id) { | ||
| 104 | return 0; | ||
| 105 | } | ||
| 106 | |||
| 107 | sample_rate = header.sample_rate; | ||
| 108 | new_connection = true; | ||
| 109 | // We need to update the size here due to the splitter bug being present and providing an | ||
| 110 | // incorrect size. We're suppose to also update the header here but we just ignore and continue | ||
| 111 | return (sizeof(s32_le) * (header.length - 1)) + (sizeof(s32_le) * 3); | ||
| 112 | } | ||
| 113 | |||
| 114 | ServerSplitterDestinationData* ServerSplitterInfo::GetHead() { | ||
| 115 | return head; | ||
| 116 | } | ||
| 117 | |||
| 118 | const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const { | ||
| 119 | return head; | ||
| 120 | } | ||
| 121 | |||
| 122 | ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) { | ||
| 123 | auto* current_head = head; | ||
| 124 | for (std::size_t i = 0; i < depth; i++) { | ||
| 125 | if (current_head == nullptr) { | ||
| 126 | return nullptr; | ||
| 127 | } | ||
| 128 | current_head = current_head->GetNextDestination(); | ||
| 129 | } | ||
| 130 | return current_head; | ||
| 131 | } | ||
| 132 | |||
| 133 | const ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) const { | ||
| 134 | auto* current_head = head; | ||
| 135 | for (std::size_t i = 0; i < depth; i++) { | ||
| 136 | if (current_head == nullptr) { | ||
| 137 | return nullptr; | ||
| 138 | } | ||
| 139 | current_head = current_head->GetNextDestination(); | ||
| 140 | } | ||
| 141 | return current_head; | ||
| 142 | } | ||
| 143 | |||
| 144 | bool ServerSplitterInfo::HasNewConnection() const { | ||
| 145 | return new_connection; | ||
| 146 | } | ||
| 147 | |||
| 148 | s32 ServerSplitterInfo::GetLength() const { | ||
| 149 | return send_length; | ||
| 150 | } | ||
| 151 | |||
| 152 | void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) { | ||
| 153 | head = new_head; | ||
| 154 | } | ||
| 155 | |||
| 156 | void ServerSplitterInfo::SetHeadDepth(s32 length) { | ||
| 157 | send_length = length; | ||
| 158 | } | ||
| 159 | |||
| 160 | SplitterContext::SplitterContext() = default; | ||
| 161 | SplitterContext::~SplitterContext() = default; | ||
| 162 | |||
| 163 | void SplitterContext::Initialize(BehaviorInfo& behavior_info, std::size_t _info_count, | ||
| 164 | std::size_t _data_count) { | ||
| 165 | if (!behavior_info.IsSplitterSupported() || _data_count == 0 || _info_count == 0) { | ||
| 166 | Setup(0, 0, false); | ||
| 167 | return; | ||
| 168 | } | ||
| 169 | // Only initialize if we're using splitters | ||
| 170 | Setup(_info_count, _data_count, behavior_info.IsSplitterBugFixed()); | ||
| 171 | } | ||
| 172 | |||
| 173 | bool SplitterContext::Update(const std::vector<u8>& input, std::size_t& input_offset, | ||
| 174 | std::size_t& bytes_read) { | ||
| 175 | const auto UpdateOffsets = [&](std::size_t read) { | ||
| 176 | input_offset += read; | ||
| 177 | bytes_read += read; | ||
| 178 | }; | ||
| 179 | |||
| 180 | if (info_count == 0 || data_count == 0) { | ||
| 181 | bytes_read = 0; | ||
| 182 | return true; | ||
| 183 | } | ||
| 184 | |||
| 185 | if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, | ||
| 186 | sizeof(SplitterInfo::InHeader))) { | ||
| 187 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 188 | return false; | ||
| 189 | } | ||
| 190 | SplitterInfo::InHeader header{}; | ||
| 191 | std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InHeader)); | ||
| 192 | UpdateOffsets(sizeof(SplitterInfo::InHeader)); | ||
| 193 | |||
| 194 | if (header.magic != SplitterMagic::SplitterHeader) { | ||
| 195 | LOG_ERROR(Audio, "Invalid header magic! Expecting {:X} but got {:X}", | ||
| 196 | SplitterMagic::SplitterHeader, header.magic); | ||
| 197 | return false; | ||
| 198 | } | ||
| 199 | |||
| 200 | // Clear all connections | ||
| 201 | for (auto& info : infos) { | ||
| 202 | info.ClearNewConnectionFlag(); | ||
| 203 | } | ||
| 204 | |||
| 205 | UpdateInfo(input, input_offset, bytes_read, header.info_count); | ||
| 206 | UpdateData(input, input_offset, bytes_read, header.data_count); | ||
| 207 | const auto aligned_bytes_read = Common::AlignUp(bytes_read, 16); | ||
| 208 | input_offset += aligned_bytes_read - bytes_read; | ||
| 209 | bytes_read = aligned_bytes_read; | ||
| 210 | return true; | ||
| 211 | } | ||
| 212 | |||
| 213 | bool SplitterContext::UsingSplitter() const { | ||
| 214 | return info_count > 0 && data_count > 0; | ||
| 215 | } | ||
| 216 | |||
| 217 | ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) { | ||
| 218 | ASSERT(i < info_count); | ||
| 219 | return infos.at(i); | ||
| 220 | } | ||
| 221 | |||
| 222 | const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const { | ||
| 223 | ASSERT(i < info_count); | ||
| 224 | return infos.at(i); | ||
| 225 | } | ||
| 226 | |||
| 227 | ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) { | ||
| 228 | ASSERT(i < data_count); | ||
| 229 | return datas.at(i); | ||
| 230 | } | ||
| 231 | |||
| 232 | const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const { | ||
| 233 | ASSERT(i < data_count); | ||
| 234 | return datas.at(i); | ||
| 235 | } | ||
| 236 | |||
| 237 | ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info, | ||
| 238 | std::size_t data) { | ||
| 239 | ASSERT(info < info_count); | ||
| 240 | auto& cur_info = GetInfo(info); | ||
| 241 | return cur_info.GetData(data); | ||
| 242 | } | ||
| 243 | |||
| 244 | const ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info, | ||
| 245 | std::size_t data) const { | ||
| 246 | ASSERT(info < info_count); | ||
| 247 | const auto& cur_info = GetInfo(info); | ||
| 248 | return cur_info.GetData(data); | ||
| 249 | } | ||
| 250 | |||
| 251 | void SplitterContext::UpdateInternalState() { | ||
| 252 | if (data_count == 0) { | ||
| 253 | return; | ||
| 254 | } | ||
| 255 | |||
| 256 | for (auto& data : datas) { | ||
| 257 | data.UpdateInternalState(); | ||
| 258 | } | ||
| 259 | } | ||
| 260 | |||
| 261 | std::size_t SplitterContext::GetInfoCount() const { | ||
| 262 | return info_count; | ||
| 263 | } | ||
| 264 | |||
| 265 | std::size_t SplitterContext::GetDataCount() const { | ||
| 266 | return data_count; | ||
| 267 | } | ||
| 268 | |||
| 269 | void SplitterContext::Setup(std::size_t info_count_, std::size_t data_count_, | ||
| 270 | bool is_splitter_bug_fixed) { | ||
| 271 | |||
| 272 | info_count = info_count_; | ||
| 273 | data_count = data_count_; | ||
| 274 | |||
| 275 | for (std::size_t i = 0; i < info_count; i++) { | ||
| 276 | auto& splitter = infos.emplace_back(static_cast<s32>(i)); | ||
| 277 | splitter.InitializeInfos(); | ||
| 278 | } | ||
| 279 | for (std::size_t i = 0; i < data_count; i++) { | ||
| 280 | datas.emplace_back(static_cast<s32>(i)); | ||
| 281 | } | ||
| 282 | |||
| 283 | bug_fixed = is_splitter_bug_fixed; | ||
| 284 | } | ||
| 285 | |||
| 286 | bool SplitterContext::UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset, | ||
| 287 | std::size_t& bytes_read, s32 in_splitter_count) { | ||
| 288 | const auto UpdateOffsets = [&](std::size_t read) { | ||
| 289 | input_offset += read; | ||
| 290 | bytes_read += read; | ||
| 291 | }; | ||
| 292 | |||
| 293 | for (s32 i = 0; i < in_splitter_count; i++) { | ||
| 294 | if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, | ||
| 295 | sizeof(SplitterInfo::InInfoPrams))) { | ||
| 296 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 297 | return false; | ||
| 298 | } | ||
| 299 | SplitterInfo::InInfoPrams header{}; | ||
| 300 | std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InInfoPrams)); | ||
| 301 | |||
| 302 | // Logged as warning as these don't actually cause a bailout for some reason | ||
| 303 | if (header.magic != SplitterMagic::InfoHeader) { | ||
| 304 | LOG_ERROR(Audio, "Bad splitter data header"); | ||
| 305 | break; | ||
| 306 | } | ||
| 307 | |||
| 308 | if (header.send_id < 0 || static_cast<std::size_t>(header.send_id) > info_count) { | ||
| 309 | LOG_ERROR(Audio, "Bad splitter data id"); | ||
| 310 | break; | ||
| 311 | } | ||
| 312 | |||
| 313 | UpdateOffsets(sizeof(SplitterInfo::InInfoPrams)); | ||
| 314 | auto& info = GetInfo(header.send_id); | ||
| 315 | if (!RecomposeDestination(info, header, input, input_offset)) { | ||
| 316 | LOG_ERROR(Audio, "Failed to recompose destination for splitter!"); | ||
| 317 | return false; | ||
| 318 | } | ||
| 319 | const std::size_t read = info.Update(header); | ||
| 320 | bytes_read += read; | ||
| 321 | input_offset += read; | ||
| 322 | } | ||
| 323 | return true; | ||
| 324 | } | ||
| 325 | |||
| 326 | bool SplitterContext::UpdateData(const std::vector<u8>& input, std::size_t& input_offset, | ||
| 327 | std::size_t& bytes_read, s32 in_data_count) { | ||
| 328 | const auto UpdateOffsets = [&](std::size_t read) { | ||
| 329 | input_offset += read; | ||
| 330 | bytes_read += read; | ||
| 331 | }; | ||
| 332 | |||
| 333 | for (s32 i = 0; i < in_data_count; i++) { | ||
| 334 | if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, | ||
| 335 | sizeof(SplitterInfo::InDestinationParams))) { | ||
| 336 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 337 | return false; | ||
| 338 | } | ||
| 339 | SplitterInfo::InDestinationParams header{}; | ||
| 340 | std::memcpy(&header, input.data() + input_offset, | ||
| 341 | sizeof(SplitterInfo::InDestinationParams)); | ||
| 342 | UpdateOffsets(sizeof(SplitterInfo::InDestinationParams)); | ||
| 343 | |||
| 344 | // Logged as warning as these don't actually cause a bailout for some reason | ||
| 345 | if (header.magic != SplitterMagic::DataHeader) { | ||
| 346 | LOG_ERROR(Audio, "Bad splitter data header"); | ||
| 347 | break; | ||
| 348 | } | ||
| 349 | |||
| 350 | if (header.splitter_id < 0 || static_cast<std::size_t>(header.splitter_id) > data_count) { | ||
| 351 | LOG_ERROR(Audio, "Bad splitter data id"); | ||
| 352 | break; | ||
| 353 | } | ||
| 354 | GetData(header.splitter_id).Update(header); | ||
| 355 | } | ||
| 356 | return true; | ||
| 357 | } | ||
| 358 | |||
| 359 | bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info, | ||
| 360 | SplitterInfo::InInfoPrams& header, | ||
| 361 | const std::vector<u8>& input, | ||
| 362 | const std::size_t& input_offset) { | ||
| 363 | // Clear our current destinations | ||
| 364 | auto* current_head = info.GetHead(); | ||
| 365 | while (current_head != nullptr) { | ||
| 366 | auto* next_head = current_head->GetNextDestination(); | ||
| 367 | current_head->SetNextDestination(nullptr); | ||
| 368 | current_head = next_head; | ||
| 369 | } | ||
| 370 | info.SetHead(nullptr); | ||
| 371 | |||
| 372 | s32 size = header.length; | ||
| 373 | // If the splitter bug is present, calculate fixed size | ||
| 374 | if (!bug_fixed) { | ||
| 375 | if (info_count > 0) { | ||
| 376 | const auto factor = data_count / info_count; | ||
| 377 | size = std::min(header.length, static_cast<s32>(factor)); | ||
| 378 | } else { | ||
| 379 | size = 0; | ||
| 380 | } | ||
| 381 | } | ||
| 382 | |||
| 383 | if (size < 1) { | ||
| 384 | LOG_ERROR(Audio, "Invalid splitter info size! size={:X}", size); | ||
| 385 | return true; | ||
| 386 | } | ||
| 387 | |||
| 388 | auto* start_head = &GetData(header.resource_id_base); | ||
| 389 | current_head = start_head; | ||
| 390 | std::vector<s32_le> resource_ids(size - 1); | ||
| 391 | if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, | ||
| 392 | resource_ids.size() * sizeof(s32_le))) { | ||
| 393 | LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||
| 394 | return false; | ||
| 395 | } | ||
| 396 | std::memcpy(resource_ids.data(), input.data() + input_offset, | ||
| 397 | resource_ids.size() * sizeof(s32_le)); | ||
| 398 | |||
| 399 | for (auto resource_id : resource_ids) { | ||
| 400 | auto* head = &GetData(resource_id); | ||
| 401 | current_head->SetNextDestination(head); | ||
| 402 | current_head = head; | ||
| 403 | } | ||
| 404 | |||
| 405 | info.SetHead(start_head); | ||
| 406 | info.SetHeadDepth(size); | ||
| 407 | |||
| 408 | return true; | ||
| 409 | } | ||
| 410 | |||
| 411 | NodeStates::NodeStates() = default; | ||
| 412 | NodeStates::~NodeStates() = default; | ||
| 413 | |||
| 414 | void NodeStates::Initialize(std::size_t node_count_) { | ||
| 415 | // Setup our work parameters | ||
| 416 | node_count = node_count_; | ||
| 417 | was_node_found.resize(node_count); | ||
| 418 | was_node_completed.resize(node_count); | ||
| 419 | index_list.resize(node_count); | ||
| 420 | index_stack.Reset(node_count * node_count); | ||
| 421 | } | ||
| 422 | |||
| 423 | bool NodeStates::Tsort(EdgeMatrix& edge_matrix) { | ||
| 424 | return DepthFirstSearch(edge_matrix); | ||
| 425 | } | ||
| 426 | |||
| 427 | std::size_t NodeStates::GetIndexPos() const { | ||
| 428 | return index_pos; | ||
| 429 | } | ||
| 430 | |||
| 431 | const std::vector<s32>& NodeStates::GetIndexList() const { | ||
| 432 | return index_list; | ||
| 433 | } | ||
| 434 | |||
| 435 | void NodeStates::PushTsortResult(s32 index) { | ||
| 436 | ASSERT(index < static_cast<s32>(node_count)); | ||
| 437 | index_list[index_pos++] = index; | ||
| 438 | } | ||
| 439 | |||
| 440 | bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) { | ||
| 441 | ResetState(); | ||
| 442 | for (std::size_t i = 0; i < node_count; i++) { | ||
| 443 | const auto node_id = static_cast<s32>(i); | ||
| 444 | |||
| 445 | // If we don't have a state, send to our index stack for work | ||
| 446 | if (GetState(i) == NodeStates::State::NoState) { | ||
| 447 | index_stack.push(node_id); | ||
| 448 | } | ||
| 449 | |||
| 450 | // While we have work to do in our stack | ||
| 451 | while (index_stack.Count() > 0) { | ||
| 452 | // Get the current node | ||
| 453 | const auto current_stack_index = index_stack.top(); | ||
| 454 | // Check if we've seen the node yet | ||
| 455 | const auto index_state = GetState(current_stack_index); | ||
| 456 | if (index_state == NodeStates::State::NoState) { | ||
| 457 | // Mark the node as seen | ||
| 458 | UpdateState(NodeStates::State::InFound, current_stack_index); | ||
| 459 | } else if (index_state == NodeStates::State::InFound) { | ||
| 460 | // We've seen this node before, mark it as completed | ||
| 461 | UpdateState(NodeStates::State::InCompleted, current_stack_index); | ||
| 462 | // Update our index list | ||
| 463 | PushTsortResult(current_stack_index); | ||
| 464 | // Pop the stack | ||
| 465 | index_stack.pop(); | ||
| 466 | continue; | ||
| 467 | } else if (index_state == NodeStates::State::InCompleted) { | ||
| 468 | // If our node is already sorted, clear it | ||
| 469 | index_stack.pop(); | ||
| 470 | continue; | ||
| 471 | } | ||
| 472 | |||
| 473 | const auto edge_node_count = edge_matrix.GetNodeCount(); | ||
| 474 | for (s32 j = 0; j < static_cast<s32>(edge_node_count); j++) { | ||
| 475 | // Check if our node is connected to our edge matrix | ||
| 476 | if (!edge_matrix.Connected(current_stack_index, j)) { | ||
| 477 | continue; | ||
| 478 | } | ||
| 479 | |||
| 480 | // Check if our node exists | ||
| 481 | const auto node_state = GetState(j); | ||
| 482 | if (node_state == NodeStates::State::NoState) { | ||
| 483 | // Add more work | ||
| 484 | index_stack.push(j); | ||
| 485 | } else if (node_state == NodeStates::State::InFound) { | ||
| 486 | ASSERT_MSG(false, "Node start marked as found"); | ||
| 487 | ResetState(); | ||
| 488 | return false; | ||
| 489 | } | ||
| 490 | } | ||
| 491 | } | ||
| 492 | } | ||
| 493 | return true; | ||
| 494 | } | ||
| 495 | |||
| 496 | void NodeStates::ResetState() { | ||
| 497 | // Reset to the start of our index stack | ||
| 498 | index_pos = 0; | ||
| 499 | for (std::size_t i = 0; i < node_count; i++) { | ||
| 500 | // Mark all nodes as not found | ||
| 501 | was_node_found[i] = false; | ||
| 502 | // Mark all nodes as uncompleted | ||
| 503 | was_node_completed[i] = false; | ||
| 504 | // Mark all indexes as invalid | ||
| 505 | index_list[i] = -1; | ||
| 506 | } | ||
| 507 | } | ||
| 508 | |||
| 509 | void NodeStates::UpdateState(NodeStates::State state, std::size_t i) { | ||
| 510 | switch (state) { | ||
| 511 | case NodeStates::State::NoState: | ||
| 512 | was_node_found[i] = false; | ||
| 513 | was_node_completed[i] = false; | ||
| 514 | break; | ||
| 515 | case NodeStates::State::InFound: | ||
| 516 | was_node_found[i] = true; | ||
| 517 | was_node_completed[i] = false; | ||
| 518 | break; | ||
| 519 | case NodeStates::State::InCompleted: | ||
| 520 | was_node_found[i] = false; | ||
| 521 | was_node_completed[i] = true; | ||
| 522 | break; | ||
| 523 | } | ||
| 524 | } | ||
| 525 | |||
| 526 | NodeStates::State NodeStates::GetState(std::size_t i) { | ||
| 527 | ASSERT(i < node_count); | ||
| 528 | if (was_node_found[i]) { | ||
| 529 | // If our node exists in our found list | ||
| 530 | return NodeStates::State::InFound; | ||
| 531 | } else if (was_node_completed[i]) { | ||
| 532 | // If node is in the completed list | ||
| 533 | return NodeStates::State::InCompleted; | ||
| 534 | } else { | ||
| 535 | // If in neither | ||
| 536 | return NodeStates::State::NoState; | ||
| 537 | } | ||
| 538 | } | ||
| 539 | |||
| 540 | NodeStates::Stack::Stack() = default; | ||
| 541 | NodeStates::Stack::~Stack() = default; | ||
| 542 | |||
| 543 | void NodeStates::Stack::Reset(std::size_t size) { | ||
| 544 | // Mark our stack as empty | ||
| 545 | stack.resize(size); | ||
| 546 | stack_size = size; | ||
| 547 | stack_pos = 0; | ||
| 548 | std::fill(stack.begin(), stack.end(), 0); | ||
| 549 | } | ||
| 550 | |||
| 551 | void NodeStates::Stack::push(s32 val) { | ||
| 552 | ASSERT(stack_pos < stack_size); | ||
| 553 | stack[stack_pos++] = val; | ||
| 554 | } | ||
| 555 | |||
| 556 | std::size_t NodeStates::Stack::Count() const { | ||
| 557 | return stack_pos; | ||
| 558 | } | ||
| 559 | |||
| 560 | s32 NodeStates::Stack::top() const { | ||
| 561 | ASSERT(stack_pos > 0); | ||
| 562 | return stack[stack_pos - 1]; | ||
| 563 | } | ||
| 564 | |||
| 565 | s32 NodeStates::Stack::pop() { | ||
| 566 | ASSERT(stack_pos > 0); | ||
| 567 | stack_pos--; | ||
| 568 | return stack[stack_pos]; | ||
| 569 | } | ||
| 570 | |||
| 571 | EdgeMatrix::EdgeMatrix() = default; | ||
| 572 | EdgeMatrix::~EdgeMatrix() = default; | ||
| 573 | |||
| 574 | void EdgeMatrix::Initialize(std::size_t _node_count) { | ||
| 575 | node_count = _node_count; | ||
| 576 | edge_matrix.resize(node_count * node_count); | ||
| 577 | } | ||
| 578 | |||
| 579 | bool EdgeMatrix::Connected(s32 a, s32 b) { | ||
| 580 | return GetState(a, b); | ||
| 581 | } | ||
| 582 | |||
| 583 | void EdgeMatrix::Connect(s32 a, s32 b) { | ||
| 584 | SetState(a, b, true); | ||
| 585 | } | ||
| 586 | |||
| 587 | void EdgeMatrix::Disconnect(s32 a, s32 b) { | ||
| 588 | SetState(a, b, false); | ||
| 589 | } | ||
| 590 | |||
| 591 | void EdgeMatrix::RemoveEdges(s32 edge) { | ||
| 592 | for (std::size_t i = 0; i < node_count; i++) { | ||
| 593 | SetState(edge, static_cast<s32>(i), false); | ||
| 594 | } | ||
| 595 | } | ||
| 596 | |||
| 597 | std::size_t EdgeMatrix::GetNodeCount() const { | ||
| 598 | return node_count; | ||
| 599 | } | ||
| 600 | |||
| 601 | void EdgeMatrix::SetState(s32 a, s32 b, bool state) { | ||
| 602 | ASSERT(InRange(a, b)); | ||
| 603 | edge_matrix.at(a * node_count + b) = state; | ||
| 604 | } | ||
| 605 | |||
| 606 | bool EdgeMatrix::GetState(s32 a, s32 b) { | ||
| 607 | ASSERT(InRange(a, b)); | ||
| 608 | return edge_matrix.at(a * node_count + b); | ||
| 609 | } | ||
| 610 | |||
| 611 | bool EdgeMatrix::InRange(s32 a, s32 b) const { | ||
| 612 | const std::size_t pos = a * node_count + b; | ||
| 613 | return pos < (node_count * node_count); | ||
| 614 | } | ||
| 615 | |||
| 616 | } // namespace AudioCore | ||
diff --git a/src/audio_core/splitter_context.h b/src/audio_core/splitter_context.h deleted file mode 100644 index 3a4b055eb..000000000 --- a/src/audio_core/splitter_context.h +++ /dev/null | |||
| @@ -1,218 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <stack> | ||
| 7 | #include <vector> | ||
| 8 | #include "audio_core/common.h" | ||
| 9 | #include "common/common_funcs.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | #include "common/swap.h" | ||
| 12 | |||
| 13 | namespace AudioCore { | ||
| 14 | class BehaviorInfo; | ||
| 15 | |||
| 16 | class EdgeMatrix { | ||
| 17 | public: | ||
| 18 | EdgeMatrix(); | ||
| 19 | ~EdgeMatrix(); | ||
| 20 | |||
| 21 | void Initialize(std::size_t _node_count); | ||
| 22 | bool Connected(s32 a, s32 b); | ||
| 23 | void Connect(s32 a, s32 b); | ||
| 24 | void Disconnect(s32 a, s32 b); | ||
| 25 | void RemoveEdges(s32 edge); | ||
| 26 | std::size_t GetNodeCount() const; | ||
| 27 | |||
| 28 | private: | ||
| 29 | void SetState(s32 a, s32 b, bool state); | ||
| 30 | bool GetState(s32 a, s32 b); | ||
| 31 | |||
| 32 | bool InRange(s32 a, s32 b) const; | ||
| 33 | std::vector<bool> edge_matrix{}; | ||
| 34 | std::size_t node_count{}; | ||
| 35 | }; | ||
| 36 | |||
| 37 | class NodeStates { | ||
| 38 | public: | ||
| 39 | enum class State { | ||
| 40 | NoState = 0, | ||
| 41 | InFound = 1, | ||
| 42 | InCompleted = 2, | ||
| 43 | }; | ||
| 44 | |||
| 45 | // Looks to be a fixed size stack. Placed within the NodeStates class based on symbols | ||
| 46 | class Stack { | ||
| 47 | public: | ||
| 48 | Stack(); | ||
| 49 | ~Stack(); | ||
| 50 | |||
| 51 | void Reset(std::size_t size); | ||
| 52 | void push(s32 val); | ||
| 53 | std::size_t Count() const; | ||
| 54 | s32 top() const; | ||
| 55 | s32 pop(); | ||
| 56 | |||
| 57 | private: | ||
| 58 | std::vector<s32> stack{}; | ||
| 59 | std::size_t stack_size{}; | ||
| 60 | std::size_t stack_pos{}; | ||
| 61 | }; | ||
| 62 | NodeStates(); | ||
| 63 | ~NodeStates(); | ||
| 64 | |||
| 65 | void Initialize(std::size_t node_count_); | ||
| 66 | bool Tsort(EdgeMatrix& edge_matrix); | ||
| 67 | std::size_t GetIndexPos() const; | ||
| 68 | const std::vector<s32>& GetIndexList() const; | ||
| 69 | |||
| 70 | private: | ||
| 71 | void PushTsortResult(s32 index); | ||
| 72 | bool DepthFirstSearch(EdgeMatrix& edge_matrix); | ||
| 73 | void ResetState(); | ||
| 74 | void UpdateState(State state, std::size_t i); | ||
| 75 | State GetState(std::size_t i); | ||
| 76 | |||
| 77 | std::size_t node_count{}; | ||
| 78 | std::vector<bool> was_node_found{}; | ||
| 79 | std::vector<bool> was_node_completed{}; | ||
| 80 | std::size_t index_pos{}; | ||
| 81 | std::vector<s32> index_list{}; | ||
| 82 | Stack index_stack{}; | ||
| 83 | }; | ||
| 84 | |||
| 85 | enum class SplitterMagic : u32_le { | ||
| 86 | SplitterHeader = Common::MakeMagic('S', 'N', 'D', 'H'), | ||
| 87 | DataHeader = Common::MakeMagic('S', 'N', 'D', 'D'), | ||
| 88 | InfoHeader = Common::MakeMagic('S', 'N', 'D', 'I'), | ||
| 89 | }; | ||
| 90 | |||
| 91 | class SplitterInfo { | ||
| 92 | public: | ||
| 93 | struct InHeader { | ||
| 94 | SplitterMagic magic{}; | ||
| 95 | s32_le info_count{}; | ||
| 96 | s32_le data_count{}; | ||
| 97 | INSERT_PADDING_WORDS(5); | ||
| 98 | }; | ||
| 99 | static_assert(sizeof(InHeader) == 0x20, "SplitterInfo::InHeader is an invalid size"); | ||
| 100 | |||
| 101 | struct InInfoPrams { | ||
| 102 | SplitterMagic magic{}; | ||
| 103 | s32_le send_id{}; | ||
| 104 | s32_le sample_rate{}; | ||
| 105 | s32_le length{}; | ||
| 106 | s32_le resource_id_base{}; | ||
| 107 | }; | ||
| 108 | static_assert(sizeof(InInfoPrams) == 0x14, "SplitterInfo::InInfoPrams is an invalid size"); | ||
| 109 | |||
| 110 | struct InDestinationParams { | ||
| 111 | SplitterMagic magic{}; | ||
| 112 | s32_le splitter_id{}; | ||
| 113 | std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volumes{}; | ||
| 114 | s32_le mix_id{}; | ||
| 115 | bool in_use{}; | ||
| 116 | INSERT_PADDING_BYTES(3); | ||
| 117 | }; | ||
| 118 | static_assert(sizeof(InDestinationParams) == 0x70, | ||
| 119 | "SplitterInfo::InDestinationParams is an invalid size"); | ||
| 120 | }; | ||
| 121 | |||
| 122 | class ServerSplitterDestinationData { | ||
| 123 | public: | ||
| 124 | explicit ServerSplitterDestinationData(s32 id_); | ||
| 125 | ~ServerSplitterDestinationData(); | ||
| 126 | |||
| 127 | void Update(SplitterInfo::InDestinationParams& header); | ||
| 128 | |||
| 129 | ServerSplitterDestinationData* GetNextDestination(); | ||
| 130 | const ServerSplitterDestinationData* GetNextDestination() const; | ||
| 131 | void SetNextDestination(ServerSplitterDestinationData* dest); | ||
| 132 | bool ValidMixId() const; | ||
| 133 | s32 GetMixId() const; | ||
| 134 | bool IsConfigured() const; | ||
| 135 | float GetMixVolume(std::size_t i) const; | ||
| 136 | const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& CurrentMixVolumes() const; | ||
| 137 | const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& LastMixVolumes() const; | ||
| 138 | void MarkDirty(); | ||
| 139 | void UpdateInternalState(); | ||
| 140 | |||
| 141 | private: | ||
| 142 | bool needs_update{}; | ||
| 143 | bool in_use{}; | ||
| 144 | s32 id{}; | ||
| 145 | s32 mix_id{}; | ||
| 146 | std::array<float, AudioCommon::MAX_MIX_BUFFERS> current_mix_volumes{}; | ||
| 147 | std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volumes{}; | ||
| 148 | ServerSplitterDestinationData* next = nullptr; | ||
| 149 | }; | ||
| 150 | |||
| 151 | class ServerSplitterInfo { | ||
| 152 | public: | ||
| 153 | explicit ServerSplitterInfo(s32 id_); | ||
| 154 | ~ServerSplitterInfo(); | ||
| 155 | |||
| 156 | void InitializeInfos(); | ||
| 157 | void ClearNewConnectionFlag(); | ||
| 158 | std::size_t Update(SplitterInfo::InInfoPrams& header); | ||
| 159 | |||
| 160 | ServerSplitterDestinationData* GetHead(); | ||
| 161 | const ServerSplitterDestinationData* GetHead() const; | ||
| 162 | ServerSplitterDestinationData* GetData(std::size_t depth); | ||
| 163 | const ServerSplitterDestinationData* GetData(std::size_t depth) const; | ||
| 164 | |||
| 165 | bool HasNewConnection() const; | ||
| 166 | s32 GetLength() const; | ||
| 167 | |||
| 168 | void SetHead(ServerSplitterDestinationData* new_head); | ||
| 169 | void SetHeadDepth(s32 length); | ||
| 170 | |||
| 171 | private: | ||
| 172 | s32 sample_rate{}; | ||
| 173 | s32 id{}; | ||
| 174 | s32 send_length{}; | ||
| 175 | ServerSplitterDestinationData* head = nullptr; | ||
| 176 | bool new_connection{}; | ||
| 177 | }; | ||
| 178 | |||
| 179 | class SplitterContext { | ||
| 180 | public: | ||
| 181 | SplitterContext(); | ||
| 182 | ~SplitterContext(); | ||
| 183 | |||
| 184 | void Initialize(BehaviorInfo& behavior_info, std::size_t splitter_count, | ||
| 185 | std::size_t data_count); | ||
| 186 | |||
| 187 | bool Update(const std::vector<u8>& input, std::size_t& input_offset, std::size_t& bytes_read); | ||
| 188 | bool UsingSplitter() const; | ||
| 189 | |||
| 190 | ServerSplitterInfo& GetInfo(std::size_t i); | ||
| 191 | const ServerSplitterInfo& GetInfo(std::size_t i) const; | ||
| 192 | ServerSplitterDestinationData& GetData(std::size_t i); | ||
| 193 | const ServerSplitterDestinationData& GetData(std::size_t i) const; | ||
| 194 | ServerSplitterDestinationData* GetDestinationData(std::size_t info, std::size_t data); | ||
| 195 | const ServerSplitterDestinationData* GetDestinationData(std::size_t info, | ||
| 196 | std::size_t data) const; | ||
| 197 | void UpdateInternalState(); | ||
| 198 | |||
| 199 | std::size_t GetInfoCount() const; | ||
| 200 | std::size_t GetDataCount() const; | ||
| 201 | |||
| 202 | private: | ||
| 203 | void Setup(std::size_t info_count, std::size_t data_count, bool is_splitter_bug_fixed); | ||
| 204 | bool UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset, | ||
| 205 | std::size_t& bytes_read, s32 in_splitter_count); | ||
| 206 | bool UpdateData(const std::vector<u8>& input, std::size_t& input_offset, | ||
| 207 | std::size_t& bytes_read, s32 in_data_count); | ||
| 208 | bool RecomposeDestination(ServerSplitterInfo& info, SplitterInfo::InInfoPrams& header, | ||
| 209 | const std::vector<u8>& input, const std::size_t& input_offset); | ||
| 210 | |||
| 211 | std::vector<ServerSplitterInfo> infos{}; | ||
| 212 | std::vector<ServerSplitterDestinationData> datas{}; | ||
| 213 | |||
| 214 | std::size_t info_count{}; | ||
| 215 | std::size_t data_count{}; | ||
| 216 | bool bug_fixed{}; | ||
| 217 | }; | ||
| 218 | } // namespace AudioCore | ||
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp deleted file mode 100644 index cf3d94c53..000000000 --- a/src/audio_core/stream.cpp +++ /dev/null | |||
| @@ -1,175 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <cmath> | ||
| 6 | |||
| 7 | #include "audio_core/sink.h" | ||
| 8 | #include "audio_core/sink_details.h" | ||
| 9 | #include "audio_core/sink_stream.h" | ||
| 10 | #include "audio_core/stream.h" | ||
| 11 | #include "common/assert.h" | ||
| 12 | #include "common/logging/log.h" | ||
| 13 | #include "common/settings.h" | ||
| 14 | #include "core/core_timing.h" | ||
| 15 | |||
| 16 | namespace AudioCore { | ||
| 17 | |||
| 18 | constexpr std::size_t MaxAudioBufferCount{32}; | ||
| 19 | |||
| 20 | u32 Stream::GetNumChannels() const { | ||
| 21 | switch (format) { | ||
| 22 | case Format::Mono16: | ||
| 23 | return 1; | ||
| 24 | case Format::Stereo16: | ||
| 25 | return 2; | ||
| 26 | case Format::Multi51Channel16: | ||
| 27 | return 6; | ||
| 28 | } | ||
| 29 | UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<u32>(format)); | ||
| 30 | return {}; | ||
| 31 | } | ||
| 32 | |||
| 33 | Stream::Stream(Core::Timing::CoreTiming& core_timing_, u32 sample_rate_, Format format_, | ||
| 34 | ReleaseCallback&& release_callback_, SinkStream& sink_stream_, std::string&& name_) | ||
| 35 | : sample_rate{sample_rate_}, format{format_}, release_callback{std::move(release_callback_)}, | ||
| 36 | sink_stream{sink_stream_}, core_timing{core_timing_}, name{std::move(name_)} { | ||
| 37 | release_event = Core::Timing::CreateEvent( | ||
| 38 | name, [this](std::uintptr_t, s64 time, std::chrono::nanoseconds ns_late) { | ||
| 39 | ReleaseActiveBuffer(ns_late); | ||
| 40 | return std::nullopt; | ||
| 41 | }); | ||
| 42 | } | ||
| 43 | |||
| 44 | void Stream::Play() { | ||
| 45 | state = State::Playing; | ||
| 46 | PlayNextBuffer(); | ||
| 47 | } | ||
| 48 | |||
| 49 | void Stream::Stop() { | ||
| 50 | state = State::Stopped; | ||
| 51 | UNIMPLEMENTED(); | ||
| 52 | } | ||
| 53 | |||
| 54 | bool Stream::Flush() { | ||
| 55 | const bool had_buffers = !queued_buffers.empty(); | ||
| 56 | while (!queued_buffers.empty()) { | ||
| 57 | queued_buffers.pop(); | ||
| 58 | } | ||
| 59 | return had_buffers; | ||
| 60 | } | ||
| 61 | |||
| 62 | void Stream::SetVolume(float volume) { | ||
| 63 | game_volume = volume; | ||
| 64 | } | ||
| 65 | |||
| 66 | Stream::State Stream::GetState() const { | ||
| 67 | return state; | ||
| 68 | } | ||
| 69 | |||
| 70 | std::chrono::nanoseconds Stream::GetBufferReleaseNS(const Buffer& buffer) const { | ||
| 71 | const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()}; | ||
| 72 | return std::chrono::nanoseconds((static_cast<u64>(num_samples) * 1000000000ULL) / sample_rate); | ||
| 73 | } | ||
| 74 | |||
| 75 | static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) { | ||
| 76 | const float volume{std::clamp(Settings::Volume() - (1.0f - game_volume), 0.0f, 1.0f)}; | ||
| 77 | |||
| 78 | if (volume == 1.0f) { | ||
| 79 | return; | ||
| 80 | } | ||
| 81 | |||
| 82 | // Perceived volume is not the same as the volume level | ||
| 83 | const float volume_scale_factor = (0.85f * ((volume * volume) - volume)) + volume; | ||
| 84 | for (auto& sample : samples) { | ||
| 85 | sample = static_cast<s16>(sample * volume_scale_factor); | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | void Stream::PlayNextBuffer(std::chrono::nanoseconds ns_late) { | ||
| 90 | if (!IsPlaying()) { | ||
| 91 | // Ensure we are in playing state before playing the next buffer | ||
| 92 | sink_stream.Flush(); | ||
| 93 | return; | ||
| 94 | } | ||
| 95 | |||
| 96 | if (active_buffer) { | ||
| 97 | // Do not queue a new buffer if we are already playing a buffer | ||
| 98 | return; | ||
| 99 | } | ||
| 100 | |||
| 101 | if (queued_buffers.empty()) { | ||
| 102 | // No queued buffers - we are effectively paused | ||
| 103 | sink_stream.Flush(); | ||
| 104 | return; | ||
| 105 | } | ||
| 106 | |||
| 107 | active_buffer = queued_buffers.front(); | ||
| 108 | queued_buffers.pop(); | ||
| 109 | |||
| 110 | auto& samples = active_buffer->GetSamples(); | ||
| 111 | |||
| 112 | VolumeAdjustSamples(samples, game_volume); | ||
| 113 | |||
| 114 | sink_stream.EnqueueSamples(GetNumChannels(), samples); | ||
| 115 | played_samples += samples.size(); | ||
| 116 | |||
| 117 | const auto buffer_release_ns = GetBufferReleaseNS(*active_buffer); | ||
| 118 | |||
| 119 | // If ns_late is higher than the update rate ignore the delay | ||
| 120 | if (ns_late > buffer_release_ns) { | ||
| 121 | ns_late = {}; | ||
| 122 | } | ||
| 123 | |||
| 124 | core_timing.ScheduleEvent(buffer_release_ns - ns_late, release_event, {}); | ||
| 125 | } | ||
| 126 | |||
| 127 | void Stream::ReleaseActiveBuffer(std::chrono::nanoseconds ns_late) { | ||
| 128 | ASSERT(active_buffer); | ||
| 129 | released_buffers.push(std::move(active_buffer)); | ||
| 130 | release_callback(); | ||
| 131 | PlayNextBuffer(ns_late); | ||
| 132 | } | ||
| 133 | |||
| 134 | bool Stream::QueueBuffer(BufferPtr&& buffer) { | ||
| 135 | if (queued_buffers.size() < MaxAudioBufferCount) { | ||
| 136 | queued_buffers.push(std::move(buffer)); | ||
| 137 | PlayNextBuffer(); | ||
| 138 | return true; | ||
| 139 | } | ||
| 140 | return false; | ||
| 141 | } | ||
| 142 | |||
| 143 | bool Stream::ContainsBuffer([[maybe_unused]] Buffer::Tag tag) const { | ||
| 144 | UNIMPLEMENTED(); | ||
| 145 | return {}; | ||
| 146 | } | ||
| 147 | |||
| 148 | std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers(std::size_t max_count) { | ||
| 149 | std::vector<Buffer::Tag> tags; | ||
| 150 | for (std::size_t count = 0; count < max_count && !released_buffers.empty(); ++count) { | ||
| 151 | if (released_buffers.front()) { | ||
| 152 | tags.push_back(released_buffers.front()->GetTag()); | ||
| 153 | } else { | ||
| 154 | ASSERT_MSG(false, "Invalid tag in released_buffers!"); | ||
| 155 | } | ||
| 156 | released_buffers.pop(); | ||
| 157 | } | ||
| 158 | return tags; | ||
| 159 | } | ||
| 160 | |||
| 161 | std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers() { | ||
| 162 | std::vector<Buffer::Tag> tags; | ||
| 163 | tags.reserve(released_buffers.size()); | ||
| 164 | while (!released_buffers.empty()) { | ||
| 165 | if (released_buffers.front()) { | ||
| 166 | tags.push_back(released_buffers.front()->GetTag()); | ||
| 167 | } else { | ||
| 168 | ASSERT_MSG(false, "Invalid tag in released_buffers!"); | ||
| 169 | } | ||
| 170 | released_buffers.pop(); | ||
| 171 | } | ||
| 172 | return tags; | ||
| 173 | } | ||
| 174 | |||
| 175 | } // namespace AudioCore | ||
diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h deleted file mode 100644 index f5de70396..000000000 --- a/src/audio_core/stream.h +++ /dev/null | |||
| @@ -1,130 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <chrono> | ||
| 7 | #include <functional> | ||
| 8 | #include <memory> | ||
| 9 | #include <string> | ||
| 10 | #include <vector> | ||
| 11 | #include <queue> | ||
| 12 | |||
| 13 | #include "audio_core/buffer.h" | ||
| 14 | #include "common/common_types.h" | ||
| 15 | |||
| 16 | namespace Core::Timing { | ||
| 17 | class CoreTiming; | ||
| 18 | struct EventType; | ||
| 19 | } // namespace Core::Timing | ||
| 20 | |||
| 21 | namespace AudioCore { | ||
| 22 | |||
| 23 | class SinkStream; | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Represents an audio stream, which is a sequence of queued buffers, to be outputed by AudioOut | ||
| 27 | */ | ||
| 28 | class Stream { | ||
| 29 | public: | ||
| 30 | /// Audio format of the stream | ||
| 31 | enum class Format { | ||
| 32 | Mono16, | ||
| 33 | Stereo16, | ||
| 34 | Multi51Channel16, | ||
| 35 | }; | ||
| 36 | |||
| 37 | /// Current state of the stream | ||
| 38 | enum class State { | ||
| 39 | Stopped, | ||
| 40 | Playing, | ||
| 41 | }; | ||
| 42 | |||
| 43 | /// Callback function type, used to change guest state on a buffer being released | ||
| 44 | using ReleaseCallback = std::function<void()>; | ||
| 45 | |||
| 46 | Stream(Core::Timing::CoreTiming& core_timing_, u32 sample_rate_, Format format_, | ||
| 47 | ReleaseCallback&& release_callback_, SinkStream& sink_stream_, std::string&& name_); | ||
| 48 | |||
| 49 | /// Plays the audio stream | ||
| 50 | void Play(); | ||
| 51 | |||
| 52 | /// Stops the audio stream | ||
| 53 | void Stop(); | ||
| 54 | |||
| 55 | /// Queues a buffer into the audio stream, returns true on success | ||
| 56 | bool QueueBuffer(BufferPtr&& buffer); | ||
| 57 | |||
| 58 | /// Flush audio buffers | ||
| 59 | bool Flush(); | ||
| 60 | |||
| 61 | /// Returns true if the audio stream contains a buffer with the specified tag | ||
| 62 | [[nodiscard]] bool ContainsBuffer(Buffer::Tag tag) const; | ||
| 63 | |||
| 64 | /// Returns a vector of recently released buffers specified by tag | ||
| 65 | [[nodiscard]] std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(std::size_t max_count); | ||
| 66 | |||
| 67 | /// Returns a vector of all recently released buffers specified by tag | ||
| 68 | [[nodiscard]] std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(); | ||
| 69 | |||
| 70 | void SetVolume(float volume); | ||
| 71 | |||
| 72 | [[nodiscard]] float GetVolume() const { | ||
| 73 | return game_volume; | ||
| 74 | } | ||
| 75 | |||
| 76 | /// Returns true if the stream is currently playing | ||
| 77 | [[nodiscard]] bool IsPlaying() const { | ||
| 78 | return state == State::Playing; | ||
| 79 | } | ||
| 80 | |||
| 81 | /// Returns the number of queued buffers | ||
| 82 | [[nodiscard]] std::size_t GetQueueSize() const { | ||
| 83 | return queued_buffers.size(); | ||
| 84 | } | ||
| 85 | |||
| 86 | /// Gets the sample rate | ||
| 87 | [[nodiscard]] u32 GetSampleRate() const { | ||
| 88 | return sample_rate; | ||
| 89 | } | ||
| 90 | |||
| 91 | /// Gets the number of samples played so far | ||
| 92 | [[nodiscard]] u64 GetPlayedSampleCount() const { | ||
| 93 | return played_samples; | ||
| 94 | } | ||
| 95 | |||
| 96 | /// Gets the number of channels | ||
| 97 | [[nodiscard]] u32 GetNumChannels() const; | ||
| 98 | |||
| 99 | /// Get the state | ||
| 100 | [[nodiscard]] State GetState() const; | ||
| 101 | |||
| 102 | private: | ||
| 103 | /// Plays the next queued buffer in the audio stream, starting playback if necessary | ||
| 104 | void PlayNextBuffer(std::chrono::nanoseconds ns_late = {}); | ||
| 105 | |||
| 106 | /// Releases the actively playing buffer, signalling that it has been completed | ||
| 107 | void ReleaseActiveBuffer(std::chrono::nanoseconds ns_late = {}); | ||
| 108 | |||
| 109 | /// Gets the number of core cycles when the specified buffer will be released | ||
| 110 | [[nodiscard]] std::chrono::nanoseconds GetBufferReleaseNS(const Buffer& buffer) const; | ||
| 111 | |||
| 112 | u32 sample_rate; ///< Sample rate of the stream | ||
| 113 | u64 played_samples{}; ///< The current played sample count | ||
| 114 | Format format; ///< Format of the stream | ||
| 115 | float game_volume = 1.0f; ///< The volume the game currently has set | ||
| 116 | ReleaseCallback release_callback; ///< Buffer release callback for the stream | ||
| 117 | State state{State::Stopped}; ///< Playback state of the stream | ||
| 118 | std::shared_ptr<Core::Timing::EventType> | ||
| 119 | release_event; ///< Core timing release event for the stream | ||
| 120 | BufferPtr active_buffer; ///< Actively playing buffer in the stream | ||
| 121 | std::queue<BufferPtr> queued_buffers; ///< Buffers queued to be played in the stream | ||
| 122 | std::queue<BufferPtr> released_buffers; ///< Buffers recently released from the stream | ||
| 123 | SinkStream& sink_stream; ///< Output sink for the stream | ||
| 124 | Core::Timing::CoreTiming& core_timing; ///< Core timing instance. | ||
| 125 | std::string name; ///< Name of the stream, must be unique | ||
| 126 | }; | ||
| 127 | |||
| 128 | using StreamPtr = std::shared_ptr<Stream>; | ||
| 129 | |||
| 130 | } // namespace AudioCore | ||
diff --git a/src/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp deleted file mode 100644 index f58a5c754..000000000 --- a/src/audio_core/voice_context.cpp +++ /dev/null | |||
| @@ -1,579 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | |||
| 6 | #include "audio_core/behavior_info.h" | ||
| 7 | #include "audio_core/voice_context.h" | ||
| 8 | #include "core/memory.h" | ||
| 9 | |||
| 10 | namespace AudioCore { | ||
| 11 | |||
| 12 | ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id_) : id(id_) {} | ||
| 13 | ServerVoiceChannelResource::~ServerVoiceChannelResource() = default; | ||
| 14 | |||
| 15 | bool ServerVoiceChannelResource::InUse() const { | ||
| 16 | return in_use; | ||
| 17 | } | ||
| 18 | |||
| 19 | float ServerVoiceChannelResource::GetCurrentMixVolumeAt(std::size_t i) const { | ||
| 20 | ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); | ||
| 21 | return mix_volume.at(i); | ||
| 22 | } | ||
| 23 | |||
| 24 | float ServerVoiceChannelResource::GetLastMixVolumeAt(std::size_t i) const { | ||
| 25 | ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); | ||
| 26 | return last_mix_volume.at(i); | ||
| 27 | } | ||
| 28 | |||
| 29 | void ServerVoiceChannelResource::Update(VoiceChannelResource::InParams& in_params) { | ||
| 30 | in_use = in_params.in_use; | ||
| 31 | // Update our mix volumes only if it's in use | ||
| 32 | if (in_params.in_use) { | ||
| 33 | mix_volume = in_params.mix_volume; | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | void ServerVoiceChannelResource::UpdateLastMixVolumes() { | ||
| 38 | last_mix_volume = mix_volume; | ||
| 39 | } | ||
| 40 | |||
| 41 | const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& | ||
| 42 | ServerVoiceChannelResource::GetCurrentMixVolume() const { | ||
| 43 | return mix_volume; | ||
| 44 | } | ||
| 45 | |||
| 46 | const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& | ||
| 47 | ServerVoiceChannelResource::GetLastMixVolume() const { | ||
| 48 | return last_mix_volume; | ||
| 49 | } | ||
| 50 | |||
| 51 | ServerVoiceInfo::ServerVoiceInfo() { | ||
| 52 | Initialize(); | ||
| 53 | } | ||
| 54 | ServerVoiceInfo::~ServerVoiceInfo() = default; | ||
| 55 | |||
| 56 | void ServerVoiceInfo::Initialize() { | ||
| 57 | in_params.in_use = false; | ||
| 58 | in_params.node_id = 0; | ||
| 59 | in_params.id = 0; | ||
| 60 | in_params.current_playstate = ServerPlayState::Stop; | ||
| 61 | in_params.priority = 255; | ||
| 62 | in_params.sample_rate = 0; | ||
| 63 | in_params.sample_format = SampleFormat::Invalid; | ||
| 64 | in_params.channel_count = 0; | ||
| 65 | in_params.pitch = 0.0f; | ||
| 66 | in_params.volume = 0.0f; | ||
| 67 | in_params.last_volume = 0.0f; | ||
| 68 | in_params.biquad_filter.fill({}); | ||
| 69 | in_params.wave_buffer_count = 0; | ||
| 70 | in_params.wave_buffer_head = 0; | ||
| 71 | in_params.mix_id = AudioCommon::NO_MIX; | ||
| 72 | in_params.splitter_info_id = AudioCommon::NO_SPLITTER; | ||
| 73 | in_params.additional_params_address = 0; | ||
| 74 | in_params.additional_params_size = 0; | ||
| 75 | in_params.is_new = false; | ||
| 76 | out_params.played_sample_count = 0; | ||
| 77 | out_params.wave_buffer_consumed = 0; | ||
| 78 | in_params.voice_drop_flag = false; | ||
| 79 | in_params.buffer_mapped = true; | ||
| 80 | in_params.wave_buffer_flush_request_count = 0; | ||
| 81 | in_params.was_biquad_filter_enabled.fill(false); | ||
| 82 | |||
| 83 | for (auto& wave_buffer : in_params.wave_buffer) { | ||
| 84 | wave_buffer.start_sample_offset = 0; | ||
| 85 | wave_buffer.end_sample_offset = 0; | ||
| 86 | wave_buffer.is_looping = false; | ||
| 87 | wave_buffer.end_of_stream = false; | ||
| 88 | wave_buffer.buffer_address = 0; | ||
| 89 | wave_buffer.buffer_size = 0; | ||
| 90 | wave_buffer.context_address = 0; | ||
| 91 | wave_buffer.context_size = 0; | ||
| 92 | wave_buffer.sent_to_dsp = true; | ||
| 93 | } | ||
| 94 | |||
| 95 | stored_samples.clear(); | ||
| 96 | } | ||
| 97 | |||
| 98 | void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in, | ||
| 99 | BehaviorInfo& behavior_info) { | ||
| 100 | in_params.in_use = voice_in.is_in_use; | ||
| 101 | in_params.id = voice_in.id; | ||
| 102 | in_params.node_id = voice_in.node_id; | ||
| 103 | in_params.last_playstate = in_params.current_playstate; | ||
| 104 | switch (voice_in.play_state) { | ||
| 105 | case PlayState::Paused: | ||
| 106 | in_params.current_playstate = ServerPlayState::Paused; | ||
| 107 | break; | ||
| 108 | case PlayState::Stopped: | ||
| 109 | if (in_params.current_playstate != ServerPlayState::Stop) { | ||
| 110 | in_params.current_playstate = ServerPlayState::RequestStop; | ||
| 111 | } | ||
| 112 | break; | ||
| 113 | case PlayState::Started: | ||
| 114 | in_params.current_playstate = ServerPlayState::Play; | ||
| 115 | break; | ||
| 116 | default: | ||
| 117 | ASSERT_MSG(false, "Unknown playstate {}", voice_in.play_state); | ||
| 118 | break; | ||
| 119 | } | ||
| 120 | |||
| 121 | in_params.priority = voice_in.priority; | ||
| 122 | in_params.sorting_order = voice_in.sorting_order; | ||
| 123 | in_params.sample_rate = voice_in.sample_rate; | ||
| 124 | in_params.sample_format = voice_in.sample_format; | ||
| 125 | in_params.channel_count = voice_in.channel_count; | ||
| 126 | in_params.pitch = voice_in.pitch; | ||
| 127 | in_params.volume = voice_in.volume; | ||
| 128 | in_params.biquad_filter = voice_in.biquad_filter; | ||
| 129 | in_params.wave_buffer_count = voice_in.wave_buffer_count; | ||
| 130 | in_params.wave_buffer_head = voice_in.wave_buffer_head; | ||
| 131 | if (behavior_info.IsFlushVoiceWaveBuffersSupported()) { | ||
| 132 | const auto in_request_count = in_params.wave_buffer_flush_request_count; | ||
| 133 | const auto voice_request_count = voice_in.wave_buffer_flush_request_count; | ||
| 134 | in_params.wave_buffer_flush_request_count = | ||
| 135 | static_cast<u8>(in_request_count + voice_request_count); | ||
| 136 | } | ||
| 137 | in_params.mix_id = voice_in.mix_id; | ||
| 138 | if (behavior_info.IsSplitterSupported()) { | ||
| 139 | in_params.splitter_info_id = voice_in.splitter_info_id; | ||
| 140 | } else { | ||
| 141 | in_params.splitter_info_id = AudioCommon::NO_SPLITTER; | ||
| 142 | } | ||
| 143 | |||
| 144 | std::memcpy(in_params.voice_channel_resource_id.data(), | ||
| 145 | voice_in.voice_channel_resource_ids.data(), | ||
| 146 | sizeof(s32) * in_params.voice_channel_resource_id.size()); | ||
| 147 | |||
| 148 | if (behavior_info.IsVoicePlayedSampleCountResetAtLoopPointSupported()) { | ||
| 149 | in_params.behavior_flags.is_played_samples_reset_at_loop_point = | ||
| 150 | voice_in.behavior_flags.is_played_samples_reset_at_loop_point; | ||
| 151 | } else { | ||
| 152 | in_params.behavior_flags.is_played_samples_reset_at_loop_point.Assign(0); | ||
| 153 | } | ||
| 154 | if (behavior_info.IsVoicePitchAndSrcSkippedSupported()) { | ||
| 155 | in_params.behavior_flags.is_pitch_and_src_skipped = | ||
| 156 | voice_in.behavior_flags.is_pitch_and_src_skipped; | ||
| 157 | } else { | ||
| 158 | in_params.behavior_flags.is_pitch_and_src_skipped.Assign(0); | ||
| 159 | } | ||
| 160 | |||
| 161 | if (voice_in.is_voice_drop_flag_clear_requested) { | ||
| 162 | in_params.voice_drop_flag = false; | ||
| 163 | } | ||
| 164 | |||
| 165 | if (in_params.additional_params_address != voice_in.additional_params_address || | ||
| 166 | in_params.additional_params_size != voice_in.additional_params_size) { | ||
| 167 | in_params.additional_params_address = voice_in.additional_params_address; | ||
| 168 | in_params.additional_params_size = voice_in.additional_params_size; | ||
| 169 | // TODO(ogniK): Reattach buffer, do we actually need to? Maybe just signal to the DSP that | ||
| 170 | // our context is new | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | void ServerVoiceInfo::UpdateWaveBuffers( | ||
| 175 | const VoiceInfo::InParams& voice_in, | ||
| 176 | std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states, | ||
| 177 | BehaviorInfo& behavior_info) { | ||
| 178 | if (voice_in.is_new) { | ||
| 179 | // Initialize our wave buffers | ||
| 180 | for (auto& wave_buffer : in_params.wave_buffer) { | ||
| 181 | wave_buffer.start_sample_offset = 0; | ||
| 182 | wave_buffer.end_sample_offset = 0; | ||
| 183 | wave_buffer.is_looping = false; | ||
| 184 | wave_buffer.end_of_stream = false; | ||
| 185 | wave_buffer.buffer_address = 0; | ||
| 186 | wave_buffer.buffer_size = 0; | ||
| 187 | wave_buffer.context_address = 0; | ||
| 188 | wave_buffer.context_size = 0; | ||
| 189 | wave_buffer.loop_start_sample = 0; | ||
| 190 | wave_buffer.loop_end_sample = 0; | ||
| 191 | wave_buffer.sent_to_dsp = true; | ||
| 192 | } | ||
| 193 | |||
| 194 | // Mark all our wave buffers as invalid | ||
| 195 | for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count); | ||
| 196 | channel++) { | ||
| 197 | for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; ++i) { | ||
| 198 | voice_states[channel]->is_wave_buffer_valid[i] = false; | ||
| 199 | } | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | // Update our wave buffers | ||
| 204 | for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) { | ||
| 205 | // Assume that we have at least 1 channel voice state | ||
| 206 | const auto have_valid_wave_buffer = voice_states[0]->is_wave_buffer_valid[i]; | ||
| 207 | |||
| 208 | UpdateWaveBuffer(in_params.wave_buffer[i], voice_in.wave_buffer[i], in_params.sample_format, | ||
| 209 | have_valid_wave_buffer, behavior_info); | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, | ||
| 214 | const WaveBuffer& in_wave_buffer, SampleFormat sample_format, | ||
| 215 | bool is_buffer_valid, | ||
| 216 | [[maybe_unused]] BehaviorInfo& behavior_info) { | ||
| 217 | if (!is_buffer_valid && out_wavebuffer.sent_to_dsp && out_wavebuffer.buffer_address != 0) { | ||
| 218 | out_wavebuffer.buffer_address = 0; | ||
| 219 | out_wavebuffer.buffer_size = 0; | ||
| 220 | } | ||
| 221 | |||
| 222 | if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) { | ||
| 223 | // Validate sample offset sizings | ||
| 224 | if (sample_format == SampleFormat::Pcm16) { | ||
| 225 | const s64 buffer_size = static_cast<s64>(in_wave_buffer.buffer_size); | ||
| 226 | const s64 start = sizeof(s16) * in_wave_buffer.start_sample_offset; | ||
| 227 | const s64 end = sizeof(s16) * in_wave_buffer.end_sample_offset; | ||
| 228 | if (0 > start || start > buffer_size || 0 > end || end > buffer_size) { | ||
| 229 | // TODO(ogniK): Write error info | ||
| 230 | LOG_ERROR(Audio, | ||
| 231 | "PCM16 wavebuffer has an invalid size. Buffer has size 0x{:08X}, but " | ||
| 232 | "offsets were " | ||
| 233 | "{:08X} - 0x{:08X}", | ||
| 234 | buffer_size, sizeof(s16) * in_wave_buffer.start_sample_offset, | ||
| 235 | sizeof(s16) * in_wave_buffer.end_sample_offset); | ||
| 236 | return; | ||
| 237 | } | ||
| 238 | } else if (sample_format == SampleFormat::Adpcm) { | ||
| 239 | const s64 buffer_size = static_cast<s64>(in_wave_buffer.buffer_size); | ||
| 240 | const s64 start_frames = in_wave_buffer.start_sample_offset / 14; | ||
| 241 | const s64 start_extra = in_wave_buffer.start_sample_offset % 14 == 0 | ||
| 242 | ? 0 | ||
| 243 | : (in_wave_buffer.start_sample_offset % 14) / 2 + 1 + | ||
| 244 | (in_wave_buffer.start_sample_offset % 2); | ||
| 245 | const s64 start = start_frames * 8 + start_extra; | ||
| 246 | const s64 end_frames = in_wave_buffer.end_sample_offset / 14; | ||
| 247 | const s64 end_extra = in_wave_buffer.end_sample_offset % 14 == 0 | ||
| 248 | ? 0 | ||
| 249 | : (in_wave_buffer.end_sample_offset % 14) / 2 + 1 + | ||
| 250 | (in_wave_buffer.end_sample_offset % 2); | ||
| 251 | const s64 end = end_frames * 8 + end_extra; | ||
| 252 | if (in_wave_buffer.start_sample_offset < 0 || start > buffer_size || | ||
| 253 | in_wave_buffer.end_sample_offset < 0 || end > buffer_size) { | ||
| 254 | LOG_ERROR(Audio, | ||
| 255 | "ADPMC wavebuffer has an invalid size. Buffer has size 0x{:08X}, but " | ||
| 256 | "offsets were " | ||
| 257 | "{:08X} - 0x{:08X}", | ||
| 258 | in_wave_buffer.buffer_size, start, end); | ||
| 259 | return; | ||
| 260 | } | ||
| 261 | } | ||
| 262 | // TODO(ogniK): ADPCM Size error | ||
| 263 | |||
| 264 | out_wavebuffer.sent_to_dsp = false; | ||
| 265 | out_wavebuffer.start_sample_offset = in_wave_buffer.start_sample_offset; | ||
| 266 | out_wavebuffer.end_sample_offset = in_wave_buffer.end_sample_offset; | ||
| 267 | out_wavebuffer.is_looping = in_wave_buffer.is_looping; | ||
| 268 | out_wavebuffer.end_of_stream = in_wave_buffer.end_of_stream; | ||
| 269 | |||
| 270 | out_wavebuffer.buffer_address = in_wave_buffer.buffer_address; | ||
| 271 | out_wavebuffer.buffer_size = in_wave_buffer.buffer_size; | ||
| 272 | out_wavebuffer.context_address = in_wave_buffer.context_address; | ||
| 273 | out_wavebuffer.context_size = in_wave_buffer.context_size; | ||
| 274 | out_wavebuffer.loop_start_sample = in_wave_buffer.loop_start_sample; | ||
| 275 | out_wavebuffer.loop_end_sample = in_wave_buffer.loop_end_sample; | ||
| 276 | in_params.buffer_mapped = | ||
| 277 | in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0; | ||
| 278 | // TODO(ogniK): Pool mapper attachment | ||
| 279 | // TODO(ogniK): IsAdpcmLoopContextBugFixed | ||
| 280 | if (sample_format == SampleFormat::Adpcm && in_wave_buffer.context_address != 0 && | ||
| 281 | in_wave_buffer.context_size != 0 && behavior_info.IsAdpcmLoopContextBugFixed()) { | ||
| 282 | } else { | ||
| 283 | out_wavebuffer.context_address = 0; | ||
| 284 | out_wavebuffer.context_size = 0; | ||
| 285 | } | ||
| 286 | } | ||
| 287 | } | ||
| 288 | |||
| 289 | void ServerVoiceInfo::WriteOutStatus( | ||
| 290 | VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in, | ||
| 291 | std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) { | ||
| 292 | if (voice_in.is_new || in_params.is_new) { | ||
| 293 | in_params.is_new = true; | ||
| 294 | voice_out.wave_buffer_consumed = 0; | ||
| 295 | voice_out.played_sample_count = 0; | ||
| 296 | voice_out.voice_dropped = false; | ||
| 297 | } else { | ||
| 298 | const auto& state = voice_states[0]; | ||
| 299 | voice_out.wave_buffer_consumed = state->wave_buffer_consumed; | ||
| 300 | voice_out.played_sample_count = state->played_sample_count; | ||
| 301 | voice_out.voice_dropped = state->voice_dropped; | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | const ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() const { | ||
| 306 | return in_params; | ||
| 307 | } | ||
| 308 | |||
| 309 | ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() { | ||
| 310 | return in_params; | ||
| 311 | } | ||
| 312 | |||
| 313 | const ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() const { | ||
| 314 | return out_params; | ||
| 315 | } | ||
| 316 | |||
| 317 | ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() { | ||
| 318 | return out_params; | ||
| 319 | } | ||
| 320 | |||
| 321 | bool ServerVoiceInfo::ShouldSkip() const { | ||
| 322 | // TODO(ogniK): Handle unmapped wave buffers or parameters | ||
| 323 | return !in_params.in_use || in_params.wave_buffer_count == 0 || !in_params.buffer_mapped || | ||
| 324 | in_params.voice_drop_flag; | ||
| 325 | } | ||
| 326 | |||
| 327 | bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) { | ||
| 328 | std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> dsp_voice_states{}; | ||
| 329 | if (in_params.is_new) { | ||
| 330 | ResetResources(voice_context); | ||
| 331 | in_params.last_volume = in_params.volume; | ||
| 332 | in_params.is_new = false; | ||
| 333 | } | ||
| 334 | |||
| 335 | const s32 channel_count = in_params.channel_count; | ||
| 336 | for (s32 i = 0; i < channel_count; i++) { | ||
| 337 | const auto channel_resource = in_params.voice_channel_resource_id[i]; | ||
| 338 | dsp_voice_states[i] = | ||
| 339 | &voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource)); | ||
| 340 | } | ||
| 341 | return UpdateParametersForCommandGeneration(dsp_voice_states); | ||
| 342 | } | ||
| 343 | |||
| 344 | void ServerVoiceInfo::ResetResources(VoiceContext& voice_context) { | ||
| 345 | const s32 channel_count = in_params.channel_count; | ||
| 346 | for (s32 i = 0; i < channel_count; i++) { | ||
| 347 | const auto channel_resource = in_params.voice_channel_resource_id[i]; | ||
| 348 | auto& dsp_state = | ||
| 349 | voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource)); | ||
| 350 | dsp_state = {}; | ||
| 351 | voice_context.GetChannelResource(static_cast<std::size_t>(channel_resource)) | ||
| 352 | .UpdateLastMixVolumes(); | ||
| 353 | } | ||
| 354 | } | ||
| 355 | |||
| 356 | bool ServerVoiceInfo::UpdateParametersForCommandGeneration( | ||
| 357 | std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states) { | ||
| 358 | const s32 channel_count = in_params.channel_count; | ||
| 359 | if (in_params.wave_buffer_flush_request_count > 0) { | ||
| 360 | FlushWaveBuffers(in_params.wave_buffer_flush_request_count, dsp_voice_states, | ||
| 361 | channel_count); | ||
| 362 | in_params.wave_buffer_flush_request_count = 0; | ||
| 363 | } | ||
| 364 | |||
| 365 | switch (in_params.current_playstate) { | ||
| 366 | case ServerPlayState::Play: { | ||
| 367 | for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) { | ||
| 368 | if (!in_params.wave_buffer[i].sent_to_dsp) { | ||
| 369 | for (s32 channel = 0; channel < channel_count; channel++) { | ||
| 370 | dsp_voice_states[channel]->is_wave_buffer_valid[i] = true; | ||
| 371 | } | ||
| 372 | in_params.wave_buffer[i].sent_to_dsp = true; | ||
| 373 | } | ||
| 374 | } | ||
| 375 | in_params.should_depop = false; | ||
| 376 | return HasValidWaveBuffer(dsp_voice_states[0]); | ||
| 377 | } | ||
| 378 | case ServerPlayState::Paused: | ||
| 379 | case ServerPlayState::Stop: { | ||
| 380 | in_params.should_depop = in_params.last_playstate == ServerPlayState::Play; | ||
| 381 | return in_params.should_depop; | ||
| 382 | } | ||
| 383 | case ServerPlayState::RequestStop: { | ||
| 384 | for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) { | ||
| 385 | in_params.wave_buffer[i].sent_to_dsp = true; | ||
| 386 | for (s32 channel = 0; channel < channel_count; channel++) { | ||
| 387 | auto* dsp_state = dsp_voice_states[channel]; | ||
| 388 | |||
| 389 | if (dsp_state->is_wave_buffer_valid[i]) { | ||
| 390 | dsp_state->wave_buffer_index = | ||
| 391 | (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; | ||
| 392 | dsp_state->wave_buffer_consumed++; | ||
| 393 | } | ||
| 394 | |||
| 395 | dsp_state->is_wave_buffer_valid[i] = false; | ||
| 396 | } | ||
| 397 | } | ||
| 398 | |||
| 399 | for (s32 channel = 0; channel < channel_count; channel++) { | ||
| 400 | auto* dsp_state = dsp_voice_states[channel]; | ||
| 401 | dsp_state->offset = 0; | ||
| 402 | dsp_state->played_sample_count = 0; | ||
| 403 | dsp_state->fraction = 0; | ||
| 404 | dsp_state->sample_history.fill(0); | ||
| 405 | dsp_state->context = {}; | ||
| 406 | } | ||
| 407 | |||
| 408 | in_params.current_playstate = ServerPlayState::Stop; | ||
| 409 | in_params.should_depop = in_params.last_playstate == ServerPlayState::Play; | ||
| 410 | return in_params.should_depop; | ||
| 411 | } | ||
| 412 | default: | ||
| 413 | ASSERT_MSG(false, "Invalid playstate {}", in_params.current_playstate); | ||
| 414 | } | ||
| 415 | |||
| 416 | return false; | ||
| 417 | } | ||
| 418 | |||
| 419 | void ServerVoiceInfo::FlushWaveBuffers( | ||
| 420 | u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states, | ||
| 421 | s32 channel_count) { | ||
| 422 | auto wave_head = in_params.wave_buffer_head; | ||
| 423 | |||
| 424 | for (u8 i = 0; i < flush_count; i++) { | ||
| 425 | in_params.wave_buffer[wave_head].sent_to_dsp = true; | ||
| 426 | for (s32 channel = 0; channel < channel_count; channel++) { | ||
| 427 | auto* dsp_state = dsp_voice_states[channel]; | ||
| 428 | dsp_state->wave_buffer_consumed++; | ||
| 429 | dsp_state->is_wave_buffer_valid[wave_head] = false; | ||
| 430 | dsp_state->wave_buffer_index = | ||
| 431 | (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; | ||
| 432 | } | ||
| 433 | wave_head = (wave_head + 1) % AudioCommon::MAX_WAVE_BUFFERS; | ||
| 434 | } | ||
| 435 | } | ||
| 436 | |||
| 437 | bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const { | ||
| 438 | const auto& valid_wb = state->is_wave_buffer_valid; | ||
| 439 | return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end(); | ||
| 440 | } | ||
| 441 | |||
| 442 | void ServerVoiceInfo::SetWaveBufferCompleted(VoiceState& dsp_state, | ||
| 443 | const ServerWaveBuffer& wave_buffer) { | ||
| 444 | dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false; | ||
| 445 | dsp_state.wave_buffer_consumed++; | ||
| 446 | dsp_state.wave_buffer_index = (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; | ||
| 447 | dsp_state.loop_count = 0; | ||
| 448 | if (wave_buffer.end_of_stream) { | ||
| 449 | dsp_state.played_sample_count = 0; | ||
| 450 | } | ||
| 451 | } | ||
| 452 | |||
| 453 | VoiceContext::VoiceContext(std::size_t voice_count_) : voice_count{voice_count_} { | ||
| 454 | for (std::size_t i = 0; i < voice_count; i++) { | ||
| 455 | voice_channel_resources.emplace_back(static_cast<s32>(i)); | ||
| 456 | sorted_voice_info.push_back(&voice_info.emplace_back()); | ||
| 457 | voice_states.emplace_back(); | ||
| 458 | dsp_voice_states.emplace_back(); | ||
| 459 | } | ||
| 460 | } | ||
| 461 | |||
| 462 | VoiceContext::~VoiceContext() { | ||
| 463 | sorted_voice_info.clear(); | ||
| 464 | } | ||
| 465 | |||
| 466 | std::size_t VoiceContext::GetVoiceCount() const { | ||
| 467 | return voice_count; | ||
| 468 | } | ||
| 469 | |||
| 470 | ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) { | ||
| 471 | ASSERT(i < voice_count); | ||
| 472 | return voice_channel_resources.at(i); | ||
| 473 | } | ||
| 474 | |||
| 475 | const ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) const { | ||
| 476 | ASSERT(i < voice_count); | ||
| 477 | return voice_channel_resources.at(i); | ||
| 478 | } | ||
| 479 | |||
| 480 | VoiceState& VoiceContext::GetState(std::size_t i) { | ||
| 481 | ASSERT(i < voice_count); | ||
| 482 | return voice_states.at(i); | ||
| 483 | } | ||
| 484 | |||
| 485 | const VoiceState& VoiceContext::GetState(std::size_t i) const { | ||
| 486 | ASSERT(i < voice_count); | ||
| 487 | return voice_states.at(i); | ||
| 488 | } | ||
| 489 | |||
| 490 | VoiceState& VoiceContext::GetDspSharedState(std::size_t i) { | ||
| 491 | ASSERT(i < voice_count); | ||
| 492 | return dsp_voice_states.at(i); | ||
| 493 | } | ||
| 494 | |||
| 495 | const VoiceState& VoiceContext::GetDspSharedState(std::size_t i) const { | ||
| 496 | ASSERT(i < voice_count); | ||
| 497 | return dsp_voice_states.at(i); | ||
| 498 | } | ||
| 499 | |||
| 500 | ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) { | ||
| 501 | ASSERT(i < voice_count); | ||
| 502 | return voice_info.at(i); | ||
| 503 | } | ||
| 504 | |||
| 505 | const ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) const { | ||
| 506 | ASSERT(i < voice_count); | ||
| 507 | return voice_info.at(i); | ||
| 508 | } | ||
| 509 | |||
| 510 | ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) { | ||
| 511 | ASSERT(i < voice_count); | ||
| 512 | return *sorted_voice_info.at(i); | ||
| 513 | } | ||
| 514 | |||
| 515 | const ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) const { | ||
| 516 | ASSERT(i < voice_count); | ||
| 517 | return *sorted_voice_info.at(i); | ||
| 518 | } | ||
| 519 | |||
| 520 | s32 VoiceContext::DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel, | ||
| 521 | s32 channel_count, s32 buffer_offset, s32 sample_count, | ||
| 522 | Core::Memory::Memory& memory) { | ||
| 523 | if (wave_buffer->buffer_address == 0) { | ||
| 524 | return 0; | ||
| 525 | } | ||
| 526 | if (wave_buffer->buffer_size == 0) { | ||
| 527 | return 0; | ||
| 528 | } | ||
| 529 | if (wave_buffer->end_sample_offset < wave_buffer->start_sample_offset) { | ||
| 530 | return 0; | ||
| 531 | } | ||
| 532 | |||
| 533 | const auto samples_remaining = | ||
| 534 | (wave_buffer->end_sample_offset - wave_buffer->start_sample_offset) - buffer_offset; | ||
| 535 | const auto start_offset = (wave_buffer->start_sample_offset + buffer_offset) * channel_count; | ||
| 536 | const auto buffer_pos = wave_buffer->buffer_address + start_offset; | ||
| 537 | |||
| 538 | s16* buffer_data = reinterpret_cast<s16*>(memory.GetPointer(buffer_pos)); | ||
| 539 | |||
| 540 | const auto samples_processed = std::min(sample_count, samples_remaining); | ||
| 541 | |||
| 542 | // Fast path | ||
| 543 | if (channel_count == 1) { | ||
| 544 | for (std::ptrdiff_t i = 0; i < samples_processed; i++) { | ||
| 545 | output_buffer[i] = buffer_data[i]; | ||
| 546 | } | ||
| 547 | } else { | ||
| 548 | for (std::ptrdiff_t i = 0; i < samples_processed; i++) { | ||
| 549 | output_buffer[i] = buffer_data[i * channel_count + channel]; | ||
| 550 | } | ||
| 551 | } | ||
| 552 | |||
| 553 | return samples_processed; | ||
| 554 | } | ||
| 555 | |||
| 556 | void VoiceContext::SortInfo() { | ||
| 557 | for (std::size_t i = 0; i < voice_count; i++) { | ||
| 558 | sorted_voice_info[i] = &voice_info[i]; | ||
| 559 | } | ||
| 560 | |||
| 561 | std::sort(sorted_voice_info.begin(), sorted_voice_info.end(), | ||
| 562 | [](const ServerVoiceInfo* lhs, const ServerVoiceInfo* rhs) { | ||
| 563 | const auto& lhs_in = lhs->GetInParams(); | ||
| 564 | const auto& rhs_in = rhs->GetInParams(); | ||
| 565 | // Sort by priority | ||
| 566 | if (lhs_in.priority != rhs_in.priority) { | ||
| 567 | return lhs_in.priority > rhs_in.priority; | ||
| 568 | } else { | ||
| 569 | // If the priorities match, sort by sorting order | ||
| 570 | return lhs_in.sorting_order > rhs_in.sorting_order; | ||
| 571 | } | ||
| 572 | }); | ||
| 573 | } | ||
| 574 | |||
| 575 | void VoiceContext::UpdateStateByDspShared() { | ||
| 576 | voice_states = dsp_voice_states; | ||
| 577 | } | ||
| 578 | |||
| 579 | } // namespace AudioCore | ||
diff --git a/src/audio_core/voice_context.h b/src/audio_core/voice_context.h deleted file mode 100644 index 259220dc7..000000000 --- a/src/audio_core/voice_context.h +++ /dev/null | |||
| @@ -1,302 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include "audio_core/algorithm/interpolate.h" | ||
| 8 | #include "audio_core/codec.h" | ||
| 9 | #include "audio_core/common.h" | ||
| 10 | #include "common/bit_field.h" | ||
| 11 | #include "common/common_funcs.h" | ||
| 12 | #include "common/common_types.h" | ||
| 13 | |||
| 14 | namespace Core::Memory { | ||
| 15 | class Memory; | ||
| 16 | } | ||
| 17 | |||
| 18 | namespace AudioCore { | ||
| 19 | |||
| 20 | class BehaviorInfo; | ||
| 21 | class VoiceContext; | ||
| 22 | |||
| 23 | enum class SampleFormat : u8 { | ||
| 24 | Invalid = 0, | ||
| 25 | Pcm8 = 1, | ||
| 26 | Pcm16 = 2, | ||
| 27 | Pcm24 = 3, | ||
| 28 | Pcm32 = 4, | ||
| 29 | PcmFloat = 5, | ||
| 30 | Adpcm = 6, | ||
| 31 | }; | ||
| 32 | |||
| 33 | enum class PlayState : u8 { | ||
| 34 | Started = 0, | ||
| 35 | Stopped = 1, | ||
| 36 | Paused = 2, | ||
| 37 | }; | ||
| 38 | |||
| 39 | enum class ServerPlayState { | ||
| 40 | Play = 0, | ||
| 41 | Stop = 1, | ||
| 42 | RequestStop = 2, | ||
| 43 | Paused = 3, | ||
| 44 | }; | ||
| 45 | |||
| 46 | struct BiquadFilterParameter { | ||
| 47 | bool enabled{}; | ||
| 48 | INSERT_PADDING_BYTES(1); | ||
| 49 | std::array<s16, 3> numerator{}; | ||
| 50 | std::array<s16, 2> denominator{}; | ||
| 51 | }; | ||
| 52 | static_assert(sizeof(BiquadFilterParameter) == 0xc, "BiquadFilterParameter is an invalid size"); | ||
| 53 | |||
| 54 | struct WaveBuffer { | ||
| 55 | u64_le buffer_address{}; | ||
| 56 | u64_le buffer_size{}; | ||
| 57 | s32_le start_sample_offset{}; | ||
| 58 | s32_le end_sample_offset{}; | ||
| 59 | u8 is_looping{}; | ||
| 60 | u8 end_of_stream{}; | ||
| 61 | u8 sent_to_server{}; | ||
| 62 | INSERT_PADDING_BYTES(1); | ||
| 63 | s32 loop_count{}; | ||
| 64 | u64 context_address{}; | ||
| 65 | u64 context_size{}; | ||
| 66 | u32 loop_start_sample{}; | ||
| 67 | u32 loop_end_sample{}; | ||
| 68 | }; | ||
| 69 | static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size"); | ||
| 70 | |||
| 71 | struct ServerWaveBuffer { | ||
| 72 | VAddr buffer_address{}; | ||
| 73 | std::size_t buffer_size{}; | ||
| 74 | s32 start_sample_offset{}; | ||
| 75 | s32 end_sample_offset{}; | ||
| 76 | bool is_looping{}; | ||
| 77 | bool end_of_stream{}; | ||
| 78 | VAddr context_address{}; | ||
| 79 | std::size_t context_size{}; | ||
| 80 | s32 loop_count{}; | ||
| 81 | u32 loop_start_sample{}; | ||
| 82 | u32 loop_end_sample{}; | ||
| 83 | bool sent_to_dsp{true}; | ||
| 84 | }; | ||
| 85 | |||
| 86 | struct BehaviorFlags { | ||
| 87 | BitField<0, 1, u16> is_played_samples_reset_at_loop_point; | ||
| 88 | BitField<1, 1, u16> is_pitch_and_src_skipped; | ||
| 89 | }; | ||
| 90 | static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size"); | ||
| 91 | |||
| 92 | struct ADPCMContext { | ||
| 93 | u16 header; | ||
| 94 | s16 yn1; | ||
| 95 | s16 yn2; | ||
| 96 | }; | ||
| 97 | static_assert(sizeof(ADPCMContext) == 0x6, "ADPCMContext is an invalid size"); | ||
| 98 | |||
| 99 | struct VoiceState { | ||
| 100 | s64 played_sample_count; | ||
| 101 | s32 offset; | ||
| 102 | s32 wave_buffer_index; | ||
| 103 | std::array<bool, AudioCommon::MAX_WAVE_BUFFERS> is_wave_buffer_valid; | ||
| 104 | s32 wave_buffer_consumed; | ||
| 105 | std::array<s32, AudioCommon::MAX_SAMPLE_HISTORY> sample_history; | ||
| 106 | s32 fraction; | ||
| 107 | VAddr context_address; | ||
| 108 | Codec::ADPCM_Coeff coeff; | ||
| 109 | ADPCMContext context; | ||
| 110 | std::array<s64, 2> biquad_filter_state; | ||
| 111 | std::array<s32, AudioCommon::MAX_MIX_BUFFERS> previous_samples; | ||
| 112 | u32 external_context_size; | ||
| 113 | bool is_external_context_used; | ||
| 114 | bool voice_dropped; | ||
| 115 | s32 loop_count; | ||
| 116 | }; | ||
| 117 | |||
| 118 | class VoiceChannelResource { | ||
| 119 | public: | ||
| 120 | struct InParams { | ||
| 121 | s32_le id{}; | ||
| 122 | std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volume{}; | ||
| 123 | bool in_use{}; | ||
| 124 | INSERT_PADDING_BYTES(11); | ||
| 125 | }; | ||
| 126 | static_assert(sizeof(InParams) == 0x70, "InParams is an invalid size"); | ||
| 127 | }; | ||
| 128 | |||
| 129 | class ServerVoiceChannelResource { | ||
| 130 | public: | ||
| 131 | explicit ServerVoiceChannelResource(s32 id_); | ||
| 132 | ~ServerVoiceChannelResource(); | ||
| 133 | |||
| 134 | bool InUse() const; | ||
| 135 | float GetCurrentMixVolumeAt(std::size_t i) const; | ||
| 136 | float GetLastMixVolumeAt(std::size_t i) const; | ||
| 137 | void Update(VoiceChannelResource::InParams& in_params); | ||
| 138 | void UpdateLastMixVolumes(); | ||
| 139 | |||
| 140 | const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetCurrentMixVolume() const; | ||
| 141 | const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetLastMixVolume() const; | ||
| 142 | |||
| 143 | private: | ||
| 144 | s32 id{}; | ||
| 145 | std::array<float, AudioCommon::MAX_MIX_BUFFERS> mix_volume{}; | ||
| 146 | std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volume{}; | ||
| 147 | bool in_use{}; | ||
| 148 | }; | ||
| 149 | |||
| 150 | class VoiceInfo { | ||
| 151 | public: | ||
| 152 | struct InParams { | ||
| 153 | s32_le id{}; | ||
| 154 | u32_le node_id{}; | ||
| 155 | u8 is_new{}; | ||
| 156 | u8 is_in_use{}; | ||
| 157 | PlayState play_state{}; | ||
| 158 | SampleFormat sample_format{}; | ||
| 159 | s32_le sample_rate{}; | ||
| 160 | s32_le priority{}; | ||
| 161 | s32_le sorting_order{}; | ||
| 162 | s32_le channel_count{}; | ||
| 163 | float_le pitch{}; | ||
| 164 | float_le volume{}; | ||
| 165 | std::array<BiquadFilterParameter, 2> biquad_filter{}; | ||
| 166 | s32_le wave_buffer_count{}; | ||
| 167 | s16_le wave_buffer_head{}; | ||
| 168 | INSERT_PADDING_BYTES(6); | ||
| 169 | u64_le additional_params_address{}; | ||
| 170 | u64_le additional_params_size{}; | ||
| 171 | s32_le mix_id{}; | ||
| 172 | s32_le splitter_info_id{}; | ||
| 173 | std::array<WaveBuffer, 4> wave_buffer{}; | ||
| 174 | std::array<u32_le, 6> voice_channel_resource_ids{}; | ||
| 175 | // TODO(ogniK): Remaining flags | ||
| 176 | u8 is_voice_drop_flag_clear_requested{}; | ||
| 177 | u8 wave_buffer_flush_request_count{}; | ||
| 178 | INSERT_PADDING_BYTES(2); | ||
| 179 | BehaviorFlags behavior_flags{}; | ||
| 180 | INSERT_PADDING_BYTES(16); | ||
| 181 | }; | ||
| 182 | static_assert(sizeof(InParams) == 0x170, "InParams is an invalid size"); | ||
| 183 | |||
| 184 | struct OutParams { | ||
| 185 | u64_le played_sample_count{}; | ||
| 186 | u32_le wave_buffer_consumed{}; | ||
| 187 | u8 voice_dropped{}; | ||
| 188 | INSERT_PADDING_BYTES(3); | ||
| 189 | }; | ||
| 190 | static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size"); | ||
| 191 | }; | ||
| 192 | |||
| 193 | class ServerVoiceInfo { | ||
| 194 | public: | ||
| 195 | struct InParams { | ||
| 196 | bool in_use{}; | ||
| 197 | bool is_new{}; | ||
| 198 | bool should_depop{}; | ||
| 199 | SampleFormat sample_format{}; | ||
| 200 | s32 sample_rate{}; | ||
| 201 | s32 channel_count{}; | ||
| 202 | s32 id{}; | ||
| 203 | s32 node_id{}; | ||
| 204 | s32 mix_id{}; | ||
| 205 | ServerPlayState current_playstate{}; | ||
| 206 | ServerPlayState last_playstate{}; | ||
| 207 | s32 priority{}; | ||
| 208 | s32 sorting_order{}; | ||
| 209 | float pitch{}; | ||
| 210 | float volume{}; | ||
| 211 | float last_volume{}; | ||
| 212 | std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{}; | ||
| 213 | s32 wave_buffer_count{}; | ||
| 214 | s16 wave_buffer_head{}; | ||
| 215 | INSERT_PADDING_BYTES(2); | ||
| 216 | BehaviorFlags behavior_flags{}; | ||
| 217 | VAddr additional_params_address{}; | ||
| 218 | std::size_t additional_params_size{}; | ||
| 219 | std::array<ServerWaveBuffer, AudioCommon::MAX_WAVE_BUFFERS> wave_buffer{}; | ||
| 220 | std::array<s32, AudioCommon::MAX_CHANNEL_COUNT> voice_channel_resource_id{}; | ||
| 221 | s32 splitter_info_id{}; | ||
| 222 | u8 wave_buffer_flush_request_count{}; | ||
| 223 | bool voice_drop_flag{}; | ||
| 224 | bool buffer_mapped{}; | ||
| 225 | std::array<bool, AudioCommon::MAX_BIQUAD_FILTERS> was_biquad_filter_enabled{}; | ||
| 226 | }; | ||
| 227 | |||
| 228 | struct OutParams { | ||
| 229 | s64 played_sample_count{}; | ||
| 230 | s32 wave_buffer_consumed{}; | ||
| 231 | }; | ||
| 232 | |||
| 233 | ServerVoiceInfo(); | ||
| 234 | ~ServerVoiceInfo(); | ||
| 235 | void Initialize(); | ||
| 236 | void UpdateParameters(const VoiceInfo::InParams& voice_in, BehaviorInfo& behavior_info); | ||
| 237 | void UpdateWaveBuffers(const VoiceInfo::InParams& voice_in, | ||
| 238 | std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states, | ||
| 239 | BehaviorInfo& behavior_info); | ||
| 240 | void UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, const WaveBuffer& in_wave_buffer, | ||
| 241 | SampleFormat sample_format, bool is_buffer_valid, | ||
| 242 | BehaviorInfo& behavior_info); | ||
| 243 | void WriteOutStatus(VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in, | ||
| 244 | std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states); | ||
| 245 | |||
| 246 | const InParams& GetInParams() const; | ||
| 247 | InParams& GetInParams(); | ||
| 248 | |||
| 249 | const OutParams& GetOutParams() const; | ||
| 250 | OutParams& GetOutParams(); | ||
| 251 | |||
| 252 | bool ShouldSkip() const; | ||
| 253 | bool UpdateForCommandGeneration(VoiceContext& voice_context); | ||
| 254 | void ResetResources(VoiceContext& voice_context); | ||
| 255 | bool UpdateParametersForCommandGeneration( | ||
| 256 | std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states); | ||
| 257 | void FlushWaveBuffers(u8 flush_count, | ||
| 258 | std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states, | ||
| 259 | s32 channel_count); | ||
| 260 | void SetWaveBufferCompleted(VoiceState& dsp_state, const ServerWaveBuffer& wave_buffer); | ||
| 261 | |||
| 262 | private: | ||
| 263 | std::vector<s16> stored_samples; | ||
| 264 | InParams in_params{}; | ||
| 265 | OutParams out_params{}; | ||
| 266 | |||
| 267 | bool HasValidWaveBuffer(const VoiceState* state) const; | ||
| 268 | }; | ||
| 269 | |||
| 270 | class VoiceContext { | ||
| 271 | public: | ||
| 272 | explicit VoiceContext(std::size_t voice_count_); | ||
| 273 | ~VoiceContext(); | ||
| 274 | |||
| 275 | std::size_t GetVoiceCount() const; | ||
| 276 | ServerVoiceChannelResource& GetChannelResource(std::size_t i); | ||
| 277 | const ServerVoiceChannelResource& GetChannelResource(std::size_t i) const; | ||
| 278 | VoiceState& GetState(std::size_t i); | ||
| 279 | const VoiceState& GetState(std::size_t i) const; | ||
| 280 | VoiceState& GetDspSharedState(std::size_t i); | ||
| 281 | const VoiceState& GetDspSharedState(std::size_t i) const; | ||
| 282 | ServerVoiceInfo& GetInfo(std::size_t i); | ||
| 283 | const ServerVoiceInfo& GetInfo(std::size_t i) const; | ||
| 284 | ServerVoiceInfo& GetSortedInfo(std::size_t i); | ||
| 285 | const ServerVoiceInfo& GetSortedInfo(std::size_t i) const; | ||
| 286 | |||
| 287 | s32 DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel, | ||
| 288 | s32 channel_count, s32 buffer_offset, s32 sample_count, | ||
| 289 | Core::Memory::Memory& memory); | ||
| 290 | void SortInfo(); | ||
| 291 | void UpdateStateByDspShared(); | ||
| 292 | |||
| 293 | private: | ||
| 294 | std::size_t voice_count{}; | ||
| 295 | std::vector<ServerVoiceChannelResource> voice_channel_resources{}; | ||
| 296 | std::vector<VoiceState> voice_states{}; | ||
| 297 | std::vector<VoiceState> dsp_voice_states{}; | ||
| 298 | std::vector<ServerVoiceInfo> voice_info{}; | ||
| 299 | std::vector<ServerVoiceInfo*> sorted_voice_info{}; | ||
| 300 | }; | ||
| 301 | |||
| 302 | } // namespace AudioCore | ||
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 73bf626d4..64bb753e6 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt | |||
| @@ -43,6 +43,7 @@ add_library(common STATIC | |||
| 43 | alignment.h | 43 | alignment.h |
| 44 | assert.cpp | 44 | assert.cpp |
| 45 | assert.h | 45 | assert.h |
| 46 | atomic_helpers.h | ||
| 46 | atomic_ops.h | 47 | atomic_ops.h |
| 47 | detached_tasks.cpp | 48 | detached_tasks.cpp |
| 48 | detached_tasks.h | 49 | detached_tasks.h |
| @@ -64,6 +65,7 @@ add_library(common STATIC | |||
| 64 | expected.h | 65 | expected.h |
| 65 | fiber.cpp | 66 | fiber.cpp |
| 66 | fiber.h | 67 | fiber.h |
| 68 | fixed_point.h | ||
| 67 | fs/file.cpp | 69 | fs/file.cpp |
| 68 | fs/file.h | 70 | fs/file.h |
| 69 | fs/fs.cpp | 71 | fs/fs.cpp |
| @@ -109,6 +111,7 @@ add_library(common STATIC | |||
| 109 | parent_of_member.h | 111 | parent_of_member.h |
| 110 | point.h | 112 | point.h |
| 111 | quaternion.h | 113 | quaternion.h |
| 114 | reader_writer_queue.h | ||
| 112 | ring_buffer.h | 115 | ring_buffer.h |
| 113 | scm_rev.cpp | 116 | scm_rev.cpp |
| 114 | scm_rev.h | 117 | scm_rev.h |
diff --git a/src/common/atomic_helpers.h b/src/common/atomic_helpers.h new file mode 100644 index 000000000..6d912b52e --- /dev/null +++ b/src/common/atomic_helpers.h | |||
| @@ -0,0 +1,772 @@ | |||
| 1 | // ©2013-2016 Cameron Desrochers. | ||
| 2 | // Distributed under the simplified BSD license (see the license file that | ||
| 3 | // should have come with this header). | ||
| 4 | // Uses Jeff Preshing's semaphore implementation (under the terms of its | ||
| 5 | // separate zlib license, embedded below). | ||
| 6 | |||
| 7 | #pragma once | ||
| 8 | |||
| 9 | // Provides portable (VC++2010+, Intel ICC 13, GCC 4.7+, and anything C++11 compliant) | ||
| 10 | // implementation of low-level memory barriers, plus a few semi-portable utility macros (for | ||
| 11 | // inlining and alignment). Also has a basic atomic type (limited to hardware-supported atomics with | ||
| 12 | // no memory ordering guarantees). Uses the AE_* prefix for macros (historical reasons), and the | ||
| 13 | // "moodycamel" namespace for symbols. | ||
| 14 | |||
| 15 | #include <cassert> | ||
| 16 | #include <cerrno> | ||
| 17 | #include <cstdint> | ||
| 18 | #include <ctime> | ||
| 19 | #include <type_traits> | ||
| 20 | |||
| 21 | // Platform detection | ||
| 22 | #if defined(__INTEL_COMPILER) | ||
| 23 | #define AE_ICC | ||
| 24 | #elif defined(_MSC_VER) | ||
| 25 | #define AE_VCPP | ||
| 26 | #elif defined(__GNUC__) | ||
| 27 | #define AE_GCC | ||
| 28 | #endif | ||
| 29 | |||
| 30 | #if defined(_M_IA64) || defined(__ia64__) | ||
| 31 | #define AE_ARCH_IA64 | ||
| 32 | #elif defined(_WIN64) || defined(__amd64__) || defined(_M_X64) || defined(__x86_64__) | ||
| 33 | #define AE_ARCH_X64 | ||
| 34 | #elif defined(_M_IX86) || defined(__i386__) | ||
| 35 | #define AE_ARCH_X86 | ||
| 36 | #elif defined(_M_PPC) || defined(__powerpc__) | ||
| 37 | #define AE_ARCH_PPC | ||
| 38 | #else | ||
| 39 | #define AE_ARCH_UNKNOWN | ||
| 40 | #endif | ||
| 41 | |||
| 42 | // AE_UNUSED | ||
| 43 | #define AE_UNUSED(x) ((void)x) | ||
| 44 | |||
| 45 | // AE_NO_TSAN/AE_TSAN_ANNOTATE_* | ||
| 46 | #if defined(__has_feature) | ||
| 47 | #if __has_feature(thread_sanitizer) | ||
| 48 | #if __cplusplus >= 201703L // inline variables require C++17 | ||
| 49 | namespace Common { | ||
| 50 | inline int ae_tsan_global; | ||
| 51 | } | ||
| 52 | #define AE_TSAN_ANNOTATE_RELEASE() \ | ||
| 53 | AnnotateHappensBefore(__FILE__, __LINE__, (void*)(&::moodycamel::ae_tsan_global)) | ||
| 54 | #define AE_TSAN_ANNOTATE_ACQUIRE() \ | ||
| 55 | AnnotateHappensAfter(__FILE__, __LINE__, (void*)(&::moodycamel::ae_tsan_global)) | ||
| 56 | extern "C" void AnnotateHappensBefore(const char*, int, void*); | ||
| 57 | extern "C" void AnnotateHappensAfter(const char*, int, void*); | ||
| 58 | #else // when we can't work with tsan, attempt to disable its warnings | ||
| 59 | #define AE_NO_TSAN __attribute__((no_sanitize("thread"))) | ||
| 60 | #endif | ||
| 61 | #endif | ||
| 62 | #endif | ||
| 63 | #ifndef AE_NO_TSAN | ||
| 64 | #define AE_NO_TSAN | ||
| 65 | #endif | ||
| 66 | #ifndef AE_TSAN_ANNOTATE_RELEASE | ||
| 67 | #define AE_TSAN_ANNOTATE_RELEASE() | ||
| 68 | #define AE_TSAN_ANNOTATE_ACQUIRE() | ||
| 69 | #endif | ||
| 70 | |||
| 71 | // AE_FORCEINLINE | ||
| 72 | #if defined(AE_VCPP) || defined(AE_ICC) | ||
| 73 | #define AE_FORCEINLINE __forceinline | ||
| 74 | #elif defined(AE_GCC) | ||
| 75 | //#define AE_FORCEINLINE __attribute__((always_inline)) | ||
| 76 | #define AE_FORCEINLINE inline | ||
| 77 | #else | ||
| 78 | #define AE_FORCEINLINE inline | ||
| 79 | #endif | ||
| 80 | |||
| 81 | // AE_ALIGN | ||
| 82 | #if defined(AE_VCPP) || defined(AE_ICC) | ||
| 83 | #define AE_ALIGN(x) __declspec(align(x)) | ||
| 84 | #elif defined(AE_GCC) | ||
| 85 | #define AE_ALIGN(x) __attribute__((aligned(x))) | ||
| 86 | #else | ||
| 87 | // Assume GCC compliant syntax... | ||
| 88 | #define AE_ALIGN(x) __attribute__((aligned(x))) | ||
| 89 | #endif | ||
| 90 | |||
| 91 | // Portable atomic fences implemented below: | ||
| 92 | |||
| 93 | namespace Common { | ||
| 94 | |||
| 95 | enum memory_order { | ||
| 96 | memory_order_relaxed, | ||
| 97 | memory_order_acquire, | ||
| 98 | memory_order_release, | ||
| 99 | memory_order_acq_rel, | ||
| 100 | memory_order_seq_cst, | ||
| 101 | |||
| 102 | // memory_order_sync: Forces a full sync: | ||
| 103 | // #LoadLoad, #LoadStore, #StoreStore, and most significantly, #StoreLoad | ||
| 104 | memory_order_sync = memory_order_seq_cst | ||
| 105 | }; | ||
| 106 | |||
| 107 | } // namespace Common | ||
| 108 | |||
| 109 | #if (defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))) || \ | ||
| 110 | (defined(AE_ICC) && __INTEL_COMPILER < 1600) | ||
| 111 | // VS2010 and ICC13 don't support std::atomic_*_fence, implement our own fences | ||
| 112 | |||
| 113 | #include <intrin.h> | ||
| 114 | |||
| 115 | #if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) | ||
| 116 | #define AeFullSync _mm_mfence | ||
| 117 | #define AeLiteSync _mm_mfence | ||
| 118 | #elif defined(AE_ARCH_IA64) | ||
| 119 | #define AeFullSync __mf | ||
| 120 | #define AeLiteSync __mf | ||
| 121 | #elif defined(AE_ARCH_PPC) | ||
| 122 | #include <ppcintrinsics.h> | ||
| 123 | #define AeFullSync __sync | ||
| 124 | #define AeLiteSync __lwsync | ||
| 125 | #endif | ||
| 126 | |||
| 127 | #ifdef AE_VCPP | ||
| 128 | #pragma warning(push) | ||
| 129 | #pragma warning(disable : 4365) // Disable erroneous 'conversion from long to unsigned int, | ||
| 130 | // signed/unsigned mismatch' error when using `assert` | ||
| 131 | #ifdef __cplusplus_cli | ||
| 132 | #pragma managed(push, off) | ||
| 133 | #endif | ||
| 134 | #endif | ||
| 135 | |||
| 136 | namespace Common { | ||
| 137 | |||
| 138 | AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN { | ||
| 139 | switch (order) { | ||
| 140 | case memory_order_relaxed: | ||
| 141 | break; | ||
| 142 | case memory_order_acquire: | ||
| 143 | _ReadBarrier(); | ||
| 144 | break; | ||
| 145 | case memory_order_release: | ||
| 146 | _WriteBarrier(); | ||
| 147 | break; | ||
| 148 | case memory_order_acq_rel: | ||
| 149 | _ReadWriteBarrier(); | ||
| 150 | break; | ||
| 151 | case memory_order_seq_cst: | ||
| 152 | _ReadWriteBarrier(); | ||
| 153 | break; | ||
| 154 | default: | ||
| 155 | assert(false); | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | // x86/x64 have a strong memory model -- all loads and stores have | ||
| 160 | // acquire and release semantics automatically (so only need compiler | ||
| 161 | // barriers for those). | ||
| 162 | #if defined(AE_ARCH_X86) || defined(AE_ARCH_X64) | ||
| 163 | AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN { | ||
| 164 | switch (order) { | ||
| 165 | case memory_order_relaxed: | ||
| 166 | break; | ||
| 167 | case memory_order_acquire: | ||
| 168 | _ReadBarrier(); | ||
| 169 | break; | ||
| 170 | case memory_order_release: | ||
| 171 | _WriteBarrier(); | ||
| 172 | break; | ||
| 173 | case memory_order_acq_rel: | ||
| 174 | _ReadWriteBarrier(); | ||
| 175 | break; | ||
| 176 | case memory_order_seq_cst: | ||
| 177 | _ReadWriteBarrier(); | ||
| 178 | AeFullSync(); | ||
| 179 | _ReadWriteBarrier(); | ||
| 180 | break; | ||
| 181 | default: | ||
| 182 | assert(false); | ||
| 183 | } | ||
| 184 | } | ||
| 185 | #else | ||
| 186 | AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN { | ||
| 187 | // Non-specialized arch, use heavier memory barriers everywhere just in case :-( | ||
| 188 | switch (order) { | ||
| 189 | case memory_order_relaxed: | ||
| 190 | break; | ||
| 191 | case memory_order_acquire: | ||
| 192 | _ReadBarrier(); | ||
| 193 | AeLiteSync(); | ||
| 194 | _ReadBarrier(); | ||
| 195 | break; | ||
| 196 | case memory_order_release: | ||
| 197 | _WriteBarrier(); | ||
| 198 | AeLiteSync(); | ||
| 199 | _WriteBarrier(); | ||
| 200 | break; | ||
| 201 | case memory_order_acq_rel: | ||
| 202 | _ReadWriteBarrier(); | ||
| 203 | AeLiteSync(); | ||
| 204 | _ReadWriteBarrier(); | ||
| 205 | break; | ||
| 206 | case memory_order_seq_cst: | ||
| 207 | _ReadWriteBarrier(); | ||
| 208 | AeFullSync(); | ||
| 209 | _ReadWriteBarrier(); | ||
| 210 | break; | ||
| 211 | default: | ||
| 212 | assert(false); | ||
| 213 | } | ||
| 214 | } | ||
| 215 | #endif | ||
| 216 | } // namespace Common | ||
| 217 | #else | ||
| 218 | // Use standard library of atomics | ||
| 219 | #include <atomic> | ||
| 220 | |||
| 221 | namespace Common { | ||
| 222 | |||
| 223 | AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN { | ||
| 224 | switch (order) { | ||
| 225 | case memory_order_relaxed: | ||
| 226 | break; | ||
| 227 | case memory_order_acquire: | ||
| 228 | std::atomic_signal_fence(std::memory_order_acquire); | ||
| 229 | break; | ||
| 230 | case memory_order_release: | ||
| 231 | std::atomic_signal_fence(std::memory_order_release); | ||
| 232 | break; | ||
| 233 | case memory_order_acq_rel: | ||
| 234 | std::atomic_signal_fence(std::memory_order_acq_rel); | ||
| 235 | break; | ||
| 236 | case memory_order_seq_cst: | ||
| 237 | std::atomic_signal_fence(std::memory_order_seq_cst); | ||
| 238 | break; | ||
| 239 | default: | ||
| 240 | assert(false); | ||
| 241 | } | ||
| 242 | } | ||
| 243 | |||
| 244 | AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN { | ||
| 245 | switch (order) { | ||
| 246 | case memory_order_relaxed: | ||
| 247 | break; | ||
| 248 | case memory_order_acquire: | ||
| 249 | AE_TSAN_ANNOTATE_ACQUIRE(); | ||
| 250 | std::atomic_thread_fence(std::memory_order_acquire); | ||
| 251 | break; | ||
| 252 | case memory_order_release: | ||
| 253 | AE_TSAN_ANNOTATE_RELEASE(); | ||
| 254 | std::atomic_thread_fence(std::memory_order_release); | ||
| 255 | break; | ||
| 256 | case memory_order_acq_rel: | ||
| 257 | AE_TSAN_ANNOTATE_ACQUIRE(); | ||
| 258 | AE_TSAN_ANNOTATE_RELEASE(); | ||
| 259 | std::atomic_thread_fence(std::memory_order_acq_rel); | ||
| 260 | break; | ||
| 261 | case memory_order_seq_cst: | ||
| 262 | AE_TSAN_ANNOTATE_ACQUIRE(); | ||
| 263 | AE_TSAN_ANNOTATE_RELEASE(); | ||
| 264 | std::atomic_thread_fence(std::memory_order_seq_cst); | ||
| 265 | break; | ||
| 266 | default: | ||
| 267 | assert(false); | ||
| 268 | } | ||
| 269 | } | ||
| 270 | |||
| 271 | } // namespace Common | ||
| 272 | |||
| 273 | #endif | ||
| 274 | |||
| 275 | #if !defined(AE_VCPP) || (_MSC_VER >= 1700 && !defined(__cplusplus_cli)) | ||
| 276 | #define AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC | ||
| 277 | #endif | ||
| 278 | |||
| 279 | #ifdef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC | ||
| 280 | #include <atomic> | ||
| 281 | #endif | ||
| 282 | #include <utility> | ||
| 283 | |||
| 284 | // WARNING: *NOT* A REPLACEMENT FOR std::atomic. READ CAREFULLY: | ||
| 285 | // Provides basic support for atomic variables -- no memory ordering guarantees are provided. | ||
| 286 | // The guarantee of atomicity is only made for types that already have atomic load and store | ||
| 287 | // guarantees at the hardware level -- on most platforms this generally means aligned pointers and | ||
| 288 | // integers (only). | ||
| 289 | namespace Common { | ||
| 290 | template <typename T> | ||
| 291 | class weak_atomic { | ||
| 292 | public: | ||
| 293 | AE_NO_TSAN weak_atomic() : value() {} | ||
| 294 | #ifdef AE_VCPP | ||
| 295 | #pragma warning(push) | ||
| 296 | #pragma warning(disable : 4100) // Get rid of (erroneous) 'unreferenced formal parameter' warning | ||
| 297 | #endif | ||
| 298 | template <typename U> | ||
| 299 | AE_NO_TSAN weak_atomic(U&& x) : value(std::forward<U>(x)) {} | ||
| 300 | #ifdef __cplusplus_cli | ||
| 301 | // Work around bug with universal reference/nullptr combination that only appears when /clr is | ||
| 302 | // on | ||
| 303 | AE_NO_TSAN weak_atomic(nullptr_t) : value(nullptr) {} | ||
| 304 | #endif | ||
| 305 | AE_NO_TSAN weak_atomic(weak_atomic const& other) : value(other.load()) {} | ||
| 306 | AE_NO_TSAN weak_atomic(weak_atomic&& other) : value(std::move(other.load())) {} | ||
| 307 | #ifdef AE_VCPP | ||
| 308 | #pragma warning(pop) | ||
| 309 | #endif | ||
| 310 | |||
| 311 | AE_FORCEINLINE operator T() const AE_NO_TSAN { | ||
| 312 | return load(); | ||
| 313 | } | ||
| 314 | |||
| 315 | #ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC | ||
| 316 | template <typename U> | ||
| 317 | AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN { | ||
| 318 | value = std::forward<U>(x); | ||
| 319 | return *this; | ||
| 320 | } | ||
| 321 | AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN { | ||
| 322 | value = other.value; | ||
| 323 | return *this; | ||
| 324 | } | ||
| 325 | |||
| 326 | AE_FORCEINLINE T load() const AE_NO_TSAN { | ||
| 327 | return value; | ||
| 328 | } | ||
| 329 | |||
| 330 | AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN { | ||
| 331 | #if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) | ||
| 332 | if (sizeof(T) == 4) | ||
| 333 | return _InterlockedExchangeAdd((long volatile*)&value, (long)increment); | ||
| 334 | #if defined(_M_AMD64) | ||
| 335 | else if (sizeof(T) == 8) | ||
| 336 | return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment); | ||
| 337 | #endif | ||
| 338 | #else | ||
| 339 | #error Unsupported platform | ||
| 340 | #endif | ||
| 341 | assert(false && "T must be either a 32 or 64 bit type"); | ||
| 342 | return value; | ||
| 343 | } | ||
| 344 | |||
| 345 | AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN { | ||
| 346 | #if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) | ||
| 347 | if (sizeof(T) == 4) | ||
| 348 | return _InterlockedExchangeAdd((long volatile*)&value, (long)increment); | ||
| 349 | #if defined(_M_AMD64) | ||
| 350 | else if (sizeof(T) == 8) | ||
| 351 | return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment); | ||
| 352 | #endif | ||
| 353 | #else | ||
| 354 | #error Unsupported platform | ||
| 355 | #endif | ||
| 356 | assert(false && "T must be either a 32 or 64 bit type"); | ||
| 357 | return value; | ||
| 358 | } | ||
| 359 | #else | ||
| 360 | template <typename U> | ||
| 361 | AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN { | ||
| 362 | value.store(std::forward<U>(x), std::memory_order_relaxed); | ||
| 363 | return *this; | ||
| 364 | } | ||
| 365 | |||
| 366 | AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN { | ||
| 367 | value.store(other.value.load(std::memory_order_relaxed), std::memory_order_relaxed); | ||
| 368 | return *this; | ||
| 369 | } | ||
| 370 | |||
| 371 | AE_FORCEINLINE T load() const AE_NO_TSAN { | ||
| 372 | return value.load(std::memory_order_relaxed); | ||
| 373 | } | ||
| 374 | |||
| 375 | AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN { | ||
| 376 | return value.fetch_add(increment, std::memory_order_acquire); | ||
| 377 | } | ||
| 378 | |||
| 379 | AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN { | ||
| 380 | return value.fetch_add(increment, std::memory_order_release); | ||
| 381 | } | ||
| 382 | #endif | ||
| 383 | |||
| 384 | private: | ||
| 385 | #ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC | ||
| 386 | // No std::atomic support, but still need to circumvent compiler optimizations. | ||
| 387 | // `volatile` will make memory access slow, but is guaranteed to be reliable. | ||
| 388 | volatile T value; | ||
| 389 | #else | ||
| 390 | std::atomic<T> value; | ||
| 391 | #endif | ||
| 392 | }; | ||
| 393 | |||
| 394 | } // namespace Common | ||
| 395 | |||
| 396 | // Portable single-producer, single-consumer semaphore below: | ||
| 397 | |||
| 398 | #if defined(_WIN32) | ||
| 399 | // Avoid including windows.h in a header; we only need a handful of | ||
| 400 | // items, so we'll redeclare them here (this is relatively safe since | ||
| 401 | // the API generally has to remain stable between Windows versions). | ||
| 402 | // I know this is an ugly hack but it still beats polluting the global | ||
| 403 | // namespace with thousands of generic names or adding a .cpp for nothing. | ||
| 404 | extern "C" { | ||
| 405 | struct _SECURITY_ATTRIBUTES; | ||
| 406 | __declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, | ||
| 407 | long lInitialCount, long lMaximumCount, | ||
| 408 | const wchar_t* lpName); | ||
| 409 | __declspec(dllimport) int __stdcall CloseHandle(void* hObject); | ||
| 410 | __declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, | ||
| 411 | unsigned long dwMilliseconds); | ||
| 412 | __declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, | ||
| 413 | long* lpPreviousCount); | ||
| 414 | } | ||
| 415 | #elif defined(__MACH__) | ||
| 416 | #include <mach/mach.h> | ||
| 417 | #elif defined(__unix__) | ||
| 418 | #include <semaphore.h> | ||
| 419 | #elif defined(FREERTOS) | ||
| 420 | #include <FreeRTOS.h> | ||
| 421 | #include <semphr.h> | ||
| 422 | #include <task.h> | ||
| 423 | #endif | ||
| 424 | |||
| 425 | namespace Common { | ||
| 426 | // Code in the spsc_sema namespace below is an adaptation of Jeff Preshing's | ||
| 427 | // portable + lightweight semaphore implementations, originally from | ||
| 428 | // https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h | ||
| 429 | // LICENSE: | ||
| 430 | // Copyright (c) 2015 Jeff Preshing | ||
| 431 | // | ||
| 432 | // This software is provided 'as-is', without any express or implied | ||
| 433 | // warranty. In no event will the authors be held liable for any damages | ||
| 434 | // arising from the use of this software. | ||
| 435 | // | ||
| 436 | // Permission is granted to anyone to use this software for any purpose, | ||
| 437 | // including commercial applications, and to alter it and redistribute it | ||
| 438 | // freely, subject to the following restrictions: | ||
| 439 | // | ||
| 440 | // 1. The origin of this software must not be misrepresented; you must not | ||
| 441 | // claim that you wrote the original software. If you use this software | ||
| 442 | // in a product, an acknowledgement in the product documentation would be | ||
| 443 | // appreciated but is not required. | ||
| 444 | // 2. Altered source versions must be plainly marked as such, and must not be | ||
| 445 | // misrepresented as being the original software. | ||
| 446 | // 3. This notice may not be removed or altered from any source distribution. | ||
| 447 | namespace spsc_sema { | ||
| 448 | #if defined(_WIN32) | ||
| 449 | class Semaphore { | ||
| 450 | private: | ||
| 451 | void* m_hSema; | ||
| 452 | |||
| 453 | Semaphore(const Semaphore& other); | ||
| 454 | Semaphore& operator=(const Semaphore& other); | ||
| 455 | |||
| 456 | public: | ||
| 457 | AE_NO_TSAN Semaphore(int initialCount = 0) : m_hSema() { | ||
| 458 | assert(initialCount >= 0); | ||
| 459 | const long maxLong = 0x7fffffff; | ||
| 460 | m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr); | ||
| 461 | assert(m_hSema); | ||
| 462 | } | ||
| 463 | |||
| 464 | AE_NO_TSAN ~Semaphore() { | ||
| 465 | CloseHandle(m_hSema); | ||
| 466 | } | ||
| 467 | |||
| 468 | bool wait() AE_NO_TSAN { | ||
| 469 | const unsigned long infinite = 0xffffffff; | ||
| 470 | return WaitForSingleObject(m_hSema, infinite) == 0; | ||
| 471 | } | ||
| 472 | |||
| 473 | bool try_wait() AE_NO_TSAN { | ||
| 474 | return WaitForSingleObject(m_hSema, 0) == 0; | ||
| 475 | } | ||
| 476 | |||
| 477 | bool timed_wait(std::uint64_t usecs) AE_NO_TSAN { | ||
| 478 | return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0; | ||
| 479 | } | ||
| 480 | |||
| 481 | void signal(int count = 1) AE_NO_TSAN { | ||
| 482 | while (!ReleaseSemaphore(m_hSema, count, nullptr)) | ||
| 483 | ; | ||
| 484 | } | ||
| 485 | }; | ||
| 486 | #elif defined(__MACH__) | ||
| 487 | //--------------------------------------------------------- | ||
| 488 | // Semaphore (Apple iOS and OSX) | ||
| 489 | // Can't use POSIX semaphores due to | ||
| 490 | // http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html | ||
| 491 | //--------------------------------------------------------- | ||
| 492 | class Semaphore { | ||
| 493 | private: | ||
| 494 | semaphore_t m_sema; | ||
| 495 | |||
| 496 | Semaphore(const Semaphore& other); | ||
| 497 | Semaphore& operator=(const Semaphore& other); | ||
| 498 | |||
| 499 | public: | ||
| 500 | AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() { | ||
| 501 | assert(initialCount >= 0); | ||
| 502 | kern_return_t rc = | ||
| 503 | semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount); | ||
| 504 | assert(rc == KERN_SUCCESS); | ||
| 505 | AE_UNUSED(rc); | ||
| 506 | } | ||
| 507 | |||
| 508 | AE_NO_TSAN ~Semaphore() { | ||
| 509 | semaphore_destroy(mach_task_self(), m_sema); | ||
| 510 | } | ||
| 511 | |||
| 512 | bool wait() AE_NO_TSAN { | ||
| 513 | return semaphore_wait(m_sema) == KERN_SUCCESS; | ||
| 514 | } | ||
| 515 | |||
| 516 | bool try_wait() AE_NO_TSAN { | ||
| 517 | return timed_wait(0); | ||
| 518 | } | ||
| 519 | |||
| 520 | bool timed_wait(std::uint64_t timeout_usecs) AE_NO_TSAN { | ||
| 521 | mach_timespec_t ts; | ||
| 522 | ts.tv_sec = static_cast<unsigned int>(timeout_usecs / 1000000); | ||
| 523 | ts.tv_nsec = static_cast<int>((timeout_usecs % 1000000) * 1000); | ||
| 524 | |||
| 525 | // added in OSX 10.10: | ||
| 526 | // https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html | ||
| 527 | kern_return_t rc = semaphore_timedwait(m_sema, ts); | ||
| 528 | return rc == KERN_SUCCESS; | ||
| 529 | } | ||
| 530 | |||
| 531 | void signal() AE_NO_TSAN { | ||
| 532 | while (semaphore_signal(m_sema) != KERN_SUCCESS) | ||
| 533 | ; | ||
| 534 | } | ||
| 535 | |||
| 536 | void signal(int count) AE_NO_TSAN { | ||
| 537 | while (count-- > 0) { | ||
| 538 | while (semaphore_signal(m_sema) != KERN_SUCCESS) | ||
| 539 | ; | ||
| 540 | } | ||
| 541 | } | ||
| 542 | }; | ||
| 543 | #elif defined(__unix__) | ||
| 544 | //--------------------------------------------------------- | ||
| 545 | // Semaphore (POSIX, Linux) | ||
| 546 | //--------------------------------------------------------- | ||
| 547 | class Semaphore { | ||
| 548 | private: | ||
| 549 | sem_t m_sema; | ||
| 550 | |||
| 551 | Semaphore(const Semaphore& other); | ||
| 552 | Semaphore& operator=(const Semaphore& other); | ||
| 553 | |||
| 554 | public: | ||
| 555 | AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() { | ||
| 556 | assert(initialCount >= 0); | ||
| 557 | int rc = sem_init(&m_sema, 0, static_cast<unsigned int>(initialCount)); | ||
| 558 | assert(rc == 0); | ||
| 559 | AE_UNUSED(rc); | ||
| 560 | } | ||
| 561 | |||
| 562 | AE_NO_TSAN ~Semaphore() { | ||
| 563 | sem_destroy(&m_sema); | ||
| 564 | } | ||
| 565 | |||
| 566 | bool wait() AE_NO_TSAN { | ||
| 567 | // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error | ||
| 568 | int rc; | ||
| 569 | do { | ||
| 570 | rc = sem_wait(&m_sema); | ||
| 571 | } while (rc == -1 && errno == EINTR); | ||
| 572 | return rc == 0; | ||
| 573 | } | ||
| 574 | |||
| 575 | bool try_wait() AE_NO_TSAN { | ||
| 576 | int rc; | ||
| 577 | do { | ||
| 578 | rc = sem_trywait(&m_sema); | ||
| 579 | } while (rc == -1 && errno == EINTR); | ||
| 580 | return rc == 0; | ||
| 581 | } | ||
| 582 | |||
| 583 | bool timed_wait(std::uint64_t usecs) AE_NO_TSAN { | ||
| 584 | struct timespec ts; | ||
| 585 | const int usecs_in_1_sec = 1000000; | ||
| 586 | const int nsecs_in_1_sec = 1000000000; | ||
| 587 | clock_gettime(CLOCK_REALTIME, &ts); | ||
| 588 | ts.tv_sec += static_cast<time_t>(usecs / usecs_in_1_sec); | ||
| 589 | ts.tv_nsec += static_cast<long>(usecs % usecs_in_1_sec) * 1000; | ||
| 590 | // sem_timedwait bombs if you have more than 1e9 in tv_nsec | ||
| 591 | // so we have to clean things up before passing it in | ||
| 592 | if (ts.tv_nsec >= nsecs_in_1_sec) { | ||
| 593 | ts.tv_nsec -= nsecs_in_1_sec; | ||
| 594 | ++ts.tv_sec; | ||
| 595 | } | ||
| 596 | |||
| 597 | int rc; | ||
| 598 | do { | ||
| 599 | rc = sem_timedwait(&m_sema, &ts); | ||
| 600 | } while (rc == -1 && errno == EINTR); | ||
| 601 | return rc == 0; | ||
| 602 | } | ||
| 603 | |||
| 604 | void signal() AE_NO_TSAN { | ||
| 605 | while (sem_post(&m_sema) == -1) | ||
| 606 | ; | ||
| 607 | } | ||
| 608 | |||
| 609 | void signal(int count) AE_NO_TSAN { | ||
| 610 | while (count-- > 0) { | ||
| 611 | while (sem_post(&m_sema) == -1) | ||
| 612 | ; | ||
| 613 | } | ||
| 614 | } | ||
| 615 | }; | ||
| 616 | #elif defined(FREERTOS) | ||
| 617 | //--------------------------------------------------------- | ||
| 618 | // Semaphore (FreeRTOS) | ||
| 619 | //--------------------------------------------------------- | ||
| 620 | class Semaphore { | ||
| 621 | private: | ||
| 622 | SemaphoreHandle_t m_sema; | ||
| 623 | |||
| 624 | Semaphore(const Semaphore& other); | ||
| 625 | Semaphore& operator=(const Semaphore& other); | ||
| 626 | |||
| 627 | public: | ||
| 628 | AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() { | ||
| 629 | assert(initialCount >= 0); | ||
| 630 | m_sema = xSemaphoreCreateCounting(static_cast<UBaseType_t>(~0ull), | ||
| 631 | static_cast<UBaseType_t>(initialCount)); | ||
| 632 | assert(m_sema); | ||
| 633 | } | ||
| 634 | |||
| 635 | AE_NO_TSAN ~Semaphore() { | ||
| 636 | vSemaphoreDelete(m_sema); | ||
| 637 | } | ||
| 638 | |||
| 639 | bool wait() AE_NO_TSAN { | ||
| 640 | return xSemaphoreTake(m_sema, portMAX_DELAY) == pdTRUE; | ||
| 641 | } | ||
| 642 | |||
| 643 | bool try_wait() AE_NO_TSAN { | ||
| 644 | // Note: In an ISR context, if this causes a task to unblock, | ||
| 645 | // the caller won't know about it | ||
| 646 | if (xPortIsInsideInterrupt()) | ||
| 647 | return xSemaphoreTakeFromISR(m_sema, NULL) == pdTRUE; | ||
| 648 | return xSemaphoreTake(m_sema, 0) == pdTRUE; | ||
| 649 | } | ||
| 650 | |||
| 651 | bool timed_wait(std::uint64_t usecs) AE_NO_TSAN { | ||
| 652 | std::uint64_t msecs = usecs / 1000; | ||
| 653 | TickType_t ticks = static_cast<TickType_t>(msecs / portTICK_PERIOD_MS); | ||
| 654 | if (ticks == 0) | ||
| 655 | return try_wait(); | ||
| 656 | return xSemaphoreTake(m_sema, ticks) == pdTRUE; | ||
| 657 | } | ||
| 658 | |||
| 659 | void signal() AE_NO_TSAN { | ||
| 660 | // Note: In an ISR context, if this causes a task to unblock, | ||
| 661 | // the caller won't know about it | ||
| 662 | BaseType_t rc; | ||
| 663 | if (xPortIsInsideInterrupt()) | ||
| 664 | rc = xSemaphoreGiveFromISR(m_sema, NULL); | ||
| 665 | else | ||
| 666 | rc = xSemaphoreGive(m_sema); | ||
| 667 | assert(rc == pdTRUE); | ||
| 668 | AE_UNUSED(rc); | ||
| 669 | } | ||
| 670 | |||
| 671 | void signal(int count) AE_NO_TSAN { | ||
| 672 | while (count-- > 0) | ||
| 673 | signal(); | ||
| 674 | } | ||
| 675 | }; | ||
| 676 | #else | ||
| 677 | #error Unsupported platform! (No semaphore wrapper available) | ||
| 678 | #endif | ||
| 679 | |||
| 680 | //--------------------------------------------------------- | ||
| 681 | // LightweightSemaphore | ||
| 682 | //--------------------------------------------------------- | ||
| 683 | class LightweightSemaphore { | ||
| 684 | public: | ||
| 685 | typedef std::make_signed<std::size_t>::type ssize_t; | ||
| 686 | |||
| 687 | private: | ||
| 688 | weak_atomic<ssize_t> m_count; | ||
| 689 | Semaphore m_sema; | ||
| 690 | |||
| 691 | bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) AE_NO_TSAN { | ||
| 692 | ssize_t oldCount; | ||
| 693 | // Is there a better way to set the initial spin count? | ||
| 694 | // If we lower it to 1000, testBenaphore becomes 15x slower on my Core i7-5930K Windows PC, | ||
| 695 | // as threads start hitting the kernel semaphore. | ||
| 696 | int spin = 1024; | ||
| 697 | while (--spin >= 0) { | ||
| 698 | if (m_count.load() > 0) { | ||
| 699 | m_count.fetch_add_acquire(-1); | ||
| 700 | return true; | ||
| 701 | } | ||
| 702 | compiler_fence(memory_order_acquire); // Prevent the compiler from collapsing the loop. | ||
| 703 | } | ||
| 704 | oldCount = m_count.fetch_add_acquire(-1); | ||
| 705 | if (oldCount > 0) | ||
| 706 | return true; | ||
| 707 | if (timeout_usecs < 0) { | ||
| 708 | if (m_sema.wait()) | ||
| 709 | return true; | ||
| 710 | } | ||
| 711 | if (timeout_usecs > 0 && m_sema.timed_wait(static_cast<uint64_t>(timeout_usecs))) | ||
| 712 | return true; | ||
| 713 | // At this point, we've timed out waiting for the semaphore, but the | ||
| 714 | // count is still decremented indicating we may still be waiting on | ||
| 715 | // it. So we have to re-adjust the count, but only if the semaphore | ||
| 716 | // wasn't signaled enough times for us too since then. If it was, we | ||
| 717 | // need to release the semaphore too. | ||
| 718 | while (true) { | ||
| 719 | oldCount = m_count.fetch_add_release(1); | ||
| 720 | if (oldCount < 0) | ||
| 721 | return false; // successfully restored things to the way they were | ||
| 722 | // Oh, the producer thread just signaled the semaphore after all. Try again: | ||
| 723 | oldCount = m_count.fetch_add_acquire(-1); | ||
| 724 | if (oldCount > 0 && m_sema.try_wait()) | ||
| 725 | return true; | ||
| 726 | } | ||
| 727 | } | ||
| 728 | |||
| 729 | public: | ||
| 730 | AE_NO_TSAN LightweightSemaphore(ssize_t initialCount = 0) : m_count(initialCount), m_sema() { | ||
| 731 | assert(initialCount >= 0); | ||
| 732 | } | ||
| 733 | |||
| 734 | bool tryWait() AE_NO_TSAN { | ||
| 735 | if (m_count.load() > 0) { | ||
| 736 | m_count.fetch_add_acquire(-1); | ||
| 737 | return true; | ||
| 738 | } | ||
| 739 | return false; | ||
| 740 | } | ||
| 741 | |||
| 742 | bool wait() AE_NO_TSAN { | ||
| 743 | return tryWait() || waitWithPartialSpinning(); | ||
| 744 | } | ||
| 745 | |||
| 746 | bool wait(std::int64_t timeout_usecs) AE_NO_TSAN { | ||
| 747 | return tryWait() || waitWithPartialSpinning(timeout_usecs); | ||
| 748 | } | ||
| 749 | |||
| 750 | void signal(ssize_t count = 1) AE_NO_TSAN { | ||
| 751 | assert(count >= 0); | ||
| 752 | ssize_t oldCount = m_count.fetch_add_release(count); | ||
| 753 | assert(oldCount >= -1); | ||
| 754 | if (oldCount < 0) { | ||
| 755 | m_sema.signal(1); | ||
| 756 | } | ||
| 757 | } | ||
| 758 | |||
| 759 | std::size_t availableApprox() const AE_NO_TSAN { | ||
| 760 | ssize_t count = m_count.load(); | ||
| 761 | return count > 0 ? static_cast<std::size_t>(count) : 0; | ||
| 762 | } | ||
| 763 | }; | ||
| 764 | } // namespace spsc_sema | ||
| 765 | } // namespace Common | ||
| 766 | |||
| 767 | #if defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli)) | ||
| 768 | #pragma warning(pop) | ||
| 769 | #ifdef __cplusplus_cli | ||
| 770 | #pragma managed(pop) | ||
| 771 | #endif | ||
| 772 | #endif | ||
diff --git a/src/common/fixed_point.h b/src/common/fixed_point.h new file mode 100644 index 000000000..1d45e51b3 --- /dev/null +++ b/src/common/fixed_point.h | |||
| @@ -0,0 +1,726 @@ | |||
| 1 | // From: https://github.com/eteran/cpp-utilities/blob/master/fixed/include/cpp-utilities/fixed.h | ||
| 2 | // See also: http://stackoverflow.com/questions/79677/whats-the-best-way-to-do-fixed-point-math | ||
| 3 | /* | ||
| 4 | * The MIT License (MIT) | ||
| 5 | * | ||
| 6 | * Copyright (c) 2015 Evan Teran | ||
| 7 | * | ||
| 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| 9 | * of this software and associated documentation files (the "Software"), to deal | ||
| 10 | * in the Software without restriction, including without limitation the rights | ||
| 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| 12 | * copies of the Software, and to permit persons to whom the Software is | ||
| 13 | * furnished to do so, subject to the following conditions: | ||
| 14 | * | ||
| 15 | * The above copyright notice and this permission notice shall be included in all | ||
| 16 | * copies or substantial portions of the Software. | ||
| 17 | * | ||
| 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 24 | * SOFTWARE. | ||
| 25 | */ | ||
| 26 | |||
| 27 | #ifndef FIXED_H_ | ||
| 28 | #define FIXED_H_ | ||
| 29 | |||
| 30 | #if __cplusplus >= 201402L | ||
| 31 | #define CONSTEXPR14 constexpr | ||
| 32 | #else | ||
| 33 | #define CONSTEXPR14 | ||
| 34 | #endif | ||
| 35 | |||
| 36 | #include <cstddef> // for size_t | ||
| 37 | #include <cstdint> | ||
| 38 | #include <exception> | ||
| 39 | #include <ostream> | ||
| 40 | #include <type_traits> | ||
| 41 | |||
| 42 | namespace Common { | ||
| 43 | |||
| 44 | template <size_t I, size_t F> | ||
| 45 | class FixedPoint; | ||
| 46 | |||
| 47 | namespace detail { | ||
| 48 | |||
| 49 | // helper templates to make magic with types :) | ||
| 50 | // these allow us to determine resonable types from | ||
| 51 | // a desired size, they also let us infer the next largest type | ||
| 52 | // from a type which is nice for the division op | ||
| 53 | template <size_t T> | ||
| 54 | struct type_from_size { | ||
| 55 | using value_type = void; | ||
| 56 | using unsigned_type = void; | ||
| 57 | using signed_type = void; | ||
| 58 | static constexpr bool is_specialized = false; | ||
| 59 | }; | ||
| 60 | |||
| 61 | #if defined(__GNUC__) && defined(__x86_64__) && !defined(__STRICT_ANSI__) | ||
| 62 | template <> | ||
| 63 | struct type_from_size<128> { | ||
| 64 | static constexpr bool is_specialized = true; | ||
| 65 | static constexpr size_t size = 128; | ||
| 66 | |||
| 67 | using value_type = __int128; | ||
| 68 | using unsigned_type = unsigned __int128; | ||
| 69 | using signed_type = __int128; | ||
| 70 | using next_size = type_from_size<256>; | ||
| 71 | }; | ||
| 72 | #endif | ||
| 73 | |||
| 74 | template <> | ||
| 75 | struct type_from_size<64> { | ||
| 76 | static constexpr bool is_specialized = true; | ||
| 77 | static constexpr size_t size = 64; | ||
| 78 | |||
| 79 | using value_type = int64_t; | ||
| 80 | using unsigned_type = std::make_unsigned<value_type>::type; | ||
| 81 | using signed_type = std::make_signed<value_type>::type; | ||
| 82 | using next_size = type_from_size<128>; | ||
| 83 | }; | ||
| 84 | |||
| 85 | template <> | ||
| 86 | struct type_from_size<32> { | ||
| 87 | static constexpr bool is_specialized = true; | ||
| 88 | static constexpr size_t size = 32; | ||
| 89 | |||
| 90 | using value_type = int32_t; | ||
| 91 | using unsigned_type = std::make_unsigned<value_type>::type; | ||
| 92 | using signed_type = std::make_signed<value_type>::type; | ||
| 93 | using next_size = type_from_size<64>; | ||
| 94 | }; | ||
| 95 | |||
| 96 | template <> | ||
| 97 | struct type_from_size<16> { | ||
| 98 | static constexpr bool is_specialized = true; | ||
| 99 | static constexpr size_t size = 16; | ||
| 100 | |||
| 101 | using value_type = int16_t; | ||
| 102 | using unsigned_type = std::make_unsigned<value_type>::type; | ||
| 103 | using signed_type = std::make_signed<value_type>::type; | ||
| 104 | using next_size = type_from_size<32>; | ||
| 105 | }; | ||
| 106 | |||
| 107 | template <> | ||
| 108 | struct type_from_size<8> { | ||
| 109 | static constexpr bool is_specialized = true; | ||
| 110 | static constexpr size_t size = 8; | ||
| 111 | |||
| 112 | using value_type = int8_t; | ||
| 113 | using unsigned_type = std::make_unsigned<value_type>::type; | ||
| 114 | using signed_type = std::make_signed<value_type>::type; | ||
| 115 | using next_size = type_from_size<16>; | ||
| 116 | }; | ||
| 117 | |||
| 118 | // this is to assist in adding support for non-native base | ||
| 119 | // types (for adding big-int support), this should be fine | ||
| 120 | // unless your bit-int class doesn't nicely support casting | ||
| 121 | template <class B, class N> | ||
| 122 | constexpr B next_to_base(N rhs) { | ||
| 123 | return static_cast<B>(rhs); | ||
| 124 | } | ||
| 125 | |||
| 126 | struct divide_by_zero : std::exception {}; | ||
| 127 | |||
| 128 | template <size_t I, size_t F> | ||
| 129 | CONSTEXPR14 FixedPoint<I, F> divide( | ||
| 130 | FixedPoint<I, F> numerator, FixedPoint<I, F> denominator, FixedPoint<I, F>& remainder, | ||
| 131 | typename std::enable_if<type_from_size<I + F>::next_size::is_specialized>::type* = nullptr) { | ||
| 132 | |||
| 133 | using next_type = typename FixedPoint<I, F>::next_type; | ||
| 134 | using base_type = typename FixedPoint<I, F>::base_type; | ||
| 135 | constexpr size_t fractional_bits = FixedPoint<I, F>::fractional_bits; | ||
| 136 | |||
| 137 | next_type t(numerator.to_raw()); | ||
| 138 | t <<= fractional_bits; | ||
| 139 | |||
| 140 | FixedPoint<I, F> quotient; | ||
| 141 | |||
| 142 | quotient = FixedPoint<I, F>::from_base(next_to_base<base_type>(t / denominator.to_raw())); | ||
| 143 | remainder = FixedPoint<I, F>::from_base(next_to_base<base_type>(t % denominator.to_raw())); | ||
| 144 | |||
| 145 | return quotient; | ||
| 146 | } | ||
| 147 | |||
| 148 | template <size_t I, size_t F> | ||
| 149 | CONSTEXPR14 FixedPoint<I, F> divide( | ||
| 150 | FixedPoint<I, F> numerator, FixedPoint<I, F> denominator, FixedPoint<I, F>& remainder, | ||
| 151 | typename std::enable_if<!type_from_size<I + F>::next_size::is_specialized>::type* = nullptr) { | ||
| 152 | |||
| 153 | using unsigned_type = typename FixedPoint<I, F>::unsigned_type; | ||
| 154 | |||
| 155 | constexpr int bits = FixedPoint<I, F>::total_bits; | ||
| 156 | |||
| 157 | if (denominator == 0) { | ||
| 158 | throw divide_by_zero(); | ||
| 159 | } else { | ||
| 160 | |||
| 161 | int sign = 0; | ||
| 162 | |||
| 163 | FixedPoint<I, F> quotient; | ||
| 164 | |||
| 165 | if (numerator < 0) { | ||
| 166 | sign ^= 1; | ||
| 167 | numerator = -numerator; | ||
| 168 | } | ||
| 169 | |||
| 170 | if (denominator < 0) { | ||
| 171 | sign ^= 1; | ||
| 172 | denominator = -denominator; | ||
| 173 | } | ||
| 174 | |||
| 175 | unsigned_type n = numerator.to_raw(); | ||
| 176 | unsigned_type d = denominator.to_raw(); | ||
| 177 | unsigned_type x = 1; | ||
| 178 | unsigned_type answer = 0; | ||
| 179 | |||
| 180 | // egyptian division algorithm | ||
| 181 | while ((n >= d) && (((d >> (bits - 1)) & 1) == 0)) { | ||
| 182 | x <<= 1; | ||
| 183 | d <<= 1; | ||
| 184 | } | ||
| 185 | |||
| 186 | while (x != 0) { | ||
| 187 | if (n >= d) { | ||
| 188 | n -= d; | ||
| 189 | answer += x; | ||
| 190 | } | ||
| 191 | |||
| 192 | x >>= 1; | ||
| 193 | d >>= 1; | ||
| 194 | } | ||
| 195 | |||
| 196 | unsigned_type l1 = n; | ||
| 197 | unsigned_type l2 = denominator.to_raw(); | ||
| 198 | |||
| 199 | // calculate the lower bits (needs to be unsigned) | ||
| 200 | while (l1 >> (bits - F) > 0) { | ||
| 201 | l1 >>= 1; | ||
| 202 | l2 >>= 1; | ||
| 203 | } | ||
| 204 | const unsigned_type lo = (l1 << F) / l2; | ||
| 205 | |||
| 206 | quotient = FixedPoint<I, F>::from_base((answer << F) | lo); | ||
| 207 | remainder = n; | ||
| 208 | |||
| 209 | if (sign) { | ||
| 210 | quotient = -quotient; | ||
| 211 | } | ||
| 212 | |||
| 213 | return quotient; | ||
| 214 | } | ||
| 215 | } | ||
| 216 | |||
| 217 | // this is the usual implementation of multiplication | ||
| 218 | template <size_t I, size_t F> | ||
| 219 | CONSTEXPR14 FixedPoint<I, F> multiply( | ||
| 220 | FixedPoint<I, F> lhs, FixedPoint<I, F> rhs, | ||
| 221 | typename std::enable_if<type_from_size<I + F>::next_size::is_specialized>::type* = nullptr) { | ||
| 222 | |||
| 223 | using next_type = typename FixedPoint<I, F>::next_type; | ||
| 224 | using base_type = typename FixedPoint<I, F>::base_type; | ||
| 225 | |||
| 226 | constexpr size_t fractional_bits = FixedPoint<I, F>::fractional_bits; | ||
| 227 | |||
| 228 | next_type t(static_cast<next_type>(lhs.to_raw()) * static_cast<next_type>(rhs.to_raw())); | ||
| 229 | t >>= fractional_bits; | ||
| 230 | |||
| 231 | return FixedPoint<I, F>::from_base(next_to_base<base_type>(t)); | ||
| 232 | } | ||
| 233 | |||
| 234 | // this is the fall back version we use when we don't have a next size | ||
| 235 | // it is slightly slower, but is more robust since it doesn't | ||
| 236 | // require and upgraded type | ||
| 237 | template <size_t I, size_t F> | ||
| 238 | CONSTEXPR14 FixedPoint<I, F> multiply( | ||
| 239 | FixedPoint<I, F> lhs, FixedPoint<I, F> rhs, | ||
| 240 | typename std::enable_if<!type_from_size<I + F>::next_size::is_specialized>::type* = nullptr) { | ||
| 241 | |||
| 242 | using base_type = typename FixedPoint<I, F>::base_type; | ||
| 243 | |||
| 244 | constexpr size_t fractional_bits = FixedPoint<I, F>::fractional_bits; | ||
| 245 | constexpr base_type integer_mask = FixedPoint<I, F>::integer_mask; | ||
| 246 | constexpr base_type fractional_mask = FixedPoint<I, F>::fractional_mask; | ||
| 247 | |||
| 248 | // more costly but doesn't need a larger type | ||
| 249 | const base_type a_hi = (lhs.to_raw() & integer_mask) >> fractional_bits; | ||
| 250 | const base_type b_hi = (rhs.to_raw() & integer_mask) >> fractional_bits; | ||
| 251 | const base_type a_lo = (lhs.to_raw() & fractional_mask); | ||
| 252 | const base_type b_lo = (rhs.to_raw() & fractional_mask); | ||
| 253 | |||
| 254 | const base_type x1 = a_hi * b_hi; | ||
| 255 | const base_type x2 = a_hi * b_lo; | ||
| 256 | const base_type x3 = a_lo * b_hi; | ||
| 257 | const base_type x4 = a_lo * b_lo; | ||
| 258 | |||
| 259 | return FixedPoint<I, F>::from_base((x1 << fractional_bits) + (x3 + x2) + | ||
| 260 | (x4 >> fractional_bits)); | ||
| 261 | } | ||
| 262 | } // namespace detail | ||
| 263 | |||
| 264 | template <size_t I, size_t F> | ||
| 265 | class FixedPoint { | ||
| 266 | static_assert(detail::type_from_size<I + F>::is_specialized, "invalid combination of sizes"); | ||
| 267 | |||
| 268 | public: | ||
| 269 | static constexpr size_t fractional_bits = F; | ||
| 270 | static constexpr size_t integer_bits = I; | ||
| 271 | static constexpr size_t total_bits = I + F; | ||
| 272 | |||
| 273 | using base_type_info = detail::type_from_size<total_bits>; | ||
| 274 | |||
| 275 | using base_type = typename base_type_info::value_type; | ||
| 276 | using next_type = typename base_type_info::next_size::value_type; | ||
| 277 | using unsigned_type = typename base_type_info::unsigned_type; | ||
| 278 | |||
| 279 | public: | ||
| 280 | #ifdef __GNUC__ | ||
| 281 | #pragma GCC diagnostic push | ||
| 282 | #pragma GCC diagnostic ignored "-Woverflow" | ||
| 283 | #endif | ||
| 284 | static constexpr base_type fractional_mask = | ||
| 285 | ~(static_cast<unsigned_type>(~base_type(0)) << fractional_bits); | ||
| 286 | static constexpr base_type integer_mask = ~fractional_mask; | ||
| 287 | #ifdef __GNUC__ | ||
| 288 | #pragma GCC diagnostic pop | ||
| 289 | #endif | ||
| 290 | |||
| 291 | public: | ||
| 292 | static constexpr base_type one = base_type(1) << fractional_bits; | ||
| 293 | |||
| 294 | public: // constructors | ||
| 295 | FixedPoint() = default; | ||
| 296 | FixedPoint(const FixedPoint&) = default; | ||
| 297 | FixedPoint(FixedPoint&&) = default; | ||
| 298 | FixedPoint& operator=(const FixedPoint&) = default; | ||
| 299 | |||
| 300 | template <class Number> | ||
| 301 | constexpr FixedPoint( | ||
| 302 | Number n, typename std::enable_if<std::is_arithmetic<Number>::value>::type* = nullptr) | ||
| 303 | : data_(static_cast<base_type>(n * one)) {} | ||
| 304 | |||
| 305 | public: // conversion | ||
| 306 | template <size_t I2, size_t F2> | ||
| 307 | CONSTEXPR14 explicit FixedPoint(FixedPoint<I2, F2> other) { | ||
| 308 | static_assert(I2 <= I && F2 <= F, "Scaling conversion can only upgrade types"); | ||
| 309 | using T = FixedPoint<I2, F2>; | ||
| 310 | |||
| 311 | const base_type fractional = (other.data_ & T::fractional_mask); | ||
| 312 | const base_type integer = (other.data_ & T::integer_mask) >> T::fractional_bits; | ||
| 313 | data_ = | ||
| 314 | (integer << fractional_bits) | (fractional << (fractional_bits - T::fractional_bits)); | ||
| 315 | } | ||
| 316 | |||
| 317 | private: | ||
| 318 | // this makes it simpler to create a FixedPoint point object from | ||
| 319 | // a native type without scaling | ||
| 320 | // use "FixedPoint::from_base" in order to perform this. | ||
| 321 | struct NoScale {}; | ||
| 322 | |||
| 323 | constexpr FixedPoint(base_type n, const NoScale&) : data_(n) {} | ||
| 324 | |||
| 325 | public: | ||
| 326 | static constexpr FixedPoint from_base(base_type n) { | ||
| 327 | return FixedPoint(n, NoScale()); | ||
| 328 | } | ||
| 329 | |||
| 330 | public: // comparison operators | ||
| 331 | constexpr bool operator==(FixedPoint rhs) const { | ||
| 332 | return data_ == rhs.data_; | ||
| 333 | } | ||
| 334 | |||
| 335 | constexpr bool operator!=(FixedPoint rhs) const { | ||
| 336 | return data_ != rhs.data_; | ||
| 337 | } | ||
| 338 | |||
| 339 | constexpr bool operator<(FixedPoint rhs) const { | ||
| 340 | return data_ < rhs.data_; | ||
| 341 | } | ||
| 342 | |||
| 343 | constexpr bool operator>(FixedPoint rhs) const { | ||
| 344 | return data_ > rhs.data_; | ||
| 345 | } | ||
| 346 | |||
| 347 | constexpr bool operator<=(FixedPoint rhs) const { | ||
| 348 | return data_ <= rhs.data_; | ||
| 349 | } | ||
| 350 | |||
| 351 | constexpr bool operator>=(FixedPoint rhs) const { | ||
| 352 | return data_ >= rhs.data_; | ||
| 353 | } | ||
| 354 | |||
| 355 | public: // unary operators | ||
| 356 | constexpr bool operator!() const { | ||
| 357 | return !data_; | ||
| 358 | } | ||
| 359 | |||
| 360 | constexpr FixedPoint operator~() const { | ||
| 361 | // NOTE(eteran): this will often appear to "just negate" the value | ||
| 362 | // that is not an error, it is because -x == (~x+1) | ||
| 363 | // and that "+1" is adding an infinitesimally small fraction to the | ||
| 364 | // complimented value | ||
| 365 | return FixedPoint::from_base(~data_); | ||
| 366 | } | ||
| 367 | |||
| 368 | constexpr FixedPoint operator-() const { | ||
| 369 | return FixedPoint::from_base(-data_); | ||
| 370 | } | ||
| 371 | |||
| 372 | constexpr FixedPoint operator+() const { | ||
| 373 | return FixedPoint::from_base(+data_); | ||
| 374 | } | ||
| 375 | |||
| 376 | CONSTEXPR14 FixedPoint& operator++() { | ||
| 377 | data_ += one; | ||
| 378 | return *this; | ||
| 379 | } | ||
| 380 | |||
| 381 | CONSTEXPR14 FixedPoint& operator--() { | ||
| 382 | data_ -= one; | ||
| 383 | return *this; | ||
| 384 | } | ||
| 385 | |||
| 386 | CONSTEXPR14 FixedPoint operator++(int) { | ||
| 387 | FixedPoint tmp(*this); | ||
| 388 | data_ += one; | ||
| 389 | return tmp; | ||
| 390 | } | ||
| 391 | |||
| 392 | CONSTEXPR14 FixedPoint operator--(int) { | ||
| 393 | FixedPoint tmp(*this); | ||
| 394 | data_ -= one; | ||
| 395 | return tmp; | ||
| 396 | } | ||
| 397 | |||
| 398 | public: // basic math operators | ||
| 399 | CONSTEXPR14 FixedPoint& operator+=(FixedPoint n) { | ||
| 400 | data_ += n.data_; | ||
| 401 | return *this; | ||
| 402 | } | ||
| 403 | |||
| 404 | CONSTEXPR14 FixedPoint& operator-=(FixedPoint n) { | ||
| 405 | data_ -= n.data_; | ||
| 406 | return *this; | ||
| 407 | } | ||
| 408 | |||
| 409 | CONSTEXPR14 FixedPoint& operator*=(FixedPoint n) { | ||
| 410 | return assign(detail::multiply(*this, n)); | ||
| 411 | } | ||
| 412 | |||
| 413 | CONSTEXPR14 FixedPoint& operator/=(FixedPoint n) { | ||
| 414 | FixedPoint temp; | ||
| 415 | return assign(detail::divide(*this, n, temp)); | ||
| 416 | } | ||
| 417 | |||
| 418 | private: | ||
| 419 | CONSTEXPR14 FixedPoint& assign(FixedPoint rhs) { | ||
| 420 | data_ = rhs.data_; | ||
| 421 | return *this; | ||
| 422 | } | ||
| 423 | |||
| 424 | public: // binary math operators, effects underlying bit pattern since these | ||
| 425 | // don't really typically make sense for non-integer values | ||
| 426 | CONSTEXPR14 FixedPoint& operator&=(FixedPoint n) { | ||
| 427 | data_ &= n.data_; | ||
| 428 | return *this; | ||
| 429 | } | ||
| 430 | |||
| 431 | CONSTEXPR14 FixedPoint& operator|=(FixedPoint n) { | ||
| 432 | data_ |= n.data_; | ||
| 433 | return *this; | ||
| 434 | } | ||
| 435 | |||
| 436 | CONSTEXPR14 FixedPoint& operator^=(FixedPoint n) { | ||
| 437 | data_ ^= n.data_; | ||
| 438 | return *this; | ||
| 439 | } | ||
| 440 | |||
| 441 | template <class Integer, | ||
| 442 | class = typename std::enable_if<std::is_integral<Integer>::value>::type> | ||
| 443 | CONSTEXPR14 FixedPoint& operator>>=(Integer n) { | ||
| 444 | data_ >>= n; | ||
| 445 | return *this; | ||
| 446 | } | ||
| 447 | |||
| 448 | template <class Integer, | ||
| 449 | class = typename std::enable_if<std::is_integral<Integer>::value>::type> | ||
| 450 | CONSTEXPR14 FixedPoint& operator<<=(Integer n) { | ||
| 451 | data_ <<= n; | ||
| 452 | return *this; | ||
| 453 | } | ||
| 454 | |||
| 455 | public: // conversion to basic types | ||
| 456 | constexpr void round_up() { | ||
| 457 | data_ += (data_ & fractional_mask) >> 1; | ||
| 458 | } | ||
| 459 | |||
| 460 | constexpr int to_int() { | ||
| 461 | round_up(); | ||
| 462 | return static_cast<int>((data_ & integer_mask) >> fractional_bits); | ||
| 463 | } | ||
| 464 | |||
| 465 | constexpr unsigned int to_uint() const { | ||
| 466 | round_up(); | ||
| 467 | return static_cast<unsigned int>((data_ & integer_mask) >> fractional_bits); | ||
| 468 | } | ||
| 469 | |||
| 470 | constexpr int64_t to_long() { | ||
| 471 | round_up(); | ||
| 472 | return static_cast<int64_t>((data_ & integer_mask) >> fractional_bits); | ||
| 473 | } | ||
| 474 | |||
| 475 | constexpr int to_int_floor() const { | ||
| 476 | return static_cast<int>((data_ & integer_mask) >> fractional_bits); | ||
| 477 | } | ||
| 478 | |||
| 479 | constexpr int64_t to_long_floor() { | ||
| 480 | return static_cast<int64_t>((data_ & integer_mask) >> fractional_bits); | ||
| 481 | } | ||
| 482 | |||
| 483 | constexpr unsigned int to_uint_floor() const { | ||
| 484 | return static_cast<unsigned int>((data_ & integer_mask) >> fractional_bits); | ||
| 485 | } | ||
| 486 | |||
| 487 | constexpr float to_float() const { | ||
| 488 | return static_cast<float>(data_) / FixedPoint::one; | ||
| 489 | } | ||
| 490 | |||
| 491 | constexpr double to_double() const { | ||
| 492 | return static_cast<double>(data_) / FixedPoint::one; | ||
| 493 | } | ||
| 494 | |||
| 495 | constexpr base_type to_raw() const { | ||
| 496 | return data_; | ||
| 497 | } | ||
| 498 | |||
| 499 | constexpr void clear_int() { | ||
| 500 | data_ &= fractional_mask; | ||
| 501 | } | ||
| 502 | |||
| 503 | constexpr base_type get_frac() const { | ||
| 504 | return data_ & fractional_mask; | ||
| 505 | } | ||
| 506 | |||
| 507 | public: | ||
| 508 | CONSTEXPR14 void swap(FixedPoint& rhs) { | ||
| 509 | using std::swap; | ||
| 510 | swap(data_, rhs.data_); | ||
| 511 | } | ||
| 512 | |||
| 513 | public: | ||
| 514 | base_type data_; | ||
| 515 | }; | ||
| 516 | |||
| 517 | // if we have the same fractional portion, but differing integer portions, we trivially upgrade the | ||
| 518 | // smaller type | ||
| 519 | template <size_t I1, size_t I2, size_t F> | ||
| 520 | CONSTEXPR14 typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type | ||
| 521 | operator+(FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) { | ||
| 522 | |||
| 523 | using T = typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type; | ||
| 524 | |||
| 525 | const T l = T::from_base(lhs.to_raw()); | ||
| 526 | const T r = T::from_base(rhs.to_raw()); | ||
| 527 | return l + r; | ||
| 528 | } | ||
| 529 | |||
| 530 | template <size_t I1, size_t I2, size_t F> | ||
| 531 | CONSTEXPR14 typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type | ||
| 532 | operator-(FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) { | ||
| 533 | |||
| 534 | using T = typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type; | ||
| 535 | |||
| 536 | const T l = T::from_base(lhs.to_raw()); | ||
| 537 | const T r = T::from_base(rhs.to_raw()); | ||
| 538 | return l - r; | ||
| 539 | } | ||
| 540 | |||
| 541 | template <size_t I1, size_t I2, size_t F> | ||
| 542 | CONSTEXPR14 typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type | ||
| 543 | operator*(FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) { | ||
| 544 | |||
| 545 | using T = typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type; | ||
| 546 | |||
| 547 | const T l = T::from_base(lhs.to_raw()); | ||
| 548 | const T r = T::from_base(rhs.to_raw()); | ||
| 549 | return l * r; | ||
| 550 | } | ||
| 551 | |||
| 552 | template <size_t I1, size_t I2, size_t F> | ||
| 553 | CONSTEXPR14 typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type | ||
| 554 | operator/(FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) { | ||
| 555 | |||
| 556 | using T = typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type; | ||
| 557 | |||
| 558 | const T l = T::from_base(lhs.to_raw()); | ||
| 559 | const T r = T::from_base(rhs.to_raw()); | ||
| 560 | return l / r; | ||
| 561 | } | ||
| 562 | |||
| 563 | template <size_t I, size_t F> | ||
| 564 | std::ostream& operator<<(std::ostream& os, FixedPoint<I, F> f) { | ||
| 565 | os << f.to_double(); | ||
| 566 | return os; | ||
| 567 | } | ||
| 568 | |||
| 569 | // basic math operators | ||
| 570 | template <size_t I, size_t F> | ||
| 571 | CONSTEXPR14 FixedPoint<I, F> operator+(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) { | ||
| 572 | lhs += rhs; | ||
| 573 | return lhs; | ||
| 574 | } | ||
| 575 | template <size_t I, size_t F> | ||
| 576 | CONSTEXPR14 FixedPoint<I, F> operator-(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) { | ||
| 577 | lhs -= rhs; | ||
| 578 | return lhs; | ||
| 579 | } | ||
| 580 | template <size_t I, size_t F> | ||
| 581 | CONSTEXPR14 FixedPoint<I, F> operator*(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) { | ||
| 582 | lhs *= rhs; | ||
| 583 | return lhs; | ||
| 584 | } | ||
| 585 | template <size_t I, size_t F> | ||
| 586 | CONSTEXPR14 FixedPoint<I, F> operator/(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) { | ||
| 587 | lhs /= rhs; | ||
| 588 | return lhs; | ||
| 589 | } | ||
| 590 | |||
| 591 | template <size_t I, size_t F, class Number, | ||
| 592 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 593 | CONSTEXPR14 FixedPoint<I, F> operator+(FixedPoint<I, F> lhs, Number rhs) { | ||
| 594 | lhs += FixedPoint<I, F>(rhs); | ||
| 595 | return lhs; | ||
| 596 | } | ||
| 597 | template <size_t I, size_t F, class Number, | ||
| 598 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 599 | CONSTEXPR14 FixedPoint<I, F> operator-(FixedPoint<I, F> lhs, Number rhs) { | ||
| 600 | lhs -= FixedPoint<I, F>(rhs); | ||
| 601 | return lhs; | ||
| 602 | } | ||
| 603 | template <size_t I, size_t F, class Number, | ||
| 604 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 605 | CONSTEXPR14 FixedPoint<I, F> operator*(FixedPoint<I, F> lhs, Number rhs) { | ||
| 606 | lhs *= FixedPoint<I, F>(rhs); | ||
| 607 | return lhs; | ||
| 608 | } | ||
| 609 | template <size_t I, size_t F, class Number, | ||
| 610 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 611 | CONSTEXPR14 FixedPoint<I, F> operator/(FixedPoint<I, F> lhs, Number rhs) { | ||
| 612 | lhs /= FixedPoint<I, F>(rhs); | ||
| 613 | return lhs; | ||
| 614 | } | ||
| 615 | |||
| 616 | template <size_t I, size_t F, class Number, | ||
| 617 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 618 | CONSTEXPR14 FixedPoint<I, F> operator+(Number lhs, FixedPoint<I, F> rhs) { | ||
| 619 | FixedPoint<I, F> tmp(lhs); | ||
| 620 | tmp += rhs; | ||
| 621 | return tmp; | ||
| 622 | } | ||
| 623 | template <size_t I, size_t F, class Number, | ||
| 624 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 625 | CONSTEXPR14 FixedPoint<I, F> operator-(Number lhs, FixedPoint<I, F> rhs) { | ||
| 626 | FixedPoint<I, F> tmp(lhs); | ||
| 627 | tmp -= rhs; | ||
| 628 | return tmp; | ||
| 629 | } | ||
| 630 | template <size_t I, size_t F, class Number, | ||
| 631 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 632 | CONSTEXPR14 FixedPoint<I, F> operator*(Number lhs, FixedPoint<I, F> rhs) { | ||
| 633 | FixedPoint<I, F> tmp(lhs); | ||
| 634 | tmp *= rhs; | ||
| 635 | return tmp; | ||
| 636 | } | ||
| 637 | template <size_t I, size_t F, class Number, | ||
| 638 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 639 | CONSTEXPR14 FixedPoint<I, F> operator/(Number lhs, FixedPoint<I, F> rhs) { | ||
| 640 | FixedPoint<I, F> tmp(lhs); | ||
| 641 | tmp /= rhs; | ||
| 642 | return tmp; | ||
| 643 | } | ||
| 644 | |||
| 645 | // shift operators | ||
| 646 | template <size_t I, size_t F, class Integer, | ||
| 647 | class = typename std::enable_if<std::is_integral<Integer>::value>::type> | ||
| 648 | CONSTEXPR14 FixedPoint<I, F> operator<<(FixedPoint<I, F> lhs, Integer rhs) { | ||
| 649 | lhs <<= rhs; | ||
| 650 | return lhs; | ||
| 651 | } | ||
| 652 | template <size_t I, size_t F, class Integer, | ||
| 653 | class = typename std::enable_if<std::is_integral<Integer>::value>::type> | ||
| 654 | CONSTEXPR14 FixedPoint<I, F> operator>>(FixedPoint<I, F> lhs, Integer rhs) { | ||
| 655 | lhs >>= rhs; | ||
| 656 | return lhs; | ||
| 657 | } | ||
| 658 | |||
| 659 | // comparison operators | ||
| 660 | template <size_t I, size_t F, class Number, | ||
| 661 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 662 | constexpr bool operator>(FixedPoint<I, F> lhs, Number rhs) { | ||
| 663 | return lhs > FixedPoint<I, F>(rhs); | ||
| 664 | } | ||
| 665 | template <size_t I, size_t F, class Number, | ||
| 666 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 667 | constexpr bool operator<(FixedPoint<I, F> lhs, Number rhs) { | ||
| 668 | return lhs < FixedPoint<I, F>(rhs); | ||
| 669 | } | ||
| 670 | template <size_t I, size_t F, class Number, | ||
| 671 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 672 | constexpr bool operator>=(FixedPoint<I, F> lhs, Number rhs) { | ||
| 673 | return lhs >= FixedPoint<I, F>(rhs); | ||
| 674 | } | ||
| 675 | template <size_t I, size_t F, class Number, | ||
| 676 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 677 | constexpr bool operator<=(FixedPoint<I, F> lhs, Number rhs) { | ||
| 678 | return lhs <= FixedPoint<I, F>(rhs); | ||
| 679 | } | ||
| 680 | template <size_t I, size_t F, class Number, | ||
| 681 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 682 | constexpr bool operator==(FixedPoint<I, F> lhs, Number rhs) { | ||
| 683 | return lhs == FixedPoint<I, F>(rhs); | ||
| 684 | } | ||
| 685 | template <size_t I, size_t F, class Number, | ||
| 686 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 687 | constexpr bool operator!=(FixedPoint<I, F> lhs, Number rhs) { | ||
| 688 | return lhs != FixedPoint<I, F>(rhs); | ||
| 689 | } | ||
| 690 | |||
| 691 | template <size_t I, size_t F, class Number, | ||
| 692 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 693 | constexpr bool operator>(Number lhs, FixedPoint<I, F> rhs) { | ||
| 694 | return FixedPoint<I, F>(lhs) > rhs; | ||
| 695 | } | ||
| 696 | template <size_t I, size_t F, class Number, | ||
| 697 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 698 | constexpr bool operator<(Number lhs, FixedPoint<I, F> rhs) { | ||
| 699 | return FixedPoint<I, F>(lhs) < rhs; | ||
| 700 | } | ||
| 701 | template <size_t I, size_t F, class Number, | ||
| 702 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 703 | constexpr bool operator>=(Number lhs, FixedPoint<I, F> rhs) { | ||
| 704 | return FixedPoint<I, F>(lhs) >= rhs; | ||
| 705 | } | ||
| 706 | template <size_t I, size_t F, class Number, | ||
| 707 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 708 | constexpr bool operator<=(Number lhs, FixedPoint<I, F> rhs) { | ||
| 709 | return FixedPoint<I, F>(lhs) <= rhs; | ||
| 710 | } | ||
| 711 | template <size_t I, size_t F, class Number, | ||
| 712 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 713 | constexpr bool operator==(Number lhs, FixedPoint<I, F> rhs) { | ||
| 714 | return FixedPoint<I, F>(lhs) == rhs; | ||
| 715 | } | ||
| 716 | template <size_t I, size_t F, class Number, | ||
| 717 | class = typename std::enable_if<std::is_arithmetic<Number>::value>::type> | ||
| 718 | constexpr bool operator!=(Number lhs, FixedPoint<I, F> rhs) { | ||
| 719 | return FixedPoint<I, F>(lhs) != rhs; | ||
| 720 | } | ||
| 721 | |||
| 722 | } // namespace Common | ||
| 723 | |||
| 724 | #undef CONSTEXPR14 | ||
| 725 | |||
| 726 | #endif | ||
diff --git a/src/common/reader_writer_queue.h b/src/common/reader_writer_queue.h new file mode 100644 index 000000000..8d2c9408c --- /dev/null +++ b/src/common/reader_writer_queue.h | |||
| @@ -0,0 +1,941 @@ | |||
| 1 | // ©2013-2020 Cameron Desrochers. | ||
| 2 | // Distributed under the simplified BSD license (see the license file that | ||
| 3 | // should have come with this header). | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <cassert> | ||
| 8 | #include <cstdint> | ||
| 9 | #include <cstdlib> // For malloc/free/abort & size_t | ||
| 10 | #include <memory> | ||
| 11 | #include <new> | ||
| 12 | #include <stdexcept> | ||
| 13 | #include <type_traits> | ||
| 14 | #include <utility> | ||
| 15 | |||
| 16 | #include "common/atomic_helpers.h" | ||
| 17 | |||
| 18 | #if __cplusplus > 199711L || _MSC_VER >= 1700 // C++11 or VS2012 | ||
| 19 | #include <chrono> | ||
| 20 | #endif | ||
| 21 | |||
| 22 | // A lock-free queue for a single-consumer, single-producer architecture. | ||
| 23 | // The queue is also wait-free in the common path (except if more memory | ||
| 24 | // needs to be allocated, in which case malloc is called). | ||
| 25 | // Allocates memory sparingly, and only once if the original maximum size | ||
| 26 | // estimate is never exceeded. | ||
| 27 | // Tested on x86/x64 processors, but semantics should be correct for all | ||
| 28 | // architectures (given the right implementations in atomicops.h), provided | ||
| 29 | // that aligned integer and pointer accesses are naturally atomic. | ||
| 30 | // Note that there should only be one consumer thread and producer thread; | ||
| 31 | // Switching roles of the threads, or using multiple consecutive threads for | ||
| 32 | // one role, is not safe unless properly synchronized. | ||
| 33 | // Using the queue exclusively from one thread is fine, though a bit silly. | ||
| 34 | |||
| 35 | #ifndef MOODYCAMEL_CACHE_LINE_SIZE | ||
| 36 | #define MOODYCAMEL_CACHE_LINE_SIZE 64 | ||
| 37 | #endif | ||
| 38 | |||
| 39 | #ifndef MOODYCAMEL_EXCEPTIONS_ENABLED | ||
| 40 | #if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || \ | ||
| 41 | (!defined(_MSC_VER) && !defined(__GNUC__)) | ||
| 42 | #define MOODYCAMEL_EXCEPTIONS_ENABLED | ||
| 43 | #endif | ||
| 44 | #endif | ||
| 45 | |||
| 46 | #ifndef MOODYCAMEL_HAS_EMPLACE | ||
| 47 | #if !defined(_MSC_VER) || \ | ||
| 48 | _MSC_VER >= 1800 // variadic templates: either a non-MS compiler or VS >= 2013 | ||
| 49 | #define MOODYCAMEL_HAS_EMPLACE 1 | ||
| 50 | #endif | ||
| 51 | #endif | ||
| 52 | |||
| 53 | #ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE | ||
| 54 | #if defined(__APPLE__) && defined(__MACH__) && __cplusplus >= 201703L | ||
| 55 | // This is required to find out what deployment target we are using | ||
| 56 | #include <CoreFoundation/CoreFoundation.h> | ||
| 57 | #if !defined(MAC_OS_X_VERSION_MIN_REQUIRED) || \ | ||
| 58 | MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_14 | ||
| 59 | // C++17 new(size_t, align_val_t) is not backwards-compatible with older versions of macOS, so we | ||
| 60 | // can't support over-alignment in this case | ||
| 61 | #define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE | ||
| 62 | #endif | ||
| 63 | #endif | ||
| 64 | #endif | ||
| 65 | |||
| 66 | #ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE | ||
| 67 | #define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE AE_ALIGN(MOODYCAMEL_CACHE_LINE_SIZE) | ||
| 68 | #endif | ||
| 69 | |||
| 70 | #ifdef AE_VCPP | ||
| 71 | #pragma warning(push) | ||
| 72 | #pragma warning(disable : 4324) // structure was padded due to __declspec(align()) | ||
| 73 | #pragma warning(disable : 4820) // padding was added | ||
| 74 | #pragma warning(disable : 4127) // conditional expression is constant | ||
| 75 | #endif | ||
| 76 | |||
| 77 | namespace Common { | ||
| 78 | |||
| 79 | template <typename T, size_t MAX_BLOCK_SIZE = 512> | ||
| 80 | class MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE ReaderWriterQueue { | ||
| 81 | // Design: Based on a queue-of-queues. The low-level queues are just | ||
| 82 | // circular buffers with front and tail indices indicating where the | ||
| 83 | // next element to dequeue is and where the next element can be enqueued, | ||
| 84 | // respectively. Each low-level queue is called a "block". Each block | ||
| 85 | // wastes exactly one element's worth of space to keep the design simple | ||
| 86 | // (if front == tail then the queue is empty, and can't be full). | ||
| 87 | // The high-level queue is a circular linked list of blocks; again there | ||
| 88 | // is a front and tail, but this time they are pointers to the blocks. | ||
| 89 | // The front block is where the next element to be dequeued is, provided | ||
| 90 | // the block is not empty. The back block is where elements are to be | ||
| 91 | // enqueued, provided the block is not full. | ||
| 92 | // The producer thread owns all the tail indices/pointers. The consumer | ||
| 93 | // thread owns all the front indices/pointers. Both threads read each | ||
| 94 | // other's variables, but only the owning thread updates them. E.g. After | ||
| 95 | // the consumer reads the producer's tail, the tail may change before the | ||
| 96 | // consumer is done dequeuing an object, but the consumer knows the tail | ||
| 97 | // will never go backwards, only forwards. | ||
| 98 | // If there is no room to enqueue an object, an additional block (of | ||
| 99 | // equal size to the last block) is added. Blocks are never removed. | ||
| 100 | |||
| 101 | public: | ||
| 102 | typedef T value_type; | ||
| 103 | |||
| 104 | // Constructs a queue that can hold at least `size` elements without further | ||
| 105 | // allocations. If more than MAX_BLOCK_SIZE elements are requested, | ||
| 106 | // then several blocks of MAX_BLOCK_SIZE each are reserved (including | ||
| 107 | // at least one extra buffer block). | ||
| 108 | AE_NO_TSAN explicit ReaderWriterQueue(size_t size = 15) | ||
| 109 | #ifndef NDEBUG | ||
| 110 | : enqueuing(false), dequeuing(false) | ||
| 111 | #endif | ||
| 112 | { | ||
| 113 | assert(MAX_BLOCK_SIZE == ceilToPow2(MAX_BLOCK_SIZE) && | ||
| 114 | "MAX_BLOCK_SIZE must be a power of 2"); | ||
| 115 | assert(MAX_BLOCK_SIZE >= 2 && "MAX_BLOCK_SIZE must be at least 2"); | ||
| 116 | |||
| 117 | Block* firstBlock = nullptr; | ||
| 118 | |||
| 119 | largestBlockSize = | ||
| 120 | ceilToPow2(size + 1); // We need a spare slot to fit size elements in the block | ||
| 121 | if (largestBlockSize > MAX_BLOCK_SIZE * 2) { | ||
| 122 | // We need a spare block in case the producer is writing to a different block the | ||
| 123 | // consumer is reading from, and wants to enqueue the maximum number of elements. We | ||
| 124 | // also need a spare element in each block to avoid the ambiguity between front == tail | ||
| 125 | // meaning "empty" and "full". So the effective number of slots that are guaranteed to | ||
| 126 | // be usable at any time is the block size - 1 times the number of blocks - 1. Solving | ||
| 127 | // for size and applying a ceiling to the division gives us (after simplifying): | ||
| 128 | size_t initialBlockCount = (size + MAX_BLOCK_SIZE * 2 - 3) / (MAX_BLOCK_SIZE - 1); | ||
| 129 | largestBlockSize = MAX_BLOCK_SIZE; | ||
| 130 | Block* lastBlock = nullptr; | ||
| 131 | for (size_t i = 0; i != initialBlockCount; ++i) { | ||
| 132 | auto block = make_block(largestBlockSize); | ||
| 133 | if (block == nullptr) { | ||
| 134 | #ifdef MOODYCAMEL_EXCEPTIONS_ENABLED | ||
| 135 | throw std::bad_alloc(); | ||
| 136 | #else | ||
| 137 | abort(); | ||
| 138 | #endif | ||
| 139 | } | ||
| 140 | if (firstBlock == nullptr) { | ||
| 141 | firstBlock = block; | ||
| 142 | } else { | ||
| 143 | lastBlock->next = block; | ||
| 144 | } | ||
| 145 | lastBlock = block; | ||
| 146 | block->next = firstBlock; | ||
| 147 | } | ||
| 148 | } else { | ||
| 149 | firstBlock = make_block(largestBlockSize); | ||
| 150 | if (firstBlock == nullptr) { | ||
| 151 | #ifdef MOODYCAMEL_EXCEPTIONS_ENABLED | ||
| 152 | throw std::bad_alloc(); | ||
| 153 | #else | ||
| 154 | abort(); | ||
| 155 | #endif | ||
| 156 | } | ||
| 157 | firstBlock->next = firstBlock; | ||
| 158 | } | ||
| 159 | frontBlock = firstBlock; | ||
| 160 | tailBlock = firstBlock; | ||
| 161 | |||
| 162 | // Make sure the reader/writer threads will have the initialized memory setup above: | ||
| 163 | fence(memory_order_sync); | ||
| 164 | } | ||
| 165 | |||
| 166 | // Note: The queue should not be accessed concurrently while it's | ||
| 167 | // being moved. It's up to the user to synchronize this. | ||
| 168 | AE_NO_TSAN ReaderWriterQueue(ReaderWriterQueue&& other) | ||
| 169 | : frontBlock(other.frontBlock.load()), tailBlock(other.tailBlock.load()), | ||
| 170 | largestBlockSize(other.largestBlockSize) | ||
| 171 | #ifndef NDEBUG | ||
| 172 | , | ||
| 173 | enqueuing(false), dequeuing(false) | ||
| 174 | #endif | ||
| 175 | { | ||
| 176 | other.largestBlockSize = 32; | ||
| 177 | Block* b = other.make_block(other.largestBlockSize); | ||
| 178 | if (b == nullptr) { | ||
| 179 | #ifdef MOODYCAMEL_EXCEPTIONS_ENABLED | ||
| 180 | throw std::bad_alloc(); | ||
| 181 | #else | ||
| 182 | abort(); | ||
| 183 | #endif | ||
| 184 | } | ||
| 185 | b->next = b; | ||
| 186 | other.frontBlock = b; | ||
| 187 | other.tailBlock = b; | ||
| 188 | } | ||
| 189 | |||
| 190 | // Note: The queue should not be accessed concurrently while it's | ||
| 191 | // being moved. It's up to the user to synchronize this. | ||
| 192 | ReaderWriterQueue& operator=(ReaderWriterQueue&& other) AE_NO_TSAN { | ||
| 193 | Block* b = frontBlock.load(); | ||
| 194 | frontBlock = other.frontBlock.load(); | ||
| 195 | other.frontBlock = b; | ||
| 196 | b = tailBlock.load(); | ||
| 197 | tailBlock = other.tailBlock.load(); | ||
| 198 | other.tailBlock = b; | ||
| 199 | std::swap(largestBlockSize, other.largestBlockSize); | ||
| 200 | return *this; | ||
| 201 | } | ||
| 202 | |||
| 203 | // Note: The queue should not be accessed concurrently while it's | ||
| 204 | // being deleted. It's up to the user to synchronize this. | ||
| 205 | AE_NO_TSAN ~ReaderWriterQueue() { | ||
| 206 | // Make sure we get the latest version of all variables from other CPUs: | ||
| 207 | fence(memory_order_sync); | ||
| 208 | |||
| 209 | // Destroy any remaining objects in queue and free memory | ||
| 210 | Block* frontBlock_ = frontBlock; | ||
| 211 | Block* block = frontBlock_; | ||
| 212 | do { | ||
| 213 | Block* nextBlock = block->next; | ||
| 214 | size_t blockFront = block->front; | ||
| 215 | size_t blockTail = block->tail; | ||
| 216 | |||
| 217 | for (size_t i = blockFront; i != blockTail; i = (i + 1) & block->sizeMask) { | ||
| 218 | auto element = reinterpret_cast<T*>(block->data + i * sizeof(T)); | ||
| 219 | element->~T(); | ||
| 220 | (void)element; | ||
| 221 | } | ||
| 222 | |||
| 223 | auto rawBlock = block->rawThis; | ||
| 224 | block->~Block(); | ||
| 225 | std::free(rawBlock); | ||
| 226 | block = nextBlock; | ||
| 227 | } while (block != frontBlock_); | ||
| 228 | } | ||
| 229 | |||
| 230 | // Enqueues a copy of element if there is room in the queue. | ||
| 231 | // Returns true if the element was enqueued, false otherwise. | ||
| 232 | // Does not allocate memory. | ||
| 233 | AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN { | ||
| 234 | return inner_enqueue<CannotAlloc>(element); | ||
| 235 | } | ||
| 236 | |||
| 237 | // Enqueues a moved copy of element if there is room in the queue. | ||
| 238 | // Returns true if the element was enqueued, false otherwise. | ||
| 239 | // Does not allocate memory. | ||
| 240 | AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN { | ||
| 241 | return inner_enqueue<CannotAlloc>(std::forward<T>(element)); | ||
| 242 | } | ||
| 243 | |||
| 244 | #if MOODYCAMEL_HAS_EMPLACE | ||
| 245 | // Like try_enqueue() but with emplace semantics (i.e. construct-in-place). | ||
| 246 | template <typename... Args> | ||
| 247 | AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN { | ||
| 248 | return inner_enqueue<CannotAlloc>(std::forward<Args>(args)...); | ||
| 249 | } | ||
| 250 | #endif | ||
| 251 | |||
| 252 | // Enqueues a copy of element on the queue. | ||
| 253 | // Allocates an additional block of memory if needed. | ||
| 254 | // Only fails (returns false) if memory allocation fails. | ||
| 255 | AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN { | ||
| 256 | return inner_enqueue<CanAlloc>(element); | ||
| 257 | } | ||
| 258 | |||
| 259 | // Enqueues a moved copy of element on the queue. | ||
| 260 | // Allocates an additional block of memory if needed. | ||
| 261 | // Only fails (returns false) if memory allocation fails. | ||
| 262 | AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN { | ||
| 263 | return inner_enqueue<CanAlloc>(std::forward<T>(element)); | ||
| 264 | } | ||
| 265 | |||
| 266 | #if MOODYCAMEL_HAS_EMPLACE | ||
| 267 | // Like enqueue() but with emplace semantics (i.e. construct-in-place). | ||
| 268 | template <typename... Args> | ||
| 269 | AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN { | ||
| 270 | return inner_enqueue<CanAlloc>(std::forward<Args>(args)...); | ||
| 271 | } | ||
| 272 | #endif | ||
| 273 | |||
| 274 | // Attempts to dequeue an element; if the queue is empty, | ||
| 275 | // returns false instead. If the queue has at least one element, | ||
| 276 | // moves front to result using operator=, then returns true. | ||
| 277 | template <typename U> | ||
| 278 | bool try_dequeue(U& result) AE_NO_TSAN { | ||
| 279 | #ifndef NDEBUG | ||
| 280 | ReentrantGuard guard(this->dequeuing); | ||
| 281 | #endif | ||
| 282 | |||
| 283 | // High-level pseudocode: | ||
| 284 | // Remember where the tail block is | ||
| 285 | // If the front block has an element in it, dequeue it | ||
| 286 | // Else | ||
| 287 | // If front block was the tail block when we entered the function, return false | ||
| 288 | // Else advance to next block and dequeue the item there | ||
| 289 | |||
| 290 | // Note that we have to use the value of the tail block from before we check if the front | ||
| 291 | // block is full or not, in case the front block is empty and then, before we check if the | ||
| 292 | // tail block is at the front block or not, the producer fills up the front block *and | ||
| 293 | // moves on*, which would make us skip a filled block. Seems unlikely, but was consistently | ||
| 294 | // reproducible in practice. | ||
| 295 | // In order to avoid overhead in the common case, though, we do a double-checked pattern | ||
| 296 | // where we have the fast path if the front block is not empty, then read the tail block, | ||
| 297 | // then re-read the front block and check if it's not empty again, then check if the tail | ||
| 298 | // block has advanced. | ||
| 299 | |||
| 300 | Block* frontBlock_ = frontBlock.load(); | ||
| 301 | size_t blockTail = frontBlock_->localTail; | ||
| 302 | size_t blockFront = frontBlock_->front.load(); | ||
| 303 | |||
| 304 | if (blockFront != blockTail || | ||
| 305 | blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { | ||
| 306 | fence(memory_order_acquire); | ||
| 307 | |||
| 308 | non_empty_front_block: | ||
| 309 | // Front block not empty, dequeue from here | ||
| 310 | auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T)); | ||
| 311 | result = std::move(*element); | ||
| 312 | element->~T(); | ||
| 313 | |||
| 314 | blockFront = (blockFront + 1) & frontBlock_->sizeMask; | ||
| 315 | |||
| 316 | fence(memory_order_release); | ||
| 317 | frontBlock_->front = blockFront; | ||
| 318 | } else if (frontBlock_ != tailBlock.load()) { | ||
| 319 | fence(memory_order_acquire); | ||
| 320 | |||
| 321 | frontBlock_ = frontBlock.load(); | ||
| 322 | blockTail = frontBlock_->localTail = frontBlock_->tail.load(); | ||
| 323 | blockFront = frontBlock_->front.load(); | ||
| 324 | fence(memory_order_acquire); | ||
| 325 | |||
| 326 | if (blockFront != blockTail) { | ||
| 327 | // Oh look, the front block isn't empty after all | ||
| 328 | goto non_empty_front_block; | ||
| 329 | } | ||
| 330 | |||
| 331 | // Front block is empty but there's another block ahead, advance to it | ||
| 332 | Block* nextBlock = frontBlock_->next; | ||
| 333 | // Don't need an acquire fence here since next can only ever be set on the tailBlock, | ||
| 334 | // and we're not the tailBlock, and we did an acquire earlier after reading tailBlock | ||
| 335 | // which ensures next is up-to-date on this CPU in case we recently were at tailBlock. | ||
| 336 | |||
| 337 | size_t nextBlockFront = nextBlock->front.load(); | ||
| 338 | size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load(); | ||
| 339 | fence(memory_order_acquire); | ||
| 340 | |||
| 341 | // Since the tailBlock is only ever advanced after being written to, | ||
| 342 | // we know there's for sure an element to dequeue on it | ||
| 343 | assert(nextBlockFront != nextBlockTail); | ||
| 344 | AE_UNUSED(nextBlockTail); | ||
| 345 | |||
| 346 | // We're done with this block, let the producer use it if it needs | ||
| 347 | fence(memory_order_release); // Expose possibly pending changes to frontBlock->front | ||
| 348 | // from last dequeue | ||
| 349 | frontBlock = frontBlock_ = nextBlock; | ||
| 350 | |||
| 351 | compiler_fence(memory_order_release); // Not strictly needed | ||
| 352 | |||
| 353 | auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T)); | ||
| 354 | |||
| 355 | result = std::move(*element); | ||
| 356 | element->~T(); | ||
| 357 | |||
| 358 | nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask; | ||
| 359 | |||
| 360 | fence(memory_order_release); | ||
| 361 | frontBlock_->front = nextBlockFront; | ||
| 362 | } else { | ||
| 363 | // No elements in current block and no other block to advance to | ||
| 364 | return false; | ||
| 365 | } | ||
| 366 | |||
| 367 | return true; | ||
| 368 | } | ||
| 369 | |||
| 370 | // Returns a pointer to the front element in the queue (the one that | ||
| 371 | // would be removed next by a call to `try_dequeue` or `pop`). If the | ||
| 372 | // queue appears empty at the time the method is called, nullptr is | ||
| 373 | // returned instead. | ||
| 374 | // Must be called only from the consumer thread. | ||
| 375 | T* peek() const AE_NO_TSAN { | ||
| 376 | #ifndef NDEBUG | ||
| 377 | ReentrantGuard guard(this->dequeuing); | ||
| 378 | #endif | ||
| 379 | // See try_dequeue() for reasoning | ||
| 380 | |||
| 381 | Block* frontBlock_ = frontBlock.load(); | ||
| 382 | size_t blockTail = frontBlock_->localTail; | ||
| 383 | size_t blockFront = frontBlock_->front.load(); | ||
| 384 | |||
| 385 | if (blockFront != blockTail || | ||
| 386 | blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { | ||
| 387 | fence(memory_order_acquire); | ||
| 388 | non_empty_front_block: | ||
| 389 | return reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T)); | ||
| 390 | } else if (frontBlock_ != tailBlock.load()) { | ||
| 391 | fence(memory_order_acquire); | ||
| 392 | frontBlock_ = frontBlock.load(); | ||
| 393 | blockTail = frontBlock_->localTail = frontBlock_->tail.load(); | ||
| 394 | blockFront = frontBlock_->front.load(); | ||
| 395 | fence(memory_order_acquire); | ||
| 396 | |||
| 397 | if (blockFront != blockTail) { | ||
| 398 | goto non_empty_front_block; | ||
| 399 | } | ||
| 400 | |||
| 401 | Block* nextBlock = frontBlock_->next; | ||
| 402 | |||
| 403 | size_t nextBlockFront = nextBlock->front.load(); | ||
| 404 | fence(memory_order_acquire); | ||
| 405 | |||
| 406 | assert(nextBlockFront != nextBlock->tail.load()); | ||
| 407 | return reinterpret_cast<T*>(nextBlock->data + nextBlockFront * sizeof(T)); | ||
| 408 | } | ||
| 409 | |||
| 410 | return nullptr; | ||
| 411 | } | ||
| 412 | |||
| 413 | // Removes the front element from the queue, if any, without returning it. | ||
| 414 | // Returns true on success, or false if the queue appeared empty at the time | ||
| 415 | // `pop` was called. | ||
| 416 | bool pop() AE_NO_TSAN { | ||
| 417 | #ifndef NDEBUG | ||
| 418 | ReentrantGuard guard(this->dequeuing); | ||
| 419 | #endif | ||
| 420 | // See try_dequeue() for reasoning | ||
| 421 | |||
| 422 | Block* frontBlock_ = frontBlock.load(); | ||
| 423 | size_t blockTail = frontBlock_->localTail; | ||
| 424 | size_t blockFront = frontBlock_->front.load(); | ||
| 425 | |||
| 426 | if (blockFront != blockTail || | ||
| 427 | blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { | ||
| 428 | fence(memory_order_acquire); | ||
| 429 | |||
| 430 | non_empty_front_block: | ||
| 431 | auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T)); | ||
| 432 | element->~T(); | ||
| 433 | |||
| 434 | blockFront = (blockFront + 1) & frontBlock_->sizeMask; | ||
| 435 | |||
| 436 | fence(memory_order_release); | ||
| 437 | frontBlock_->front = blockFront; | ||
| 438 | } else if (frontBlock_ != tailBlock.load()) { | ||
| 439 | fence(memory_order_acquire); | ||
| 440 | frontBlock_ = frontBlock.load(); | ||
| 441 | blockTail = frontBlock_->localTail = frontBlock_->tail.load(); | ||
| 442 | blockFront = frontBlock_->front.load(); | ||
| 443 | fence(memory_order_acquire); | ||
| 444 | |||
| 445 | if (blockFront != blockTail) { | ||
| 446 | goto non_empty_front_block; | ||
| 447 | } | ||
| 448 | |||
| 449 | // Front block is empty but there's another block ahead, advance to it | ||
| 450 | Block* nextBlock = frontBlock_->next; | ||
| 451 | |||
| 452 | size_t nextBlockFront = nextBlock->front.load(); | ||
| 453 | size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load(); | ||
| 454 | fence(memory_order_acquire); | ||
| 455 | |||
| 456 | assert(nextBlockFront != nextBlockTail); | ||
| 457 | AE_UNUSED(nextBlockTail); | ||
| 458 | |||
| 459 | fence(memory_order_release); | ||
| 460 | frontBlock = frontBlock_ = nextBlock; | ||
| 461 | |||
| 462 | compiler_fence(memory_order_release); | ||
| 463 | |||
| 464 | auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T)); | ||
| 465 | element->~T(); | ||
| 466 | |||
| 467 | nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask; | ||
| 468 | |||
| 469 | fence(memory_order_release); | ||
| 470 | frontBlock_->front = nextBlockFront; | ||
| 471 | } else { | ||
| 472 | // No elements in current block and no other block to advance to | ||
| 473 | return false; | ||
| 474 | } | ||
| 475 | |||
| 476 | return true; | ||
| 477 | } | ||
| 478 | |||
| 479 | // Returns the approximate number of items currently in the queue. | ||
| 480 | // Safe to call from both the producer and consumer threads. | ||
| 481 | inline size_t size_approx() const AE_NO_TSAN { | ||
| 482 | size_t result = 0; | ||
| 483 | Block* frontBlock_ = frontBlock.load(); | ||
| 484 | Block* block = frontBlock_; | ||
| 485 | do { | ||
| 486 | fence(memory_order_acquire); | ||
| 487 | size_t blockFront = block->front.load(); | ||
| 488 | size_t blockTail = block->tail.load(); | ||
| 489 | result += (blockTail - blockFront) & block->sizeMask; | ||
| 490 | block = block->next.load(); | ||
| 491 | } while (block != frontBlock_); | ||
| 492 | return result; | ||
| 493 | } | ||
| 494 | |||
| 495 | // Returns the total number of items that could be enqueued without incurring | ||
| 496 | // an allocation when this queue is empty. | ||
| 497 | // Safe to call from both the producer and consumer threads. | ||
| 498 | // | ||
| 499 | // NOTE: The actual capacity during usage may be different depending on the consumer. | ||
| 500 | // If the consumer is removing elements concurrently, the producer cannot add to | ||
| 501 | // the block the consumer is removing from until it's completely empty, except in | ||
| 502 | // the case where the producer was writing to the same block the consumer was | ||
| 503 | // reading from the whole time. | ||
| 504 | inline size_t max_capacity() const { | ||
| 505 | size_t result = 0; | ||
| 506 | Block* frontBlock_ = frontBlock.load(); | ||
| 507 | Block* block = frontBlock_; | ||
| 508 | do { | ||
| 509 | fence(memory_order_acquire); | ||
| 510 | result += block->sizeMask; | ||
| 511 | block = block->next.load(); | ||
| 512 | } while (block != frontBlock_); | ||
| 513 | return result; | ||
| 514 | } | ||
| 515 | |||
| 516 | private: | ||
| 517 | enum AllocationMode { CanAlloc, CannotAlloc }; | ||
| 518 | |||
| 519 | #if MOODYCAMEL_HAS_EMPLACE | ||
| 520 | template <AllocationMode canAlloc, typename... Args> | ||
| 521 | bool inner_enqueue(Args&&... args) AE_NO_TSAN | ||
| 522 | #else | ||
| 523 | template <AllocationMode canAlloc, typename U> | ||
| 524 | bool inner_enqueue(U&& element) AE_NO_TSAN | ||
| 525 | #endif | ||
| 526 | { | ||
| 527 | #ifndef NDEBUG | ||
| 528 | ReentrantGuard guard(this->enqueuing); | ||
| 529 | #endif | ||
| 530 | |||
| 531 | // High-level pseudocode (assuming we're allowed to alloc a new block): | ||
| 532 | // If room in tail block, add to tail | ||
| 533 | // Else check next block | ||
| 534 | // If next block is not the head block, enqueue on next block | ||
| 535 | // Else create a new block and enqueue there | ||
| 536 | // Advance tail to the block we just enqueued to | ||
| 537 | |||
| 538 | Block* tailBlock_ = tailBlock.load(); | ||
| 539 | size_t blockFront = tailBlock_->localFront; | ||
| 540 | size_t blockTail = tailBlock_->tail.load(); | ||
| 541 | |||
| 542 | size_t nextBlockTail = (blockTail + 1) & tailBlock_->sizeMask; | ||
| 543 | if (nextBlockTail != blockFront || | ||
| 544 | nextBlockTail != (tailBlock_->localFront = tailBlock_->front.load())) { | ||
| 545 | fence(memory_order_acquire); | ||
| 546 | // This block has room for at least one more element | ||
| 547 | char* location = tailBlock_->data + blockTail * sizeof(T); | ||
| 548 | #if MOODYCAMEL_HAS_EMPLACE | ||
| 549 | new (location) T(std::forward<Args>(args)...); | ||
| 550 | #else | ||
| 551 | new (location) T(std::forward<U>(element)); | ||
| 552 | #endif | ||
| 553 | |||
| 554 | fence(memory_order_release); | ||
| 555 | tailBlock_->tail = nextBlockTail; | ||
| 556 | } else { | ||
| 557 | fence(memory_order_acquire); | ||
| 558 | if (tailBlock_->next.load() != frontBlock) { | ||
| 559 | // Note that the reason we can't advance to the frontBlock and start adding new | ||
| 560 | // entries there is because if we did, then dequeue would stay in that block, | ||
| 561 | // eventually reading the new values, instead of advancing to the next full block | ||
| 562 | // (whose values were enqueued first and so should be consumed first). | ||
| 563 | |||
| 564 | fence(memory_order_acquire); // Ensure we get latest writes if we got the latest | ||
| 565 | // frontBlock | ||
| 566 | |||
| 567 | // tailBlock is full, but there's a free block ahead, use it | ||
| 568 | Block* tailBlockNext = tailBlock_->next.load(); | ||
| 569 | size_t nextBlockFront = tailBlockNext->localFront = tailBlockNext->front.load(); | ||
| 570 | nextBlockTail = tailBlockNext->tail.load(); | ||
| 571 | fence(memory_order_acquire); | ||
| 572 | |||
| 573 | // This block must be empty since it's not the head block and we | ||
| 574 | // go through the blocks in a circle | ||
| 575 | assert(nextBlockFront == nextBlockTail); | ||
| 576 | tailBlockNext->localFront = nextBlockFront; | ||
| 577 | |||
| 578 | char* location = tailBlockNext->data + nextBlockTail * sizeof(T); | ||
| 579 | #if MOODYCAMEL_HAS_EMPLACE | ||
| 580 | new (location) T(std::forward<Args>(args)...); | ||
| 581 | #else | ||
| 582 | new (location) T(std::forward<U>(element)); | ||
| 583 | #endif | ||
| 584 | |||
| 585 | tailBlockNext->tail = (nextBlockTail + 1) & tailBlockNext->sizeMask; | ||
| 586 | |||
| 587 | fence(memory_order_release); | ||
| 588 | tailBlock = tailBlockNext; | ||
| 589 | } else if (canAlloc == CanAlloc) { | ||
| 590 | // tailBlock is full and there's no free block ahead; create a new block | ||
| 591 | auto newBlockSize = | ||
| 592 | largestBlockSize >= MAX_BLOCK_SIZE ? largestBlockSize : largestBlockSize * 2; | ||
| 593 | auto newBlock = make_block(newBlockSize); | ||
| 594 | if (newBlock == nullptr) { | ||
| 595 | // Could not allocate a block! | ||
| 596 | return false; | ||
| 597 | } | ||
| 598 | largestBlockSize = newBlockSize; | ||
| 599 | |||
| 600 | #if MOODYCAMEL_HAS_EMPLACE | ||
| 601 | new (newBlock->data) T(std::forward<Args>(args)...); | ||
| 602 | #else | ||
| 603 | new (newBlock->data) T(std::forward<U>(element)); | ||
| 604 | #endif | ||
| 605 | assert(newBlock->front == 0); | ||
| 606 | newBlock->tail = newBlock->localTail = 1; | ||
| 607 | |||
| 608 | newBlock->next = tailBlock_->next.load(); | ||
| 609 | tailBlock_->next = newBlock; | ||
| 610 | |||
| 611 | // Might be possible for the dequeue thread to see the new tailBlock->next | ||
| 612 | // *without* seeing the new tailBlock value, but this is OK since it can't | ||
| 613 | // advance to the next block until tailBlock is set anyway (because the only | ||
| 614 | // case where it could try to read the next is if it's already at the tailBlock, | ||
| 615 | // and it won't advance past tailBlock in any circumstance). | ||
| 616 | |||
| 617 | fence(memory_order_release); | ||
| 618 | tailBlock = newBlock; | ||
| 619 | } else if (canAlloc == CannotAlloc) { | ||
| 620 | // Would have had to allocate a new block to enqueue, but not allowed | ||
| 621 | return false; | ||
| 622 | } else { | ||
| 623 | assert(false && "Should be unreachable code"); | ||
| 624 | return false; | ||
| 625 | } | ||
| 626 | } | ||
| 627 | |||
| 628 | return true; | ||
| 629 | } | ||
| 630 | |||
| 631 | // Disable copying | ||
| 632 | ReaderWriterQueue(ReaderWriterQueue const&) {} | ||
| 633 | |||
| 634 | // Disable assignment | ||
| 635 | ReaderWriterQueue& operator=(ReaderWriterQueue const&) {} | ||
| 636 | |||
| 637 | AE_FORCEINLINE static size_t ceilToPow2(size_t x) { | ||
| 638 | // From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 | ||
| 639 | --x; | ||
| 640 | x |= x >> 1; | ||
| 641 | x |= x >> 2; | ||
| 642 | x |= x >> 4; | ||
| 643 | for (size_t i = 1; i < sizeof(size_t); i <<= 1) { | ||
| 644 | x |= x >> (i << 3); | ||
| 645 | } | ||
| 646 | ++x; | ||
| 647 | return x; | ||
| 648 | } | ||
| 649 | |||
| 650 | template <typename U> | ||
| 651 | static AE_FORCEINLINE char* align_for(char* ptr) AE_NO_TSAN { | ||
| 652 | const std::size_t alignment = std::alignment_of<U>::value; | ||
| 653 | return ptr + (alignment - (reinterpret_cast<std::uintptr_t>(ptr) % alignment)) % alignment; | ||
| 654 | } | ||
| 655 | |||
| 656 | private: | ||
| 657 | #ifndef NDEBUG | ||
| 658 | struct ReentrantGuard { | ||
| 659 | AE_NO_TSAN ReentrantGuard(weak_atomic<bool>& _inSection) : inSection(_inSection) { | ||
| 660 | assert(!inSection && | ||
| 661 | "Concurrent (or re-entrant) enqueue or dequeue operation detected (only one " | ||
| 662 | "thread at a time may hold the producer or consumer role)"); | ||
| 663 | inSection = true; | ||
| 664 | } | ||
| 665 | |||
| 666 | AE_NO_TSAN ~ReentrantGuard() { | ||
| 667 | inSection = false; | ||
| 668 | } | ||
| 669 | |||
| 670 | private: | ||
| 671 | ReentrantGuard& operator=(ReentrantGuard const&); | ||
| 672 | |||
| 673 | private: | ||
| 674 | weak_atomic<bool>& inSection; | ||
| 675 | }; | ||
| 676 | #endif | ||
| 677 | |||
| 678 | struct Block { | ||
| 679 | // Avoid false-sharing by putting highly contended variables on their own cache lines | ||
| 680 | weak_atomic<size_t> front; // (Atomic) Elements are read from here | ||
| 681 | size_t localTail; // An uncontended shadow copy of tail, owned by the consumer | ||
| 682 | |||
| 683 | char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<size_t>) - | ||
| 684 | sizeof(size_t)]; | ||
| 685 | weak_atomic<size_t> tail; // (Atomic) Elements are enqueued here | ||
| 686 | size_t localFront; | ||
| 687 | |||
| 688 | char cachelineFiller1[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<size_t>) - | ||
| 689 | sizeof(size_t)]; // next isn't very contended, but we don't want it on | ||
| 690 | // the same cache line as tail (which is) | ||
| 691 | weak_atomic<Block*> next; // (Atomic) | ||
| 692 | |||
| 693 | char* data; // Contents (on heap) are aligned to T's alignment | ||
| 694 | |||
| 695 | const size_t sizeMask; | ||
| 696 | |||
| 697 | // size must be a power of two (and greater than 0) | ||
| 698 | AE_NO_TSAN Block(size_t const& _size, char* _rawThis, char* _data) | ||
| 699 | : front(0UL), localTail(0), tail(0UL), localFront(0), next(nullptr), data(_data), | ||
| 700 | sizeMask(_size - 1), rawThis(_rawThis) {} | ||
| 701 | |||
| 702 | private: | ||
| 703 | // C4512 - Assignment operator could not be generated | ||
| 704 | Block& operator=(Block const&); | ||
| 705 | |||
| 706 | public: | ||
| 707 | char* rawThis; | ||
| 708 | }; | ||
| 709 | |||
| 710 | static Block* make_block(size_t capacity) AE_NO_TSAN { | ||
| 711 | // Allocate enough memory for the block itself, as well as all the elements it will contain | ||
| 712 | auto size = sizeof(Block) + std::alignment_of<Block>::value - 1; | ||
| 713 | size += sizeof(T) * capacity + std::alignment_of<T>::value - 1; | ||
| 714 | auto newBlockRaw = static_cast<char*>(std::malloc(size)); | ||
| 715 | if (newBlockRaw == nullptr) { | ||
| 716 | return nullptr; | ||
| 717 | } | ||
| 718 | |||
| 719 | auto newBlockAligned = align_for<Block>(newBlockRaw); | ||
| 720 | auto newBlockData = align_for<T>(newBlockAligned + sizeof(Block)); | ||
| 721 | return new (newBlockAligned) Block(capacity, newBlockRaw, newBlockData); | ||
| 722 | } | ||
| 723 | |||
| 724 | private: | ||
| 725 | weak_atomic<Block*> frontBlock; // (Atomic) Elements are dequeued from this block | ||
| 726 | |||
| 727 | char cachelineFiller[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<Block*>)]; | ||
| 728 | weak_atomic<Block*> tailBlock; // (Atomic) Elements are enqueued to this block | ||
| 729 | |||
| 730 | size_t largestBlockSize; | ||
| 731 | |||
| 732 | #ifndef NDEBUG | ||
| 733 | weak_atomic<bool> enqueuing; | ||
| 734 | mutable weak_atomic<bool> dequeuing; | ||
| 735 | #endif | ||
| 736 | }; | ||
| 737 | |||
| 738 | // Like ReaderWriterQueue, but also providees blocking operations | ||
| 739 | template <typename T, size_t MAX_BLOCK_SIZE = 512> | ||
| 740 | class BlockingReaderWriterQueue { | ||
| 741 | private: | ||
| 742 | typedef ::Common::ReaderWriterQueue<T, MAX_BLOCK_SIZE> ReaderWriterQueue; | ||
| 743 | |||
| 744 | public: | ||
| 745 | explicit BlockingReaderWriterQueue(size_t size = 15) AE_NO_TSAN | ||
| 746 | : inner(size), | ||
| 747 | sema(new spsc_sema::LightweightSemaphore()) {} | ||
| 748 | |||
| 749 | BlockingReaderWriterQueue(BlockingReaderWriterQueue&& other) AE_NO_TSAN | ||
| 750 | : inner(std::move(other.inner)), | ||
| 751 | sema(std::move(other.sema)) {} | ||
| 752 | |||
| 753 | BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue&& other) AE_NO_TSAN { | ||
| 754 | std::swap(sema, other.sema); | ||
| 755 | std::swap(inner, other.inner); | ||
| 756 | return *this; | ||
| 757 | } | ||
| 758 | |||
| 759 | // Enqueues a copy of element if there is room in the queue. | ||
| 760 | // Returns true if the element was enqueued, false otherwise. | ||
| 761 | // Does not allocate memory. | ||
| 762 | AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN { | ||
| 763 | if (inner.try_enqueue(element)) { | ||
| 764 | sema->signal(); | ||
| 765 | return true; | ||
| 766 | } | ||
| 767 | return false; | ||
| 768 | } | ||
| 769 | |||
| 770 | // Enqueues a moved copy of element if there is room in the queue. | ||
| 771 | // Returns true if the element was enqueued, false otherwise. | ||
| 772 | // Does not allocate memory. | ||
| 773 | AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN { | ||
| 774 | if (inner.try_enqueue(std::forward<T>(element))) { | ||
| 775 | sema->signal(); | ||
| 776 | return true; | ||
| 777 | } | ||
| 778 | return false; | ||
| 779 | } | ||
| 780 | |||
| 781 | #if MOODYCAMEL_HAS_EMPLACE | ||
| 782 | // Like try_enqueue() but with emplace semantics (i.e. construct-in-place). | ||
| 783 | template <typename... Args> | ||
| 784 | AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN { | ||
| 785 | if (inner.try_emplace(std::forward<Args>(args)...)) { | ||
| 786 | sema->signal(); | ||
| 787 | return true; | ||
| 788 | } | ||
| 789 | return false; | ||
| 790 | } | ||
| 791 | #endif | ||
| 792 | |||
| 793 | // Enqueues a copy of element on the queue. | ||
| 794 | // Allocates an additional block of memory if needed. | ||
| 795 | // Only fails (returns false) if memory allocation fails. | ||
| 796 | AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN { | ||
| 797 | if (inner.enqueue(element)) { | ||
| 798 | sema->signal(); | ||
| 799 | return true; | ||
| 800 | } | ||
| 801 | return false; | ||
| 802 | } | ||
| 803 | |||
| 804 | // Enqueues a moved copy of element on the queue. | ||
| 805 | // Allocates an additional block of memory if needed. | ||
| 806 | // Only fails (returns false) if memory allocation fails. | ||
| 807 | AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN { | ||
| 808 | if (inner.enqueue(std::forward<T>(element))) { | ||
| 809 | sema->signal(); | ||
| 810 | return true; | ||
| 811 | } | ||
| 812 | return false; | ||
| 813 | } | ||
| 814 | |||
| 815 | #if MOODYCAMEL_HAS_EMPLACE | ||
| 816 | // Like enqueue() but with emplace semantics (i.e. construct-in-place). | ||
| 817 | template <typename... Args> | ||
| 818 | AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN { | ||
| 819 | if (inner.emplace(std::forward<Args>(args)...)) { | ||
| 820 | sema->signal(); | ||
| 821 | return true; | ||
| 822 | } | ||
| 823 | return false; | ||
| 824 | } | ||
| 825 | #endif | ||
| 826 | |||
| 827 | // Attempts to dequeue an element; if the queue is empty, | ||
| 828 | // returns false instead. If the queue has at least one element, | ||
| 829 | // moves front to result using operator=, then returns true. | ||
| 830 | template <typename U> | ||
| 831 | bool try_dequeue(U& result) AE_NO_TSAN { | ||
| 832 | if (sema->tryWait()) { | ||
| 833 | bool success = inner.try_dequeue(result); | ||
| 834 | assert(success); | ||
| 835 | AE_UNUSED(success); | ||
| 836 | return true; | ||
| 837 | } | ||
| 838 | return false; | ||
| 839 | } | ||
| 840 | |||
| 841 | // Attempts to dequeue an element; if the queue is empty, | ||
| 842 | // waits until an element is available, then dequeues it. | ||
| 843 | template <typename U> | ||
| 844 | void wait_dequeue(U& result) AE_NO_TSAN { | ||
| 845 | while (!sema->wait()) | ||
| 846 | ; | ||
| 847 | bool success = inner.try_dequeue(result); | ||
| 848 | AE_UNUSED(result); | ||
| 849 | assert(success); | ||
| 850 | AE_UNUSED(success); | ||
| 851 | } | ||
| 852 | |||
| 853 | // Attempts to dequeue an element; if the queue is empty, | ||
| 854 | // waits until an element is available up to the specified timeout, | ||
| 855 | // then dequeues it and returns true, or returns false if the timeout | ||
| 856 | // expires before an element can be dequeued. | ||
| 857 | // Using a negative timeout indicates an indefinite timeout, | ||
| 858 | // and is thus functionally equivalent to calling wait_dequeue. | ||
| 859 | template <typename U> | ||
| 860 | bool wait_dequeue_timed(U& result, std::int64_t timeout_usecs) AE_NO_TSAN { | ||
| 861 | if (!sema->wait(timeout_usecs)) { | ||
| 862 | return false; | ||
| 863 | } | ||
| 864 | bool success = inner.try_dequeue(result); | ||
| 865 | AE_UNUSED(result); | ||
| 866 | assert(success); | ||
| 867 | AE_UNUSED(success); | ||
| 868 | return true; | ||
| 869 | } | ||
| 870 | |||
| 871 | #if __cplusplus > 199711L || _MSC_VER >= 1700 | ||
| 872 | // Attempts to dequeue an element; if the queue is empty, | ||
| 873 | // waits until an element is available up to the specified timeout, | ||
| 874 | // then dequeues it and returns true, or returns false if the timeout | ||
| 875 | // expires before an element can be dequeued. | ||
| 876 | // Using a negative timeout indicates an indefinite timeout, | ||
| 877 | // and is thus functionally equivalent to calling wait_dequeue. | ||
| 878 | template <typename U, typename Rep, typename Period> | ||
| 879 | inline bool wait_dequeue_timed(U& result, | ||
| 880 | std::chrono::duration<Rep, Period> const& timeout) AE_NO_TSAN { | ||
| 881 | return wait_dequeue_timed( | ||
| 882 | result, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count()); | ||
| 883 | } | ||
| 884 | #endif | ||
| 885 | |||
| 886 | // Returns a pointer to the front element in the queue (the one that | ||
| 887 | // would be removed next by a call to `try_dequeue` or `pop`). If the | ||
| 888 | // queue appears empty at the time the method is called, nullptr is | ||
| 889 | // returned instead. | ||
| 890 | // Must be called only from the consumer thread. | ||
| 891 | AE_FORCEINLINE T* peek() const AE_NO_TSAN { | ||
| 892 | return inner.peek(); | ||
| 893 | } | ||
| 894 | |||
| 895 | // Removes the front element from the queue, if any, without returning it. | ||
| 896 | // Returns true on success, or false if the queue appeared empty at the time | ||
| 897 | // `pop` was called. | ||
| 898 | AE_FORCEINLINE bool pop() AE_NO_TSAN { | ||
| 899 | if (sema->tryWait()) { | ||
| 900 | bool result = inner.pop(); | ||
| 901 | assert(result); | ||
| 902 | AE_UNUSED(result); | ||
| 903 | return true; | ||
| 904 | } | ||
| 905 | return false; | ||
| 906 | } | ||
| 907 | |||
| 908 | // Returns the approximate number of items currently in the queue. | ||
| 909 | // Safe to call from both the producer and consumer threads. | ||
| 910 | AE_FORCEINLINE size_t size_approx() const AE_NO_TSAN { | ||
| 911 | return sema->availableApprox(); | ||
| 912 | } | ||
| 913 | |||
| 914 | // Returns the total number of items that could be enqueued without incurring | ||
| 915 | // an allocation when this queue is empty. | ||
| 916 | // Safe to call from both the producer and consumer threads. | ||
| 917 | // | ||
| 918 | // NOTE: The actual capacity during usage may be different depending on the consumer. | ||
| 919 | // If the consumer is removing elements concurrently, the producer cannot add to | ||
| 920 | // the block the consumer is removing from until it's completely empty, except in | ||
| 921 | // the case where the producer was writing to the same block the consumer was | ||
| 922 | // reading from the whole time. | ||
| 923 | AE_FORCEINLINE size_t max_capacity() const { | ||
| 924 | return inner.max_capacity(); | ||
| 925 | } | ||
| 926 | |||
| 927 | private: | ||
| 928 | // Disable copying & assignment | ||
| 929 | BlockingReaderWriterQueue(BlockingReaderWriterQueue const&) {} | ||
| 930 | BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue const&) {} | ||
| 931 | |||
| 932 | private: | ||
| 933 | ReaderWriterQueue inner; | ||
| 934 | std::unique_ptr<spsc_sema::LightweightSemaphore> sema; | ||
| 935 | }; | ||
| 936 | |||
| 937 | } // namespace Common | ||
| 938 | |||
| 939 | #ifdef AE_VCPP | ||
| 940 | #pragma warning(pop) | ||
| 941 | #endif | ||
diff --git a/src/common/settings.cpp b/src/common/settings.cpp index d4c52989a..1c7b6dfae 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp | |||
| @@ -62,7 +62,8 @@ void LogSettings() { | |||
| 62 | log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue()); | 62 | log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue()); |
| 63 | log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue()); | 63 | log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue()); |
| 64 | log_setting("Audio_OutputEngine", values.sink_id.GetValue()); | 64 | log_setting("Audio_OutputEngine", values.sink_id.GetValue()); |
| 65 | log_setting("Audio_OutputDevice", values.audio_device_id.GetValue()); | 65 | log_setting("Audio_OutputDevice", values.audio_output_device_id.GetValue()); |
| 66 | log_setting("Audio_InputDevice", values.audio_input_device_id.GetValue()); | ||
| 66 | log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd.GetValue()); | 67 | log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd.GetValue()); |
| 67 | log_path("DataStorage_CacheDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir)); | 68 | log_path("DataStorage_CacheDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir)); |
| 68 | log_path("DataStorage_ConfigDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir)); | 69 | log_path("DataStorage_ConfigDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir)); |
diff --git a/src/common/settings.h b/src/common/settings.h index 2bccb8642..06d72c8bf 100644 --- a/src/common/settings.h +++ b/src/common/settings.h | |||
| @@ -370,10 +370,12 @@ struct TouchFromButtonMap { | |||
| 370 | 370 | ||
| 371 | struct Values { | 371 | struct Values { |
| 372 | // Audio | 372 | // Audio |
| 373 | Setting<std::string> audio_device_id{"auto", "output_device"}; | ||
| 374 | Setting<std::string> sink_id{"auto", "output_engine"}; | 373 | Setting<std::string> sink_id{"auto", "output_engine"}; |
| 374 | Setting<std::string> audio_output_device_id{"auto", "output_device"}; | ||
| 375 | Setting<std::string> audio_input_device_id{"auto", "input_device"}; | ||
| 375 | Setting<bool> audio_muted{false, "audio_muted"}; | 376 | Setting<bool> audio_muted{false, "audio_muted"}; |
| 376 | SwitchableSetting<u8, true> volume{100, 0, 100, "volume"}; | 377 | SwitchableSetting<u8, true> volume{100, 0, 100, "volume"}; |
| 378 | Setting<bool> dump_audio_commands{false, "dump_audio_commands"}; | ||
| 377 | 379 | ||
| 378 | // Core | 380 | // Core |
| 379 | SwitchableSetting<bool> use_multi_core{true, "use_multi_core"}; | 381 | SwitchableSetting<bool> use_multi_core{true, "use_multi_core"}; |
diff --git a/src/core/core.cpp b/src/core/core.cpp index 7723d9782..0ede0d85c 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -8,6 +8,7 @@ | |||
| 8 | #include <memory> | 8 | #include <memory> |
| 9 | #include <utility> | 9 | #include <utility> |
| 10 | 10 | ||
| 11 | #include "audio_core/audio_core.h" | ||
| 11 | #include "common/fs/fs.h" | 12 | #include "common/fs/fs.h" |
| 12 | #include "common/logging/log.h" | 13 | #include "common/logging/log.h" |
| 13 | #include "common/microprofile.h" | 14 | #include "common/microprofile.h" |
| @@ -140,6 +141,8 @@ struct System::Impl { | |||
| 140 | core_timing.SyncPause(false); | 141 | core_timing.SyncPause(false); |
| 141 | is_paused = false; | 142 | is_paused = false; |
| 142 | 143 | ||
| 144 | audio_core->PauseSinks(false); | ||
| 145 | |||
| 143 | return status; | 146 | return status; |
| 144 | } | 147 | } |
| 145 | 148 | ||
| @@ -147,6 +150,8 @@ struct System::Impl { | |||
| 147 | std::unique_lock<std::mutex> lk(suspend_guard); | 150 | std::unique_lock<std::mutex> lk(suspend_guard); |
| 148 | status = SystemResultStatus::Success; | 151 | status = SystemResultStatus::Success; |
| 149 | 152 | ||
| 153 | audio_core->PauseSinks(true); | ||
| 154 | |||
| 150 | core_timing.SyncPause(true); | 155 | core_timing.SyncPause(true); |
| 151 | kernel.Suspend(true); | 156 | kernel.Suspend(true); |
| 152 | is_paused = true; | 157 | is_paused = true; |
| @@ -154,6 +159,11 @@ struct System::Impl { | |||
| 154 | return status; | 159 | return status; |
| 155 | } | 160 | } |
| 156 | 161 | ||
| 162 | bool IsPaused() const { | ||
| 163 | std::unique_lock lk(suspend_guard); | ||
| 164 | return is_paused; | ||
| 165 | } | ||
| 166 | |||
| 157 | std::unique_lock<std::mutex> StallProcesses() { | 167 | std::unique_lock<std::mutex> StallProcesses() { |
| 158 | std::unique_lock<std::mutex> lk(suspend_guard); | 168 | std::unique_lock<std::mutex> lk(suspend_guard); |
| 159 | kernel.Suspend(true); | 169 | kernel.Suspend(true); |
| @@ -214,6 +224,8 @@ struct System::Impl { | |||
| 214 | return SystemResultStatus::ErrorVideoCore; | 224 | return SystemResultStatus::ErrorVideoCore; |
| 215 | } | 225 | } |
| 216 | 226 | ||
| 227 | audio_core = std::make_unique<AudioCore::AudioCore>(system); | ||
| 228 | |||
| 217 | service_manager = std::make_shared<Service::SM::ServiceManager>(kernel); | 229 | service_manager = std::make_shared<Service::SM::ServiceManager>(kernel); |
| 218 | services = std::make_unique<Service::Services>(service_manager, system); | 230 | services = std::make_unique<Service::Services>(service_manager, system); |
| 219 | interrupt_manager = std::make_unique<Hardware::InterruptManager>(system); | 231 | interrupt_manager = std::make_unique<Hardware::InterruptManager>(system); |
| @@ -290,7 +302,7 @@ struct System::Impl { | |||
| 290 | if (Settings::values.gamecard_current_game) { | 302 | if (Settings::values.gamecard_current_game) { |
| 291 | fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, filepath)); | 303 | fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, filepath)); |
| 292 | } else if (!Settings::values.gamecard_path.GetValue().empty()) { | 304 | } else if (!Settings::values.gamecard_path.GetValue().empty()) { |
| 293 | const auto gamecard_path = Settings::values.gamecard_path.GetValue(); | 305 | const auto& gamecard_path = Settings::values.gamecard_path.GetValue(); |
| 294 | fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, gamecard_path)); | 306 | fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, gamecard_path)); |
| 295 | } | 307 | } |
| 296 | } | 308 | } |
| @@ -308,6 +320,8 @@ struct System::Impl { | |||
| 308 | } | 320 | } |
| 309 | 321 | ||
| 310 | void Shutdown() { | 322 | void Shutdown() { |
| 323 | SetShuttingDown(true); | ||
| 324 | |||
| 311 | // Log last frame performance stats if game was loded | 325 | // Log last frame performance stats if game was loded |
| 312 | if (perf_stats) { | 326 | if (perf_stats) { |
| 313 | const auto perf_results = GetAndResetPerfStats(); | 327 | const auto perf_results = GetAndResetPerfStats(); |
| @@ -333,14 +347,15 @@ struct System::Impl { | |||
| 333 | kernel.ShutdownCores(); | 347 | kernel.ShutdownCores(); |
| 334 | cpu_manager.Shutdown(); | 348 | cpu_manager.Shutdown(); |
| 335 | debugger.reset(); | 349 | debugger.reset(); |
| 350 | kernel.CloseServices(); | ||
| 336 | services.reset(); | 351 | services.reset(); |
| 337 | service_manager.reset(); | 352 | service_manager.reset(); |
| 338 | cheat_engine.reset(); | 353 | cheat_engine.reset(); |
| 339 | telemetry_session.reset(); | 354 | telemetry_session.reset(); |
| 340 | cpu_manager.Shutdown(); | ||
| 341 | time_manager.Shutdown(); | 355 | time_manager.Shutdown(); |
| 342 | core_timing.Shutdown(); | 356 | core_timing.Shutdown(); |
| 343 | app_loader.reset(); | 357 | app_loader.reset(); |
| 358 | audio_core.reset(); | ||
| 344 | gpu_core.reset(); | 359 | gpu_core.reset(); |
| 345 | perf_stats.reset(); | 360 | perf_stats.reset(); |
| 346 | kernel.Shutdown(); | 361 | kernel.Shutdown(); |
| @@ -350,6 +365,14 @@ struct System::Impl { | |||
| 350 | LOG_DEBUG(Core, "Shutdown OK"); | 365 | LOG_DEBUG(Core, "Shutdown OK"); |
| 351 | } | 366 | } |
| 352 | 367 | ||
| 368 | bool IsShuttingDown() const { | ||
| 369 | return is_shutting_down; | ||
| 370 | } | ||
| 371 | |||
| 372 | void SetShuttingDown(bool shutting_down) { | ||
| 373 | is_shutting_down = shutting_down; | ||
| 374 | } | ||
| 375 | |||
| 353 | Loader::ResultStatus GetGameName(std::string& out) const { | 376 | Loader::ResultStatus GetGameName(std::string& out) const { |
| 354 | if (app_loader == nullptr) | 377 | if (app_loader == nullptr) |
| 355 | return Loader::ResultStatus::ErrorNotInitialized; | 378 | return Loader::ResultStatus::ErrorNotInitialized; |
| @@ -392,8 +415,9 @@ struct System::Impl { | |||
| 392 | return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs()); | 415 | return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs()); |
| 393 | } | 416 | } |
| 394 | 417 | ||
| 395 | std::mutex suspend_guard; | 418 | mutable std::mutex suspend_guard; |
| 396 | bool is_paused{}; | 419 | bool is_paused{}; |
| 420 | std::atomic<bool> is_shutting_down{}; | ||
| 397 | 421 | ||
| 398 | Timing::CoreTiming core_timing; | 422 | Timing::CoreTiming core_timing; |
| 399 | Kernel::KernelCore kernel; | 423 | Kernel::KernelCore kernel; |
| @@ -407,6 +431,7 @@ struct System::Impl { | |||
| 407 | std::unique_ptr<Tegra::GPU> gpu_core; | 431 | std::unique_ptr<Tegra::GPU> gpu_core; |
| 408 | std::unique_ptr<Hardware::InterruptManager> interrupt_manager; | 432 | std::unique_ptr<Hardware::InterruptManager> interrupt_manager; |
| 409 | std::unique_ptr<Core::DeviceMemory> device_memory; | 433 | std::unique_ptr<Core::DeviceMemory> device_memory; |
| 434 | std::unique_ptr<AudioCore::AudioCore> audio_core; | ||
| 410 | Core::Memory::Memory memory; | 435 | Core::Memory::Memory memory; |
| 411 | Core::HID::HIDCore hid_core; | 436 | Core::HID::HIDCore hid_core; |
| 412 | CpuManager cpu_manager; | 437 | CpuManager cpu_manager; |
| @@ -479,6 +504,10 @@ SystemResultStatus System::Pause() { | |||
| 479 | return impl->Pause(); | 504 | return impl->Pause(); |
| 480 | } | 505 | } |
| 481 | 506 | ||
| 507 | bool System::IsPaused() const { | ||
| 508 | return impl->IsPaused(); | ||
| 509 | } | ||
| 510 | |||
| 482 | void System::InvalidateCpuInstructionCaches() { | 511 | void System::InvalidateCpuInstructionCaches() { |
| 483 | impl->kernel.InvalidateAllInstructionCaches(); | 512 | impl->kernel.InvalidateAllInstructionCaches(); |
| 484 | } | 513 | } |
| @@ -491,6 +520,14 @@ void System::Shutdown() { | |||
| 491 | impl->Shutdown(); | 520 | impl->Shutdown(); |
| 492 | } | 521 | } |
| 493 | 522 | ||
| 523 | bool System::IsShuttingDown() const { | ||
| 524 | return impl->IsShuttingDown(); | ||
| 525 | } | ||
| 526 | |||
| 527 | void System::SetShuttingDown(bool shutting_down) { | ||
| 528 | impl->SetShuttingDown(shutting_down); | ||
| 529 | } | ||
| 530 | |||
| 494 | void System::DetachDebugger() { | 531 | void System::DetachDebugger() { |
| 495 | if (impl->debugger) { | 532 | if (impl->debugger) { |
| 496 | impl->debugger->NotifyShutdown(); | 533 | impl->debugger->NotifyShutdown(); |
| @@ -640,6 +677,14 @@ const HID::HIDCore& System::HIDCore() const { | |||
| 640 | return impl->hid_core; | 677 | return impl->hid_core; |
| 641 | } | 678 | } |
| 642 | 679 | ||
| 680 | AudioCore::AudioCore& System::AudioCore() { | ||
| 681 | return *impl->audio_core; | ||
| 682 | } | ||
| 683 | |||
| 684 | const AudioCore::AudioCore& System::AudioCore() const { | ||
| 685 | return *impl->audio_core; | ||
| 686 | } | ||
| 687 | |||
| 643 | Timing::CoreTiming& System::CoreTiming() { | 688 | Timing::CoreTiming& System::CoreTiming() { |
| 644 | return impl->core_timing; | 689 | return impl->core_timing; |
| 645 | } | 690 | } |
diff --git a/src/core/core.h b/src/core/core.h index 60efe4410..a49d1214b 100644 --- a/src/core/core.h +++ b/src/core/core.h | |||
| @@ -81,6 +81,10 @@ namespace VideoCore { | |||
| 81 | class RendererBase; | 81 | class RendererBase; |
| 82 | } // namespace VideoCore | 82 | } // namespace VideoCore |
| 83 | 83 | ||
| 84 | namespace AudioCore { | ||
| 85 | class AudioCore; | ||
| 86 | } // namespace AudioCore | ||
| 87 | |||
| 84 | namespace Core::Timing { | 88 | namespace Core::Timing { |
| 85 | class CoreTiming; | 89 | class CoreTiming; |
| 86 | } | 90 | } |
| @@ -148,6 +152,9 @@ public: | |||
| 148 | */ | 152 | */ |
| 149 | [[nodiscard]] SystemResultStatus Pause(); | 153 | [[nodiscard]] SystemResultStatus Pause(); |
| 150 | 154 | ||
| 155 | /// Check if the core is currently paused. | ||
| 156 | [[nodiscard]] bool IsPaused() const; | ||
| 157 | |||
| 151 | /** | 158 | /** |
| 152 | * Invalidate the CPU instruction caches | 159 | * Invalidate the CPU instruction caches |
| 153 | * This function should only be used by GDB Stub to support breakpoints, memory updates and | 160 | * This function should only be used by GDB Stub to support breakpoints, memory updates and |
| @@ -160,6 +167,12 @@ public: | |||
| 160 | /// Shutdown the emulated system. | 167 | /// Shutdown the emulated system. |
| 161 | void Shutdown(); | 168 | void Shutdown(); |
| 162 | 169 | ||
| 170 | /// Check if the core is shutting down. | ||
| 171 | [[nodiscard]] bool IsShuttingDown() const; | ||
| 172 | |||
| 173 | /// Set the shutting down state. | ||
| 174 | void SetShuttingDown(bool shutting_down); | ||
| 175 | |||
| 163 | /// Forcibly detach the debugger if it is running. | 176 | /// Forcibly detach the debugger if it is running. |
| 164 | void DetachDebugger(); | 177 | void DetachDebugger(); |
| 165 | 178 | ||
| @@ -250,6 +263,12 @@ public: | |||
| 250 | /// Gets an immutable reference to the renderer. | 263 | /// Gets an immutable reference to the renderer. |
| 251 | [[nodiscard]] const VideoCore::RendererBase& Renderer() const; | 264 | [[nodiscard]] const VideoCore::RendererBase& Renderer() const; |
| 252 | 265 | ||
| 266 | /// Gets a mutable reference to the audio interface | ||
| 267 | [[nodiscard]] AudioCore::AudioCore& AudioCore(); | ||
| 268 | |||
| 269 | /// Gets an immutable reference to the audio interface. | ||
| 270 | [[nodiscard]] const AudioCore::AudioCore& AudioCore() const; | ||
| 271 | |||
| 253 | /// Gets the global scheduler | 272 | /// Gets the global scheduler |
| 254 | [[nodiscard]] Kernel::GlobalSchedulerContext& GlobalSchedulerContext(); | 273 | [[nodiscard]] Kernel::GlobalSchedulerContext& GlobalSchedulerContext(); |
| 255 | 274 | ||
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 45135a07f..5b3feec66 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp | |||
| @@ -287,18 +287,52 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size, | |||
| 287 | BufferDescriptorB().size() > buffer_index && | 287 | BufferDescriptorB().size() > buffer_index && |
| 288 | BufferDescriptorB()[buffer_index].Size() >= size, | 288 | BufferDescriptorB()[buffer_index].Size() >= size, |
| 289 | { return 0; }, "BufferDescriptorB is invalid, index={}, size={}", buffer_index, size); | 289 | { return 0; }, "BufferDescriptorB is invalid, index={}, size={}", buffer_index, size); |
| 290 | memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size); | 290 | WriteBufferB(buffer, size, buffer_index); |
| 291 | } else { | 291 | } else { |
| 292 | ASSERT_OR_EXECUTE_MSG( | 292 | ASSERT_OR_EXECUTE_MSG( |
| 293 | BufferDescriptorC().size() > buffer_index && | 293 | BufferDescriptorC().size() > buffer_index && |
| 294 | BufferDescriptorC()[buffer_index].Size() >= size, | 294 | BufferDescriptorC()[buffer_index].Size() >= size, |
| 295 | { return 0; }, "BufferDescriptorC is invalid, index={}, size={}", buffer_index, size); | 295 | { return 0; }, "BufferDescriptorC is invalid, index={}, size={}", buffer_index, size); |
| 296 | memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size); | 296 | WriteBufferC(buffer, size, buffer_index); |
| 297 | } | 297 | } |
| 298 | 298 | ||
| 299 | return size; | 299 | return size; |
| 300 | } | 300 | } |
| 301 | 301 | ||
| 302 | std::size_t HLERequestContext::WriteBufferB(const void* buffer, std::size_t size, | ||
| 303 | std::size_t buffer_index) const { | ||
| 304 | if (buffer_index >= BufferDescriptorB().size() || size == 0) { | ||
| 305 | return 0; | ||
| 306 | } | ||
| 307 | |||
| 308 | const auto buffer_size{BufferDescriptorB()[buffer_index].Size()}; | ||
| 309 | if (size > buffer_size) { | ||
| 310 | LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size, | ||
| 311 | buffer_size); | ||
| 312 | size = buffer_size; // TODO(bunnei): This needs to be HW tested | ||
| 313 | } | ||
| 314 | |||
| 315 | memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size); | ||
| 316 | return size; | ||
| 317 | } | ||
| 318 | |||
| 319 | std::size_t HLERequestContext::WriteBufferC(const void* buffer, std::size_t size, | ||
| 320 | std::size_t buffer_index) const { | ||
| 321 | if (buffer_index >= BufferDescriptorC().size() || size == 0) { | ||
| 322 | return 0; | ||
| 323 | } | ||
| 324 | |||
| 325 | const auto buffer_size{BufferDescriptorC()[buffer_index].Size()}; | ||
| 326 | if (size > buffer_size) { | ||
| 327 | LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size, | ||
| 328 | buffer_size); | ||
| 329 | size = buffer_size; // TODO(bunnei): This needs to be HW tested | ||
| 330 | } | ||
| 331 | |||
| 332 | memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size); | ||
| 333 | return size; | ||
| 334 | } | ||
| 335 | |||
| 302 | std::size_t HLERequestContext::GetReadBufferSize(std::size_t buffer_index) const { | 336 | std::size_t HLERequestContext::GetReadBufferSize(std::size_t buffer_index) const { |
| 303 | const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && | 337 | const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && |
| 304 | BufferDescriptorA()[buffer_index].Size()}; | 338 | BufferDescriptorA()[buffer_index].Size()}; |
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index d3abeee85..99265ce90 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h | |||
| @@ -277,6 +277,14 @@ public: | |||
| 277 | std::size_t WriteBuffer(const void* buffer, std::size_t size, | 277 | std::size_t WriteBuffer(const void* buffer, std::size_t size, |
| 278 | std::size_t buffer_index = 0) const; | 278 | std::size_t buffer_index = 0) const; |
| 279 | 279 | ||
| 280 | /// Helper function to write buffer B | ||
| 281 | std::size_t WriteBufferB(const void* buffer, std::size_t size, | ||
| 282 | std::size_t buffer_index = 0) const; | ||
| 283 | |||
| 284 | /// Helper function to write buffer C | ||
| 285 | std::size_t WriteBufferC(const void* buffer, std::size_t size, | ||
| 286 | std::size_t buffer_index = 0) const; | ||
| 287 | |||
| 280 | /* Helper function to write a buffer using the appropriate buffer descriptor | 288 | /* Helper function to write a buffer using the appropriate buffer descriptor |
| 281 | * | 289 | * |
| 282 | * @tparam T an arbitrary container that satisfies the | 290 | * @tparam T an arbitrary container that satisfies the |
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 7307cf262..f23c629dc 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp | |||
| @@ -95,19 +95,7 @@ struct KernelCore::Impl { | |||
| 95 | 95 | ||
| 96 | process_list.clear(); | 96 | process_list.clear(); |
| 97 | 97 | ||
| 98 | // Close all open server sessions and ports. | 98 | CloseServices(); |
| 99 | std::unordered_set<KAutoObject*> server_objects_; | ||
| 100 | { | ||
| 101 | std::scoped_lock lk(server_objects_lock); | ||
| 102 | server_objects_ = server_objects; | ||
| 103 | server_objects.clear(); | ||
| 104 | } | ||
| 105 | for (auto* server_object : server_objects_) { | ||
| 106 | server_object->Close(); | ||
| 107 | } | ||
| 108 | |||
| 109 | // Ensures all service threads gracefully shutdown. | ||
| 110 | ClearServiceThreads(); | ||
| 111 | 99 | ||
| 112 | next_object_id = 0; | 100 | next_object_id = 0; |
| 113 | next_kernel_process_id = KProcess::InitialKIPIDMin; | 101 | next_kernel_process_id = KProcess::InitialKIPIDMin; |
| @@ -191,6 +179,22 @@ struct KernelCore::Impl { | |||
| 191 | global_object_list_container.reset(); | 179 | global_object_list_container.reset(); |
| 192 | } | 180 | } |
| 193 | 181 | ||
| 182 | void CloseServices() { | ||
| 183 | // Close all open server sessions and ports. | ||
| 184 | std::unordered_set<KAutoObject*> server_objects_; | ||
| 185 | { | ||
| 186 | std::scoped_lock lk(server_objects_lock); | ||
| 187 | server_objects_ = server_objects; | ||
| 188 | server_objects.clear(); | ||
| 189 | } | ||
| 190 | for (auto* server_object : server_objects_) { | ||
| 191 | server_object->Close(); | ||
| 192 | } | ||
| 193 | |||
| 194 | // Ensures all service threads gracefully shutdown. | ||
| 195 | ClearServiceThreads(); | ||
| 196 | } | ||
| 197 | |||
| 194 | void InitializePhysicalCores() { | 198 | void InitializePhysicalCores() { |
| 195 | exclusive_monitor = | 199 | exclusive_monitor = |
| 196 | Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES); | 200 | Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES); |
| @@ -813,6 +817,10 @@ void KernelCore::Shutdown() { | |||
| 813 | impl->Shutdown(); | 817 | impl->Shutdown(); |
| 814 | } | 818 | } |
| 815 | 819 | ||
| 820 | void KernelCore::CloseServices() { | ||
| 821 | impl->CloseServices(); | ||
| 822 | } | ||
| 823 | |||
| 816 | const KResourceLimit* KernelCore::GetSystemResourceLimit() const { | 824 | const KResourceLimit* KernelCore::GetSystemResourceLimit() const { |
| 817 | return impl->system_resource_limit; | 825 | return impl->system_resource_limit; |
| 818 | } | 826 | } |
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index aa0ebaa02..6c7cf6af2 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h | |||
| @@ -109,6 +109,9 @@ public: | |||
| 109 | /// Clears all resources in use by the kernel instance. | 109 | /// Clears all resources in use by the kernel instance. |
| 110 | void Shutdown(); | 110 | void Shutdown(); |
| 111 | 111 | ||
| 112 | /// Close all active services in use by the kernel instance. | ||
| 113 | void CloseServices(); | ||
| 114 | |||
| 112 | /// Retrieves a shared pointer to the system resource limit instance. | 115 | /// Retrieves a shared pointer to the system resource limit instance. |
| 113 | const KResourceLimit* GetSystemResourceLimit() const; | 116 | const KResourceLimit* GetSystemResourceLimit() const; |
| 114 | 117 | ||
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 9116dd77c..118f226e4 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp | |||
| @@ -5,7 +5,6 @@ | |||
| 5 | #include <array> | 5 | #include <array> |
| 6 | #include <cinttypes> | 6 | #include <cinttypes> |
| 7 | #include <cstring> | 7 | #include <cstring> |
| 8 | #include "audio_core/audio_renderer.h" | ||
| 9 | #include "common/settings.h" | 8 | #include "common/settings.h" |
| 10 | #include "core/core.h" | 9 | #include "core/core.h" |
| 11 | #include "core/file_sys/control_metadata.h" | 10 | #include "core/file_sys/control_metadata.h" |
| @@ -286,7 +285,7 @@ ISelfController::ISelfController(Core::System& system_, NVFlinger::NVFlinger& nv | |||
| 286 | {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"}, | 285 | {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"}, |
| 287 | {63, &ISelfController::GetIdleTimeDetectionExtension, "GetIdleTimeDetectionExtension"}, | 286 | {63, &ISelfController::GetIdleTimeDetectionExtension, "GetIdleTimeDetectionExtension"}, |
| 288 | {64, nullptr, "SetInputDetectionSourceSet"}, | 287 | {64, nullptr, "SetInputDetectionSourceSet"}, |
| 289 | {65, nullptr, "ReportUserIsActive"}, | 288 | {65, &ISelfController::ReportUserIsActive, "ReportUserIsActive"}, |
| 290 | {66, nullptr, "GetCurrentIlluminance"}, | 289 | {66, nullptr, "GetCurrentIlluminance"}, |
| 291 | {67, nullptr, "IsIlluminanceAvailable"}, | 290 | {67, nullptr, "IsIlluminanceAvailable"}, |
| 292 | {68, &ISelfController::SetAutoSleepDisabled, "SetAutoSleepDisabled"}, | 291 | {68, &ISelfController::SetAutoSleepDisabled, "SetAutoSleepDisabled"}, |
| @@ -518,6 +517,13 @@ void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& c | |||
| 518 | rb.Push<u32>(idle_time_detection_extension); | 517 | rb.Push<u32>(idle_time_detection_extension); |
| 519 | } | 518 | } |
| 520 | 519 | ||
| 520 | void ISelfController::ReportUserIsActive(Kernel::HLERequestContext& ctx) { | ||
| 521 | LOG_WARNING(Service_AM, "(STUBBED) called"); | ||
| 522 | |||
| 523 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 524 | rb.Push(ResultSuccess); | ||
| 525 | } | ||
| 526 | |||
| 521 | void ISelfController::SetAutoSleepDisabled(Kernel::HLERequestContext& ctx) { | 527 | void ISelfController::SetAutoSleepDisabled(Kernel::HLERequestContext& ctx) { |
| 522 | IPC::RequestParser rp{ctx}; | 528 | IPC::RequestParser rp{ctx}; |
| 523 | is_auto_sleep_disabled = rp.Pop<bool>(); | 529 | is_auto_sleep_disabled = rp.Pop<bool>(); |
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 53144427b..bb75c6281 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h | |||
| @@ -175,6 +175,7 @@ private: | |||
| 175 | void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx); | 175 | void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx); |
| 176 | void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); | 176 | void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); |
| 177 | void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); | 177 | void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); |
| 178 | void ReportUserIsActive(Kernel::HLERequestContext& ctx); | ||
| 178 | void SetAutoSleepDisabled(Kernel::HLERequestContext& ctx); | 179 | void SetAutoSleepDisabled(Kernel::HLERequestContext& ctx); |
| 179 | void IsAutoSleepDisabled(Kernel::HLERequestContext& ctx); | 180 | void IsAutoSleepDisabled(Kernel::HLERequestContext& ctx); |
| 180 | void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx); | 181 | void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx); |
diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp index 18d3ae682..48a9a73a0 100644 --- a/src/core/hle/service/audio/audin_u.cpp +++ b/src/core/hle/service/audio/audin_u.cpp | |||
| @@ -1,68 +1,211 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/in/audio_in_system.h" | ||
| 5 | #include "audio_core/renderer/audio_device.h" | ||
| 6 | #include "common/common_funcs.h" | ||
| 4 | #include "common/logging/log.h" | 7 | #include "common/logging/log.h" |
| 8 | #include "common/string_util.h" | ||
| 5 | #include "core/core.h" | 9 | #include "core/core.h" |
| 6 | #include "core/hle/ipc_helpers.h" | 10 | #include "core/hle/ipc_helpers.h" |
| 7 | #include "core/hle/kernel/k_event.h" | 11 | #include "core/hle/kernel/k_event.h" |
| 8 | #include "core/hle/service/audio/audin_u.h" | 12 | #include "core/hle/service/audio/audin_u.h" |
| 9 | 13 | ||
| 10 | namespace Service::Audio { | 14 | namespace Service::Audio { |
| 15 | using namespace AudioCore::AudioIn; | ||
| 11 | 16 | ||
| 12 | IAudioIn::IAudioIn(Core::System& system_) | 17 | class IAudioIn final : public ServiceFramework<IAudioIn> { |
| 13 | : ServiceFramework{system_, "IAudioIn"}, service_context{system_, "IAudioIn"} { | 18 | public: |
| 14 | // clang-format off | 19 | explicit IAudioIn(Core::System& system_, Manager& manager, size_t session_id, |
| 15 | static const FunctionInfo functions[] = { | 20 | std::string& device_name, const AudioInParameter& in_params, u32 handle, |
| 16 | {0, nullptr, "GetAudioInState"}, | 21 | u64 applet_resource_user_id) |
| 17 | {1, &IAudioIn::Start, "Start"}, | 22 | : ServiceFramework{system_, "IAudioIn"}, |
| 18 | {2, nullptr, "Stop"}, | 23 | service_context{system_, "IAudioIn"}, event{service_context.CreateEvent("AudioInEvent")}, |
| 19 | {3, nullptr, "AppendAudioInBuffer"}, | 24 | impl{std::make_shared<In>(system_, manager, event, session_id)} { |
| 20 | {4, &IAudioIn::RegisterBufferEvent, "RegisterBufferEvent"}, | 25 | // clang-format off |
| 21 | {5, nullptr, "GetReleasedAudioInBuffer"}, | 26 | static const FunctionInfo functions[] = { |
| 22 | {6, nullptr, "ContainsAudioInBuffer"}, | 27 | {0, &IAudioIn::GetAudioInState, "GetAudioInState"}, |
| 23 | {7, nullptr, "AppendUacInBuffer"}, | 28 | {1, &IAudioIn::Start, "Start"}, |
| 24 | {8, &IAudioIn::AppendAudioInBufferAuto, "AppendAudioInBufferAuto"}, | 29 | {2, &IAudioIn::Stop, "Stop"}, |
| 25 | {9, nullptr, "GetReleasedAudioInBuffersAuto"}, | 30 | {3, &IAudioIn::AppendAudioInBuffer, "AppendAudioInBuffer"}, |
| 26 | {10, nullptr, "AppendUacInBufferAuto"}, | 31 | {4, &IAudioIn::RegisterBufferEvent, "RegisterBufferEvent"}, |
| 27 | {11, nullptr, "GetAudioInBufferCount"}, | 32 | {5, &IAudioIn::GetReleasedAudioInBuffer, "GetReleasedAudioInBuffer"}, |
| 28 | {12, nullptr, "SetDeviceGain"}, | 33 | {6, &IAudioIn::ContainsAudioInBuffer, "ContainsAudioInBuffer"}, |
| 29 | {13, nullptr, "GetDeviceGain"}, | 34 | {7, &IAudioIn::AppendAudioInBuffer, "AppendUacInBuffer"}, |
| 30 | {14, nullptr, "FlushAudioInBuffers"}, | 35 | {8, &IAudioIn::AppendAudioInBuffer, "AppendAudioInBufferAuto"}, |
| 31 | }; | 36 | {9, &IAudioIn::GetReleasedAudioInBuffer, "GetReleasedAudioInBuffersAuto"}, |
| 32 | // clang-format on | 37 | {10, &IAudioIn::AppendAudioInBuffer, "AppendUacInBufferAuto"}, |
| 38 | {11, &IAudioIn::GetAudioInBufferCount, "GetAudioInBufferCount"}, | ||
| 39 | {12, &IAudioIn::SetDeviceGain, "SetDeviceGain"}, | ||
| 40 | {13, &IAudioIn::GetDeviceGain, "GetDeviceGain"}, | ||
| 41 | {14, &IAudioIn::FlushAudioInBuffers, "FlushAudioInBuffers"}, | ||
| 42 | }; | ||
| 43 | // clang-format on | ||
| 33 | 44 | ||
| 34 | RegisterHandlers(functions); | 45 | RegisterHandlers(functions); |
| 35 | 46 | ||
| 36 | buffer_event = service_context.CreateEvent("IAudioIn:BufferEvent"); | 47 | if (impl->GetSystem() |
| 37 | } | 48 | .Initialize(device_name, in_params, handle, applet_resource_user_id) |
| 49 | .IsError()) { | ||
| 50 | LOG_ERROR(Service_Audio, "Failed to initialize the AudioIn System!"); | ||
| 51 | } | ||
| 52 | } | ||
| 38 | 53 | ||
| 39 | IAudioIn::~IAudioIn() { | 54 | ~IAudioIn() override { |
| 40 | service_context.CloseEvent(buffer_event); | 55 | impl->Free(); |
| 41 | } | 56 | service_context.CloseEvent(event); |
| 57 | } | ||
| 42 | 58 | ||
| 43 | void IAudioIn::Start(Kernel::HLERequestContext& ctx) { | 59 | [[nodiscard]] std::shared_ptr<In> GetImpl() { |
| 44 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | 60 | return impl; |
| 61 | } | ||
| 45 | 62 | ||
| 46 | IPC::ResponseBuilder rb{ctx, 2}; | 63 | private: |
| 47 | rb.Push(ResultSuccess); | 64 | void GetAudioInState(Kernel::HLERequestContext& ctx) { |
| 48 | } | 65 | const auto state = static_cast<u32>(impl->GetState()); |
| 49 | 66 | ||
| 50 | void IAudioIn::RegisterBufferEvent(Kernel::HLERequestContext& ctx) { | 67 | LOG_DEBUG(Service_Audio, "called. State={}", state); |
| 51 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | ||
| 52 | 68 | ||
| 53 | IPC::ResponseBuilder rb{ctx, 2, 1}; | 69 | IPC::ResponseBuilder rb{ctx, 3}; |
| 54 | rb.Push(ResultSuccess); | 70 | rb.Push(ResultSuccess); |
| 55 | rb.PushCopyObjects(buffer_event->GetReadableEvent()); | 71 | rb.Push(state); |
| 56 | } | 72 | } |
| 57 | 73 | ||
| 58 | void IAudioIn::AppendAudioInBufferAuto(Kernel::HLERequestContext& ctx) { | 74 | void Start(Kernel::HLERequestContext& ctx) { |
| 59 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | 75 | LOG_DEBUG(Service_Audio, "called"); |
| 60 | 76 | ||
| 61 | IPC::ResponseBuilder rb{ctx, 2}; | 77 | auto result = impl->StartSystem(); |
| 62 | rb.Push(ResultSuccess); | 78 | |
| 63 | } | 79 | IPC::ResponseBuilder rb{ctx, 2}; |
| 80 | rb.Push(result); | ||
| 81 | } | ||
| 82 | |||
| 83 | void Stop(Kernel::HLERequestContext& ctx) { | ||
| 84 | LOG_DEBUG(Service_Audio, "called"); | ||
| 85 | |||
| 86 | auto result = impl->StopSystem(); | ||
| 87 | |||
| 88 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 89 | rb.Push(result); | ||
| 90 | } | ||
| 91 | |||
| 92 | void AppendAudioInBuffer(Kernel::HLERequestContext& ctx) { | ||
| 93 | IPC::RequestParser rp{ctx}; | ||
| 94 | u64 tag = rp.PopRaw<u64>(); | ||
| 64 | 95 | ||
| 65 | AudInU::AudInU(Core::System& system_) : ServiceFramework{system_, "audin:u"} { | 96 | const auto in_buffer_size{ctx.GetReadBufferSize()}; |
| 97 | if (in_buffer_size < sizeof(AudioInBuffer)) { | ||
| 98 | LOG_ERROR(Service_Audio, "Input buffer is too small for an AudioInBuffer!"); | ||
| 99 | } | ||
| 100 | |||
| 101 | const auto& in_buffer = ctx.ReadBuffer(); | ||
| 102 | AudioInBuffer buffer{}; | ||
| 103 | std::memcpy(&buffer, in_buffer.data(), sizeof(AudioInBuffer)); | ||
| 104 | |||
| 105 | [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()}; | ||
| 106 | LOG_TRACE(Service_Audio, "called. Session {} Appending buffer {:08X}", sessionid, tag); | ||
| 107 | |||
| 108 | auto result = impl->AppendBuffer(buffer, tag); | ||
| 109 | |||
| 110 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 111 | rb.Push(result); | ||
| 112 | } | ||
| 113 | |||
| 114 | void RegisterBufferEvent(Kernel::HLERequestContext& ctx) { | ||
| 115 | LOG_DEBUG(Service_Audio, "called"); | ||
| 116 | |||
| 117 | auto& buffer_event = impl->GetBufferEvent(); | ||
| 118 | |||
| 119 | IPC::ResponseBuilder rb{ctx, 2, 1}; | ||
| 120 | rb.Push(ResultSuccess); | ||
| 121 | rb.PushCopyObjects(buffer_event); | ||
| 122 | } | ||
| 123 | |||
| 124 | void GetReleasedAudioInBuffer(Kernel::HLERequestContext& ctx) { | ||
| 125 | auto write_buffer_size = ctx.GetWriteBufferSize() / sizeof(u64); | ||
| 126 | std::vector<u64> released_buffers(write_buffer_size, 0); | ||
| 127 | |||
| 128 | auto count = impl->GetReleasedBuffers(released_buffers); | ||
| 129 | |||
| 130 | [[maybe_unused]] std::string tags{}; | ||
| 131 | for (u32 i = 0; i < count; i++) { | ||
| 132 | tags += fmt::format("{:08X}, ", released_buffers[i]); | ||
| 133 | } | ||
| 134 | [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()}; | ||
| 135 | LOG_TRACE(Service_Audio, "called. Session {} released {} buffers: {}", sessionid, count, | ||
| 136 | tags); | ||
| 137 | |||
| 138 | ctx.WriteBuffer(released_buffers); | ||
| 139 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 140 | rb.Push(ResultSuccess); | ||
| 141 | rb.Push(count); | ||
| 142 | } | ||
| 143 | |||
| 144 | void ContainsAudioInBuffer(Kernel::HLERequestContext& ctx) { | ||
| 145 | IPC::RequestParser rp{ctx}; | ||
| 146 | |||
| 147 | const u64 tag{rp.Pop<u64>()}; | ||
| 148 | const auto buffer_queued{impl->ContainsAudioBuffer(tag)}; | ||
| 149 | |||
| 150 | LOG_DEBUG(Service_Audio, "called. Is buffer {:08X} registered? {}", tag, buffer_queued); | ||
| 151 | |||
| 152 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 153 | rb.Push(ResultSuccess); | ||
| 154 | rb.Push(buffer_queued); | ||
| 155 | } | ||
| 156 | |||
| 157 | void GetAudioInBufferCount(Kernel::HLERequestContext& ctx) { | ||
| 158 | const auto buffer_count = impl->GetBufferCount(); | ||
| 159 | |||
| 160 | LOG_DEBUG(Service_Audio, "called. Buffer count={}", buffer_count); | ||
| 161 | |||
| 162 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 163 | |||
| 164 | rb.Push(ResultSuccess); | ||
| 165 | rb.Push(buffer_count); | ||
| 166 | } | ||
| 167 | |||
| 168 | void SetDeviceGain(Kernel::HLERequestContext& ctx) { | ||
| 169 | IPC::RequestParser rp{ctx}; | ||
| 170 | |||
| 171 | const auto volume{rp.Pop<f32>()}; | ||
| 172 | LOG_DEBUG(Service_Audio, "called. Gain {}", volume); | ||
| 173 | |||
| 174 | impl->SetVolume(volume); | ||
| 175 | |||
| 176 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 177 | rb.Push(ResultSuccess); | ||
| 178 | } | ||
| 179 | |||
| 180 | void GetDeviceGain(Kernel::HLERequestContext& ctx) { | ||
| 181 | auto volume{impl->GetVolume()}; | ||
| 182 | |||
| 183 | LOG_DEBUG(Service_Audio, "called. Gain {}", volume); | ||
| 184 | |||
| 185 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 186 | rb.Push(ResultSuccess); | ||
| 187 | rb.Push(volume); | ||
| 188 | } | ||
| 189 | |||
| 190 | void FlushAudioInBuffers(Kernel::HLERequestContext& ctx) { | ||
| 191 | bool flushed{impl->FlushAudioInBuffers()}; | ||
| 192 | |||
| 193 | LOG_DEBUG(Service_Audio, "called. Were any buffers flushed? {}", flushed); | ||
| 194 | |||
| 195 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 196 | rb.Push(ResultSuccess); | ||
| 197 | rb.Push(flushed); | ||
| 198 | } | ||
| 199 | |||
| 200 | KernelHelpers::ServiceContext service_context; | ||
| 201 | Kernel::KEvent* event; | ||
| 202 | std::shared_ptr<AudioCore::AudioIn::In> impl; | ||
| 203 | }; | ||
| 204 | |||
| 205 | AudInU::AudInU(Core::System& system_) | ||
| 206 | : ServiceFramework{system_, "audin:u", ServiceThreadType::CreateNew}, | ||
| 207 | service_context{system_, "AudInU"}, impl{std::make_unique<AudioCore::AudioIn::Manager>( | ||
| 208 | system_)} { | ||
| 66 | // clang-format off | 209 | // clang-format off |
| 67 | static const FunctionInfo functions[] = { | 210 | static const FunctionInfo functions[] = { |
| 68 | {0, &AudInU::ListAudioIns, "ListAudioIns"}, | 211 | {0, &AudInU::ListAudioIns, "ListAudioIns"}, |
| @@ -80,59 +223,152 @@ AudInU::AudInU(Core::System& system_) : ServiceFramework{system_, "audin:u"} { | |||
| 80 | AudInU::~AudInU() = default; | 223 | AudInU::~AudInU() = default; |
| 81 | 224 | ||
| 82 | void AudInU::ListAudioIns(Kernel::HLERequestContext& ctx) { | 225 | void AudInU::ListAudioIns(Kernel::HLERequestContext& ctx) { |
| 226 | using namespace AudioCore::AudioRenderer; | ||
| 227 | |||
| 83 | LOG_DEBUG(Service_Audio, "called"); | 228 | LOG_DEBUG(Service_Audio, "called"); |
| 84 | const std::size_t count = ctx.GetWriteBufferSize() / sizeof(AudioInDeviceName); | ||
| 85 | 229 | ||
| 86 | const std::size_t device_count = std::min(count, audio_device_names.size()); | 230 | const auto write_count = |
| 87 | std::vector<AudioInDeviceName> device_names; | 231 | static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName)); |
| 88 | device_names.reserve(device_count); | 232 | std::vector<AudioDevice::AudioDeviceName> device_names{}; |
| 89 | 233 | ||
| 90 | for (std::size_t i = 0; i < device_count; i++) { | 234 | u32 out_count{0}; |
| 91 | const auto& device_name = audio_device_names[i]; | 235 | if (write_count > 0) { |
| 92 | auto& entry = device_names.emplace_back(); | 236 | out_count = impl->GetDeviceNames(device_names, write_count, false); |
| 93 | device_name.copy(entry.data(), device_name.size()); | 237 | ctx.WriteBuffer(device_names); |
| 94 | } | 238 | } |
| 95 | 239 | ||
| 96 | ctx.WriteBuffer(device_names); | ||
| 97 | |||
| 98 | IPC::ResponseBuilder rb{ctx, 3}; | 240 | IPC::ResponseBuilder rb{ctx, 3}; |
| 99 | rb.Push(ResultSuccess); | 241 | rb.Push(ResultSuccess); |
| 100 | rb.Push(static_cast<u32>(device_names.size())); | 242 | rb.Push(out_count); |
| 101 | } | 243 | } |
| 102 | 244 | ||
| 103 | void AudInU::ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx) { | 245 | void AudInU::ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx) { |
| 246 | using namespace AudioCore::AudioRenderer; | ||
| 247 | |||
| 104 | LOG_DEBUG(Service_Audio, "called"); | 248 | LOG_DEBUG(Service_Audio, "called"); |
| 105 | constexpr u32 device_count = 0; | ||
| 106 | 249 | ||
| 107 | // Since we don't actually use any other audio input devices, we return 0 devices. Filtered | 250 | const auto write_count = |
| 108 | // device listing just omits the default input device | 251 | static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName)); |
| 252 | std::vector<AudioDevice::AudioDeviceName> device_names{}; | ||
| 253 | |||
| 254 | u32 out_count{0}; | ||
| 255 | if (write_count > 0) { | ||
| 256 | out_count = impl->GetDeviceNames(device_names, write_count, true); | ||
| 257 | ctx.WriteBuffer(device_names); | ||
| 258 | } | ||
| 109 | 259 | ||
| 110 | IPC::ResponseBuilder rb{ctx, 3}; | 260 | IPC::ResponseBuilder rb{ctx, 3}; |
| 111 | rb.Push(ResultSuccess); | 261 | rb.Push(ResultSuccess); |
| 112 | rb.Push(static_cast<u32>(device_count)); | 262 | rb.Push(out_count); |
| 113 | } | 263 | } |
| 114 | 264 | ||
| 115 | void AudInU::OpenInOutImpl(Kernel::HLERequestContext& ctx) { | 265 | void AudInU::OpenAudioIn(Kernel::HLERequestContext& ctx) { |
| 116 | AudInOutParams params{}; | 266 | IPC::RequestParser rp{ctx}; |
| 117 | params.channel_count = 2; | 267 | auto in_params{rp.PopRaw<AudioInParameter>()}; |
| 118 | params.sample_format = SampleFormat::PCM16; | 268 | auto applet_resource_user_id{rp.PopRaw<u64>()}; |
| 119 | params.sample_rate = 48000; | 269 | const auto device_name_data{ctx.ReadBuffer()}; |
| 120 | params.state = State::Started; | 270 | auto device_name = Common::StringFromBuffer(device_name_data); |
| 271 | auto handle{ctx.GetCopyHandle(0)}; | ||
| 272 | |||
| 273 | std::scoped_lock l{impl->mutex}; | ||
| 274 | auto link{impl->LinkToManager()}; | ||
| 275 | if (link.IsError()) { | ||
| 276 | LOG_ERROR(Service_Audio, "Failed to link Audio In to Audio Manager"); | ||
| 277 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 278 | rb.Push(link); | ||
| 279 | return; | ||
| 280 | } | ||
| 281 | |||
| 282 | size_t new_session_id{}; | ||
| 283 | auto result{impl->AcquireSessionId(new_session_id)}; | ||
| 284 | if (result.IsError()) { | ||
| 285 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 286 | rb.Push(result); | ||
| 287 | return; | ||
| 288 | } | ||
| 289 | |||
| 290 | LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id, | ||
| 291 | impl->num_free_sessions); | ||
| 292 | |||
| 293 | auto audio_in = std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name, | ||
| 294 | in_params, handle, applet_resource_user_id); | ||
| 295 | impl->sessions[new_session_id] = audio_in->GetImpl(); | ||
| 296 | impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id; | ||
| 297 | |||
| 298 | auto& out_system = impl->sessions[new_session_id]->GetSystem(); | ||
| 299 | AudioInParameterInternal out_params{.sample_rate = out_system.GetSampleRate(), | ||
| 300 | .channel_count = out_system.GetChannelCount(), | ||
| 301 | .sample_format = | ||
| 302 | static_cast<u32>(out_system.GetSampleFormat()), | ||
| 303 | .state = static_cast<u32>(out_system.GetState())}; | ||
| 121 | 304 | ||
| 122 | IPC::ResponseBuilder rb{ctx, 6, 0, 1}; | 305 | IPC::ResponseBuilder rb{ctx, 6, 0, 1}; |
| 123 | rb.Push(ResultSuccess); | ||
| 124 | rb.PushRaw<AudInOutParams>(params); | ||
| 125 | rb.PushIpcInterface<IAudioIn>(system); | ||
| 126 | } | ||
| 127 | 306 | ||
| 128 | void AudInU::OpenAudioIn(Kernel::HLERequestContext& ctx) { | 307 | std::string out_name{out_system.GetName()}; |
| 129 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | 308 | ctx.WriteBuffer(out_name); |
| 130 | OpenInOutImpl(ctx); | 309 | |
| 310 | rb.Push(ResultSuccess); | ||
| 311 | rb.PushRaw<AudioInParameterInternal>(out_params); | ||
| 312 | rb.PushIpcInterface<IAudioIn>(audio_in); | ||
| 131 | } | 313 | } |
| 132 | 314 | ||
| 133 | void AudInU::OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx) { | 315 | void AudInU::OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx) { |
| 134 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | 316 | IPC::RequestParser rp{ctx}; |
| 135 | OpenInOutImpl(ctx); | 317 | auto protocol_specified{rp.PopRaw<u64>()}; |
| 318 | auto in_params{rp.PopRaw<AudioInParameter>()}; | ||
| 319 | auto applet_resource_user_id{rp.PopRaw<u64>()}; | ||
| 320 | const auto device_name_data{ctx.ReadBuffer()}; | ||
| 321 | auto device_name = Common::StringFromBuffer(device_name_data); | ||
| 322 | auto handle{ctx.GetCopyHandle(0)}; | ||
| 323 | |||
| 324 | std::scoped_lock l{impl->mutex}; | ||
| 325 | auto link{impl->LinkToManager()}; | ||
| 326 | if (link.IsError()) { | ||
| 327 | LOG_ERROR(Service_Audio, "Failed to link Audio In to Audio Manager"); | ||
| 328 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 329 | rb.Push(link); | ||
| 330 | return; | ||
| 331 | } | ||
| 332 | |||
| 333 | size_t new_session_id{}; | ||
| 334 | auto result{impl->AcquireSessionId(new_session_id)}; | ||
| 335 | if (result.IsError()) { | ||
| 336 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 337 | rb.Push(result); | ||
| 338 | return; | ||
| 339 | } | ||
| 340 | |||
| 341 | LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id, | ||
| 342 | impl->num_free_sessions); | ||
| 343 | |||
| 344 | auto audio_in = std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name, | ||
| 345 | in_params, handle, applet_resource_user_id); | ||
| 346 | impl->sessions[new_session_id] = audio_in->GetImpl(); | ||
| 347 | impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id; | ||
| 348 | |||
| 349 | auto& out_system = impl->sessions[new_session_id]->GetSystem(); | ||
| 350 | AudioInParameterInternal out_params{.sample_rate = out_system.GetSampleRate(), | ||
| 351 | .channel_count = out_system.GetChannelCount(), | ||
| 352 | .sample_format = | ||
| 353 | static_cast<u32>(out_system.GetSampleFormat()), | ||
| 354 | .state = static_cast<u32>(out_system.GetState())}; | ||
| 355 | |||
| 356 | IPC::ResponseBuilder rb{ctx, 6, 0, 1}; | ||
| 357 | |||
| 358 | std::string out_name{out_system.GetName()}; | ||
| 359 | if (protocol_specified == 0) { | ||
| 360 | if (out_system.IsUac()) { | ||
| 361 | out_name = "UacIn"; | ||
| 362 | } else { | ||
| 363 | out_name = "DeviceIn"; | ||
| 364 | } | ||
| 365 | } | ||
| 366 | |||
| 367 | ctx.WriteBuffer(out_name); | ||
| 368 | |||
| 369 | rb.Push(ResultSuccess); | ||
| 370 | rb.PushRaw<AudioInParameterInternal>(out_params); | ||
| 371 | rb.PushIpcInterface<IAudioIn>(audio_in); | ||
| 136 | } | 372 | } |
| 137 | 373 | ||
| 138 | } // namespace Service::Audio | 374 | } // namespace Service::Audio |
diff --git a/src/core/hle/service/audio/audin_u.h b/src/core/hle/service/audio/audin_u.h index 2bfa38ecc..b45fda78a 100644 --- a/src/core/hle/service/audio/audin_u.h +++ b/src/core/hle/service/audio/audin_u.h | |||
| @@ -3,6 +3,8 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include "audio_core/audio_in_manager.h" | ||
| 7 | #include "audio_core/in/audio_in.h" | ||
| 6 | #include "core/hle/service/kernel_helpers.h" | 8 | #include "core/hle/service/kernel_helpers.h" |
| 7 | #include "core/hle/service/service.h" | 9 | #include "core/hle/service/service.h" |
| 8 | 10 | ||
| @@ -14,22 +16,12 @@ namespace Kernel { | |||
| 14 | class HLERequestContext; | 16 | class HLERequestContext; |
| 15 | } | 17 | } |
| 16 | 18 | ||
| 17 | namespace Service::Audio { | 19 | namespace AudioCore::AudioOut { |
| 18 | 20 | class Manager; | |
| 19 | class IAudioIn final : public ServiceFramework<IAudioIn> { | 21 | class In; |
| 20 | public: | 22 | } // namespace AudioCore::AudioOut |
| 21 | explicit IAudioIn(Core::System& system_); | ||
| 22 | ~IAudioIn() override; | ||
| 23 | |||
| 24 | private: | ||
| 25 | void Start(Kernel::HLERequestContext& ctx); | ||
| 26 | void RegisterBufferEvent(Kernel::HLERequestContext& ctx); | ||
| 27 | void AppendAudioInBufferAuto(Kernel::HLERequestContext& ctx); | ||
| 28 | |||
| 29 | KernelHelpers::ServiceContext service_context; | ||
| 30 | 23 | ||
| 31 | Kernel::KEvent* buffer_event; | 24 | namespace Service::Audio { |
| 32 | }; | ||
| 33 | 25 | ||
| 34 | class AudInU final : public ServiceFramework<AudInU> { | 26 | class AudInU final : public ServiceFramework<AudInU> { |
| 35 | public: | 27 | public: |
| @@ -37,33 +29,14 @@ public: | |||
| 37 | ~AudInU() override; | 29 | ~AudInU() override; |
| 38 | 30 | ||
| 39 | private: | 31 | private: |
| 40 | enum class SampleFormat : u32_le { | ||
| 41 | PCM16 = 2, | ||
| 42 | }; | ||
| 43 | |||
| 44 | enum class State : u32_le { | ||
| 45 | Started = 0, | ||
| 46 | Stopped = 1, | ||
| 47 | }; | ||
| 48 | |||
| 49 | struct AudInOutParams { | ||
| 50 | u32_le sample_rate{}; | ||
| 51 | u32_le channel_count{}; | ||
| 52 | SampleFormat sample_format{}; | ||
| 53 | State state{}; | ||
| 54 | }; | ||
| 55 | static_assert(sizeof(AudInOutParams) == 0x10, "AudInOutParams is an invalid size"); | ||
| 56 | |||
| 57 | using AudioInDeviceName = std::array<char, 256>; | ||
| 58 | static constexpr std::array<std::string_view, 1> audio_device_names{{ | ||
| 59 | "BuiltInHeadset", | ||
| 60 | }}; | ||
| 61 | |||
| 62 | void ListAudioIns(Kernel::HLERequestContext& ctx); | 32 | void ListAudioIns(Kernel::HLERequestContext& ctx); |
| 63 | void ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx); | 33 | void ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx); |
| 64 | void OpenInOutImpl(Kernel::HLERequestContext& ctx); | 34 | void OpenInOutImpl(Kernel::HLERequestContext& ctx); |
| 65 | void OpenAudioIn(Kernel::HLERequestContext& ctx); | 35 | void OpenAudioIn(Kernel::HLERequestContext& ctx); |
| 66 | void OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx); | 36 | void OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx); |
| 37 | |||
| 38 | KernelHelpers::ServiceContext service_context; | ||
| 39 | std::unique_ptr<AudioCore::AudioIn::Manager> impl; | ||
| 67 | }; | 40 | }; |
| 68 | 41 | ||
| 69 | } // namespace Service::Audio | 42 | } // namespace Service::Audio |
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index b0dad6053..a44dd842a 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp | |||
| @@ -5,56 +5,43 @@ | |||
| 5 | #include <cstring> | 5 | #include <cstring> |
| 6 | #include <vector> | 6 | #include <vector> |
| 7 | 7 | ||
| 8 | #include "audio_core/audio_out.h" | 8 | #include "audio_core/out/audio_out_system.h" |
| 9 | #include "audio_core/codec.h" | 9 | #include "audio_core/renderer/audio_device.h" |
| 10 | #include "common/common_funcs.h" | 10 | #include "common/common_funcs.h" |
| 11 | #include "common/logging/log.h" | 11 | #include "common/logging/log.h" |
| 12 | #include "common/string_util.h" | ||
| 12 | #include "common/swap.h" | 13 | #include "common/swap.h" |
| 13 | #include "core/core.h" | 14 | #include "core/core.h" |
| 14 | #include "core/hle/ipc_helpers.h" | 15 | #include "core/hle/ipc_helpers.h" |
| 15 | #include "core/hle/kernel/k_event.h" | 16 | #include "core/hle/kernel/k_event.h" |
| 16 | #include "core/hle/service/audio/audout_u.h" | 17 | #include "core/hle/service/audio/audout_u.h" |
| 17 | #include "core/hle/service/audio/errors.h" | 18 | #include "core/hle/service/audio/errors.h" |
| 18 | #include "core/hle/service/kernel_helpers.h" | ||
| 19 | #include "core/memory.h" | 19 | #include "core/memory.h" |
| 20 | 20 | ||
| 21 | namespace Service::Audio { | 21 | namespace Service::Audio { |
| 22 | 22 | using namespace AudioCore::AudioOut; | |
| 23 | constexpr std::array<char, 10> DefaultDevice{{"DeviceOut"}}; | ||
| 24 | constexpr int DefaultSampleRate{48000}; | ||
| 25 | |||
| 26 | struct AudoutParams { | ||
| 27 | s32_le sample_rate; | ||
| 28 | u16_le channel_count; | ||
| 29 | INSERT_PADDING_BYTES_NOINIT(2); | ||
| 30 | }; | ||
| 31 | static_assert(sizeof(AudoutParams) == 0x8, "AudoutParams is an invalid size"); | ||
| 32 | |||
| 33 | enum class AudioState : u32 { | ||
| 34 | Started, | ||
| 35 | Stopped, | ||
| 36 | }; | ||
| 37 | 23 | ||
| 38 | class IAudioOut final : public ServiceFramework<IAudioOut> { | 24 | class IAudioOut final : public ServiceFramework<IAudioOut> { |
| 39 | public: | 25 | public: |
| 40 | explicit IAudioOut(Core::System& system_, AudoutParams audio_params_, | 26 | explicit IAudioOut(Core::System& system_, AudioCore::AudioOut::Manager& manager, |
| 41 | AudioCore::AudioOut& audio_core_, std::string&& device_name_, | 27 | size_t session_id, std::string& device_name, |
| 42 | std::string&& unique_name) | 28 | const AudioOutParameter& in_params, u32 handle, u64 applet_resource_user_id) |
| 43 | : ServiceFramework{system_, "IAudioOut", ServiceThreadType::CreateNew}, | 29 | : ServiceFramework{system_, "IAudioOut", ServiceThreadType::CreateNew}, |
| 44 | audio_core{audio_core_}, device_name{std::move(device_name_)}, | 30 | service_context{system_, "IAudioOut"}, event{service_context.CreateEvent( |
| 45 | audio_params{audio_params_}, main_memory{system.Memory()}, service_context{system_, | 31 | "AudioOutEvent")}, |
| 46 | "IAudioOut"} { | 32 | impl{std::make_shared<AudioCore::AudioOut::Out>(system_, manager, event, session_id)} { |
| 33 | |||
| 47 | // clang-format off | 34 | // clang-format off |
| 48 | static const FunctionInfo functions[] = { | 35 | static const FunctionInfo functions[] = { |
| 49 | {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"}, | 36 | {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"}, |
| 50 | {1, &IAudioOut::StartAudioOut, "Start"}, | 37 | {1, &IAudioOut::Start, "Start"}, |
| 51 | {2, &IAudioOut::StopAudioOut, "Stop"}, | 38 | {2, &IAudioOut::Stop, "Stop"}, |
| 52 | {3, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBuffer"}, | 39 | {3, &IAudioOut::AppendAudioOutBuffer, "AppendAudioOutBuffer"}, |
| 53 | {4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"}, | 40 | {4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"}, |
| 54 | {5, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBuffers"}, | 41 | {5, &IAudioOut::GetReleasedAudioOutBuffers, "GetReleasedAudioOutBuffers"}, |
| 55 | {6, &IAudioOut::ContainsAudioOutBuffer, "ContainsAudioOutBuffer"}, | 42 | {6, &IAudioOut::ContainsAudioOutBuffer, "ContainsAudioOutBuffer"}, |
| 56 | {7, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBufferAuto"}, | 43 | {7, &IAudioOut::AppendAudioOutBuffer, "AppendAudioOutBufferAuto"}, |
| 57 | {8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"}, | 44 | {8, &IAudioOut::GetReleasedAudioOutBuffers, "GetReleasedAudioOutBuffersAuto"}, |
| 58 | {9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"}, | 45 | {9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"}, |
| 59 | {10, &IAudioOut::GetAudioOutPlayedSampleCount, "GetAudioOutPlayedSampleCount"}, | 46 | {10, &IAudioOut::GetAudioOutPlayedSampleCount, "GetAudioOutPlayedSampleCount"}, |
| 60 | {11, &IAudioOut::FlushAudioOutBuffers, "FlushAudioOutBuffers"}, | 47 | {11, &IAudioOut::FlushAudioOutBuffers, "FlushAudioOutBuffers"}, |
| @@ -64,241 +51,263 @@ public: | |||
| 64 | // clang-format on | 51 | // clang-format on |
| 65 | RegisterHandlers(functions); | 52 | RegisterHandlers(functions); |
| 66 | 53 | ||
| 67 | // This is the event handle used to check if the audio buffer was released | 54 | if (impl->GetSystem() |
| 68 | buffer_event = service_context.CreateEvent("IAudioOutBufferReleased"); | 55 | .Initialize(device_name, in_params, handle, applet_resource_user_id) |
| 69 | 56 | .IsError()) { | |
| 70 | stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate, | 57 | LOG_ERROR(Service_Audio, "Failed to initialize the AudioOut System!"); |
| 71 | audio_params.channel_count, std::move(unique_name), [this] { | 58 | } |
| 72 | const auto guard = LockService(); | ||
| 73 | buffer_event->GetWritableEvent().Signal(); | ||
| 74 | }); | ||
| 75 | } | 59 | } |
| 76 | 60 | ||
| 77 | ~IAudioOut() override { | 61 | ~IAudioOut() override { |
| 78 | service_context.CloseEvent(buffer_event); | 62 | impl->Free(); |
| 63 | service_context.CloseEvent(event); | ||
| 79 | } | 64 | } |
| 80 | 65 | ||
| 81 | private: | 66 | [[nodiscard]] std::shared_ptr<AudioCore::AudioOut::Out> GetImpl() { |
| 82 | struct AudioBuffer { | 67 | return impl; |
| 83 | u64_le next; | 68 | } |
| 84 | u64_le buffer; | ||
| 85 | u64_le buffer_capacity; | ||
| 86 | u64_le buffer_size; | ||
| 87 | u64_le offset; | ||
| 88 | }; | ||
| 89 | static_assert(sizeof(AudioBuffer) == 0x28, "AudioBuffer is an invalid size"); | ||
| 90 | 69 | ||
| 70 | private: | ||
| 91 | void GetAudioOutState(Kernel::HLERequestContext& ctx) { | 71 | void GetAudioOutState(Kernel::HLERequestContext& ctx) { |
| 92 | LOG_DEBUG(Service_Audio, "called"); | 72 | const auto state = static_cast<u32>(impl->GetState()); |
| 73 | |||
| 74 | LOG_DEBUG(Service_Audio, "called. State={}", state); | ||
| 93 | 75 | ||
| 94 | IPC::ResponseBuilder rb{ctx, 3}; | 76 | IPC::ResponseBuilder rb{ctx, 3}; |
| 95 | rb.Push(ResultSuccess); | 77 | rb.Push(ResultSuccess); |
| 96 | rb.Push(static_cast<u32>(stream->IsPlaying() ? AudioState::Started : AudioState::Stopped)); | 78 | rb.Push(state); |
| 97 | } | 79 | } |
| 98 | 80 | ||
| 99 | void StartAudioOut(Kernel::HLERequestContext& ctx) { | 81 | void Start(Kernel::HLERequestContext& ctx) { |
| 100 | LOG_DEBUG(Service_Audio, "called"); | 82 | LOG_DEBUG(Service_Audio, "called"); |
| 101 | 83 | ||
| 102 | if (stream->IsPlaying()) { | 84 | auto result = impl->StartSystem(); |
| 103 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 104 | rb.Push(ERR_OPERATION_FAILED); | ||
| 105 | return; | ||
| 106 | } | ||
| 107 | |||
| 108 | audio_core.StartStream(stream); | ||
| 109 | 85 | ||
| 110 | IPC::ResponseBuilder rb{ctx, 2}; | 86 | IPC::ResponseBuilder rb{ctx, 2}; |
| 111 | rb.Push(ResultSuccess); | 87 | rb.Push(result); |
| 112 | } | 88 | } |
| 113 | 89 | ||
| 114 | void StopAudioOut(Kernel::HLERequestContext& ctx) { | 90 | void Stop(Kernel::HLERequestContext& ctx) { |
| 115 | LOG_DEBUG(Service_Audio, "called"); | 91 | LOG_DEBUG(Service_Audio, "called"); |
| 116 | 92 | ||
| 117 | if (stream->IsPlaying()) { | 93 | auto result = impl->StopSystem(); |
| 118 | audio_core.StopStream(stream); | ||
| 119 | } | ||
| 120 | 94 | ||
| 121 | IPC::ResponseBuilder rb{ctx, 2}; | 95 | IPC::ResponseBuilder rb{ctx, 2}; |
| 122 | rb.Push(ResultSuccess); | 96 | rb.Push(result); |
| 123 | } | 97 | } |
| 124 | 98 | ||
| 125 | void RegisterBufferEvent(Kernel::HLERequestContext& ctx) { | 99 | void AppendAudioOutBuffer(Kernel::HLERequestContext& ctx) { |
| 126 | LOG_DEBUG(Service_Audio, "called"); | ||
| 127 | |||
| 128 | IPC::ResponseBuilder rb{ctx, 2, 1}; | ||
| 129 | rb.Push(ResultSuccess); | ||
| 130 | rb.PushCopyObjects(buffer_event->GetReadableEvent()); | ||
| 131 | } | ||
| 132 | |||
| 133 | void AppendAudioOutBufferImpl(Kernel::HLERequestContext& ctx) { | ||
| 134 | LOG_DEBUG(Service_Audio, "(STUBBED) called {}", ctx.Description()); | ||
| 135 | IPC::RequestParser rp{ctx}; | 100 | IPC::RequestParser rp{ctx}; |
| 101 | u64 tag = rp.PopRaw<u64>(); | ||
| 136 | 102 | ||
| 137 | const auto& input_buffer{ctx.ReadBuffer()}; | 103 | const auto in_buffer_size{ctx.GetReadBufferSize()}; |
| 138 | ASSERT_MSG(input_buffer.size() == sizeof(AudioBuffer), | 104 | if (in_buffer_size < sizeof(AudioOutBuffer)) { |
| 139 | "AudioBuffer input is an invalid size!"); | 105 | LOG_ERROR(Service_Audio, "Input buffer is too small for an AudioOutBuffer!"); |
| 140 | AudioBuffer audio_buffer{}; | 106 | } |
| 141 | std::memcpy(&audio_buffer, input_buffer.data(), sizeof(AudioBuffer)); | ||
| 142 | const u64 tag{rp.Pop<u64>()}; | ||
| 143 | 107 | ||
| 144 | std::vector<s16> samples(audio_buffer.buffer_size / sizeof(s16)); | 108 | const auto& in_buffer = ctx.ReadBuffer(); |
| 145 | main_memory.ReadBlock(audio_buffer.buffer, samples.data(), audio_buffer.buffer_size); | 109 | AudioOutBuffer buffer{}; |
| 110 | std::memcpy(&buffer, in_buffer.data(), sizeof(AudioOutBuffer)); | ||
| 146 | 111 | ||
| 147 | if (!audio_core.QueueBuffer(stream, tag, std::move(samples))) { | 112 | [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()}; |
| 148 | IPC::ResponseBuilder rb{ctx, 2}; | 113 | LOG_TRACE(Service_Audio, "called. Session {} Appending buffer {:08X}", sessionid, tag); |
| 149 | rb.Push(ERR_BUFFER_COUNT_EXCEEDED); | 114 | |
| 150 | return; | 115 | auto result = impl->AppendBuffer(buffer, tag); |
| 151 | } | ||
| 152 | 116 | ||
| 153 | IPC::ResponseBuilder rb{ctx, 2}; | 117 | IPC::ResponseBuilder rb{ctx, 2}; |
| 118 | rb.Push(result); | ||
| 119 | } | ||
| 120 | |||
| 121 | void RegisterBufferEvent(Kernel::HLERequestContext& ctx) { | ||
| 122 | LOG_DEBUG(Service_Audio, "called"); | ||
| 123 | |||
| 124 | auto& buffer_event = impl->GetBufferEvent(); | ||
| 125 | |||
| 126 | IPC::ResponseBuilder rb{ctx, 2, 1}; | ||
| 154 | rb.Push(ResultSuccess); | 127 | rb.Push(ResultSuccess); |
| 128 | rb.PushCopyObjects(buffer_event); | ||
| 155 | } | 129 | } |
| 156 | 130 | ||
| 157 | void GetReleasedAudioOutBufferImpl(Kernel::HLERequestContext& ctx) { | 131 | void GetReleasedAudioOutBuffers(Kernel::HLERequestContext& ctx) { |
| 158 | LOG_DEBUG(Service_Audio, "called {}", ctx.Description()); | 132 | auto write_buffer_size = ctx.GetWriteBufferSize() / sizeof(u64); |
| 133 | std::vector<u64> released_buffers(write_buffer_size, 0); | ||
| 159 | 134 | ||
| 160 | const u64 max_count{ctx.GetWriteBufferSize() / sizeof(u64)}; | 135 | auto count = impl->GetReleasedBuffers(released_buffers); |
| 161 | const auto released_buffers{audio_core.GetTagsAndReleaseBuffers(stream, max_count)}; | ||
| 162 | 136 | ||
| 163 | std::vector<u64> tags{released_buffers}; | 137 | [[maybe_unused]] std::string tags{}; |
| 164 | tags.resize(max_count); | 138 | for (u32 i = 0; i < count; i++) { |
| 165 | ctx.WriteBuffer(tags); | 139 | tags += fmt::format("{:08X}, ", released_buffers[i]); |
| 140 | } | ||
| 141 | [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()}; | ||
| 142 | LOG_TRACE(Service_Audio, "called. Session {} released {} buffers: {}", sessionid, count, | ||
| 143 | tags); | ||
| 166 | 144 | ||
| 145 | ctx.WriteBuffer(released_buffers); | ||
| 167 | IPC::ResponseBuilder rb{ctx, 3}; | 146 | IPC::ResponseBuilder rb{ctx, 3}; |
| 168 | rb.Push(ResultSuccess); | 147 | rb.Push(ResultSuccess); |
| 169 | rb.Push<u32>(static_cast<u32>(released_buffers.size())); | 148 | rb.Push(count); |
| 170 | } | 149 | } |
| 171 | 150 | ||
| 172 | void ContainsAudioOutBuffer(Kernel::HLERequestContext& ctx) { | 151 | void ContainsAudioOutBuffer(Kernel::HLERequestContext& ctx) { |
| 173 | LOG_DEBUG(Service_Audio, "called"); | ||
| 174 | |||
| 175 | IPC::RequestParser rp{ctx}; | 152 | IPC::RequestParser rp{ctx}; |
| 153 | |||
| 176 | const u64 tag{rp.Pop<u64>()}; | 154 | const u64 tag{rp.Pop<u64>()}; |
| 155 | const auto buffer_queued{impl->ContainsAudioBuffer(tag)}; | ||
| 156 | |||
| 157 | LOG_DEBUG(Service_Audio, "called. Is buffer {:08X} registered? {}", tag, buffer_queued); | ||
| 158 | |||
| 177 | IPC::ResponseBuilder rb{ctx, 3}; | 159 | IPC::ResponseBuilder rb{ctx, 3}; |
| 178 | rb.Push(ResultSuccess); | 160 | rb.Push(ResultSuccess); |
| 179 | rb.Push(stream->ContainsBuffer(tag)); | 161 | rb.Push(buffer_queued); |
| 180 | } | 162 | } |
| 181 | 163 | ||
| 182 | void GetAudioOutBufferCount(Kernel::HLERequestContext& ctx) { | 164 | void GetAudioOutBufferCount(Kernel::HLERequestContext& ctx) { |
| 183 | LOG_DEBUG(Service_Audio, "called"); | 165 | const auto buffer_count = impl->GetBufferCount(); |
| 166 | |||
| 167 | LOG_DEBUG(Service_Audio, "called. Buffer count={}", buffer_count); | ||
| 184 | 168 | ||
| 185 | IPC::ResponseBuilder rb{ctx, 3}; | 169 | IPC::ResponseBuilder rb{ctx, 3}; |
| 170 | |||
| 186 | rb.Push(ResultSuccess); | 171 | rb.Push(ResultSuccess); |
| 187 | rb.Push(static_cast<u32>(stream->GetQueueSize())); | 172 | rb.Push(buffer_count); |
| 188 | } | 173 | } |
| 189 | 174 | ||
| 190 | void GetAudioOutPlayedSampleCount(Kernel::HLERequestContext& ctx) { | 175 | void GetAudioOutPlayedSampleCount(Kernel::HLERequestContext& ctx) { |
| 191 | LOG_DEBUG(Service_Audio, "called"); | 176 | const auto samples_played = impl->GetPlayedSampleCount(); |
| 177 | |||
| 178 | LOG_DEBUG(Service_Audio, "called. Played samples={}", samples_played); | ||
| 192 | 179 | ||
| 193 | IPC::ResponseBuilder rb{ctx, 4}; | 180 | IPC::ResponseBuilder rb{ctx, 4}; |
| 181 | |||
| 194 | rb.Push(ResultSuccess); | 182 | rb.Push(ResultSuccess); |
| 195 | rb.Push(stream->GetPlayedSampleCount()); | 183 | rb.Push(samples_played); |
| 196 | } | 184 | } |
| 197 | 185 | ||
| 198 | void FlushAudioOutBuffers(Kernel::HLERequestContext& ctx) { | 186 | void FlushAudioOutBuffers(Kernel::HLERequestContext& ctx) { |
| 199 | LOG_DEBUG(Service_Audio, "called"); | 187 | bool flushed{impl->FlushAudioOutBuffers()}; |
| 188 | |||
| 189 | LOG_DEBUG(Service_Audio, "called. Were any buffers flushed? {}", flushed); | ||
| 200 | 190 | ||
| 201 | IPC::ResponseBuilder rb{ctx, 3}; | 191 | IPC::ResponseBuilder rb{ctx, 3}; |
| 202 | rb.Push(ResultSuccess); | 192 | rb.Push(ResultSuccess); |
| 203 | rb.Push(stream->Flush()); | 193 | rb.Push(flushed); |
| 204 | } | 194 | } |
| 205 | 195 | ||
| 206 | void SetAudioOutVolume(Kernel::HLERequestContext& ctx) { | 196 | void SetAudioOutVolume(Kernel::HLERequestContext& ctx) { |
| 207 | IPC::RequestParser rp{ctx}; | 197 | IPC::RequestParser rp{ctx}; |
| 208 | const float volume = rp.Pop<float>(); | 198 | const auto volume = rp.Pop<f32>(); |
| 209 | LOG_DEBUG(Service_Audio, "called, volume={}", volume); | 199 | |
| 200 | LOG_DEBUG(Service_Audio, "called. Volume={}", volume); | ||
| 210 | 201 | ||
| 211 | stream->SetVolume(volume); | 202 | impl->SetVolume(volume); |
| 212 | 203 | ||
| 213 | IPC::ResponseBuilder rb{ctx, 2}; | 204 | IPC::ResponseBuilder rb{ctx, 2}; |
| 214 | rb.Push(ResultSuccess); | 205 | rb.Push(ResultSuccess); |
| 215 | } | 206 | } |
| 216 | 207 | ||
| 217 | void GetAudioOutVolume(Kernel::HLERequestContext& ctx) { | 208 | void GetAudioOutVolume(Kernel::HLERequestContext& ctx) { |
| 218 | LOG_DEBUG(Service_Audio, "called"); | 209 | const auto volume = impl->GetVolume(); |
| 210 | |||
| 211 | LOG_DEBUG(Service_Audio, "called. Volume={}", volume); | ||
| 219 | 212 | ||
| 220 | IPC::ResponseBuilder rb{ctx, 3}; | 213 | IPC::ResponseBuilder rb{ctx, 3}; |
| 221 | rb.Push(ResultSuccess); | 214 | rb.Push(ResultSuccess); |
| 222 | rb.Push(stream->GetVolume()); | 215 | rb.Push(volume); |
| 223 | } | 216 | } |
| 224 | 217 | ||
| 225 | AudioCore::AudioOut& audio_core; | ||
| 226 | AudioCore::StreamPtr stream; | ||
| 227 | std::string device_name; | ||
| 228 | |||
| 229 | [[maybe_unused]] AudoutParams audio_params{}; | ||
| 230 | |||
| 231 | Core::Memory::Memory& main_memory; | ||
| 232 | |||
| 233 | KernelHelpers::ServiceContext service_context; | 218 | KernelHelpers::ServiceContext service_context; |
| 234 | 219 | Kernel::KEvent* event; | |
| 235 | /// This is the event handle used to check if the audio buffer was released | 220 | std::shared_ptr<AudioCore::AudioOut::Out> impl; |
| 236 | Kernel::KEvent* buffer_event; | ||
| 237 | }; | 221 | }; |
| 238 | 222 | ||
| 239 | AudOutU::AudOutU(Core::System& system_) : ServiceFramework{system_, "audout:u"} { | 223 | AudOutU::AudOutU(Core::System& system_) |
| 224 | : ServiceFramework{system_, "audout:u", ServiceThreadType::CreateNew}, | ||
| 225 | service_context{system_, "AudOutU"}, impl{std::make_unique<AudioCore::AudioOut::Manager>( | ||
| 226 | system_)} { | ||
| 240 | // clang-format off | 227 | // clang-format off |
| 241 | static const FunctionInfo functions[] = { | 228 | static const FunctionInfo functions[] = { |
| 242 | {0, &AudOutU::ListAudioOutsImpl, "ListAudioOuts"}, | 229 | {0, &AudOutU::ListAudioOuts, "ListAudioOuts"}, |
| 243 | {1, &AudOutU::OpenAudioOutImpl, "OpenAudioOut"}, | 230 | {1, &AudOutU::OpenAudioOut, "OpenAudioOut"}, |
| 244 | {2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"}, | 231 | {2, &AudOutU::ListAudioOuts, "ListAudioOutsAuto"}, |
| 245 | {3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"}, | 232 | {3, &AudOutU::OpenAudioOut, "OpenAudioOutAuto"}, |
| 246 | }; | 233 | }; |
| 247 | // clang-format on | 234 | // clang-format on |
| 248 | 235 | ||
| 249 | RegisterHandlers(functions); | 236 | RegisterHandlers(functions); |
| 250 | audio_core = std::make_unique<AudioCore::AudioOut>(); | ||
| 251 | } | 237 | } |
| 252 | 238 | ||
| 253 | AudOutU::~AudOutU() = default; | 239 | AudOutU::~AudOutU() = default; |
| 254 | 240 | ||
| 255 | void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) { | 241 | void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) { |
| 256 | LOG_DEBUG(Service_Audio, "called"); | 242 | using namespace AudioCore::AudioRenderer; |
| 257 | 243 | ||
| 258 | ctx.WriteBuffer(DefaultDevice); | 244 | std::scoped_lock l{impl->mutex}; |
| 245 | |||
| 246 | const auto write_count = | ||
| 247 | static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName)); | ||
| 248 | std::vector<AudioDevice::AudioDeviceName> device_names{}; | ||
| 249 | std::string print_names{}; | ||
| 250 | if (write_count > 0) { | ||
| 251 | device_names.push_back(AudioDevice::AudioDeviceName("DeviceOut")); | ||
| 252 | LOG_DEBUG(Service_Audio, "called. \nName=DeviceOut"); | ||
| 253 | } else { | ||
| 254 | LOG_DEBUG(Service_Audio, "called. Empty buffer passed in."); | ||
| 255 | } | ||
| 256 | |||
| 257 | ctx.WriteBuffer(device_names); | ||
| 259 | 258 | ||
| 260 | IPC::ResponseBuilder rb{ctx, 3}; | 259 | IPC::ResponseBuilder rb{ctx, 3}; |
| 261 | rb.Push(ResultSuccess); | 260 | rb.Push(ResultSuccess); |
| 262 | rb.Push<u32>(1); // Amount of audio devices | 261 | rb.Push<u32>(static_cast<u32>(device_names.size())); |
| 263 | } | 262 | } |
| 264 | 263 | ||
| 265 | void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) { | 264 | void AudOutU::OpenAudioOut(Kernel::HLERequestContext& ctx) { |
| 266 | LOG_DEBUG(Service_Audio, "called"); | 265 | IPC::RequestParser rp{ctx}; |
| 267 | 266 | auto in_params{rp.PopRaw<AudioOutParameter>()}; | |
| 267 | auto applet_resource_user_id{rp.PopRaw<u64>()}; | ||
| 268 | const auto device_name_data{ctx.ReadBuffer()}; | 268 | const auto device_name_data{ctx.ReadBuffer()}; |
| 269 | std::string device_name; | 269 | auto device_name = Common::StringFromBuffer(device_name_data); |
| 270 | if (device_name_data[0] != '\0') { | 270 | auto handle{ctx.GetCopyHandle(0)}; |
| 271 | device_name.assign(device_name_data.begin(), device_name_data.end()); | ||
| 272 | } else { | ||
| 273 | device_name.assign(DefaultDevice.begin(), DefaultDevice.end()); | ||
| 274 | } | ||
| 275 | ctx.WriteBuffer(device_name); | ||
| 276 | 271 | ||
| 277 | IPC::RequestParser rp{ctx}; | 272 | auto link{impl->LinkToManager()}; |
| 278 | auto params{rp.PopRaw<AudoutParams>()}; | 273 | if (link.IsError()) { |
| 279 | if (params.channel_count <= 2) { | 274 | LOG_ERROR(Service_Audio, "Failed to link Audio Out to Audio Manager"); |
| 280 | // Mono does not exist for audout | 275 | IPC::ResponseBuilder rb{ctx, 2}; |
| 281 | params.channel_count = 2; | 276 | rb.Push(link); |
| 282 | } else { | 277 | return; |
| 283 | params.channel_count = 6; | ||
| 284 | } | 278 | } |
| 285 | if (!params.sample_rate) { | 279 | |
| 286 | params.sample_rate = DefaultSampleRate; | 280 | size_t new_session_id{}; |
| 281 | auto result{impl->AcquireSessionId(new_session_id)}; | ||
| 282 | if (result.IsError()) { | ||
| 283 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 284 | rb.Push(result); | ||
| 285 | return; | ||
| 287 | } | 286 | } |
| 288 | 287 | ||
| 289 | std::string unique_name{fmt::format("{}-{}", device_name, audio_out_interfaces.size())}; | 288 | LOG_DEBUG(Service_Audio, "Opening new AudioOut, sessionid={}, free sessions={}", new_session_id, |
| 290 | auto audio_out_interface = std::make_shared<IAudioOut>( | 289 | impl->num_free_sessions); |
| 291 | system, params, *audio_core, std::move(device_name), std::move(unique_name)); | 290 | |
| 291 | auto audio_out = std::make_shared<IAudioOut>(system, *impl, new_session_id, device_name, | ||
| 292 | in_params, handle, applet_resource_user_id); | ||
| 293 | |||
| 294 | impl->sessions[new_session_id] = audio_out->GetImpl(); | ||
| 295 | impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id; | ||
| 296 | |||
| 297 | auto& out_system = impl->sessions[new_session_id]->GetSystem(); | ||
| 298 | AudioOutParameterInternal out_params{.sample_rate = out_system.GetSampleRate(), | ||
| 299 | .channel_count = out_system.GetChannelCount(), | ||
| 300 | .sample_format = | ||
| 301 | static_cast<u32>(out_system.GetSampleFormat()), | ||
| 302 | .state = static_cast<u32>(out_system.GetState())}; | ||
| 292 | 303 | ||
| 293 | IPC::ResponseBuilder rb{ctx, 6, 0, 1}; | 304 | IPC::ResponseBuilder rb{ctx, 6, 0, 1}; |
| 294 | rb.Push(ResultSuccess); | ||
| 295 | rb.Push<u32>(DefaultSampleRate); | ||
| 296 | rb.Push<u32>(params.channel_count); | ||
| 297 | rb.Push<u32>(static_cast<u32>(AudioCore::Codec::PcmFormat::Int16)); | ||
| 298 | rb.Push<u32>(static_cast<u32>(AudioState::Stopped)); | ||
| 299 | rb.PushIpcInterface<IAudioOut>(audio_out_interface); | ||
| 300 | 305 | ||
| 301 | audio_out_interfaces.push_back(std::move(audio_out_interface)); | 306 | ctx.WriteBuffer(out_system.GetName()); |
| 307 | |||
| 308 | rb.Push(ResultSuccess); | ||
| 309 | rb.PushRaw<AudioOutParameterInternal>(out_params); | ||
| 310 | rb.PushIpcInterface<IAudioOut>(audio_out); | ||
| 302 | } | 311 | } |
| 303 | 312 | ||
| 304 | } // namespace Service::Audio | 313 | } // namespace Service::Audio |
diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h index d82004c2e..fdc0ee754 100644 --- a/src/core/hle/service/audio/audout_u.h +++ b/src/core/hle/service/audio/audout_u.h | |||
| @@ -3,13 +3,11 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <vector> | 6 | #include "audio_core/audio_out_manager.h" |
| 7 | #include "audio_core/out/audio_out.h" | ||
| 8 | #include "core/hle/service/kernel_helpers.h" | ||
| 7 | #include "core/hle/service/service.h" | 9 | #include "core/hle/service/service.h" |
| 8 | 10 | ||
| 9 | namespace AudioCore { | ||
| 10 | class AudioOut; | ||
| 11 | } | ||
| 12 | |||
| 13 | namespace Core { | 11 | namespace Core { |
| 14 | class System; | 12 | class System; |
| 15 | } | 13 | } |
| @@ -18,6 +16,11 @@ namespace Kernel { | |||
| 18 | class HLERequestContext; | 16 | class HLERequestContext; |
| 19 | } | 17 | } |
| 20 | 18 | ||
| 19 | namespace AudioCore::AudioOut { | ||
| 20 | class Manager; | ||
| 21 | class Out; | ||
| 22 | } // namespace AudioCore::AudioOut | ||
| 23 | |||
| 21 | namespace Service::Audio { | 24 | namespace Service::Audio { |
| 22 | 25 | ||
| 23 | class IAudioOut; | 26 | class IAudioOut; |
| @@ -28,11 +31,11 @@ public: | |||
| 28 | ~AudOutU() override; | 31 | ~AudOutU() override; |
| 29 | 32 | ||
| 30 | private: | 33 | private: |
| 31 | void ListAudioOutsImpl(Kernel::HLERequestContext& ctx); | 34 | void ListAudioOuts(Kernel::HLERequestContext& ctx); |
| 32 | void OpenAudioOutImpl(Kernel::HLERequestContext& ctx); | 35 | void OpenAudioOut(Kernel::HLERequestContext& ctx); |
| 33 | 36 | ||
| 34 | std::vector<std::shared_ptr<IAudioOut>> audio_out_interfaces; | 37 | KernelHelpers::ServiceContext service_context; |
| 35 | std::unique_ptr<AudioCore::AudioOut> audio_core; | 38 | std::unique_ptr<AudioCore::AudioOut::Manager> impl; |
| 36 | }; | 39 | }; |
| 37 | 40 | ||
| 38 | } // namespace Service::Audio | 41 | } // namespace Service::Audio |
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 2ce63c004..381a66ba5 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp | |||
| @@ -4,7 +4,12 @@ | |||
| 4 | #include <array> | 4 | #include <array> |
| 5 | #include <memory> | 5 | #include <memory> |
| 6 | 6 | ||
| 7 | #include "audio_core/audio_renderer.h" | 7 | #include "audio_core/audio_core.h" |
| 8 | #include "audio_core/common/audio_renderer_parameter.h" | ||
| 9 | #include "audio_core/common/feature_support.h" | ||
| 10 | #include "audio_core/renderer/audio_device.h" | ||
| 11 | #include "audio_core/renderer/audio_renderer.h" | ||
| 12 | #include "audio_core/renderer/voice/voice_info.h" | ||
| 8 | #include "common/alignment.h" | 13 | #include "common/alignment.h" |
| 9 | #include "common/bit_util.h" | 14 | #include "common/bit_util.h" |
| 10 | #include "common/common_funcs.h" | 15 | #include "common/common_funcs.h" |
| @@ -13,91 +18,112 @@ | |||
| 13 | #include "core/core.h" | 18 | #include "core/core.h" |
| 14 | #include "core/hle/ipc_helpers.h" | 19 | #include "core/hle/ipc_helpers.h" |
| 15 | #include "core/hle/kernel/k_event.h" | 20 | #include "core/hle/kernel/k_event.h" |
| 21 | #include "core/hle/kernel/k_process.h" | ||
| 22 | #include "core/hle/kernel/k_transfer_memory.h" | ||
| 16 | #include "core/hle/service/audio/audren_u.h" | 23 | #include "core/hle/service/audio/audren_u.h" |
| 17 | #include "core/hle/service/audio/errors.h" | 24 | #include "core/hle/service/audio/errors.h" |
| 25 | #include "core/memory.h" | ||
| 26 | |||
| 27 | using namespace AudioCore::AudioRenderer; | ||
| 18 | 28 | ||
| 19 | namespace Service::Audio { | 29 | namespace Service::Audio { |
| 20 | 30 | ||
| 21 | class IAudioRenderer final : public ServiceFramework<IAudioRenderer> { | 31 | class IAudioRenderer final : public ServiceFramework<IAudioRenderer> { |
| 22 | public: | 32 | public: |
| 23 | explicit IAudioRenderer(Core::System& system_, | 33 | explicit IAudioRenderer(Core::System& system_, Manager& manager_, |
| 24 | const AudioCommon::AudioRendererParameter& audren_params, | 34 | AudioCore::AudioRendererParameterInternal& params, |
| 25 | const std::size_t instance_number) | 35 | Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size, |
| 36 | u32 process_handle, u64 applet_resource_user_id, s32 session_id) | ||
| 26 | : ServiceFramework{system_, "IAudioRenderer", ServiceThreadType::CreateNew}, | 37 | : ServiceFramework{system_, "IAudioRenderer", ServiceThreadType::CreateNew}, |
| 27 | service_context{system_, "IAudioRenderer"} { | 38 | service_context{system_, "IAudioRenderer"}, rendered_event{service_context.CreateEvent( |
| 39 | "IAudioRendererEvent")}, | ||
| 40 | manager{manager_}, impl{std::make_unique<Renderer>(system_, manager, rendered_event)} { | ||
| 28 | // clang-format off | 41 | // clang-format off |
| 29 | static const FunctionInfo functions[] = { | 42 | static const FunctionInfo functions[] = { |
| 30 | {0, &IAudioRenderer::GetSampleRate, "GetSampleRate"}, | 43 | {0, &IAudioRenderer::GetSampleRate, "GetSampleRate"}, |
| 31 | {1, &IAudioRenderer::GetSampleCount, "GetSampleCount"}, | 44 | {1, &IAudioRenderer::GetSampleCount, "GetSampleCount"}, |
| 32 | {2, &IAudioRenderer::GetMixBufferCount, "GetMixBufferCount"}, | 45 | {2, &IAudioRenderer::GetMixBufferCount, "GetMixBufferCount"}, |
| 33 | {3, &IAudioRenderer::GetState, "GetState"}, | 46 | {3, &IAudioRenderer::GetState, "GetState"}, |
| 34 | {4, &IAudioRenderer::RequestUpdateImpl, "RequestUpdate"}, | 47 | {4, &IAudioRenderer::RequestUpdate, "RequestUpdate"}, |
| 35 | {5, &IAudioRenderer::Start, "Start"}, | 48 | {5, &IAudioRenderer::Start, "Start"}, |
| 36 | {6, &IAudioRenderer::Stop, "Stop"}, | 49 | {6, &IAudioRenderer::Stop, "Stop"}, |
| 37 | {7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"}, | 50 | {7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"}, |
| 38 | {8, &IAudioRenderer::SetRenderingTimeLimit, "SetRenderingTimeLimit"}, | 51 | {8, &IAudioRenderer::SetRenderingTimeLimit, "SetRenderingTimeLimit"}, |
| 39 | {9, &IAudioRenderer::GetRenderingTimeLimit, "GetRenderingTimeLimit"}, | 52 | {9, &IAudioRenderer::GetRenderingTimeLimit, "GetRenderingTimeLimit"}, |
| 40 | {10, &IAudioRenderer::RequestUpdateImpl, "RequestUpdateAuto"}, | 53 | {10, nullptr, "RequestUpdateAuto"}, |
| 41 | {11, &IAudioRenderer::ExecuteAudioRendererRendering, "ExecuteAudioRendererRendering"}, | 54 | {11, nullptr, "ExecuteAudioRendererRendering"}, |
| 42 | }; | 55 | }; |
| 43 | // clang-format on | 56 | // clang-format on |
| 44 | RegisterHandlers(functions); | 57 | RegisterHandlers(functions); |
| 45 | 58 | ||
| 46 | system_event = service_context.CreateEvent("IAudioRenderer:SystemEvent"); | 59 | impl->Initialize(params, transfer_memory, transfer_memory_size, process_handle, |
| 47 | renderer = std::make_unique<AudioCore::AudioRenderer>( | 60 | applet_resource_user_id, session_id); |
| 48 | system.CoreTiming(), system.Memory(), audren_params, | ||
| 49 | [this]() { | ||
| 50 | const auto guard = LockService(); | ||
| 51 | system_event->GetWritableEvent().Signal(); | ||
| 52 | }, | ||
| 53 | instance_number); | ||
| 54 | } | 61 | } |
| 55 | 62 | ||
| 56 | ~IAudioRenderer() override { | 63 | ~IAudioRenderer() override { |
| 57 | service_context.CloseEvent(system_event); | 64 | impl->Finalize(); |
| 65 | service_context.CloseEvent(rendered_event); | ||
| 58 | } | 66 | } |
| 59 | 67 | ||
| 60 | private: | 68 | private: |
| 61 | void GetSampleRate(Kernel::HLERequestContext& ctx) { | 69 | void GetSampleRate(Kernel::HLERequestContext& ctx) { |
| 62 | LOG_DEBUG(Service_Audio, "called"); | 70 | const auto sample_rate{impl->GetSystem().GetSampleRate()}; |
| 71 | |||
| 72 | LOG_DEBUG(Service_Audio, "called. Sample rate {}", sample_rate); | ||
| 63 | 73 | ||
| 64 | IPC::ResponseBuilder rb{ctx, 3}; | 74 | IPC::ResponseBuilder rb{ctx, 3}; |
| 65 | rb.Push(ResultSuccess); | 75 | rb.Push(ResultSuccess); |
| 66 | rb.Push<u32>(renderer->GetSampleRate()); | 76 | rb.Push(sample_rate); |
| 67 | } | 77 | } |
| 68 | 78 | ||
| 69 | void GetSampleCount(Kernel::HLERequestContext& ctx) { | 79 | void GetSampleCount(Kernel::HLERequestContext& ctx) { |
| 70 | LOG_DEBUG(Service_Audio, "called"); | 80 | const auto sample_count{impl->GetSystem().GetSampleCount()}; |
| 81 | |||
| 82 | LOG_DEBUG(Service_Audio, "called. Sample count {}", sample_count); | ||
| 71 | 83 | ||
| 72 | IPC::ResponseBuilder rb{ctx, 3}; | 84 | IPC::ResponseBuilder rb{ctx, 3}; |
| 73 | rb.Push(ResultSuccess); | 85 | rb.Push(ResultSuccess); |
| 74 | rb.Push<u32>(renderer->GetSampleCount()); | 86 | rb.Push(sample_count); |
| 75 | } | 87 | } |
| 76 | 88 | ||
| 77 | void GetState(Kernel::HLERequestContext& ctx) { | 89 | void GetState(Kernel::HLERequestContext& ctx) { |
| 78 | LOG_DEBUG(Service_Audio, "called"); | 90 | const u32 state{!impl->GetSystem().IsActive()}; |
| 91 | |||
| 92 | LOG_DEBUG(Service_Audio, "called, state {}", state); | ||
| 79 | 93 | ||
| 80 | IPC::ResponseBuilder rb{ctx, 3}; | 94 | IPC::ResponseBuilder rb{ctx, 3}; |
| 81 | rb.Push(ResultSuccess); | 95 | rb.Push(ResultSuccess); |
| 82 | rb.Push<u32>(static_cast<u32>(renderer->GetStreamState())); | 96 | rb.Push(state); |
| 83 | } | 97 | } |
| 84 | 98 | ||
| 85 | void GetMixBufferCount(Kernel::HLERequestContext& ctx) { | 99 | void GetMixBufferCount(Kernel::HLERequestContext& ctx) { |
| 86 | LOG_DEBUG(Service_Audio, "called"); | 100 | LOG_DEBUG(Service_Audio, "called"); |
| 87 | 101 | ||
| 102 | const auto buffer_count{impl->GetSystem().GetMixBufferCount()}; | ||
| 103 | |||
| 88 | IPC::ResponseBuilder rb{ctx, 3}; | 104 | IPC::ResponseBuilder rb{ctx, 3}; |
| 89 | rb.Push(ResultSuccess); | 105 | rb.Push(ResultSuccess); |
| 90 | rb.Push<u32>(renderer->GetMixBufferCount()); | 106 | rb.Push(buffer_count); |
| 91 | } | 107 | } |
| 92 | 108 | ||
| 93 | void RequestUpdateImpl(Kernel::HLERequestContext& ctx) { | 109 | void RequestUpdate(Kernel::HLERequestContext& ctx) { |
| 94 | LOG_DEBUG(Service_Audio, "(STUBBED) called"); | 110 | LOG_TRACE(Service_Audio, "called"); |
| 95 | 111 | ||
| 96 | std::vector<u8> output_params(ctx.GetWriteBufferSize(), 0); | 112 | std::vector<u8> input{ctx.ReadBuffer(0)}; |
| 97 | auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params); | 113 | |
| 114 | // These buffers are written manually to avoid an issue with WriteBuffer throwing errors for | ||
| 115 | // checking size 0. Performance size is 0 for most games. | ||
| 116 | const auto buffers{ctx.BufferDescriptorB()}; | ||
| 117 | std::vector<u8> output(buffers[0].Size(), 0); | ||
| 118 | std::vector<u8> performance(buffers[1].Size(), 0); | ||
| 119 | |||
| 120 | auto result = impl->RequestUpdate(input, performance, output); | ||
| 98 | 121 | ||
| 99 | if (result.IsSuccess()) { | 122 | if (result.IsSuccess()) { |
| 100 | ctx.WriteBuffer(output_params); | 123 | ctx.WriteBufferB(output.data(), output.size(), 0); |
| 124 | ctx.WriteBufferB(performance.data(), performance.size(), 1); | ||
| 125 | } else { | ||
| 126 | LOG_ERROR(Service_Audio, "RequestUpdate failed error 0x{:02X}!", result.description); | ||
| 101 | } | 127 | } |
| 102 | 128 | ||
| 103 | IPC::ResponseBuilder rb{ctx, 2}; | 129 | IPC::ResponseBuilder rb{ctx, 2}; |
| @@ -105,38 +131,45 @@ private: | |||
| 105 | } | 131 | } |
| 106 | 132 | ||
| 107 | void Start(Kernel::HLERequestContext& ctx) { | 133 | void Start(Kernel::HLERequestContext& ctx) { |
| 108 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | 134 | LOG_DEBUG(Service_Audio, "called"); |
| 109 | 135 | ||
| 110 | const auto result = renderer->Start(); | 136 | impl->Start(); |
| 111 | 137 | ||
| 112 | IPC::ResponseBuilder rb{ctx, 2}; | 138 | IPC::ResponseBuilder rb{ctx, 2}; |
| 113 | rb.Push(result); | 139 | rb.Push(ResultSuccess); |
| 114 | } | 140 | } |
| 115 | 141 | ||
| 116 | void Stop(Kernel::HLERequestContext& ctx) { | 142 | void Stop(Kernel::HLERequestContext& ctx) { |
| 117 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | 143 | LOG_DEBUG(Service_Audio, "called"); |
| 118 | 144 | ||
| 119 | const auto result = renderer->Stop(); | 145 | impl->Stop(); |
| 120 | 146 | ||
| 121 | IPC::ResponseBuilder rb{ctx, 2}; | 147 | IPC::ResponseBuilder rb{ctx, 2}; |
| 122 | rb.Push(result); | 148 | rb.Push(ResultSuccess); |
| 123 | } | 149 | } |
| 124 | 150 | ||
| 125 | void QuerySystemEvent(Kernel::HLERequestContext& ctx) { | 151 | void QuerySystemEvent(Kernel::HLERequestContext& ctx) { |
| 126 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | 152 | LOG_DEBUG(Service_Audio, "called"); |
| 153 | |||
| 154 | if (impl->GetSystem().GetExecutionMode() == AudioCore::ExecutionMode::Manual) { | ||
| 155 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 156 | rb.Push(ERR_NOT_SUPPORTED); | ||
| 157 | return; | ||
| 158 | } | ||
| 127 | 159 | ||
| 128 | IPC::ResponseBuilder rb{ctx, 2, 1}; | 160 | IPC::ResponseBuilder rb{ctx, 2, 1}; |
| 129 | rb.Push(ResultSuccess); | 161 | rb.Push(ResultSuccess); |
| 130 | rb.PushCopyObjects(system_event->GetReadableEvent()); | 162 | rb.PushCopyObjects(rendered_event->GetReadableEvent()); |
| 131 | } | 163 | } |
| 132 | 164 | ||
| 133 | void SetRenderingTimeLimit(Kernel::HLERequestContext& ctx) { | 165 | void SetRenderingTimeLimit(Kernel::HLERequestContext& ctx) { |
| 166 | LOG_DEBUG(Service_Audio, "called"); | ||
| 167 | |||
| 134 | IPC::RequestParser rp{ctx}; | 168 | IPC::RequestParser rp{ctx}; |
| 135 | rendering_time_limit_percent = rp.Pop<u32>(); | 169 | auto limit = rp.PopRaw<u32>(); |
| 136 | LOG_DEBUG(Service_Audio, "called. rendering_time_limit_percent={}", | ||
| 137 | rendering_time_limit_percent); | ||
| 138 | 170 | ||
| 139 | ASSERT(rendering_time_limit_percent <= 100); | 171 | auto& system_ = impl->GetSystem(); |
| 172 | system_.SetRenderingTimeLimit(limit); | ||
| 140 | 173 | ||
| 141 | IPC::ResponseBuilder rb{ctx, 2}; | 174 | IPC::ResponseBuilder rb{ctx, 2}; |
| 142 | rb.Push(ResultSuccess); | 175 | rb.Push(ResultSuccess); |
| @@ -145,34 +178,34 @@ private: | |||
| 145 | void GetRenderingTimeLimit(Kernel::HLERequestContext& ctx) { | 178 | void GetRenderingTimeLimit(Kernel::HLERequestContext& ctx) { |
| 146 | LOG_DEBUG(Service_Audio, "called"); | 179 | LOG_DEBUG(Service_Audio, "called"); |
| 147 | 180 | ||
| 181 | auto& system_ = impl->GetSystem(); | ||
| 182 | auto time = system_.GetRenderingTimeLimit(); | ||
| 183 | |||
| 148 | IPC::ResponseBuilder rb{ctx, 3}; | 184 | IPC::ResponseBuilder rb{ctx, 3}; |
| 149 | rb.Push(ResultSuccess); | 185 | rb.Push(ResultSuccess); |
| 150 | rb.Push(rendering_time_limit_percent); | 186 | rb.Push(time); |
| 151 | } | 187 | } |
| 152 | 188 | ||
| 153 | void ExecuteAudioRendererRendering(Kernel::HLERequestContext& ctx) { | 189 | void ExecuteAudioRendererRendering(Kernel::HLERequestContext& ctx) { |
| 154 | LOG_DEBUG(Service_Audio, "called"); | 190 | LOG_DEBUG(Service_Audio, "called"); |
| 155 | |||
| 156 | // This service command currently only reports an unsupported operation | ||
| 157 | // error code, or aborts. Given that, we just always return an error | ||
| 158 | // code in this case. | ||
| 159 | |||
| 160 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 161 | rb.Push(ERR_NOT_SUPPORTED); | ||
| 162 | } | 191 | } |
| 163 | 192 | ||
| 164 | KernelHelpers::ServiceContext service_context; | 193 | KernelHelpers::ServiceContext service_context; |
| 165 | 194 | Kernel::KEvent* rendered_event; | |
| 166 | Kernel::KEvent* system_event; | 195 | Manager& manager; |
| 167 | std::unique_ptr<AudioCore::AudioRenderer> renderer; | 196 | std::unique_ptr<Renderer> impl; |
| 168 | u32 rendering_time_limit_percent = 100; | ||
| 169 | }; | 197 | }; |
| 170 | 198 | ||
| 171 | class IAudioDevice final : public ServiceFramework<IAudioDevice> { | 199 | class IAudioDevice final : public ServiceFramework<IAudioDevice> { |
| 200 | |||
| 172 | public: | 201 | public: |
| 173 | explicit IAudioDevice(Core::System& system_, Kernel::KEvent* buffer_event_, u32_le revision_) | 202 | explicit IAudioDevice(Core::System& system_, u64 applet_resource_user_id, u32 revision, |
| 174 | : ServiceFramework{system_, "IAudioDevice"}, buffer_event{buffer_event_}, revision{ | 203 | u32 device_num) |
| 175 | revision_} { | 204 | : ServiceFramework{system_, "IAudioDevice", ServiceThreadType::CreateNew}, |
| 205 | service_context{system_, "IAudioDevice"}, impl{std::make_unique<AudioDevice>( | ||
| 206 | system_, applet_resource_user_id, | ||
| 207 | revision)}, | ||
| 208 | event{service_context.CreateEvent(fmt::format("IAudioDeviceEvent-{}", device_num))} { | ||
| 176 | static const FunctionInfo functions[] = { | 209 | static const FunctionInfo functions[] = { |
| 177 | {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, | 210 | {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, |
| 178 | {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, | 211 | {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, |
| @@ -186,54 +219,45 @@ public: | |||
| 186 | {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"}, | 219 | {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"}, |
| 187 | {11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"}, | 220 | {11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"}, |
| 188 | {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"}, | 221 | {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"}, |
| 189 | {13, nullptr, "GetActiveAudioOutputDeviceName"}, | 222 | {13, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioOutputDeviceName"}, |
| 190 | {14, nullptr, "ListAudioOutputDeviceName"}, | 223 | {14, &IAudioDevice::ListAudioOutputDeviceName, "ListAudioOutputDeviceName"}, |
| 191 | }; | 224 | }; |
| 192 | RegisterHandlers(functions); | 225 | RegisterHandlers(functions); |
| 226 | |||
| 227 | event->GetWritableEvent().Signal(); | ||
| 193 | } | 228 | } |
| 194 | 229 | ||
| 195 | private: | 230 | ~IAudioDevice() override { |
| 196 | using AudioDeviceName = std::array<char, 256>; | 231 | service_context.CloseEvent(event); |
| 197 | static constexpr std::array<std::string_view, 4> audio_device_names{{ | 232 | } |
| 198 | "AudioStereoJackOutput", | ||
| 199 | "AudioBuiltInSpeakerOutput", | ||
| 200 | "AudioTvOutput", | ||
| 201 | "AudioUsbDeviceOutput", | ||
| 202 | }}; | ||
| 203 | enum class DeviceType { | ||
| 204 | AHUBHeadphones, | ||
| 205 | AHUBSpeakers, | ||
| 206 | HDA, | ||
| 207 | USBOutput, | ||
| 208 | }; | ||
| 209 | 233 | ||
| 234 | private: | ||
| 210 | void ListAudioDeviceName(Kernel::HLERequestContext& ctx) { | 235 | void ListAudioDeviceName(Kernel::HLERequestContext& ctx) { |
| 211 | LOG_DEBUG(Service_Audio, "called"); | 236 | const size_t in_count = ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName); |
| 212 | 237 | ||
| 213 | const bool usb_output_supported = | 238 | std::vector<AudioDevice::AudioDeviceName> out_names{}; |
| 214 | IsFeatureSupported(AudioFeatures::AudioUSBDeviceOutput, revision); | ||
| 215 | const std::size_t count = ctx.GetWriteBufferSize() / sizeof(AudioDeviceName); | ||
| 216 | 239 | ||
| 217 | std::vector<AudioDeviceName> name_buffer; | 240 | u32 out_count = impl->ListAudioDeviceName(out_names, in_count); |
| 218 | name_buffer.reserve(audio_device_names.size()); | ||
| 219 | 241 | ||
| 220 | for (std::size_t i = 0; i < count && i < audio_device_names.size(); i++) { | 242 | std::string out{}; |
| 221 | const auto type = static_cast<DeviceType>(i); | 243 | for (u32 i = 0; i < out_count; i++) { |
| 222 | 244 | std::string a{}; | |
| 223 | if (!usb_output_supported && type == DeviceType::USBOutput) { | 245 | u32 j = 0; |
| 224 | continue; | 246 | while (out_names[i].name[j] != '\0') { |
| 247 | a += out_names[i].name[j]; | ||
| 248 | j++; | ||
| 225 | } | 249 | } |
| 226 | 250 | out += "\n\t" + a; | |
| 227 | const auto& device_name = audio_device_names[i]; | ||
| 228 | auto& entry = name_buffer.emplace_back(); | ||
| 229 | device_name.copy(entry.data(), device_name.size()); | ||
| 230 | } | 251 | } |
| 231 | 252 | ||
| 232 | ctx.WriteBuffer(name_buffer); | 253 | LOG_DEBUG(Service_Audio, "called.\nNames={}", out); |
| 233 | 254 | ||
| 234 | IPC::ResponseBuilder rb{ctx, 3}; | 255 | IPC::ResponseBuilder rb{ctx, 3}; |
| 256 | |||
| 257 | ctx.WriteBuffer(out_names); | ||
| 258 | |||
| 235 | rb.Push(ResultSuccess); | 259 | rb.Push(ResultSuccess); |
| 236 | rb.Push(static_cast<u32>(name_buffer.size())); | 260 | rb.Push(out_count); |
| 237 | } | 261 | } |
| 238 | 262 | ||
| 239 | void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) { | 263 | void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) { |
| @@ -243,7 +267,11 @@ private: | |||
| 243 | const auto device_name_buffer = ctx.ReadBuffer(); | 267 | const auto device_name_buffer = ctx.ReadBuffer(); |
| 244 | const std::string name = Common::StringFromBuffer(device_name_buffer); | 268 | const std::string name = Common::StringFromBuffer(device_name_buffer); |
| 245 | 269 | ||
| 246 | LOG_WARNING(Service_Audio, "(STUBBED) called. name={}, volume={}", name, volume); | 270 | LOG_DEBUG(Service_Audio, "called. name={}, volume={}", name, volume); |
| 271 | |||
| 272 | if (name == "AudioTvOutput") { | ||
| 273 | impl->SetDeviceVolumes(volume); | ||
| 274 | } | ||
| 247 | 275 | ||
| 248 | IPC::ResponseBuilder rb{ctx, 2}; | 276 | IPC::ResponseBuilder rb{ctx, 2}; |
| 249 | rb.Push(ResultSuccess); | 277 | rb.Push(ResultSuccess); |
| @@ -253,53 +281,60 @@ private: | |||
| 253 | const auto device_name_buffer = ctx.ReadBuffer(); | 281 | const auto device_name_buffer = ctx.ReadBuffer(); |
| 254 | const std::string name = Common::StringFromBuffer(device_name_buffer); | 282 | const std::string name = Common::StringFromBuffer(device_name_buffer); |
| 255 | 283 | ||
| 256 | LOG_WARNING(Service_Audio, "(STUBBED) called. name={}", name); | 284 | LOG_DEBUG(Service_Audio, "called. Name={}", name); |
| 285 | |||
| 286 | f32 volume{1.0f}; | ||
| 287 | if (name == "AudioTvOutput") { | ||
| 288 | volume = impl->GetDeviceVolume(name); | ||
| 289 | } | ||
| 257 | 290 | ||
| 258 | IPC::ResponseBuilder rb{ctx, 3}; | 291 | IPC::ResponseBuilder rb{ctx, 3}; |
| 259 | rb.Push(ResultSuccess); | 292 | rb.Push(ResultSuccess); |
| 260 | rb.Push(1.0f); | 293 | rb.Push(volume); |
| 261 | } | 294 | } |
| 262 | 295 | ||
| 263 | void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) { | 296 | void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) { |
| 264 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | 297 | const auto write_size = ctx.GetWriteBufferSize() / sizeof(char); |
| 298 | std::string out_name{"AudioTvOutput"}; | ||
| 265 | 299 | ||
| 266 | // Currently set to always be TV audio output. | 300 | LOG_DEBUG(Service_Audio, "(STUBBED) called. Name={}", out_name); |
| 267 | const auto& device_name = audio_device_names[2]; | ||
| 268 | 301 | ||
| 269 | AudioDeviceName out_device_name{}; | 302 | out_name.resize(write_size); |
| 270 | device_name.copy(out_device_name.data(), device_name.size()); | ||
| 271 | 303 | ||
| 272 | ctx.WriteBuffer(out_device_name); | 304 | ctx.WriteBuffer(out_name); |
| 273 | 305 | ||
| 274 | IPC::ResponseBuilder rb{ctx, 2}; | 306 | IPC::ResponseBuilder rb{ctx, 2}; |
| 275 | rb.Push(ResultSuccess); | 307 | rb.Push(ResultSuccess); |
| 276 | } | 308 | } |
| 277 | 309 | ||
| 278 | void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) { | 310 | void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) { |
| 279 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | 311 | LOG_DEBUG(Service_Audio, "(STUBBED) called"); |
| 280 | 312 | ||
| 281 | buffer_event->GetWritableEvent().Signal(); | 313 | event->GetWritableEvent().Signal(); |
| 282 | 314 | ||
| 283 | IPC::ResponseBuilder rb{ctx, 2, 1}; | 315 | IPC::ResponseBuilder rb{ctx, 2, 1}; |
| 284 | rb.Push(ResultSuccess); | 316 | rb.Push(ResultSuccess); |
| 285 | rb.PushCopyObjects(buffer_event->GetReadableEvent()); | 317 | rb.PushCopyObjects(event->GetReadableEvent()); |
| 286 | } | 318 | } |
| 287 | 319 | ||
| 288 | void GetActiveChannelCount(Kernel::HLERequestContext& ctx) { | 320 | void GetActiveChannelCount(Kernel::HLERequestContext& ctx) { |
| 289 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | 321 | const auto& sink{system.AudioCore().GetOutputSink()}; |
| 322 | u32 channel_count{sink.GetDeviceChannels()}; | ||
| 323 | |||
| 324 | LOG_DEBUG(Service_Audio, "(STUBBED) called. Channels={}", channel_count); | ||
| 290 | 325 | ||
| 291 | IPC::ResponseBuilder rb{ctx, 3}; | 326 | IPC::ResponseBuilder rb{ctx, 3}; |
| 327 | |||
| 292 | rb.Push(ResultSuccess); | 328 | rb.Push(ResultSuccess); |
| 293 | rb.Push<u32>(2); | 329 | rb.Push<u32>(channel_count); |
| 294 | } | 330 | } |
| 295 | 331 | ||
| 296 | // Should be similar to QueryAudioDeviceOutputEvent | ||
| 297 | void QueryAudioDeviceInputEvent(Kernel::HLERequestContext& ctx) { | 332 | void QueryAudioDeviceInputEvent(Kernel::HLERequestContext& ctx) { |
| 298 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | 333 | LOG_DEBUG(Service_Audio, "(STUBBED) called"); |
| 299 | 334 | ||
| 300 | IPC::ResponseBuilder rb{ctx, 2, 1}; | 335 | IPC::ResponseBuilder rb{ctx, 2, 1}; |
| 301 | rb.Push(ResultSuccess); | 336 | rb.Push(ResultSuccess); |
| 302 | rb.PushCopyObjects(buffer_event->GetReadableEvent()); | 337 | rb.PushCopyObjects(event->GetReadableEvent()); |
| 303 | } | 338 | } |
| 304 | 339 | ||
| 305 | void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) { | 340 | void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) { |
| @@ -307,402 +342,167 @@ private: | |||
| 307 | 342 | ||
| 308 | IPC::ResponseBuilder rb{ctx, 2, 1}; | 343 | IPC::ResponseBuilder rb{ctx, 2, 1}; |
| 309 | rb.Push(ResultSuccess); | 344 | rb.Push(ResultSuccess); |
| 310 | rb.PushCopyObjects(buffer_event->GetReadableEvent()); | 345 | rb.PushCopyObjects(event->GetReadableEvent()); |
| 311 | } | 346 | } |
| 312 | 347 | ||
| 313 | Kernel::KEvent* buffer_event; | 348 | void ListAudioOutputDeviceName(Kernel::HLERequestContext& ctx) { |
| 314 | u32_le revision = 0; | 349 | const size_t in_count = ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName); |
| 350 | |||
| 351 | std::vector<AudioDevice::AudioDeviceName> out_names{}; | ||
| 352 | |||
| 353 | u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count); | ||
| 354 | |||
| 355 | std::string out{}; | ||
| 356 | for (u32 i = 0; i < out_count; i++) { | ||
| 357 | std::string a{}; | ||
| 358 | u32 j = 0; | ||
| 359 | while (out_names[i].name[j] != '\0') { | ||
| 360 | a += out_names[i].name[j]; | ||
| 361 | j++; | ||
| 362 | } | ||
| 363 | out += "\n\t" + a; | ||
| 364 | } | ||
| 365 | |||
| 366 | LOG_DEBUG(Service_Audio, "called.\nNames={}", out); | ||
| 367 | |||
| 368 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 369 | |||
| 370 | ctx.WriteBuffer(out_names); | ||
| 371 | |||
| 372 | rb.Push(ResultSuccess); | ||
| 373 | rb.Push(out_count); | ||
| 374 | } | ||
| 375 | |||
| 376 | KernelHelpers::ServiceContext service_context; | ||
| 377 | std::unique_ptr<AudioDevice> impl; | ||
| 378 | Kernel::KEvent* event; | ||
| 315 | }; | 379 | }; |
| 316 | 380 | ||
| 317 | AudRenU::AudRenU(Core::System& system_) | 381 | AudRenU::AudRenU(Core::System& system_) |
| 318 | : ServiceFramework{system_, "audren:u"}, service_context{system_, "audren:u"} { | 382 | : ServiceFramework{system_, "audren:u", ServiceThreadType::CreateNew}, |
| 383 | service_context{system_, "audren:u"}, impl{std::make_unique<Manager>(system_)} { | ||
| 319 | // clang-format off | 384 | // clang-format off |
| 320 | static const FunctionInfo functions[] = { | 385 | static const FunctionInfo functions[] = { |
| 321 | {0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"}, | 386 | {0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"}, |
| 322 | {1, &AudRenU::GetAudioRendererWorkBufferSize, "GetWorkBufferSize"}, | 387 | {1, &AudRenU::GetWorkBufferSize, "GetWorkBufferSize"}, |
| 323 | {2, &AudRenU::GetAudioDeviceService, "GetAudioDeviceService"}, | 388 | {2, &AudRenU::GetAudioDeviceService, "GetAudioDeviceService"}, |
| 324 | {3, &AudRenU::OpenAudioRendererForManualExecution, "OpenAudioRendererForManualExecution"}, | 389 | {3, nullptr, "OpenAudioRendererForManualExecution"}, |
| 325 | {4, &AudRenU::GetAudioDeviceServiceWithRevisionInfo, "GetAudioDeviceServiceWithRevisionInfo"}, | 390 | {4, &AudRenU::GetAudioDeviceServiceWithRevisionInfo, "GetAudioDeviceServiceWithRevisionInfo"}, |
| 326 | }; | 391 | }; |
| 327 | // clang-format on | 392 | // clang-format on |
| 328 | 393 | ||
| 329 | RegisterHandlers(functions); | 394 | RegisterHandlers(functions); |
| 330 | |||
| 331 | buffer_event = service_context.CreateEvent("IAudioOutBufferReleasedEvent"); | ||
| 332 | } | 395 | } |
| 333 | 396 | ||
| 334 | AudRenU::~AudRenU() { | 397 | AudRenU::~AudRenU() = default; |
| 335 | service_context.CloseEvent(buffer_event); | ||
| 336 | } | ||
| 337 | 398 | ||
| 338 | void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { | 399 | void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { |
| 339 | LOG_DEBUG(Service_Audio, "called"); | 400 | IPC::RequestParser rp{ctx}; |
| 340 | |||
| 341 | OpenAudioRendererImpl(ctx); | ||
| 342 | } | ||
| 343 | |||
| 344 | static u64 CalculateNumPerformanceEntries(const AudioCommon::AudioRendererParameter& params) { | ||
| 345 | // +1 represents the final mix. | ||
| 346 | return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count + | ||
| 347 | 1; | ||
| 348 | } | ||
| 349 | |||
| 350 | void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { | ||
| 351 | LOG_DEBUG(Service_Audio, "called"); | ||
| 352 | |||
| 353 | // Several calculations below align the sizes being calculated | ||
| 354 | // onto a 64 byte boundary. | ||
| 355 | static constexpr u64 buffer_alignment_size = 64; | ||
| 356 | |||
| 357 | // Some calculations that calculate portions of the buffer | ||
| 358 | // that will contain information, on the other hand, align | ||
| 359 | // the result of some of their calcularions on a 16 byte boundary. | ||
| 360 | static constexpr u64 info_field_alignment_size = 16; | ||
| 361 | |||
| 362 | // Maximum detail entries that may exist at one time for performance | ||
| 363 | // frame statistics. | ||
| 364 | static constexpr u64 max_perf_detail_entries = 100; | ||
| 365 | |||
| 366 | // Size of the data structure representing the bulk of the voice-related state. | ||
| 367 | static constexpr u64 voice_state_size_bytes = 0x100; | ||
| 368 | |||
| 369 | // Size of the upsampler manager data structure | ||
| 370 | constexpr u64 upsampler_manager_size = 0x48; | ||
| 371 | |||
| 372 | // Calculates the part of the size that relates to mix buffers. | ||
| 373 | const auto calculate_mix_buffer_sizes = [](const AudioCommon::AudioRendererParameter& params) { | ||
| 374 | // As of 8.0.0 this is the maximum on voice channels. | ||
| 375 | constexpr u64 max_voice_channels = 6; | ||
| 376 | |||
| 377 | // The service expects the sample_count member of the parameters to either be | ||
| 378 | // a value of 160 or 240, so the maximum sample count is assumed in order | ||
| 379 | // to adequately handle all values at runtime. | ||
| 380 | constexpr u64 default_max_sample_count = 240; | ||
| 381 | |||
| 382 | const u64 total_mix_buffers = params.mix_buffer_count + max_voice_channels; | ||
| 383 | |||
| 384 | u64 size = 0; | ||
| 385 | size += total_mix_buffers * (sizeof(s32) * params.sample_count); | ||
| 386 | size += total_mix_buffers * (sizeof(s32) * default_max_sample_count); | ||
| 387 | size += u64{params.submix_count} + params.sink_count; | ||
| 388 | size = Common::AlignUp(size, buffer_alignment_size); | ||
| 389 | size += Common::AlignUp(params.unknown_30, buffer_alignment_size); | ||
| 390 | size += Common::AlignUp(sizeof(s32) * params.mix_buffer_count, buffer_alignment_size); | ||
| 391 | return size; | ||
| 392 | }; | ||
| 393 | |||
| 394 | // Calculates the portion of the size related to the mix data (and the sorting thereof). | ||
| 395 | const auto calculate_mix_info_size = [](const AudioCommon::AudioRendererParameter& params) { | ||
| 396 | // The size of the mixing info data structure. | ||
| 397 | constexpr u64 mix_info_size = 0x940; | ||
| 398 | |||
| 399 | // Consists of total submixes with the final mix included. | ||
| 400 | const u64 total_mix_count = u64{params.submix_count} + 1; | ||
| 401 | |||
| 402 | // The total number of effects that may be available to the audio renderer at any time. | ||
| 403 | constexpr u64 max_effects = 256; | ||
| 404 | |||
| 405 | // Calculates the part of the size related to the audio node state. | ||
| 406 | // This will only be used if the audio revision supports the splitter. | ||
| 407 | const auto calculate_node_state_size = [](std::size_t num_nodes) { | ||
| 408 | // Internally within a nodestate, it appears to use a data structure | ||
| 409 | // similar to a std::bitset<64> twice. | ||
| 410 | constexpr u64 bit_size = Common::BitSize<u64>(); | ||
| 411 | constexpr u64 num_bitsets = 2; | ||
| 412 | |||
| 413 | // Node state instances have three states internally for performing | ||
| 414 | // depth-first searches of nodes. Initialized, Found, and Done Sorting. | ||
| 415 | constexpr u64 num_states = 3; | ||
| 416 | |||
| 417 | u64 size = 0; | ||
| 418 | size += (num_nodes * num_nodes) * sizeof(s32); | ||
| 419 | size += num_states * (num_nodes * sizeof(s32)); | ||
| 420 | size += num_bitsets * (Common::AlignUp(num_nodes, bit_size) / Common::BitSize<u8>()); | ||
| 421 | return size; | ||
| 422 | }; | ||
| 423 | |||
| 424 | // Calculates the part of the size related to the adjacency (aka edge) matrix. | ||
| 425 | const auto calculate_edge_matrix_size = [](std::size_t num_nodes) { | ||
| 426 | return (num_nodes * num_nodes) * sizeof(s32); | ||
| 427 | }; | ||
| 428 | |||
| 429 | u64 size = 0; | ||
| 430 | size += Common::AlignUp(sizeof(void*) * total_mix_count, info_field_alignment_size); | ||
| 431 | size += Common::AlignUp(mix_info_size * total_mix_count, info_field_alignment_size); | ||
| 432 | size += Common::AlignUp(sizeof(s32) * max_effects * params.submix_count, | ||
| 433 | info_field_alignment_size); | ||
| 434 | |||
| 435 | if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) { | ||
| 436 | size += Common::AlignUp(calculate_node_state_size(total_mix_count) + | ||
| 437 | calculate_edge_matrix_size(total_mix_count), | ||
| 438 | info_field_alignment_size); | ||
| 439 | } | ||
| 440 | |||
| 441 | return size; | ||
| 442 | }; | ||
| 443 | |||
| 444 | // Calculates the part of the size related to voice channel info. | ||
| 445 | const auto calculate_voice_info_size = [](const AudioCommon::AudioRendererParameter& params) { | ||
| 446 | constexpr u64 voice_info_size = 0x220; | ||
| 447 | constexpr u64 voice_resource_size = 0xD0; | ||
| 448 | |||
| 449 | u64 size = 0; | ||
| 450 | size += Common::AlignUp(sizeof(void*) * params.voice_count, info_field_alignment_size); | ||
| 451 | size += Common::AlignUp(voice_info_size * params.voice_count, info_field_alignment_size); | ||
| 452 | size += | ||
| 453 | Common::AlignUp(voice_resource_size * params.voice_count, info_field_alignment_size); | ||
| 454 | size += | ||
| 455 | Common::AlignUp(voice_state_size_bytes * params.voice_count, info_field_alignment_size); | ||
| 456 | return size; | ||
| 457 | }; | ||
| 458 | |||
| 459 | // Calculates the part of the size related to memory pools. | ||
| 460 | const auto calculate_memory_pools_size = [](const AudioCommon::AudioRendererParameter& params) { | ||
| 461 | const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count); | ||
| 462 | const u64 memory_pool_info_size = 0x20; | ||
| 463 | return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size); | ||
| 464 | }; | ||
| 465 | |||
| 466 | // Calculates the part of the size related to the splitter context. | ||
| 467 | const auto calculate_splitter_context_size = | ||
| 468 | [](const AudioCommon::AudioRendererParameter& params) -> u64 { | ||
| 469 | if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) { | ||
| 470 | return 0; | ||
| 471 | } | ||
| 472 | |||
| 473 | constexpr u64 splitter_info_size = 0x20; | ||
| 474 | constexpr u64 splitter_destination_data_size = 0xE0; | ||
| 475 | |||
| 476 | u64 size = 0; | ||
| 477 | size += params.num_splitter_send_channels; | ||
| 478 | size += | ||
| 479 | Common::AlignUp(splitter_info_size * params.splitter_count, info_field_alignment_size); | ||
| 480 | size += Common::AlignUp(splitter_destination_data_size * params.num_splitter_send_channels, | ||
| 481 | info_field_alignment_size); | ||
| 482 | |||
| 483 | return size; | ||
| 484 | }; | ||
| 485 | |||
| 486 | // Calculates the part of the size related to the upsampler info. | ||
| 487 | const auto calculate_upsampler_info_size = | ||
| 488 | [](const AudioCommon::AudioRendererParameter& params) { | ||
| 489 | constexpr u64 upsampler_info_size = 0x280; | ||
| 490 | // Yes, using the buffer size over info alignment size is intentional here. | ||
| 491 | return Common::AlignUp(upsampler_info_size * | ||
| 492 | (u64{params.submix_count} + params.sink_count), | ||
| 493 | buffer_alignment_size); | ||
| 494 | }; | ||
| 495 | |||
| 496 | // Calculates the part of the size related to effect info. | ||
| 497 | const auto calculate_effect_info_size = [](const AudioCommon::AudioRendererParameter& params) { | ||
| 498 | constexpr u64 effect_info_size = 0x2B0; | ||
| 499 | return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size); | ||
| 500 | }; | ||
| 501 | |||
| 502 | // Calculates the part of the size related to audio sink info. | ||
| 503 | const auto calculate_sink_info_size = [](const AudioCommon::AudioRendererParameter& params) { | ||
| 504 | const u64 sink_info_size = 0x170; | ||
| 505 | return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size); | ||
| 506 | }; | ||
| 507 | |||
| 508 | // Calculates the part of the size related to voice state info. | ||
| 509 | const auto calculate_voice_state_size = [](const AudioCommon::AudioRendererParameter& params) { | ||
| 510 | const u64 voice_state_size = 0x100; | ||
| 511 | const u64 additional_size = buffer_alignment_size - 1; | ||
| 512 | return Common::AlignUp(voice_state_size * params.voice_count + additional_size, | ||
| 513 | info_field_alignment_size); | ||
| 514 | }; | ||
| 515 | |||
| 516 | // Calculates the part of the size related to performance statistics. | ||
| 517 | const auto calculate_perf_size = [](const AudioCommon::AudioRendererParameter& params) { | ||
| 518 | // Extra size value appended to the end of the calculation. | ||
| 519 | constexpr u64 appended = 128; | ||
| 520 | |||
| 521 | // Whether or not we assume the newer version of performance metrics data structures. | ||
| 522 | const bool is_v2 = | ||
| 523 | IsFeatureSupported(AudioFeatures::PerformanceMetricsVersion2, params.revision); | ||
| 524 | |||
| 525 | // Data structure sizes | ||
| 526 | constexpr u64 perf_statistics_size = 0x0C; | ||
| 527 | const u64 header_size = is_v2 ? 0x30 : 0x18; | ||
| 528 | const u64 entry_size = is_v2 ? 0x18 : 0x10; | ||
| 529 | const u64 detail_size = is_v2 ? 0x18 : 0x10; | ||
| 530 | |||
| 531 | const u64 entry_count = CalculateNumPerformanceEntries(params); | ||
| 532 | const u64 size_per_frame = | ||
| 533 | header_size + (entry_size * entry_count) + (detail_size * max_perf_detail_entries); | ||
| 534 | |||
| 535 | u64 size = 0; | ||
| 536 | size += Common::AlignUp(size_per_frame * params.performance_frame_count + 1, | ||
| 537 | buffer_alignment_size); | ||
| 538 | size += Common::AlignUp(perf_statistics_size, buffer_alignment_size); | ||
| 539 | size += appended; | ||
| 540 | return size; | ||
| 541 | }; | ||
| 542 | |||
| 543 | // Calculates the part of the size that relates to the audio command buffer. | ||
| 544 | const auto calculate_command_buffer_size = | ||
| 545 | [](const AudioCommon::AudioRendererParameter& params) { | ||
| 546 | constexpr u64 alignment = (buffer_alignment_size - 1) * 2; | ||
| 547 | |||
| 548 | if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) { | ||
| 549 | constexpr u64 command_buffer_size = 0x18000; | ||
| 550 | |||
| 551 | return command_buffer_size + alignment; | ||
| 552 | } | ||
| 553 | |||
| 554 | // When the variadic command buffer is supported, this means | ||
| 555 | // the command generator for the audio renderer can issue commands | ||
| 556 | // that are (as one would expect), variable in size. So what we need to do | ||
| 557 | // is determine the maximum possible size for a few command data structures | ||
| 558 | // then multiply them by the amount of present commands indicated by the given | ||
| 559 | // respective audio parameters. | ||
| 560 | |||
| 561 | constexpr u64 max_biquad_filters = 2; | ||
| 562 | constexpr u64 max_mix_buffers = 24; | ||
| 563 | |||
| 564 | constexpr u64 biquad_filter_command_size = 0x2C; | ||
| 565 | |||
| 566 | constexpr u64 depop_mix_command_size = 0x24; | ||
| 567 | constexpr u64 depop_setup_command_size = 0x50; | ||
| 568 | |||
| 569 | constexpr u64 effect_command_max_size = 0x540; | ||
| 570 | |||
| 571 | constexpr u64 mix_command_size = 0x1C; | ||
| 572 | constexpr u64 mix_ramp_command_size = 0x24; | ||
| 573 | constexpr u64 mix_ramp_grouped_command_size = 0x13C; | ||
| 574 | |||
| 575 | constexpr u64 perf_command_size = 0x28; | ||
| 576 | |||
| 577 | constexpr u64 sink_command_size = 0x130; | ||
| 578 | |||
| 579 | constexpr u64 submix_command_max_size = | ||
| 580 | depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers; | ||
| 581 | |||
| 582 | constexpr u64 volume_command_size = 0x1C; | ||
| 583 | constexpr u64 volume_ramp_command_size = 0x20; | ||
| 584 | |||
| 585 | constexpr u64 voice_biquad_filter_command_size = | ||
| 586 | biquad_filter_command_size * max_biquad_filters; | ||
| 587 | constexpr u64 voice_data_command_size = 0x9C; | ||
| 588 | const u64 voice_command_max_size = | ||
| 589 | (params.splitter_count * depop_setup_command_size) + | ||
| 590 | (voice_data_command_size + voice_biquad_filter_command_size + | ||
| 591 | volume_ramp_command_size + mix_ramp_grouped_command_size); | ||
| 592 | 401 | ||
| 593 | // Now calculate the individual elements that comprise the size and add them together. | 402 | AudioCore::AudioRendererParameterInternal params; |
| 594 | const u64 effect_commands_size = params.effect_count * effect_command_max_size; | 403 | rp.PopRaw<AudioCore::AudioRendererParameterInternal>(params); |
| 404 | auto transfer_memory_handle = ctx.GetCopyHandle(0); | ||
| 405 | auto process_handle = ctx.GetCopyHandle(1); | ||
| 406 | auto transfer_memory_size = rp.Pop<u64>(); | ||
| 407 | auto applet_resource_user_id = rp.Pop<u64>(); | ||
| 595 | 408 | ||
| 596 | const u64 final_mix_commands_size = | 409 | if (impl->GetSessionCount() + 1 > AudioCore::MaxRendererSessions) { |
| 597 | depop_mix_command_size + volume_command_size * max_mix_buffers; | 410 | LOG_ERROR(Service_Audio, "Too many AudioRenderer sessions open!"); |
| 411 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 412 | rb.Push(ERR_MAXIMUM_SESSIONS_REACHED); | ||
| 413 | return; | ||
| 414 | } | ||
| 598 | 415 | ||
| 599 | const u64 perf_commands_size = | 416 | const auto& handle_table{system.CurrentProcess()->GetHandleTable()}; |
| 600 | perf_command_size * | 417 | auto process{handle_table.GetObject<Kernel::KProcess>(process_handle)}; |
| 601 | (CalculateNumPerformanceEntries(params) + max_perf_detail_entries); | 418 | auto transfer_memory{ |
| 419 | process->GetHandleTable().GetObject<Kernel::KTransferMemory>(transfer_memory_handle)}; | ||
| 602 | 420 | ||
| 603 | const u64 sink_commands_size = params.sink_count * sink_command_size; | 421 | const auto session_id{impl->GetSessionId()}; |
| 422 | if (session_id == -1) { | ||
| 423 | LOG_ERROR(Service_Audio, "Tried to open a session that's already in use!"); | ||
| 424 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 425 | rb.Push(ERR_MAXIMUM_SESSIONS_REACHED); | ||
| 426 | return; | ||
| 427 | } | ||
| 604 | 428 | ||
| 605 | const u64 splitter_commands_size = | 429 | LOG_DEBUG(Service_Audio, "Opened new AudioRenderer session {} sessions open {}", session_id, |
| 606 | params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size; | 430 | impl->GetSessionCount()); |
| 607 | 431 | ||
| 608 | const u64 submix_commands_size = params.submix_count * submix_command_max_size; | 432 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 433 | rb.Push(ResultSuccess); | ||
| 434 | rb.PushIpcInterface<IAudioRenderer>(system, *impl, params, transfer_memory.GetPointerUnsafe(), | ||
| 435 | transfer_memory_size, process_handle, | ||
| 436 | applet_resource_user_id, session_id); | ||
| 437 | } | ||
| 609 | 438 | ||
| 610 | const u64 voice_commands_size = params.voice_count * voice_command_max_size; | 439 | void AudRenU::GetWorkBufferSize(Kernel::HLERequestContext& ctx) { |
| 611 | 440 | AudioCore::AudioRendererParameterInternal params; | |
| 612 | return effect_commands_size + final_mix_commands_size + perf_commands_size + | ||
| 613 | sink_commands_size + splitter_commands_size + submix_commands_size + | ||
| 614 | voice_commands_size + alignment; | ||
| 615 | }; | ||
| 616 | 441 | ||
| 617 | IPC::RequestParser rp{ctx}; | 442 | IPC::RequestParser rp{ctx}; |
| 618 | const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>(); | 443 | rp.PopRaw<AudioCore::AudioRendererParameterInternal>(params); |
| 619 | 444 | ||
| 620 | u64 size = 0; | 445 | u64 size{0}; |
| 621 | size += calculate_mix_buffer_sizes(params); | 446 | auto result = impl->GetWorkBufferSize(params, size); |
| 622 | size += calculate_mix_info_size(params); | 447 | |
| 623 | size += calculate_voice_info_size(params); | 448 | std::string output_info{}; |
| 624 | size += upsampler_manager_size; | 449 | output_info += fmt::format("\tRevision {}", AudioCore::GetRevisionNum(params.revision)); |
| 625 | size += calculate_memory_pools_size(params); | 450 | output_info += |
| 626 | size += calculate_splitter_context_size(params); | 451 | fmt::format("\n\tSample Rate {}, Sample Count {}", params.sample_rate, params.sample_count); |
| 627 | 452 | output_info += fmt::format("\n\tExecution Mode {}, Voice Drop Enabled {}", | |
| 628 | size = Common::AlignUp(size, buffer_alignment_size); | 453 | static_cast<u32>(params.execution_mode), params.voice_drop_enabled); |
| 629 | 454 | output_info += fmt::format( | |
| 630 | size += calculate_upsampler_info_size(params); | 455 | "\n\tSizes: Effects {:04X}, Mixes {:04X}, Sinks {:04X}, Submixes {:04X}, Splitter Infos " |
| 631 | size += calculate_effect_info_size(params); | 456 | "{:04X}, Splitter Destinations {:04X}, Voices {:04X}, Performance Frames {:04X} External " |
| 632 | size += calculate_sink_info_size(params); | 457 | "Context {:04X}", |
| 633 | size += calculate_voice_state_size(params); | 458 | params.effects, params.mixes, params.sinks, params.sub_mixes, params.splitter_infos, |
| 634 | size += calculate_perf_size(params); | 459 | params.splitter_destinations, params.voices, params.perf_frames, |
| 635 | size += calculate_command_buffer_size(params); | 460 | params.external_context_size); |
| 636 | 461 | ||
| 637 | // finally, 4KB page align the size, and we're done. | 462 | LOG_DEBUG(Service_Audio, "called.\nInput params:\n{}\nOutput params:\n\tWorkbuffer size {:08X}", |
| 638 | size = Common::AlignUp(size, 4096); | 463 | output_info, size); |
| 639 | 464 | ||
| 640 | IPC::ResponseBuilder rb{ctx, 4}; | 465 | IPC::ResponseBuilder rb{ctx, 4}; |
| 641 | rb.Push(ResultSuccess); | 466 | rb.Push(result); |
| 642 | rb.Push<u64>(size); | 467 | rb.Push<u64>(size); |
| 643 | |||
| 644 | LOG_DEBUG(Service_Audio, "buffer_size=0x{:X}", size); | ||
| 645 | } | 468 | } |
| 646 | 469 | ||
| 647 | void AudRenU::GetAudioDeviceService(Kernel::HLERequestContext& ctx) { | 470 | void AudRenU::GetAudioDeviceService(Kernel::HLERequestContext& ctx) { |
| 648 | IPC::RequestParser rp{ctx}; | 471 | IPC::RequestParser rp{ctx}; |
| 649 | const u64 aruid = rp.Pop<u64>(); | ||
| 650 | 472 | ||
| 651 | LOG_DEBUG(Service_Audio, "called. aruid={:016X}", aruid); | 473 | const auto applet_resource_user_id = rp.Pop<u64>(); |
| 474 | |||
| 475 | LOG_DEBUG(Service_Audio, "called. Applet resource id {}", applet_resource_user_id); | ||
| 652 | 476 | ||
| 653 | // Revisionless variant of GetAudioDeviceServiceWithRevisionInfo that | ||
| 654 | // always assumes the initial release revision (REV1). | ||
| 655 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 477 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 478 | |||
| 656 | rb.Push(ResultSuccess); | 479 | rb.Push(ResultSuccess); |
| 657 | rb.PushIpcInterface<IAudioDevice>(system, buffer_event, Common::MakeMagic('R', 'E', 'V', '1')); | 480 | rb.PushIpcInterface<IAudioDevice>(system, applet_resource_user_id, |
| 481 | ::Common::MakeMagic('R', 'E', 'V', '1'), num_audio_devices++); | ||
| 658 | } | 482 | } |
| 659 | 483 | ||
| 660 | void AudRenU::OpenAudioRendererForManualExecution(Kernel::HLERequestContext& ctx) { | 484 | void AudRenU::OpenAudioRendererForManualExecution(Kernel::HLERequestContext& ctx) { |
| 661 | LOG_DEBUG(Service_Audio, "called"); | 485 | LOG_DEBUG(Service_Audio, "called"); |
| 662 | |||
| 663 | OpenAudioRendererImpl(ctx); | ||
| 664 | } | 486 | } |
| 665 | 487 | ||
| 666 | void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx) { | 488 | void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx) { |
| 667 | struct Parameters { | 489 | struct Parameters { |
| 668 | u32 revision; | 490 | u32 revision; |
| 669 | u64 aruid; | 491 | u64 applet_resource_user_id; |
| 670 | }; | 492 | }; |
| 671 | 493 | ||
| 672 | IPC::RequestParser rp{ctx}; | 494 | IPC::RequestParser rp{ctx}; |
| 673 | const auto [revision, aruid] = rp.PopRaw<Parameters>(); | ||
| 674 | 495 | ||
| 675 | LOG_DEBUG(Service_Audio, "called. revision={:08X}, aruid={:016X}", revision, aruid); | 496 | const auto [revision, applet_resource_user_id] = rp.PopRaw<Parameters>(); |
| 676 | 497 | ||
| 677 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 498 | LOG_DEBUG(Service_Audio, "called. Revision {} Applet resource id {}", |
| 678 | rb.Push(ResultSuccess); | 499 | AudioCore::GetRevisionNum(revision), applet_resource_user_id); |
| 679 | rb.PushIpcInterface<IAudioDevice>(system, buffer_event, revision); | ||
| 680 | } | ||
| 681 | 500 | ||
| 682 | void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) { | ||
| 683 | IPC::RequestParser rp{ctx}; | ||
| 684 | const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>(); | ||
| 685 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 501 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 686 | 502 | ||
| 687 | rb.Push(ResultSuccess); | 503 | rb.Push(ResultSuccess); |
| 688 | rb.PushIpcInterface<IAudioRenderer>(system, params, audren_instance_count++); | 504 | rb.PushIpcInterface<IAudioDevice>(system, applet_resource_user_id, revision, |
| 689 | } | 505 | num_audio_devices++); |
| 690 | |||
| 691 | bool IsFeatureSupported(AudioFeatures feature, u32_le revision) { | ||
| 692 | // Byte swap | ||
| 693 | const u32_be version_num = revision - Common::MakeMagic('R', 'E', 'V', '0'); | ||
| 694 | |||
| 695 | switch (feature) { | ||
| 696 | case AudioFeatures::AudioUSBDeviceOutput: | ||
| 697 | return version_num >= 4U; | ||
| 698 | case AudioFeatures::Splitter: | ||
| 699 | return version_num >= 2U; | ||
| 700 | case AudioFeatures::PerformanceMetricsVersion2: | ||
| 701 | case AudioFeatures::VariadicCommandBuffer: | ||
| 702 | return version_num >= 5U; | ||
| 703 | default: | ||
| 704 | return false; | ||
| 705 | } | ||
| 706 | } | 506 | } |
| 707 | 507 | ||
| 708 | } // namespace Service::Audio | 508 | } // namespace Service::Audio |
diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h index 869d39002..4384a9b3c 100644 --- a/src/core/hle/service/audio/audren_u.h +++ b/src/core/hle/service/audio/audren_u.h | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include "audio_core/audio_render_manager.h" | ||
| 6 | #include "core/hle/service/kernel_helpers.h" | 7 | #include "core/hle/service/kernel_helpers.h" |
| 7 | #include "core/hle/service/service.h" | 8 | #include "core/hle/service/service.h" |
| 8 | 9 | ||
| @@ -15,6 +16,7 @@ class HLERequestContext; | |||
| 15 | } | 16 | } |
| 16 | 17 | ||
| 17 | namespace Service::Audio { | 18 | namespace Service::Audio { |
| 19 | class IAudioRenderer; | ||
| 18 | 20 | ||
| 19 | class AudRenU final : public ServiceFramework<AudRenU> { | 21 | class AudRenU final : public ServiceFramework<AudRenU> { |
| 20 | public: | 22 | public: |
| @@ -23,28 +25,14 @@ public: | |||
| 23 | 25 | ||
| 24 | private: | 26 | private: |
| 25 | void OpenAudioRenderer(Kernel::HLERequestContext& ctx); | 27 | void OpenAudioRenderer(Kernel::HLERequestContext& ctx); |
| 26 | void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx); | 28 | void GetWorkBufferSize(Kernel::HLERequestContext& ctx); |
| 27 | void GetAudioDeviceService(Kernel::HLERequestContext& ctx); | 29 | void GetAudioDeviceService(Kernel::HLERequestContext& ctx); |
| 28 | void OpenAudioRendererForManualExecution(Kernel::HLERequestContext& ctx); | 30 | void OpenAudioRendererForManualExecution(Kernel::HLERequestContext& ctx); |
| 29 | void GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx); | 31 | void GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx); |
| 30 | 32 | ||
| 31 | void OpenAudioRendererImpl(Kernel::HLERequestContext& ctx); | ||
| 32 | |||
| 33 | KernelHelpers::ServiceContext service_context; | 33 | KernelHelpers::ServiceContext service_context; |
| 34 | 34 | std::unique_ptr<AudioCore::AudioRenderer::Manager> impl; | |
| 35 | std::size_t audren_instance_count = 0; | 35 | u32 num_audio_devices{0}; |
| 36 | Kernel::KEvent* buffer_event; | ||
| 37 | }; | 36 | }; |
| 38 | 37 | ||
| 39 | // Describes a particular audio feature that may be supported in a particular revision. | ||
| 40 | enum class AudioFeatures : u32 { | ||
| 41 | AudioUSBDeviceOutput, | ||
| 42 | Splitter, | ||
| 43 | PerformanceMetricsVersion2, | ||
| 44 | VariadicCommandBuffer, | ||
| 45 | }; | ||
| 46 | |||
| 47 | // Tests if a particular audio feature is supported with a given audio revision. | ||
| 48 | bool IsFeatureSupported(AudioFeatures feature, u32_le revision); | ||
| 49 | |||
| 50 | } // namespace Service::Audio | 38 | } // namespace Service::Audio |
diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h index ac6c514af..d706978cb 100644 --- a/src/core/hle/service/audio/errors.h +++ b/src/core/hle/service/audio/errors.h | |||
| @@ -7,8 +7,17 @@ | |||
| 7 | 7 | ||
| 8 | namespace Service::Audio { | 8 | namespace Service::Audio { |
| 9 | 9 | ||
| 10 | constexpr Result ERR_INVALID_DEVICE_NAME{ErrorModule::Audio, 1}; | ||
| 10 | constexpr Result ERR_OPERATION_FAILED{ErrorModule::Audio, 2}; | 11 | constexpr Result ERR_OPERATION_FAILED{ErrorModule::Audio, 2}; |
| 12 | constexpr Result ERR_INVALID_SAMPLE_RATE{ErrorModule::Audio, 3}; | ||
| 13 | constexpr Result ERR_INSUFFICIENT_BUFFER_SIZE{ErrorModule::Audio, 4}; | ||
| 14 | constexpr Result ERR_MAXIMUM_SESSIONS_REACHED{ErrorModule::Audio, 5}; | ||
| 11 | constexpr Result ERR_BUFFER_COUNT_EXCEEDED{ErrorModule::Audio, 8}; | 15 | constexpr Result ERR_BUFFER_COUNT_EXCEEDED{ErrorModule::Audio, 8}; |
| 16 | constexpr Result ERR_INVALID_CHANNEL_COUNT{ErrorModule::Audio, 10}; | ||
| 17 | constexpr Result ERR_INVALID_UPDATE_DATA{ErrorModule::Audio, 41}; | ||
| 18 | constexpr Result ERR_POOL_MAPPING_FAILED{ErrorModule::Audio, 42}; | ||
| 12 | constexpr Result ERR_NOT_SUPPORTED{ErrorModule::Audio, 513}; | 19 | constexpr Result ERR_NOT_SUPPORTED{ErrorModule::Audio, 513}; |
| 20 | constexpr Result ERR_INVALID_PROCESS_HANDLE{ErrorModule::Audio, 1536}; | ||
| 21 | constexpr Result ERR_INVALID_REVISION{ErrorModule::Audio, 1537}; | ||
| 13 | 22 | ||
| 14 | } // namespace Service::Audio | 23 | } // namespace Service::Audio |
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 75da659e5..4f2ed2d52 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp | |||
| @@ -298,7 +298,7 @@ void HwOpus::OpenHardwareOpusDecoderEx(Kernel::HLERequestContext& ctx) { | |||
| 298 | const auto sample_rate = rp.Pop<u32>(); | 298 | const auto sample_rate = rp.Pop<u32>(); |
| 299 | const auto channel_count = rp.Pop<u32>(); | 299 | const auto channel_count = rp.Pop<u32>(); |
| 300 | 300 | ||
| 301 | LOG_CRITICAL(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count); | 301 | LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count); |
| 302 | 302 | ||
| 303 | ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || | 303 | ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || |
| 304 | sample_rate == 12000 || sample_rate == 8000, | 304 | sample_rate == 12000 || sample_rate == 8000, |
diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 584808d50..635449fce 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp | |||
| @@ -511,7 +511,7 @@ struct Memory::Impl { | |||
| 511 | 511 | ||
| 512 | [[nodiscard]] u8* GetPointerImpl(VAddr vaddr, auto on_unmapped, auto on_rasterizer) const { | 512 | [[nodiscard]] u8* GetPointerImpl(VAddr vaddr, auto on_unmapped, auto on_rasterizer) const { |
| 513 | // AARCH64 masks the upper 16 bit of all memory accesses | 513 | // AARCH64 masks the upper 16 bit of all memory accesses |
| 514 | vaddr &= 0xffffffffffffLL; | 514 | vaddr &= 0xffffffffffffULL; |
| 515 | 515 | ||
| 516 | if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) { | 516 | if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) { |
| 517 | on_unmapped(); | 517 | on_unmapped(); |
| @@ -776,6 +776,10 @@ void Memory::CopyBlock(const Kernel::KProcess& process, VAddr dest_addr, VAddr s | |||
| 776 | impl->CopyBlock(process, dest_addr, src_addr, size); | 776 | impl->CopyBlock(process, dest_addr, src_addr, size); |
| 777 | } | 777 | } |
| 778 | 778 | ||
| 779 | void Memory::ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, const std::size_t size) { | ||
| 780 | impl->ZeroBlock(process, dest_addr, size); | ||
| 781 | } | ||
| 782 | |||
| 779 | void Memory::RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) { | 783 | void Memory::RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) { |
| 780 | impl->RasterizerMarkRegionCached(vaddr, size, cached); | 784 | impl->RasterizerMarkRegionCached(vaddr, size, cached); |
| 781 | } | 785 | } |
diff --git a/src/core/memory.h b/src/core/memory.h index f22c0a2d8..780c45385 100644 --- a/src/core/memory.h +++ b/src/core/memory.h | |||
| @@ -437,6 +437,19 @@ public: | |||
| 437 | std::size_t size); | 437 | std::size_t size); |
| 438 | 438 | ||
| 439 | /** | 439 | /** |
| 440 | * Zeros a range of bytes within the current process' address space at the specified | ||
| 441 | * virtual address. | ||
| 442 | * | ||
| 443 | * @param process The process that will have data zeroed within its address space. | ||
| 444 | * @param dest_addr The destination virtual address to zero the data from. | ||
| 445 | * @param size The size of the range to zero out, in bytes. | ||
| 446 | * | ||
| 447 | * @post The range [dest_addr, size) within the process' address space contains the | ||
| 448 | * value 0. | ||
| 449 | */ | ||
| 450 | void ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size); | ||
| 451 | |||
| 452 | /** | ||
| 440 | * Marks each page within the specified address range as cached or uncached. | 453 | * Marks each page within the specified address range as cached or uncached. |
| 441 | * | 454 | * |
| 442 | * @param vaddr The virtual address indicating the start of the address range. | 455 | * @param vaddr The virtual address indicating the start of the address range. |
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 0a61839da..2840bc5eb 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp | |||
| @@ -372,8 +372,9 @@ void Config::ReadAudioValues() { | |||
| 372 | qt_config->beginGroup(QStringLiteral("Audio")); | 372 | qt_config->beginGroup(QStringLiteral("Audio")); |
| 373 | 373 | ||
| 374 | if (global) { | 374 | if (global) { |
| 375 | ReadBasicSetting(Settings::values.audio_device_id); | ||
| 376 | ReadBasicSetting(Settings::values.sink_id); | 375 | ReadBasicSetting(Settings::values.sink_id); |
| 376 | ReadBasicSetting(Settings::values.audio_output_device_id); | ||
| 377 | ReadBasicSetting(Settings::values.audio_input_device_id); | ||
| 377 | } | 378 | } |
| 378 | ReadGlobalSetting(Settings::values.volume); | 379 | ReadGlobalSetting(Settings::values.volume); |
| 379 | 380 | ||
| @@ -1027,7 +1028,8 @@ void Config::SaveAudioValues() { | |||
| 1027 | 1028 | ||
| 1028 | if (global) { | 1029 | if (global) { |
| 1029 | WriteBasicSetting(Settings::values.sink_id); | 1030 | WriteBasicSetting(Settings::values.sink_id); |
| 1030 | WriteBasicSetting(Settings::values.audio_device_id); | 1031 | WriteBasicSetting(Settings::values.audio_output_device_id); |
| 1032 | WriteBasicSetting(Settings::values.audio_input_device_id); | ||
| 1031 | } | 1033 | } |
| 1032 | WriteGlobalSetting(Settings::values.volume); | 1034 | WriteGlobalSetting(Settings::values.volume); |
| 1033 | 1035 | ||
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index 512bdfc22..19b8b15ef 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp | |||
| @@ -3,8 +3,8 @@ | |||
| 3 | 3 | ||
| 4 | #include <memory> | 4 | #include <memory> |
| 5 | 5 | ||
| 6 | #include "audio_core/sink.h" | 6 | #include "audio_core/sink/sink.h" |
| 7 | #include "audio_core/sink_details.h" | 7 | #include "audio_core/sink/sink_details.h" |
| 8 | #include "common/settings.h" | 8 | #include "common/settings.h" |
| 9 | #include "core/core.h" | 9 | #include "core/core.h" |
| 10 | #include "ui_configure_audio.h" | 10 | #include "ui_configure_audio.h" |
| @@ -15,11 +15,11 @@ ConfigureAudio::ConfigureAudio(const Core::System& system_, QWidget* parent) | |||
| 15 | : QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()), system{system_} { | 15 | : QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()), system{system_} { |
| 16 | ui->setupUi(this); | 16 | ui->setupUi(this); |
| 17 | 17 | ||
| 18 | InitializeAudioOutputSinkComboBox(); | 18 | InitializeAudioSinkComboBox(); |
| 19 | 19 | ||
| 20 | connect(ui->volume_slider, &QSlider::valueChanged, this, | 20 | connect(ui->volume_slider, &QSlider::valueChanged, this, |
| 21 | &ConfigureAudio::SetVolumeIndicatorText); | 21 | &ConfigureAudio::SetVolumeIndicatorText); |
| 22 | connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this, | 22 | connect(ui->sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this, |
| 23 | &ConfigureAudio::UpdateAudioDevices); | 23 | &ConfigureAudio::UpdateAudioDevices); |
| 24 | 24 | ||
| 25 | ui->volume_label->setVisible(Settings::IsConfiguringGlobal()); | 25 | ui->volume_label->setVisible(Settings::IsConfiguringGlobal()); |
| @@ -30,8 +30,9 @@ ConfigureAudio::ConfigureAudio(const Core::System& system_, QWidget* parent) | |||
| 30 | SetConfiguration(); | 30 | SetConfiguration(); |
| 31 | 31 | ||
| 32 | const bool is_powered_on = system_.IsPoweredOn(); | 32 | const bool is_powered_on = system_.IsPoweredOn(); |
| 33 | ui->output_sink_combo_box->setEnabled(!is_powered_on); | 33 | ui->sink_combo_box->setEnabled(!is_powered_on); |
| 34 | ui->audio_device_combo_box->setEnabled(!is_powered_on); | 34 | ui->output_combo_box->setEnabled(!is_powered_on); |
| 35 | ui->input_combo_box->setEnabled(!is_powered_on); | ||
| 35 | } | 36 | } |
| 36 | 37 | ||
| 37 | ConfigureAudio::~ConfigureAudio() = default; | 38 | ConfigureAudio::~ConfigureAudio() = default; |
| @@ -40,9 +41,9 @@ void ConfigureAudio::SetConfiguration() { | |||
| 40 | SetOutputSinkFromSinkID(); | 41 | SetOutputSinkFromSinkID(); |
| 41 | 42 | ||
| 42 | // The device list cannot be pre-populated (nor listed) until the output sink is known. | 43 | // The device list cannot be pre-populated (nor listed) until the output sink is known. |
| 43 | UpdateAudioDevices(ui->output_sink_combo_box->currentIndex()); | 44 | UpdateAudioDevices(ui->sink_combo_box->currentIndex()); |
| 44 | 45 | ||
| 45 | SetAudioDeviceFromDeviceID(); | 46 | SetAudioDevicesFromDeviceID(); |
| 46 | 47 | ||
| 47 | const auto volume_value = static_cast<int>(Settings::values.volume.GetValue()); | 48 | const auto volume_value = static_cast<int>(Settings::values.volume.GetValue()); |
| 48 | ui->volume_slider->setValue(volume_value); | 49 | ui->volume_slider->setValue(volume_value); |
| @@ -62,32 +63,45 @@ void ConfigureAudio::SetConfiguration() { | |||
| 62 | } | 63 | } |
| 63 | 64 | ||
| 64 | void ConfigureAudio::SetOutputSinkFromSinkID() { | 65 | void ConfigureAudio::SetOutputSinkFromSinkID() { |
| 65 | [[maybe_unused]] const QSignalBlocker blocker(ui->output_sink_combo_box); | 66 | [[maybe_unused]] const QSignalBlocker blocker(ui->sink_combo_box); |
| 66 | 67 | ||
| 67 | int new_sink_index = 0; | 68 | int new_sink_index = 0; |
| 68 | const QString sink_id = QString::fromStdString(Settings::values.sink_id.GetValue()); | 69 | const QString sink_id = QString::fromStdString(Settings::values.sink_id.GetValue()); |
| 69 | for (int index = 0; index < ui->output_sink_combo_box->count(); index++) { | 70 | for (int index = 0; index < ui->sink_combo_box->count(); index++) { |
| 70 | if (ui->output_sink_combo_box->itemText(index) == sink_id) { | 71 | if (ui->sink_combo_box->itemText(index) == sink_id) { |
| 71 | new_sink_index = index; | 72 | new_sink_index = index; |
| 72 | break; | 73 | break; |
| 73 | } | 74 | } |
| 74 | } | 75 | } |
| 75 | 76 | ||
| 76 | ui->output_sink_combo_box->setCurrentIndex(new_sink_index); | 77 | ui->sink_combo_box->setCurrentIndex(new_sink_index); |
| 77 | } | 78 | } |
| 78 | 79 | ||
| 79 | void ConfigureAudio::SetAudioDeviceFromDeviceID() { | 80 | void ConfigureAudio::SetAudioDevicesFromDeviceID() { |
| 80 | int new_device_index = -1; | 81 | int new_device_index = -1; |
| 81 | 82 | ||
| 82 | const QString device_id = QString::fromStdString(Settings::values.audio_device_id.GetValue()); | 83 | const QString output_device_id = |
| 83 | for (int index = 0; index < ui->audio_device_combo_box->count(); index++) { | 84 | QString::fromStdString(Settings::values.audio_output_device_id.GetValue()); |
| 84 | if (ui->audio_device_combo_box->itemText(index) == device_id) { | 85 | for (int index = 0; index < ui->output_combo_box->count(); index++) { |
| 86 | if (ui->output_combo_box->itemText(index) == output_device_id) { | ||
| 85 | new_device_index = index; | 87 | new_device_index = index; |
| 86 | break; | 88 | break; |
| 87 | } | 89 | } |
| 88 | } | 90 | } |
| 89 | 91 | ||
| 90 | ui->audio_device_combo_box->setCurrentIndex(new_device_index); | 92 | ui->output_combo_box->setCurrentIndex(new_device_index); |
| 93 | |||
| 94 | new_device_index = -1; | ||
| 95 | const QString input_device_id = | ||
| 96 | QString::fromStdString(Settings::values.audio_input_device_id.GetValue()); | ||
| 97 | for (int index = 0; index < ui->input_combo_box->count(); index++) { | ||
| 98 | if (ui->input_combo_box->itemText(index) == input_device_id) { | ||
| 99 | new_device_index = index; | ||
| 100 | break; | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | ui->input_combo_box->setCurrentIndex(new_device_index); | ||
| 91 | } | 105 | } |
| 92 | 106 | ||
| 93 | void ConfigureAudio::SetVolumeIndicatorText(int percentage) { | 107 | void ConfigureAudio::SetVolumeIndicatorText(int percentage) { |
| @@ -95,14 +109,13 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) { | |||
| 95 | } | 109 | } |
| 96 | 110 | ||
| 97 | void ConfigureAudio::ApplyConfiguration() { | 111 | void ConfigureAudio::ApplyConfiguration() { |
| 98 | |||
| 99 | if (Settings::IsConfiguringGlobal()) { | 112 | if (Settings::IsConfiguringGlobal()) { |
| 100 | Settings::values.sink_id = | 113 | Settings::values.sink_id = |
| 101 | ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) | 114 | ui->sink_combo_box->itemText(ui->sink_combo_box->currentIndex()).toStdString(); |
| 102 | .toStdString(); | 115 | Settings::values.audio_output_device_id.SetValue( |
| 103 | Settings::values.audio_device_id.SetValue( | 116 | ui->output_combo_box->itemText(ui->output_combo_box->currentIndex()).toStdString()); |
| 104 | ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) | 117 | Settings::values.audio_input_device_id.SetValue( |
| 105 | .toStdString()); | 118 | ui->input_combo_box->itemText(ui->input_combo_box->currentIndex()).toStdString()); |
| 106 | 119 | ||
| 107 | // Guard if during game and set to game-specific value | 120 | // Guard if during game and set to game-specific value |
| 108 | if (Settings::values.volume.UsingGlobal()) { | 121 | if (Settings::values.volume.UsingGlobal()) { |
| @@ -129,21 +142,27 @@ void ConfigureAudio::changeEvent(QEvent* event) { | |||
| 129 | } | 142 | } |
| 130 | 143 | ||
| 131 | void ConfigureAudio::UpdateAudioDevices(int sink_index) { | 144 | void ConfigureAudio::UpdateAudioDevices(int sink_index) { |
| 132 | ui->audio_device_combo_box->clear(); | 145 | ui->output_combo_box->clear(); |
| 133 | ui->audio_device_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name)); | 146 | ui->output_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name)); |
| 147 | |||
| 148 | const std::string sink_id = ui->sink_combo_box->itemText(sink_index).toStdString(); | ||
| 149 | for (const auto& device : AudioCore::Sink::GetDeviceListForSink(sink_id, false)) { | ||
| 150 | ui->output_combo_box->addItem(QString::fromStdString(device)); | ||
| 151 | } | ||
| 134 | 152 | ||
| 135 | const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString(); | 153 | ui->input_combo_box->clear(); |
| 136 | for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) { | 154 | ui->input_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name)); |
| 137 | ui->audio_device_combo_box->addItem(QString::fromStdString(device)); | 155 | for (const auto& device : AudioCore::Sink::GetDeviceListForSink(sink_id, true)) { |
| 156 | ui->input_combo_box->addItem(QString::fromStdString(device)); | ||
| 138 | } | 157 | } |
| 139 | } | 158 | } |
| 140 | 159 | ||
| 141 | void ConfigureAudio::InitializeAudioOutputSinkComboBox() { | 160 | void ConfigureAudio::InitializeAudioSinkComboBox() { |
| 142 | ui->output_sink_combo_box->clear(); | 161 | ui->sink_combo_box->clear(); |
| 143 | ui->output_sink_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name)); | 162 | ui->sink_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name)); |
| 144 | 163 | ||
| 145 | for (const char* id : AudioCore::GetSinkIDs()) { | 164 | for (const char* id : AudioCore::Sink::GetSinkIDs()) { |
| 146 | ui->output_sink_combo_box->addItem(QString::fromUtf8(id)); | 165 | ui->sink_combo_box->addItem(QString::fromUtf8(id)); |
| 147 | } | 166 | } |
| 148 | } | 167 | } |
| 149 | 168 | ||
| @@ -164,8 +183,10 @@ void ConfigureAudio::SetupPerGameUI() { | |||
| 164 | ConfigurationShared::SetHighlight(ui->volume_layout, index == 1); | 183 | ConfigurationShared::SetHighlight(ui->volume_layout, index == 1); |
| 165 | }); | 184 | }); |
| 166 | 185 | ||
| 167 | ui->output_sink_combo_box->setVisible(false); | 186 | ui->sink_combo_box->setVisible(false); |
| 168 | ui->output_sink_label->setVisible(false); | 187 | ui->sink_label->setVisible(false); |
| 169 | ui->audio_device_combo_box->setVisible(false); | 188 | ui->output_combo_box->setVisible(false); |
| 170 | ui->audio_device_label->setVisible(false); | 189 | ui->output_label->setVisible(false); |
| 190 | ui->input_combo_box->setVisible(false); | ||
| 191 | ui->input_label->setVisible(false); | ||
| 171 | } | 192 | } |
diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h index 08c278eeb..0d03aae1d 100644 --- a/src/yuzu/configuration/configure_audio.h +++ b/src/yuzu/configuration/configure_audio.h | |||
| @@ -31,14 +31,14 @@ public: | |||
| 31 | private: | 31 | private: |
| 32 | void changeEvent(QEvent* event) override; | 32 | void changeEvent(QEvent* event) override; |
| 33 | 33 | ||
| 34 | void InitializeAudioOutputSinkComboBox(); | 34 | void InitializeAudioSinkComboBox(); |
| 35 | 35 | ||
| 36 | void RetranslateUI(); | 36 | void RetranslateUI(); |
| 37 | 37 | ||
| 38 | void UpdateAudioDevices(int sink_index); | 38 | void UpdateAudioDevices(int sink_index); |
| 39 | 39 | ||
| 40 | void SetOutputSinkFromSinkID(); | 40 | void SetOutputSinkFromSinkID(); |
| 41 | void SetAudioDeviceFromDeviceID(); | 41 | void SetAudioDevicesFromDeviceID(); |
| 42 | void SetVolumeIndicatorText(int percentage); | 42 | void SetVolumeIndicatorText(int percentage); |
| 43 | 43 | ||
| 44 | void SetupPerGameUI(); | 44 | void SetupPerGameUI(); |
diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui index d1ac8ad02..a5bcee415 100644 --- a/src/yuzu/configuration/configure_audio.ui +++ b/src/yuzu/configuration/configure_audio.ui | |||
| @@ -21,30 +21,44 @@ | |||
| 21 | </property> | 21 | </property> |
| 22 | <layout class="QVBoxLayout"> | 22 | <layout class="QVBoxLayout"> |
| 23 | <item> | 23 | <item> |
| 24 | <layout class="QHBoxLayout" name="_3"> | 24 | <layout class="QHBoxLayout" name="engine_layout"> |
| 25 | <item> | 25 | <item> |
| 26 | <widget class="QLabel" name="output_sink_label"> | 26 | <widget class="QLabel" name="sink_label"> |
| 27 | <property name="text"> | 27 | <property name="text"> |
| 28 | <string>Output Engine:</string> | 28 | <string>Output Engine:</string> |
| 29 | </property> | 29 | </property> |
| 30 | </widget> | 30 | </widget> |
| 31 | </item> | 31 | </item> |
| 32 | <item> | 32 | <item> |
| 33 | <widget class="QComboBox" name="output_sink_combo_box"/> | 33 | <widget class="QComboBox" name="sink_combo_box"/> |
| 34 | </item> | 34 | </item> |
| 35 | </layout> | 35 | </layout> |
| 36 | </item> | 36 | </item> |
| 37 | <item> | 37 | <item> |
| 38 | <layout class="QHBoxLayout" name="_2"> | 38 | <layout class="QHBoxLayout" name="output_layout"> |
| 39 | <item> | 39 | <item> |
| 40 | <widget class="QLabel" name="audio_device_label"> | 40 | <widget class="QLabel" name="output_label"> |
| 41 | <property name="text"> | 41 | <property name="text"> |
| 42 | <string>Audio Device:</string> | 42 | <string>Output Device</string> |
| 43 | </property> | 43 | </property> |
| 44 | </widget> | 44 | </widget> |
| 45 | </item> | 45 | </item> |
| 46 | <item> | 46 | <item> |
| 47 | <widget class="QComboBox" name="audio_device_combo_box"/> | 47 | <widget class="QComboBox" name="output_combo_box"/> |
| 48 | </item> | ||
| 49 | </layout> | ||
| 50 | </item> | ||
| 51 | <item> | ||
| 52 | <layout class="QHBoxLayout" name="input_layout"> | ||
| 53 | <item> | ||
| 54 | <widget class="QLabel" name="input_label"> | ||
| 55 | <property name="text"> | ||
| 56 | <string>Input Device</string> | ||
| 57 | </property> | ||
| 58 | </widget> | ||
| 59 | </item> | ||
| 60 | <item> | ||
| 61 | <widget class="QComboBox" name="input_combo_box"/> | ||
| 48 | </item> | 62 | </item> |
| 49 | </layout> | 63 | </layout> |
| 50 | </item> | 64 | </item> |
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 343d2aee1..84808f678 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp | |||
| @@ -44,6 +44,7 @@ void ConfigureDebug::SetConfiguration() { | |||
| 44 | ui->fs_access_log->setEnabled(runtime_lock); | 44 | ui->fs_access_log->setEnabled(runtime_lock); |
| 45 | ui->fs_access_log->setChecked(Settings::values.enable_fs_access_log.GetValue()); | 45 | ui->fs_access_log->setChecked(Settings::values.enable_fs_access_log.GetValue()); |
| 46 | ui->reporting_services->setChecked(Settings::values.reporting_services.GetValue()); | 46 | ui->reporting_services->setChecked(Settings::values.reporting_services.GetValue()); |
| 47 | ui->dump_audio_commands->setChecked(Settings::values.dump_audio_commands.GetValue()); | ||
| 47 | ui->quest_flag->setChecked(Settings::values.quest_flag.GetValue()); | 48 | ui->quest_flag->setChecked(Settings::values.quest_flag.GetValue()); |
| 48 | ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue()); | 49 | ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue()); |
| 49 | ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue()); | 50 | ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue()); |
| @@ -83,6 +84,7 @@ void ConfigureDebug::ApplyConfiguration() { | |||
| 83 | Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); | 84 | Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); |
| 84 | Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked(); | 85 | Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked(); |
| 85 | Settings::values.reporting_services = ui->reporting_services->isChecked(); | 86 | Settings::values.reporting_services = ui->reporting_services->isChecked(); |
| 87 | Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked(); | ||
| 86 | Settings::values.quest_flag = ui->quest_flag->isChecked(); | 88 | Settings::values.quest_flag = ui->quest_flag->isChecked(); |
| 87 | Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); | 89 | Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); |
| 88 | Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); | 90 | Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); |
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 1152fa6c6..4c16274fc 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui | |||
| @@ -235,6 +235,16 @@ | |||
| 235 | </widget> | 235 | </widget> |
| 236 | </item> | 236 | </item> |
| 237 | <item row="1" column="0"> | 237 | <item row="1" column="0"> |
| 238 | <widget class="QCheckBox" name="dump_audio_commands"> | ||
| 239 | <property name="text"> | ||
| 240 | <string>Dump Audio Commands To Console**</string> | ||
| 241 | </property> | ||
| 242 | <property name="toolTip"> | ||
| 243 | <string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string> | ||
| 244 | </property> | ||
| 245 | </widget> | ||
| 246 | </item> | ||
| 247 | <item row="2" column="0"> | ||
| 238 | <widget class="QCheckBox" name="reporting_services"> | 248 | <widget class="QCheckBox" name="reporting_services"> |
| 239 | <property name="text"> | 249 | <property name="text"> |
| 240 | <string>Enable Verbose Reporting Services**</string> | 250 | <string>Enable Verbose Reporting Services**</string> |
| @@ -325,6 +335,7 @@ | |||
| 325 | <tabstop>disable_loop_safety_checks</tabstop> | 335 | <tabstop>disable_loop_safety_checks</tabstop> |
| 326 | <tabstop>fs_access_log</tabstop> | 336 | <tabstop>fs_access_log</tabstop> |
| 327 | <tabstop>reporting_services</tabstop> | 337 | <tabstop>reporting_services</tabstop> |
| 338 | <tabstop>dump_audio_commands</tabstop> | ||
| 328 | <tabstop>quest_flag</tabstop> | 339 | <tabstop>quest_flag</tabstop> |
| 329 | <tabstop>enable_cpu_debugging</tabstop> | 340 | <tabstop>enable_cpu_debugging</tabstop> |
| 330 | <tabstop>use_debug_asserts</tabstop> | 341 | <tabstop>use_debug_asserts</tabstop> |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e60d84054..a120f2662 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -1498,6 +1498,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t | |||
| 1498 | if (!LoadROM(filename, program_id, program_index)) | 1498 | if (!LoadROM(filename, program_id, program_index)) |
| 1499 | return; | 1499 | return; |
| 1500 | 1500 | ||
| 1501 | system->SetShuttingDown(false); | ||
| 1502 | |||
| 1501 | // Create and start the emulation thread | 1503 | // Create and start the emulation thread |
| 1502 | emu_thread = std::make_unique<EmuThread>(*system); | 1504 | emu_thread = std::make_unique<EmuThread>(*system); |
| 1503 | emit EmulationStarting(emu_thread.get()); | 1505 | emit EmulationStarting(emu_thread.get()); |
| @@ -1588,6 +1590,7 @@ void GMainWindow::ShutdownGame() { | |||
| 1588 | 1590 | ||
| 1589 | AllowOSSleep(); | 1591 | AllowOSSleep(); |
| 1590 | 1592 | ||
| 1593 | system->SetShuttingDown(true); | ||
| 1591 | system->DetachDebugger(); | 1594 | system->DetachDebugger(); |
| 1592 | discord_rpc->Pause(); | 1595 | discord_rpc->Pause(); |
| 1593 | emu_thread->RequestStop(); | 1596 | emu_thread->RequestStop(); |
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 5576fb795..ad7f9d239 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp | |||
| @@ -322,7 +322,7 @@ void Config::ReadValues() { | |||
| 322 | 322 | ||
| 323 | // Audio | 323 | // Audio |
| 324 | ReadSetting("Audio", Settings::values.sink_id); | 324 | ReadSetting("Audio", Settings::values.sink_id); |
| 325 | ReadSetting("Audio", Settings::values.audio_device_id); | 325 | ReadSetting("Audio", Settings::values.audio_output_device_id); |
| 326 | ReadSetting("Audio", Settings::values.volume); | 326 | ReadSetting("Audio", Settings::values.volume); |
| 327 | 327 | ||
| 328 | // Miscellaneous | 328 | // Miscellaneous |