Project Andio
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -3,7 +3,7 @@ | ||||
|     url = https://github.com/benhoyt/inih.git | ||||
| [submodule "cubeb"] | ||||
|     path = externals/cubeb | ||||
|     url = https://github.com/kinetiknz/cubeb.git | ||||
|     url = https://github.com/mozilla/cubeb.git | ||||
| [submodule "dynarmic"] | ||||
|     path = externals/dynarmic | ||||
|     url = https://github.com/MerryMage/dynarmic.git | ||||
|   | ||||
| @@ -1,54 +1,218 @@ | ||||
| add_library(audio_core STATIC | ||||
|     algorithm/filter.cpp | ||||
|     algorithm/filter.h | ||||
|     algorithm/interpolate.cpp | ||||
|     algorithm/interpolate.h | ||||
|     audio_out.cpp | ||||
|     audio_out.h | ||||
|     audio_renderer.cpp | ||||
|     audio_renderer.h | ||||
|     behavior_info.cpp | ||||
|     behavior_info.h | ||||
|     buffer.h | ||||
|     codec.cpp | ||||
|     codec.h | ||||
|     command_generator.cpp | ||||
|     command_generator.h | ||||
|     common.h | ||||
|     delay_line.cpp | ||||
|     delay_line.h | ||||
|     effect_context.cpp | ||||
|     effect_context.h | ||||
|     info_updater.cpp | ||||
|     info_updater.h | ||||
|     memory_pool.cpp | ||||
|     memory_pool.h | ||||
|     mix_context.cpp | ||||
|     mix_context.h | ||||
|     null_sink.h | ||||
|     sink.h | ||||
|     sink_context.cpp | ||||
|     sink_context.h | ||||
|     sink_details.cpp | ||||
|     sink_details.h | ||||
|     sink_stream.h | ||||
|     splitter_context.cpp | ||||
|     splitter_context.h | ||||
|     stream.cpp | ||||
|     stream.h | ||||
|     voice_context.cpp | ||||
|     voice_context.h | ||||
|  | ||||
|     $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h> | ||||
|     $<$<BOOL:${ENABLE_SDL2}>:sdl2_sink.cpp sdl2_sink.h> | ||||
|     audio_core.cpp | ||||
|     audio_core.h | ||||
|     audio_event.h | ||||
|     audio_event.cpp | ||||
|     audio_render_manager.cpp | ||||
|     audio_render_manager.h | ||||
|     audio_in_manager.cpp | ||||
|     audio_in_manager.h | ||||
|     audio_out_manager.cpp | ||||
|     audio_out_manager.h | ||||
|     audio_manager.cpp | ||||
|     audio_manager.h | ||||
|     common/audio_renderer_parameter.h | ||||
|     common/common.h | ||||
|     common/feature_support.h | ||||
|     common/wave_buffer.h | ||||
|     common/workbuffer_allocator.h | ||||
|     device/audio_buffer.h | ||||
|     device/audio_buffers.h | ||||
|     device/device_session.cpp | ||||
|     device/device_session.h | ||||
|     in/audio_in.cpp | ||||
|     in/audio_in.h | ||||
|     in/audio_in_system.cpp | ||||
|     in/audio_in_system.h | ||||
|     out/audio_out.cpp | ||||
|     out/audio_out.h | ||||
|     out/audio_out_system.cpp | ||||
|     out/audio_out_system.h | ||||
|     renderer/adsp/adsp.cpp | ||||
|     renderer/adsp/adsp.h | ||||
|     renderer/adsp/audio_renderer.cpp | ||||
|     renderer/adsp/audio_renderer.h | ||||
|     renderer/adsp/command_buffer.h | ||||
|     renderer/adsp/command_list_processor.cpp | ||||
|     renderer/adsp/command_list_processor.h | ||||
|     renderer/audio_device.cpp | ||||
|     renderer/audio_device.h | ||||
|     renderer/audio_renderer.h | ||||
|     renderer/audio_renderer.cpp | ||||
|     renderer/behavior/behavior_info.cpp | ||||
|     renderer/behavior/behavior_info.h | ||||
|     renderer/behavior/info_updater.cpp | ||||
|     renderer/behavior/info_updater.h | ||||
|     renderer/command/data_source/adpcm.cpp | ||||
|     renderer/command/data_source/adpcm.h | ||||
|     renderer/command/data_source/decode.cpp | ||||
|     renderer/command/data_source/decode.h | ||||
|     renderer/command/data_source/pcm_float.cpp | ||||
|     renderer/command/data_source/pcm_float.h | ||||
|     renderer/command/data_source/pcm_int16.cpp | ||||
|     renderer/command/data_source/pcm_int16.h | ||||
|     renderer/command/effect/aux_.cpp | ||||
|     renderer/command/effect/aux_.h | ||||
|     renderer/command/effect/biquad_filter.cpp | ||||
|     renderer/command/effect/biquad_filter.h | ||||
|     renderer/command/effect/capture.cpp | ||||
|     renderer/command/effect/capture.h | ||||
|     renderer/command/effect/compressor.cpp | ||||
|     renderer/command/effect/compressor.h | ||||
|     renderer/command/effect/delay.cpp | ||||
|     renderer/command/effect/delay.h | ||||
|     renderer/command/effect/i3dl2_reverb.cpp | ||||
|     renderer/command/effect/i3dl2_reverb.h | ||||
|     renderer/command/effect/light_limiter.cpp | ||||
|     renderer/command/effect/light_limiter.h | ||||
|     renderer/command/effect/multi_tap_biquad_filter.cpp | ||||
|     renderer/command/effect/multi_tap_biquad_filter.h | ||||
|     renderer/command/effect/reverb.cpp | ||||
|     renderer/command/effect/reverb.h | ||||
|     renderer/command/mix/clear_mix.cpp | ||||
|     renderer/command/mix/clear_mix.h | ||||
|     renderer/command/mix/copy_mix.cpp | ||||
|     renderer/command/mix/copy_mix.h | ||||
|     renderer/command/mix/depop_for_mix_buffers.cpp | ||||
|     renderer/command/mix/depop_for_mix_buffers.h | ||||
|     renderer/command/mix/depop_prepare.cpp | ||||
|     renderer/command/mix/depop_prepare.h | ||||
|     renderer/command/mix/mix.cpp | ||||
|     renderer/command/mix/mix.h | ||||
|     renderer/command/mix/mix_ramp.cpp | ||||
|     renderer/command/mix/mix_ramp.h | ||||
|     renderer/command/mix/mix_ramp_grouped.cpp | ||||
|     renderer/command/mix/mix_ramp_grouped.h | ||||
|     renderer/command/mix/volume.cpp | ||||
|     renderer/command/mix/volume.h | ||||
|     renderer/command/mix/volume_ramp.cpp | ||||
|     renderer/command/mix/volume_ramp.h | ||||
|     renderer/command/performance/performance.cpp | ||||
|     renderer/command/performance/performance.h | ||||
|     renderer/command/resample/downmix_6ch_to_2ch.cpp | ||||
|     renderer/command/resample/downmix_6ch_to_2ch.h | ||||
|     renderer/command/resample/resample.h | ||||
|     renderer/command/resample/resample.cpp | ||||
|     renderer/command/resample/upsample.cpp | ||||
|     renderer/command/resample/upsample.h | ||||
|     renderer/command/sink/device.cpp | ||||
|     renderer/command/sink/device.h | ||||
|     renderer/command/sink/circular_buffer.cpp | ||||
|     renderer/command/sink/circular_buffer.h | ||||
|     renderer/command/command_buffer.cpp | ||||
|     renderer/command/command_buffer.h | ||||
|     renderer/command/command_generator.cpp | ||||
|     renderer/command/command_generator.h | ||||
|     renderer/command/command_list_header.h | ||||
|     renderer/command/command_processing_time_estimator.cpp | ||||
|     renderer/command/command_processing_time_estimator.h | ||||
|     renderer/command/commands.h | ||||
|     renderer/command/icommand.h | ||||
|     renderer/effect/aux_.cpp | ||||
|     renderer/effect/aux_.h | ||||
|     renderer/effect/biquad_filter.cpp | ||||
|     renderer/effect/biquad_filter.h | ||||
|     renderer/effect/buffer_mixer.cpp | ||||
|     renderer/effect/buffer_mixer.h | ||||
|     renderer/effect/capture.cpp | ||||
|     renderer/effect/capture.h | ||||
|     renderer/effect/compressor.cpp | ||||
|     renderer/effect/compressor.h | ||||
|     renderer/effect/delay.cpp | ||||
|     renderer/effect/delay.h | ||||
|     renderer/effect/effect_context.cpp | ||||
|     renderer/effect/effect_context.h | ||||
|     renderer/effect/effect_info_base.h | ||||
|     renderer/effect/effect_reset.h | ||||
|     renderer/effect/effect_result_state.h | ||||
|     renderer/effect/i3dl2.cpp | ||||
|     renderer/effect/i3dl2.h | ||||
|     renderer/effect/light_limiter.cpp | ||||
|     renderer/effect/light_limiter.h | ||||
|     renderer/effect/reverb.h | ||||
|     renderer/effect/reverb.cpp | ||||
|     renderer/mix/mix_context.cpp | ||||
|     renderer/mix/mix_context.h | ||||
|     renderer/mix/mix_info.cpp | ||||
|     renderer/mix/mix_info.h | ||||
|     renderer/memory/address_info.h | ||||
|     renderer/memory/memory_pool_info.cpp | ||||
|     renderer/memory/memory_pool_info.h | ||||
|     renderer/memory/pool_mapper.cpp | ||||
|     renderer/memory/pool_mapper.h | ||||
|     renderer/nodes/bit_array.h | ||||
|     renderer/nodes/edge_matrix.cpp | ||||
|     renderer/nodes/edge_matrix.h | ||||
|     renderer/nodes/node_states.cpp | ||||
|     renderer/nodes/node_states.h | ||||
|     renderer/performance/detail_aspect.cpp | ||||
|     renderer/performance/detail_aspect.h | ||||
|     renderer/performance/entry_aspect.cpp | ||||
|     renderer/performance/entry_aspect.h | ||||
|     renderer/performance/performance_detail.h | ||||
|     renderer/performance/performance_entry.h | ||||
|     renderer/performance/performance_entry_addresses.h | ||||
|     renderer/performance/performance_frame_header.h | ||||
|     renderer/performance/performance_manager.cpp | ||||
|     renderer/performance/performance_manager.h | ||||
|     renderer/sink/circular_buffer_sink_info.cpp | ||||
|     renderer/sink/circular_buffer_sink_info.h | ||||
|     renderer/sink/device_sink_info.cpp | ||||
|     renderer/sink/device_sink_info.h | ||||
|     renderer/sink/sink_context.cpp | ||||
|     renderer/sink/sink_context.h | ||||
|     renderer/sink/sink_info_base.cpp | ||||
|     renderer/sink/sink_info_base.h | ||||
|     renderer/splitter/splitter_context.cpp | ||||
|     renderer/splitter/splitter_context.h | ||||
|     renderer/splitter/splitter_destinations_data.cpp | ||||
|     renderer/splitter/splitter_destinations_data.h | ||||
|     renderer/splitter/splitter_info.cpp | ||||
|     renderer/splitter/splitter_info.h | ||||
|     renderer/system.cpp | ||||
|     renderer/system.h | ||||
|     renderer/system_manager.cpp | ||||
|     renderer/system_manager.h | ||||
|     renderer/upsampler/upsampler_info.h | ||||
|     renderer/upsampler/upsampler_manager.cpp | ||||
|     renderer/upsampler/upsampler_manager.h | ||||
|     renderer/upsampler/upsampler_state.h | ||||
|     renderer/voice/voice_channel_resource.h | ||||
|     renderer/voice/voice_context.cpp | ||||
|     renderer/voice/voice_context.h | ||||
|     renderer/voice/voice_info.cpp | ||||
|     renderer/voice/voice_info.h | ||||
|     renderer/voice/voice_state.h | ||||
|     sink/cubeb_sink.cpp | ||||
|     sink/cubeb_sink.h | ||||
|     sink/null_sink.h | ||||
|     sink/sdl2_sink.cpp | ||||
|     sink/sdl2_sink.h | ||||
|     sink/sink.h | ||||
|     sink/sink_details.cpp | ||||
|     sink/sink_details.h | ||||
|     sink/sink_stream.h | ||||
| ) | ||||
|  | ||||
| create_target_directory_groups(audio_core) | ||||
|  | ||||
| if (NOT MSVC) | ||||
| if (MSVC) | ||||
|     target_compile_options(audio_core PRIVATE | ||||
|         /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data | ||||
|         /we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data | ||||
|         /we4245 # 'conversion': conversion from 'type1' to 'type2', signed/unsigned mismatch | ||||
|         /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data | ||||
|         /we4456 # Declaration of 'identifier' hides previous local declaration | ||||
|         /we4457 # Declaration of 'identifier' hides function parameter | ||||
|         /we4458 # Declaration of 'identifier' hides class member | ||||
|         /we4459 # Declaration of 'identifier' hides global declaration | ||||
|     ) | ||||
| else() | ||||
|     target_compile_options(audio_core PRIVATE | ||||
|         -Werror=conversion | ||||
|         -Werror=ignored-qualifiers | ||||
|         -Werror=shadow | ||||
|         -Werror=unused-variable | ||||
|  | ||||
|         $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter> | ||||
|         $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable> | ||||
| @@ -58,6 +222,9 @@ if (NOT MSVC) | ||||
| endif() | ||||
|  | ||||
| target_link_libraries(audio_core PUBLIC common core) | ||||
| if (ARCHITECTURE_x86_64) | ||||
|     target_link_libraries(audio_core PRIVATE dynarmic) | ||||
| endif() | ||||
|  | ||||
| if(ENABLE_CUBEB) | ||||
|     target_link_libraries(audio_core PRIVATE cubeb) | ||||
|   | ||||
| @@ -1,79 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #define _USE_MATH_DEFINES | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <array> | ||||
| #include <cmath> | ||||
| #include <vector> | ||||
| #include "audio_core/algorithm/filter.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| Filter Filter::LowPass(double cutoff, double Q) { | ||||
|     const double w0 = 2.0 * M_PI * cutoff; | ||||
|     const double sin_w0 = std::sin(w0); | ||||
|     const double cos_w0 = std::cos(w0); | ||||
|     const double alpha = sin_w0 / (2 * Q); | ||||
|  | ||||
|     const double a0 = 1 + alpha; | ||||
|     const double a1 = -2.0 * cos_w0; | ||||
|     const double a2 = 1 - alpha; | ||||
|     const double b0 = 0.5 * (1 - cos_w0); | ||||
|     const double b1 = 1.0 * (1 - cos_w0); | ||||
|     const double b2 = 0.5 * (1 - cos_w0); | ||||
|  | ||||
|     return {a0, a1, a2, b0, b1, b2}; | ||||
| } | ||||
|  | ||||
| Filter::Filter() : Filter(1.0, 0.0, 0.0, 1.0, 0.0, 0.0) {} | ||||
|  | ||||
| Filter::Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_) | ||||
|     : a1(a1_ / a0_), a2(a2_ / a0_), b0(b0_ / a0_), b1(b1_ / a0_), b2(b2_ / a0_) {} | ||||
|  | ||||
| void Filter::Process(std::vector<s16>& signal) { | ||||
|     const std::size_t num_frames = signal.size() / 2; | ||||
|     for (std::size_t i = 0; i < num_frames; i++) { | ||||
|         std::rotate(in.begin(), in.end() - 1, in.end()); | ||||
|         std::rotate(out.begin(), out.end() - 1, out.end()); | ||||
|  | ||||
|         for (std::size_t ch = 0; ch < channel_count; ch++) { | ||||
|             in[0][ch] = signal[i * channel_count + ch]; | ||||
|  | ||||
|             out[0][ch] = b0 * in[0][ch] + b1 * in[1][ch] + b2 * in[2][ch] - a1 * out[1][ch] - | ||||
|                          a2 * out[2][ch]; | ||||
|  | ||||
|             signal[i * 2 + ch] = static_cast<s16>(std::clamp(out[0][ch], -32768.0, 32767.0)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Calculates the appropriate Q for each biquad in a cascading filter. | ||||
| /// @param total_count The total number of biquads to be cascaded. | ||||
| /// @param index 0-index of the biquad to calculate the Q value for. | ||||
| static double CascadingBiquadQ(std::size_t total_count, std::size_t index) { | ||||
|     const auto pole = | ||||
|         M_PI * static_cast<double>(2 * index + 1) / (4.0 * static_cast<double>(total_count)); | ||||
|     return 1.0 / (2.0 * std::cos(pole)); | ||||
| } | ||||
|  | ||||
| CascadingFilter CascadingFilter::LowPass(double cutoff, std::size_t cascade_size) { | ||||
|     std::vector<Filter> cascade(cascade_size); | ||||
|     for (std::size_t i = 0; i < cascade_size; i++) { | ||||
|         cascade[i] = Filter::LowPass(cutoff, CascadingBiquadQ(cascade_size, i)); | ||||
|     } | ||||
|     return CascadingFilter{std::move(cascade)}; | ||||
| } | ||||
|  | ||||
| CascadingFilter::CascadingFilter() = default; | ||||
| CascadingFilter::CascadingFilter(std::vector<Filter> filters_) : filters(std::move(filters_)) {} | ||||
|  | ||||
| void CascadingFilter::Process(std::vector<s16>& signal) { | ||||
|     for (auto& filter : filters) { | ||||
|         filter.Process(signal); | ||||
|     } | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,61 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <vector> | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| /// Digital biquad filter: | ||||
| /// | ||||
| ///          b0 + b1 z^-1 + b2 z^-2 | ||||
| ///  H(z) = ------------------------ | ||||
| ///          a0 + a1 z^-1 + b2 z^-2 | ||||
| class Filter { | ||||
| public: | ||||
|     /// Creates a low-pass filter. | ||||
|     /// @param cutoff Determines the cutoff frequency. A value from 0.0 to 1.0. | ||||
|     /// @param Q Determines the quality factor of this filter. | ||||
|     static Filter LowPass(double cutoff, double Q = 0.7071); | ||||
|  | ||||
|     /// Passthrough filter. | ||||
|     Filter(); | ||||
|  | ||||
|     Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_); | ||||
|  | ||||
|     void Process(std::vector<s16>& signal); | ||||
|  | ||||
| private: | ||||
|     static constexpr std::size_t channel_count = 2; | ||||
|  | ||||
|     /// Coefficients are in normalized form (a0 = 1.0). | ||||
|     double a1, a2, b0, b1, b2; | ||||
|     /// Input History | ||||
|     std::array<std::array<double, channel_count>, 3> in; | ||||
|     /// Output History | ||||
|     std::array<std::array<double, channel_count>, 3> out; | ||||
| }; | ||||
|  | ||||
| /// Cascade filters to build up higher-order filters from lower-order ones. | ||||
| class CascadingFilter { | ||||
| public: | ||||
|     /// Creates a cascading low-pass filter. | ||||
|     /// @param cutoff Determines the cutoff frequency. A value from 0.0 to 1.0. | ||||
|     /// @param cascade_size Number of biquads in cascade. | ||||
|     static CascadingFilter LowPass(double cutoff, std::size_t cascade_size); | ||||
|  | ||||
|     /// Passthrough. | ||||
|     CascadingFilter(); | ||||
|  | ||||
|     explicit CascadingFilter(std::vector<Filter> filters_); | ||||
|  | ||||
|     void Process(std::vector<s16>& signal); | ||||
|  | ||||
| private: | ||||
|     std::vector<Filter> filters; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,232 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #define _USE_MATH_DEFINES | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <climits> | ||||
| #include <cmath> | ||||
| #include <vector> | ||||
|  | ||||
| #include "audio_core/algorithm/interpolate.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/logging/log.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| constexpr std::array<s16, 512> curve_lut0{ | ||||
|     6600,  19426, 6722,  3,     6479,  19424, 6845,  9,     6359,  19419, 6968,  15,    6239, | ||||
|     19412, 7093,  22,    6121,  19403, 7219,  28,    6004,  19391, 7345,  34,    5888,  19377, | ||||
|     7472,  41,    5773,  19361, 7600,  48,    5659,  19342, 7728,  55,    5546,  19321, 7857, | ||||
|     62,    5434,  19298, 7987,  69,    5323,  19273, 8118,  77,    5213,  19245, 8249,  84, | ||||
|     5104,  19215, 8381,  92,    4997,  19183, 8513,  101,   4890,  19148, 8646,  109,   4785, | ||||
|     19112, 8780,  118,   4681,  19073, 8914,  127,   4579,  19031, 9048,  137,   4477,  18988, | ||||
|     9183,  147,   4377,  18942, 9318,  157,   4277,  18895, 9454,  168,   4179,  18845, 9590, | ||||
|     179,   4083,  18793, 9726,  190,   3987,  18738, 9863,  202,   3893,  18682, 10000, 215, | ||||
|     3800,  18624, 10137, 228,   3709,  18563, 10274, 241,   3618,  18500, 10411, 255,   3529, | ||||
|     18436, 10549, 270,   3441,  18369, 10687, 285,   3355,  18300, 10824, 300,   3269,  18230, | ||||
|     10962, 317,   3186,  18157, 11100, 334,   3103,  18082, 11238, 351,   3022,  18006, 11375, | ||||
|     369,   2942,  17927, 11513, 388,   2863,  17847, 11650, 408,   2785,  17765, 11788, 428, | ||||
|     2709,  17681, 11925, 449,   2635,  17595, 12062, 471,   2561,  17507, 12198, 494,   2489, | ||||
|     17418, 12334, 517,   2418,  17327, 12470, 541,   2348,  17234, 12606, 566,   2280,  17140, | ||||
|     12741, 592,   2213,  17044, 12876, 619,   2147,  16946, 13010, 647,   2083,  16846, 13144, | ||||
|     675,   2020,  16745, 13277, 704,   1958,  16643, 13409, 735,   1897,  16539, 13541, 766, | ||||
|     1838,  16434, 13673, 798,   1780,  16327, 13803, 832,   1723,  16218, 13933, 866,   1667, | ||||
|     16109, 14062, 901,   1613,  15998, 14191, 937,   1560,  15885, 14318, 975,   1508,  15772, | ||||
|     14445, 1013,  1457,  15657, 14571, 1052,  1407,  15540, 14695, 1093,  1359,  15423, 14819, | ||||
|     1134,  1312,  15304, 14942, 1177,  1266,  15185, 15064, 1221,  1221,  15064, 15185, 1266, | ||||
|     1177,  14942, 15304, 1312,  1134,  14819, 15423, 1359,  1093,  14695, 15540, 1407,  1052, | ||||
|     14571, 15657, 1457,  1013,  14445, 15772, 1508,  975,   14318, 15885, 1560,  937,   14191, | ||||
|     15998, 1613,  901,   14062, 16109, 1667,  866,   13933, 16218, 1723,  832,   13803, 16327, | ||||
|     1780,  798,   13673, 16434, 1838,  766,   13541, 16539, 1897,  735,   13409, 16643, 1958, | ||||
|     704,   13277, 16745, 2020,  675,   13144, 16846, 2083,  647,   13010, 16946, 2147,  619, | ||||
|     12876, 17044, 2213,  592,   12741, 17140, 2280,  566,   12606, 17234, 2348,  541,   12470, | ||||
|     17327, 2418,  517,   12334, 17418, 2489,  494,   12198, 17507, 2561,  471,   12062, 17595, | ||||
|     2635,  449,   11925, 17681, 2709,  428,   11788, 17765, 2785,  408,   11650, 17847, 2863, | ||||
|     388,   11513, 17927, 2942,  369,   11375, 18006, 3022,  351,   11238, 18082, 3103,  334, | ||||
|     11100, 18157, 3186,  317,   10962, 18230, 3269,  300,   10824, 18300, 3355,  285,   10687, | ||||
|     18369, 3441,  270,   10549, 18436, 3529,  255,   10411, 18500, 3618,  241,   10274, 18563, | ||||
|     3709,  228,   10137, 18624, 3800,  215,   10000, 18682, 3893,  202,   9863,  18738, 3987, | ||||
|     190,   9726,  18793, 4083,  179,   9590,  18845, 4179,  168,   9454,  18895, 4277,  157, | ||||
|     9318,  18942, 4377,  147,   9183,  18988, 4477,  137,   9048,  19031, 4579,  127,   8914, | ||||
|     19073, 4681,  118,   8780,  19112, 4785,  109,   8646,  19148, 4890,  101,   8513,  19183, | ||||
|     4997,  92,    8381,  19215, 5104,  84,    8249,  19245, 5213,  77,    8118,  19273, 5323, | ||||
|     69,    7987,  19298, 5434,  62,    7857,  19321, 5546,  55,    7728,  19342, 5659,  48, | ||||
|     7600,  19361, 5773,  41,    7472,  19377, 5888,  34,    7345,  19391, 6004,  28,    7219, | ||||
|     19403, 6121,  22,    7093,  19412, 6239,  15,    6968,  19419, 6359,  9,     6845,  19424, | ||||
|     6479,  3,     6722,  19426, 6600}; | ||||
|  | ||||
| constexpr std::array<s16, 512> curve_lut1{ | ||||
|     -68,   32639, 69,    -5,    -200,  32630, 212,   -15,   -328,  32613, 359,   -26,   -450, | ||||
|     32586, 512,   -36,   -568,  32551, 669,   -47,   -680,  32507, 832,   -58,   -788,  32454, | ||||
|     1000,  -69,   -891,  32393, 1174,  -80,   -990,  32323, 1352,  -92,   -1084, 32244, 1536, | ||||
|     -103,  -1173, 32157, 1724,  -115,  -1258, 32061, 1919,  -128,  -1338, 31956, 2118,  -140, | ||||
|     -1414, 31844, 2322,  -153,  -1486, 31723, 2532,  -167,  -1554, 31593, 2747,  -180,  -1617, | ||||
|     31456, 2967,  -194,  -1676, 31310, 3192,  -209,  -1732, 31157, 3422,  -224,  -1783, 30995, | ||||
|     3657,  -240,  -1830, 30826, 3897,  -256,  -1874, 30649, 4143,  -272,  -1914, 30464, 4393, | ||||
|     -289,  -1951, 30272, 4648,  -307,  -1984, 30072, 4908,  -325,  -2014, 29866, 5172,  -343, | ||||
|     -2040, 29652, 5442,  -362,  -2063, 29431, 5716,  -382,  -2083, 29203, 5994,  -403,  -2100, | ||||
|     28968, 6277,  -424,  -2114, 28727, 6565,  -445,  -2125, 28480, 6857,  -468,  -2133, 28226, | ||||
|     7153,  -490,  -2139, 27966, 7453,  -514,  -2142, 27700, 7758,  -538,  -2142, 27428, 8066, | ||||
|     -563,  -2141, 27151, 8378,  -588,  -2136, 26867, 8694,  -614,  -2130, 26579, 9013,  -641, | ||||
|     -2121, 26285, 9336,  -668,  -2111, 25987, 9663,  -696,  -2098, 25683, 9993,  -724,  -2084, | ||||
|     25375, 10326, -753,  -2067, 25063, 10662, -783,  -2049, 24746, 11000, -813,  -2030, 24425, | ||||
|     11342, -844,  -2009, 24100, 11686, -875,  -1986, 23771, 12033, -907,  -1962, 23438, 12382, | ||||
|     -939,  -1937, 23103, 12733, -972,  -1911, 22764, 13086, -1005, -1883, 22422, 13441, -1039, | ||||
|     -1855, 22077, 13798, -1072, -1825, 21729, 14156, -1107, -1795, 21380, 14516, -1141, -1764, | ||||
|     21027, 14877, -1176, -1732, 20673, 15239, -1211, -1700, 20317, 15602, -1246, -1667, 19959, | ||||
|     15965, -1282, -1633, 19600, 16329, -1317, -1599, 19239, 16694, -1353, -1564, 18878, 17058, | ||||
|     -1388, -1530, 18515, 17423, -1424, -1495, 18151, 17787, -1459, -1459, 17787, 18151, -1495, | ||||
|     -1424, 17423, 18515, -1530, -1388, 17058, 18878, -1564, -1353, 16694, 19239, -1599, -1317, | ||||
|     16329, 19600, -1633, -1282, 15965, 19959, -1667, -1246, 15602, 20317, -1700, -1211, 15239, | ||||
|     20673, -1732, -1176, 14877, 21027, -1764, -1141, 14516, 21380, -1795, -1107, 14156, 21729, | ||||
|     -1825, -1072, 13798, 22077, -1855, -1039, 13441, 22422, -1883, -1005, 13086, 22764, -1911, | ||||
|     -972,  12733, 23103, -1937, -939,  12382, 23438, -1962, -907,  12033, 23771, -1986, -875, | ||||
|     11686, 24100, -2009, -844,  11342, 24425, -2030, -813,  11000, 24746, -2049, -783,  10662, | ||||
|     25063, -2067, -753,  10326, 25375, -2084, -724,  9993,  25683, -2098, -696,  9663,  25987, | ||||
|     -2111, -668,  9336,  26285, -2121, -641,  9013,  26579, -2130, -614,  8694,  26867, -2136, | ||||
|     -588,  8378,  27151, -2141, -563,  8066,  27428, -2142, -538,  7758,  27700, -2142, -514, | ||||
|     7453,  27966, -2139, -490,  7153,  28226, -2133, -468,  6857,  28480, -2125, -445,  6565, | ||||
|     28727, -2114, -424,  6277,  28968, -2100, -403,  5994,  29203, -2083, -382,  5716,  29431, | ||||
|     -2063, -362,  5442,  29652, -2040, -343,  5172,  29866, -2014, -325,  4908,  30072, -1984, | ||||
|     -307,  4648,  30272, -1951, -289,  4393,  30464, -1914, -272,  4143,  30649, -1874, -256, | ||||
|     3897,  30826, -1830, -240,  3657,  30995, -1783, -224,  3422,  31157, -1732, -209,  3192, | ||||
|     31310, -1676, -194,  2967,  31456, -1617, -180,  2747,  31593, -1554, -167,  2532,  31723, | ||||
|     -1486, -153,  2322,  31844, -1414, -140,  2118,  31956, -1338, -128,  1919,  32061, -1258, | ||||
|     -115,  1724,  32157, -1173, -103,  1536,  32244, -1084, -92,   1352,  32323, -990,  -80, | ||||
|     1174,  32393, -891,  -69,   1000,  32454, -788,  -58,   832,   32507, -680,  -47,   669, | ||||
|     32551, -568,  -36,   512,   32586, -450,  -26,   359,   32613, -328,  -15,   212,   32630, | ||||
|     -200,  -5,    69,    32639, -68}; | ||||
|  | ||||
| constexpr std::array<s16, 512> curve_lut2{ | ||||
|     3195,  26287, 3329,  -32,   3064,  26281, 3467,  -34,   2936,  26270, 3608,  -38,   2811, | ||||
|     26253, 3751,  -42,   2688,  26230, 3897,  -46,   2568,  26202, 4046,  -50,   2451,  26169, | ||||
|     4199,  -54,   2338,  26130, 4354,  -58,   2227,  26085, 4512,  -63,   2120,  26035, 4673, | ||||
|     -67,   2015,  25980, 4837,  -72,   1912,  25919, 5004,  -76,   1813,  25852, 5174,  -81, | ||||
|     1716,  25780, 5347,  -87,   1622,  25704, 5522,  -92,   1531,  25621, 5701,  -98,   1442, | ||||
|     25533, 5882,  -103,  1357,  25440, 6066,  -109,  1274,  25342, 6253,  -115,  1193,  25239, | ||||
|     6442,  -121,  1115,  25131, 6635,  -127,  1040,  25018, 6830,  -133,  967,   24899, 7027, | ||||
|     -140,  897,   24776, 7227,  -146,  829,   24648, 7430,  -153,  764,   24516, 7635,  -159, | ||||
|     701,   24379, 7842,  -166,  641,   24237, 8052,  -174,  583,   24091, 8264,  -181,  526, | ||||
|     23940, 8478,  -187,  472,   23785, 8695,  -194,  420,   23626, 8914,  -202,  371,   23462, | ||||
|     9135,  -209,  324,   23295, 9358,  -215,  279,   23123, 9583,  -222,  236,   22948, 9809, | ||||
|     -230,  194,   22769, 10038, -237,  154,   22586, 10269, -243,  117,   22399, 10501, -250, | ||||
|     81,    22208, 10735, -258,  47,    22015, 10970, -265,  15,    21818, 11206, -271,  -16, | ||||
|     21618, 11444, -277,  -44,   21415, 11684, -283,  -71,   21208, 11924, -290,  -97,   20999, | ||||
|     12166, -296,  -121,  20786, 12409, -302,  -143,  20571, 12653, -306,  -163,  20354, 12898, | ||||
|     -311,  -183,  20134, 13143, -316,  -201,  19911, 13389, -321,  -218,  19686, 13635, -325, | ||||
|     -234,  19459, 13882, -328,  -248,  19230, 14130, -332,  -261,  18998, 14377, -335,  -273, | ||||
|     18765, 14625, -337,  -284,  18531, 14873, -339,  -294,  18295, 15121, -341,  -302,  18057, | ||||
|     15369, -341,  -310,  17817, 15617, -341,  -317,  17577, 15864, -340,  -323,  17335, 16111, | ||||
|     -340,  -328,  17092, 16357, -338,  -332,  16848, 16603, -336,  -336,  16603, 16848, -332, | ||||
|     -338,  16357, 17092, -328,  -340,  16111, 17335, -323,  -340,  15864, 17577, -317,  -341, | ||||
|     15617, 17817, -310,  -341,  15369, 18057, -302,  -341,  15121, 18295, -294,  -339,  14873, | ||||
|     18531, -284,  -337,  14625, 18765, -273,  -335,  14377, 18998, -261,  -332,  14130, 19230, | ||||
|     -248,  -328,  13882, 19459, -234,  -325,  13635, 19686, -218,  -321,  13389, 19911, -201, | ||||
|     -316,  13143, 20134, -183,  -311,  12898, 20354, -163,  -306,  12653, 20571, -143,  -302, | ||||
|     12409, 20786, -121,  -296,  12166, 20999, -97,   -290,  11924, 21208, -71,   -283,  11684, | ||||
|     21415, -44,   -277,  11444, 21618, -16,   -271,  11206, 21818, 15,    -265,  10970, 22015, | ||||
|     47,    -258,  10735, 22208, 81,    -250,  10501, 22399, 117,   -243,  10269, 22586, 154, | ||||
|     -237,  10038, 22769, 194,   -230,  9809,  22948, 236,   -222,  9583,  23123, 279,   -215, | ||||
|     9358,  23295, 324,   -209,  9135,  23462, 371,   -202,  8914,  23626, 420,   -194,  8695, | ||||
|     23785, 472,   -187,  8478,  23940, 526,   -181,  8264,  24091, 583,   -174,  8052,  24237, | ||||
|     641,   -166,  7842,  24379, 701,   -159,  7635,  24516, 764,   -153,  7430,  24648, 829, | ||||
|     -146,  7227,  24776, 897,   -140,  7027,  24899, 967,   -133,  6830,  25018, 1040,  -127, | ||||
|     6635,  25131, 1115,  -121,  6442,  25239, 1193,  -115,  6253,  25342, 1274,  -109,  6066, | ||||
|     25440, 1357,  -103,  5882,  25533, 1442,  -98,   5701,  25621, 1531,  -92,   5522,  25704, | ||||
|     1622,  -87,   5347,  25780, 1716,  -81,   5174,  25852, 1813,  -76,   5004,  25919, 1912, | ||||
|     -72,   4837,  25980, 2015,  -67,   4673,  26035, 2120,  -63,   4512,  26085, 2227,  -58, | ||||
|     4354,  26130, 2338,  -54,   4199,  26169, 2451,  -50,   4046,  26202, 2568,  -46,   3897, | ||||
|     26230, 2688,  -42,   3751,  26253, 2811,  -38,   3608,  26270, 2936,  -34,   3467,  26281, | ||||
|     3064,  -32,   3329,  26287, 3195}; | ||||
|  | ||||
| std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, double ratio) { | ||||
|     if (input.size() < 2) | ||||
|         return {}; | ||||
|  | ||||
|     if (ratio <= 0) { | ||||
|         LOG_ERROR(Audio, "Nonsensical interpolation ratio {}", ratio); | ||||
|         return input; | ||||
|     } | ||||
|  | ||||
|     const s32 step{static_cast<s32>(ratio * 0x8000)}; | ||||
|     const std::array<s16, 512>& lut = [step] { | ||||
|         if (step > 0xaaaa) { | ||||
|             return curve_lut0; | ||||
|         } | ||||
|         if (step <= 0x8000) { | ||||
|             return curve_lut1; | ||||
|         } | ||||
|         return curve_lut2; | ||||
|     }(); | ||||
|  | ||||
|     const std::size_t num_frames{input.size() / 2}; | ||||
|  | ||||
|     std::vector<s16> output; | ||||
|     output.reserve(static_cast<std::size_t>(static_cast<double>(input.size()) / ratio + | ||||
|                                             InterpolationState::taps)); | ||||
|  | ||||
|     for (std::size_t frame{}; frame < num_frames; ++frame) { | ||||
|         const std::size_t lut_index{(state.fraction >> 8) * InterpolationState::taps}; | ||||
|  | ||||
|         std::rotate(state.history.begin(), state.history.end() - 1, state.history.end()); | ||||
|         state.history[0][0] = input[frame * 2 + 0]; | ||||
|         state.history[0][1] = input[frame * 2 + 1]; | ||||
|  | ||||
|         while (state.position <= 1.0) { | ||||
|             const s32 left{state.history[0][0] * lut[lut_index + 0] + | ||||
|                            state.history[1][0] * lut[lut_index + 1] + | ||||
|                            state.history[2][0] * lut[lut_index + 2] + | ||||
|                            state.history[3][0] * lut[lut_index + 3]}; | ||||
|             const s32 right{state.history[0][1] * lut[lut_index + 0] + | ||||
|                             state.history[1][1] * lut[lut_index + 1] + | ||||
|                             state.history[2][1] * lut[lut_index + 2] + | ||||
|                             state.history[3][1] * lut[lut_index + 3]}; | ||||
|             const s32 new_offset{state.fraction + step}; | ||||
|  | ||||
|             state.fraction = new_offset & 0x7fff; | ||||
|  | ||||
|             output.emplace_back(static_cast<s16>(std::clamp(left >> 15, SHRT_MIN, SHRT_MAX))); | ||||
|             output.emplace_back(static_cast<s16>(std::clamp(right >> 15, SHRT_MIN, SHRT_MAX))); | ||||
|  | ||||
|             state.position += ratio; | ||||
|         } | ||||
|         state.position -= 1.0; | ||||
|     } | ||||
|  | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count) { | ||||
|     const std::array<s16, 512>& lut = [pitch] { | ||||
|         if (pitch > 0xaaaa) { | ||||
|             return curve_lut0; | ||||
|         } | ||||
|         if (pitch <= 0x8000) { | ||||
|             return curve_lut1; | ||||
|         } | ||||
|         return curve_lut2; | ||||
|     }(); | ||||
|  | ||||
|     std::size_t index{}; | ||||
|  | ||||
|     for (std::size_t i = 0; i < sample_count; i++) { | ||||
|         const std::size_t lut_index{(static_cast<std::size_t>(fraction) >> 8) * 4}; | ||||
|         const auto l0 = lut[lut_index + 0]; | ||||
|         const auto l1 = lut[lut_index + 1]; | ||||
|         const auto l2 = lut[lut_index + 2]; | ||||
|         const auto l3 = lut[lut_index + 3]; | ||||
|  | ||||
|         const auto s0 = static_cast<s32>(input[index + 0]); | ||||
|         const auto s1 = static_cast<s32>(input[index + 1]); | ||||
|         const auto s2 = static_cast<s32>(input[index + 2]); | ||||
|         const auto s3 = static_cast<s32>(input[index + 3]); | ||||
|  | ||||
|         output[i] = (l0 * s0 + l1 * s1 + l2 * s2 + l3 * s3) >> 15; | ||||
|         fraction += pitch; | ||||
|         index += (fraction >> 15); | ||||
|         fraction &= 0x7fff; | ||||
|     } | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,43 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <vector> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| struct InterpolationState { | ||||
|     static constexpr std::size_t taps{4}; | ||||
|     static constexpr std::size_t history_size{taps * 2 - 1}; | ||||
|     std::array<std::array<s16, 2>, history_size> history{}; | ||||
|     double position{}; | ||||
|     s32 fraction{}; | ||||
| }; | ||||
|  | ||||
| /// Interpolates input signal to produce output signal. | ||||
| /// @param input The signal to interpolate. | ||||
| /// @param ratio Interpolation ratio. | ||||
| ///              ratio > 1.0 results in fewer output samples. | ||||
| ///              ratio < 1.0 results in more output samples. | ||||
| /// @returns Output signal. | ||||
| std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, double ratio); | ||||
|  | ||||
| /// Interpolates input signal to produce output signal. | ||||
| /// @param input The signal to interpolate. | ||||
| /// @param input_rate The sample rate of input. | ||||
| /// @param output_rate The desired sample rate of the output. | ||||
| /// @returns Output signal. | ||||
| inline std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, | ||||
|                                     u32 input_rate, u32 output_rate) { | ||||
|     const double ratio = static_cast<double>(input_rate) / static_cast<double>(output_rate); | ||||
|     return Interpolate(state, std::move(input), ratio); | ||||
| } | ||||
|  | ||||
| /// Nintendo Switchs DSP resampling algorithm. Based on a single channel | ||||
| void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count); | ||||
|  | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										68
									
								
								src/audio_core/audio_core.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/audio_core/audio_core.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/audio_core.h" | ||||
| #include "audio_core/sink/sink_details.h" | ||||
| #include "common/settings.h" | ||||
| #include "core/core.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>(system)} { | ||||
|     CreateSinks(); | ||||
|     // Must be created after the sinks | ||||
|     adsp = std::make_unique<AudioRenderer::ADSP::ADSP>(system, *output_sink); | ||||
| } | ||||
|  | ||||
| AudioCore ::~AudioCore() { | ||||
|     Shutdown(); | ||||
| } | ||||
|  | ||||
| void AudioCore::CreateSinks() { | ||||
|     const auto& sink_id{Settings::values.sink_id}; | ||||
|     const auto& audio_output_device_id{Settings::values.audio_output_device_id}; | ||||
|     const auto& audio_input_device_id{Settings::values.audio_input_device_id}; | ||||
|  | ||||
|     output_sink = Sink::CreateSinkFromID(sink_id.GetValue(), audio_output_device_id.GetValue()); | ||||
|     input_sink = Sink::CreateSinkFromID(sink_id.GetValue(), audio_input_device_id.GetValue()); | ||||
| } | ||||
|  | ||||
| void AudioCore::Shutdown() { | ||||
|     audio_manager->Shutdown(); | ||||
| } | ||||
|  | ||||
| AudioManager& AudioCore::GetAudioManager() { | ||||
|     return *audio_manager; | ||||
| } | ||||
|  | ||||
| Sink::Sink& AudioCore::GetOutputSink() { | ||||
|     return *output_sink; | ||||
| } | ||||
|  | ||||
| Sink::Sink& AudioCore::GetInputSink() { | ||||
|     return *input_sink; | ||||
| } | ||||
|  | ||||
| AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() { | ||||
|     return *adsp; | ||||
| } | ||||
|  | ||||
| void AudioCore::PauseSinks(const bool pausing) const { | ||||
|     if (pausing) { | ||||
|         output_sink->PauseStreams(); | ||||
|         input_sink->PauseStreams(); | ||||
|     } else { | ||||
|         output_sink->UnpauseStreams(); | ||||
|         input_sink->UnpauseStreams(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| u32 AudioCore::GetStreamQueue() const { | ||||
|     return estimated_queue.load(); | ||||
| } | ||||
|  | ||||
| void AudioCore::SetStreamQueue(u32 size) { | ||||
|     estimated_queue.store(size); | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										100
									
								
								src/audio_core/audio_core.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/audio_core/audio_core.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
|  | ||||
| #include "audio_core/audio_manager.h" | ||||
| #include "audio_core/renderer/adsp/adsp.h" | ||||
| #include "audio_core/sink/sink.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| class AudioManager; | ||||
| /** | ||||
|  * Main audio class, sotred inside the core, and holding the audio manager, all sinks, and the ADSP. | ||||
|  */ | ||||
| class AudioCore { | ||||
| public: | ||||
|     explicit AudioCore(Core::System& system); | ||||
|     ~AudioCore(); | ||||
|  | ||||
|     /** | ||||
|      * Shutdown the audio core. | ||||
|      */ | ||||
|     void Shutdown(); | ||||
|  | ||||
|     /** | ||||
|      * Get a reference to the audio manager. | ||||
|      * | ||||
|      * @return Ref to the audio manager. | ||||
|      */ | ||||
|     AudioManager& GetAudioManager(); | ||||
|  | ||||
|     /** | ||||
|      * Get the audio output sink currently in use. | ||||
|      * | ||||
|      * @return Ref to the sink. | ||||
|      */ | ||||
|     Sink::Sink& GetOutputSink(); | ||||
|  | ||||
|     /** | ||||
|      * Get the audio input sink currently in use. | ||||
|      * | ||||
|      * @return Ref to the sink. | ||||
|      */ | ||||
|     Sink::Sink& GetInputSink(); | ||||
|  | ||||
|     /** | ||||
|      * Get the ADSP. | ||||
|      * | ||||
|      * @return Ref to the ADSP. | ||||
|      */ | ||||
|     AudioRenderer::ADSP::ADSP& GetADSP(); | ||||
|  | ||||
|     /** | ||||
|      * Pause the sink. Called from the core. | ||||
|      * | ||||
|      * @param pausing - Is this pause due to an actual pause, or shutdown? | ||||
|      *                  Unfortunately, shutdown also pauses streams, which can cause issues. | ||||
|      */ | ||||
|     void PauseSinks(bool pausing) const; | ||||
|  | ||||
|     /** | ||||
|      * Get the size of the current stream queue. | ||||
|      * | ||||
|      * @return Current stream queue size. | ||||
|      */ | ||||
|     u32 GetStreamQueue() const; | ||||
|  | ||||
|     /** | ||||
|      * Get the size of the current stream queue. | ||||
|      * | ||||
|      * @param size - New stream size. | ||||
|      */ | ||||
|     void SetStreamQueue(u32 size); | ||||
|  | ||||
| private: | ||||
|     /** | ||||
|      * Create the sinks on startup. | ||||
|      */ | ||||
|     void CreateSinks(); | ||||
|  | ||||
|     /// Main audio manager for audio in/out | ||||
|     std::unique_ptr<AudioManager> audio_manager; | ||||
|     /// Sink used for audio renderer and audio out | ||||
|     std::unique_ptr<Sink::Sink> output_sink; | ||||
|     /// Sink used for audio input | ||||
|     std::unique_ptr<Sink::Sink> input_sink; | ||||
|     /// The ADSP in the sysmodule | ||||
|     std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp; | ||||
|     /// Current size of the stream queue | ||||
|     std::atomic<u32> estimated_queue{0}; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										61
									
								
								src/audio_core/audio_event.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/audio_core/audio_event.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/audio_event.h" | ||||
| #include "common/assert.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| size_t Event::GetManagerIndex(const Type type) const { | ||||
|     switch (type) { | ||||
|     case Type::AudioInManager: | ||||
|         return 0; | ||||
|     case Type::AudioOutManager: | ||||
|         return 1; | ||||
|     case Type::FinalOutputRecorderManager: | ||||
|         return 2; | ||||
|     case Type::Max: | ||||
|         return 3; | ||||
|     default: | ||||
|         UNREACHABLE(); | ||||
|     } | ||||
|     return 3; | ||||
| } | ||||
|  | ||||
| void Event::SetAudioEvent(const Type type, const bool signalled) { | ||||
|     events_signalled[GetManagerIndex(type)] = signalled; | ||||
|     if (signalled) { | ||||
|         manager_event.notify_one(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool Event::CheckAudioEventSet(const Type type) const { | ||||
|     return events_signalled[GetManagerIndex(type)]; | ||||
| } | ||||
|  | ||||
| std::mutex& Event::GetAudioEventLock() { | ||||
|     return event_lock; | ||||
| } | ||||
|  | ||||
| std::condition_variable_any& Event::GetAudioEvent() { | ||||
|     return manager_event; | ||||
| } | ||||
|  | ||||
| bool Event::Wait(std::unique_lock<std::mutex>& l, const std::chrono::seconds timeout) { | ||||
|     bool timed_out{false}; | ||||
|     if (!manager_event.wait_for(l, timeout, [&]() { | ||||
|             return std::ranges::any_of(events_signalled, [](bool x) { return x; }); | ||||
|         })) { | ||||
|         timed_out = true; | ||||
|     } | ||||
|     return timed_out; | ||||
| } | ||||
|  | ||||
| void Event::ClearEvents() { | ||||
|     events_signalled[0] = false; | ||||
|     events_signalled[1] = false; | ||||
|     events_signalled[2] = false; | ||||
|     events_signalled[3] = false; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										92
									
								
								src/audio_core/audio_event.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/audio_core/audio_event.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <atomic> | ||||
| #include <chrono> | ||||
| #include <condition_variable> | ||||
| #include <mutex> | ||||
|  | ||||
| namespace AudioCore { | ||||
| /** | ||||
|  * Responsible for the input/output events, set by the stream backend when buffers are consumed, and | ||||
|  * waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer | ||||
|  * recycling going. | ||||
|  * In a real Switch this is not a seprate class, and exists entirely within the audio manager. | ||||
|  * On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can | ||||
|  * wait on multiple events at once, and the events are not needed by the backend. | ||||
|  */ | ||||
| class Event { | ||||
| public: | ||||
|     enum class Type { | ||||
|         AudioInManager, | ||||
|         AudioOutManager, | ||||
|         FinalOutputRecorderManager, | ||||
|         Max, | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Convert a manager type to an index. | ||||
|      * | ||||
|      * @param type - The manager type to convert | ||||
|      * @return The index of the type. | ||||
|      */ | ||||
|     size_t GetManagerIndex(Type type) const; | ||||
|  | ||||
|     /** | ||||
|      * Set an audio event to true or false. | ||||
|      * | ||||
|      * @param type      - The manager type to signal. | ||||
|      * @param signalled - Its signal state. | ||||
|      */ | ||||
|     void SetAudioEvent(Type type, bool signalled); | ||||
|  | ||||
|     /** | ||||
|      * Check if the given manager type is signalled. | ||||
|      * | ||||
|      * @param type - The manager type to check. | ||||
|      * @return True if the event is signalled, otherwise false. | ||||
|      */ | ||||
|     bool CheckAudioEventSet(Type type) const; | ||||
|  | ||||
|     /** | ||||
|      * Get the lock for audio events. | ||||
|      * | ||||
|      * @return Reference to the lock. | ||||
|      */ | ||||
|     std::mutex& GetAudioEventLock(); | ||||
|  | ||||
|     /** | ||||
|      * Get the manager event, this signals the audio manager to release buffers and signal the game | ||||
|      * for more. | ||||
|      * | ||||
|      * @return Reference to the condition variable. | ||||
|      */ | ||||
|     std::condition_variable_any& GetAudioEvent(); | ||||
|  | ||||
|     /** | ||||
|      * Wait on the manager_event. | ||||
|      * | ||||
|      * @param l       - Lock held by the wait. | ||||
|      * @param timeout - Timeout for the wait. This is 2 seconds by default. | ||||
|      * @return True if the wait timed out, otherwise false if signalled. | ||||
|      */ | ||||
|     bool Wait(std::unique_lock<std::mutex>& l, std::chrono::seconds timeout); | ||||
|  | ||||
|     /** | ||||
|      * Reset all manager events. | ||||
|      */ | ||||
|     void ClearEvents(); | ||||
|  | ||||
| private: | ||||
|     /// Lock, used bythe audio manager | ||||
|     std::mutex event_lock; | ||||
|     /// Array of events, one per system type (see Type), last event is used to terminate | ||||
|     std::array<std::atomic<bool>, 4> events_signalled; | ||||
|     /// Event to signal the audio manager | ||||
|     std::condition_variable_any manager_event; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										91
									
								
								src/audio_core/audio_in_manager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/audio_core/audio_in_manager.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/audio_core.h" | ||||
| #include "audio_core/audio_in_manager.h" | ||||
| #include "audio_core/audio_manager.h" | ||||
| #include "audio_core/in/audio_in.h" | ||||
| #include "audio_core/sink/sink_details.h" | ||||
| #include "common/settings.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hle/service/audio/errors.h" | ||||
|  | ||||
| namespace AudioCore::AudioIn { | ||||
|  | ||||
| Manager::Manager(Core::System& system_) : system{system_} { | ||||
|     std::iota(session_ids.begin(), session_ids.end(), 0); | ||||
|     num_free_sessions = MaxInSessions; | ||||
| } | ||||
|  | ||||
| Result Manager::AcquireSessionId(size_t& session_id) { | ||||
|     if (num_free_sessions == 0) { | ||||
|         LOG_ERROR(Service_Audio, "All 4 AudioIn sessions are in use, cannot create any more"); | ||||
|         return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED; | ||||
|     } | ||||
|     session_id = session_ids[next_session_id]; | ||||
|     next_session_id = (next_session_id + 1) % MaxInSessions; | ||||
|     num_free_sessions--; | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| void Manager::ReleaseSessionId(const size_t session_id) { | ||||
|     std::scoped_lock l{mutex}; | ||||
|     LOG_DEBUG(Service_Audio, "Freeing AudioIn session {}", session_id); | ||||
|     session_ids[free_session_id] = session_id; | ||||
|     num_free_sessions++; | ||||
|     free_session_id = (free_session_id + 1) % MaxInSessions; | ||||
|     sessions[session_id].reset(); | ||||
|     applet_resource_user_ids[session_id] = 0; | ||||
| } | ||||
|  | ||||
| Result Manager::LinkToManager() { | ||||
|     std::scoped_lock l{mutex}; | ||||
|     if (!linked_to_manager) { | ||||
|         AudioManager& manager{system.AudioCore().GetAudioManager()}; | ||||
|         manager.SetInManager(std::bind(&Manager::BufferReleaseAndRegister, this)); | ||||
|         linked_to_manager = true; | ||||
|     } | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| void Manager::Start() { | ||||
|     if (sessions_started) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     std::scoped_lock l{mutex}; | ||||
|     for (auto& session : sessions) { | ||||
|         if (session) { | ||||
|             session->StartSession(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     sessions_started = true; | ||||
| } | ||||
|  | ||||
| void Manager::BufferReleaseAndRegister() { | ||||
|     std::scoped_lock l{mutex}; | ||||
|     for (auto& session : sessions) { | ||||
|         if (session != nullptr) { | ||||
|             session->ReleaseAndRegisterBuffers(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names, | ||||
|                             [[maybe_unused]] const u32 max_count, | ||||
|                             [[maybe_unused]] const bool filter) { | ||||
|     std::scoped_lock l{mutex}; | ||||
|  | ||||
|     LinkToManager(); | ||||
|  | ||||
|     auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)}; | ||||
|     if (input_devices.size() > 1) { | ||||
|         names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac")); | ||||
|         return 1; | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioIn | ||||
							
								
								
									
										92
									
								
								src/audio_core/audio_in_manager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/audio_core/audio_in_manager.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <mutex> | ||||
| #include <vector> | ||||
|  | ||||
| #include "audio_core/renderer/audio_device.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace AudioCore::AudioIn { | ||||
| class In; | ||||
|  | ||||
| constexpr size_t MaxInSessions = 4; | ||||
| /** | ||||
|  * Manages all audio in sessions. | ||||
|  */ | ||||
| class Manager { | ||||
| public: | ||||
|     explicit Manager(Core::System& system); | ||||
|  | ||||
|     /** | ||||
|      * Acquire a free session id for opening a new audio in. | ||||
|      * | ||||
|      * @param session_id - Output session_id. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result AcquireSessionId(size_t& session_id); | ||||
|  | ||||
|     /** | ||||
|      * Release a session id on close. | ||||
|      * | ||||
|      * @param session_id - Session id to free. | ||||
|      */ | ||||
|     void ReleaseSessionId(size_t session_id); | ||||
|  | ||||
|     /** | ||||
|      * Link the audio in manager to the main audio manager. | ||||
|      * | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result LinkToManager(); | ||||
|  | ||||
|     /** | ||||
|      * Start the audio in manager. | ||||
|      */ | ||||
|     void Start(); | ||||
|  | ||||
|     /** | ||||
|      * Callback function, called by the audio manager when the audio in event is signalled. | ||||
|      */ | ||||
|     void BufferReleaseAndRegister(); | ||||
|  | ||||
|     /** | ||||
|      * Get a list of audio in device names. | ||||
|      * | ||||
|      * @oaram names     - Output container to write names to. | ||||
|      * @param max_count - Maximum numebr of deivce names to write. Unused | ||||
|      * @param filter    - Should the list be filtered? Unused. | ||||
|      * @return Number of names written. | ||||
|      */ | ||||
|     u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names, | ||||
|                        u32 max_count, bool filter); | ||||
|  | ||||
|     /// Core system | ||||
|     Core::System& system; | ||||
|     /// Array of session ids | ||||
|     std::array<size_t, MaxInSessions> session_ids{}; | ||||
|     /// Array of resource user ids | ||||
|     std::array<size_t, MaxInSessions> applet_resource_user_ids{}; | ||||
|     /// Pointer to each open session | ||||
|     std::array<std::shared_ptr<In>, MaxInSessions> sessions{}; | ||||
|     /// The number of free sessions | ||||
|     size_t num_free_sessions{}; | ||||
|     /// The next session id to be taken | ||||
|     size_t next_session_id{}; | ||||
|     /// The next session id to be freed | ||||
|     size_t free_session_id{}; | ||||
|     /// Whether this is linked to the audio manager | ||||
|     bool linked_to_manager{}; | ||||
|     /// Whether the sessions have been started | ||||
|     bool sessions_started{}; | ||||
|     /// Protect state due to audio manager callback | ||||
|     std::recursive_mutex mutex{}; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioIn | ||||
							
								
								
									
										80
									
								
								src/audio_core/audio_manager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/audio_core/audio_manager.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/audio_in_manager.h" | ||||
| #include "audio_core/audio_manager.h" | ||||
| #include "audio_core/audio_out_manager.h" | ||||
| #include "core/core.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| AudioManager::AudioManager(Core::System& system_) : system{system_} { | ||||
|     thread = std::jthread([this]() { ThreadFunc(); }); | ||||
| } | ||||
|  | ||||
| void AudioManager::Shutdown() { | ||||
|     running = false; | ||||
|     events.SetAudioEvent(Event::Type::Max, true); | ||||
|     thread.join(); | ||||
| } | ||||
|  | ||||
| Result AudioManager::SetOutManager(BufferEventFunc buffer_func) { | ||||
|     if (!running) { | ||||
|         return Service::Audio::ERR_OPERATION_FAILED; | ||||
|     } | ||||
|  | ||||
|     std::scoped_lock l{lock}; | ||||
|  | ||||
|     const auto index{events.GetManagerIndex(Event::Type::AudioOutManager)}; | ||||
|     if (buffer_events[index] == nullptr) { | ||||
|         buffer_events[index] = buffer_func; | ||||
|         needs_update = true; | ||||
|         events.SetAudioEvent(Event::Type::AudioOutManager, true); | ||||
|     } | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result AudioManager::SetInManager(BufferEventFunc buffer_func) { | ||||
|     if (!running) { | ||||
|         return Service::Audio::ERR_OPERATION_FAILED; | ||||
|     } | ||||
|  | ||||
|     std::scoped_lock l{lock}; | ||||
|  | ||||
|     const auto index{events.GetManagerIndex(Event::Type::AudioInManager)}; | ||||
|     if (buffer_events[index] == nullptr) { | ||||
|         buffer_events[index] = buffer_func; | ||||
|         needs_update = true; | ||||
|         events.SetAudioEvent(Event::Type::AudioInManager, true); | ||||
|     } | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| void AudioManager::SetEvent(const Event::Type type, const bool signalled) { | ||||
|     events.SetAudioEvent(type, signalled); | ||||
| } | ||||
|  | ||||
| void AudioManager::ThreadFunc() { | ||||
|     std::unique_lock l{events.GetAudioEventLock()}; | ||||
|     events.ClearEvents(); | ||||
|     running = true; | ||||
|  | ||||
|     while (running) { | ||||
|         auto timed_out{events.Wait(l, std::chrono::seconds(2))}; | ||||
|  | ||||
|         if (events.CheckAudioEventSet(Event::Type::Max)) { | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         for (size_t i = 0; i < buffer_events.size(); i++) { | ||||
|             if (events.CheckAudioEventSet(Event::Type(i)) || timed_out) { | ||||
|                 if (buffer_events[i]) { | ||||
|                     buffer_events[i](); | ||||
|                 } | ||||
|             } | ||||
|             events.SetAudioEvent(Event::Type(i), false); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										101
									
								
								src/audio_core/audio_manager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/audio_core/audio_manager.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <atomic> | ||||
| #include <functional> | ||||
| #include <mutex> | ||||
| #include <thread> | ||||
|  | ||||
| #include "audio_core/audio_event.h" | ||||
| #include "core/hle/service/audio/errors.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| namespace AudioOut { | ||||
| class Manager; | ||||
| } | ||||
|  | ||||
| namespace AudioIn { | ||||
| class Manager; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * The AudioManager's main purpose is to wait for buffer events for the audio in and out managers, | ||||
|  * and call an associated callback to release buffers. | ||||
|  * | ||||
|  * Execution pattern is: | ||||
|  *     Buffers appended -> | ||||
|  *     Buffers queued and played by the backend stream -> | ||||
|  *     When consumed, set the corresponding manager event and signal the audio manager -> | ||||
|  *     Consumed buffers are released, game is signalled -> | ||||
|  *     Game appends more buffers. | ||||
|  * | ||||
|  * This is only used by audio in and audio out. | ||||
|  */ | ||||
| class AudioManager { | ||||
|     using BufferEventFunc = std::function<void()>; | ||||
|  | ||||
| public: | ||||
|     explicit AudioManager(Core::System& system); | ||||
|  | ||||
|     /** | ||||
|      * Shutdown the audio manager. | ||||
|      */ | ||||
|     void Shutdown(); | ||||
|  | ||||
|     /** | ||||
|      * Register the out manager, keeping a function to be called when the out event is signalled. | ||||
|      * | ||||
|      * @param buffer_func - Function to be called on signal. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result SetOutManager(BufferEventFunc buffer_func); | ||||
|  | ||||
|     /** | ||||
|      * Register the in manager, keeping a function to be called when the in event is signalled. | ||||
|      * | ||||
|      * @param buffer_func - Function to be called on signal. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result SetInManager(BufferEventFunc buffer_func); | ||||
|  | ||||
|     /** | ||||
|      * Set an event to signalled, and signal the thread. | ||||
|      * | ||||
|      * @param type      - Manager type to set. | ||||
|      * @param signalled - Set the event to true or false? | ||||
|      */ | ||||
|     void SetEvent(Event::Type type, bool signalled); | ||||
|  | ||||
| private: | ||||
|     /** | ||||
|      * Main thread, waiting on a manager signal and calling the registered fucntion. | ||||
|      */ | ||||
|     void ThreadFunc(); | ||||
|  | ||||
|     /// Core system | ||||
|     Core::System& system; | ||||
|     /// Have sessions started palying? | ||||
|     bool sessions_started{}; | ||||
|     /// Is the main thread running? | ||||
|     std::atomic<bool> running{}; | ||||
|     /// Unused | ||||
|     bool needs_update{}; | ||||
|     /// Events to be set and signalled | ||||
|     Event events{}; | ||||
|     /// Callbacks for each manager | ||||
|     std::array<BufferEventFunc, 3> buffer_events{}; | ||||
|     /// General lock | ||||
|     std::mutex lock{}; | ||||
|     /// Main thread for waiting and callbacks | ||||
|     std::jthread thread; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,62 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/audio_out.h" | ||||
| #include "audio_core/sink.h" | ||||
| #include "audio_core/sink_details.h" | ||||
| #include "common/assert.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/settings.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| /// Returns the stream format from the specified number of channels | ||||
| static Stream::Format ChannelsToStreamFormat(u32 num_channels) { | ||||
|     switch (num_channels) { | ||||
|     case 1: | ||||
|         return Stream::Format::Mono16; | ||||
|     case 2: | ||||
|         return Stream::Format::Stereo16; | ||||
|     case 6: | ||||
|         return Stream::Format::Multi51Channel16; | ||||
|     } | ||||
|  | ||||
|     UNIMPLEMENTED_MSG("Unimplemented num_channels={}", num_channels); | ||||
|     return {}; | ||||
| } | ||||
|  | ||||
| StreamPtr AudioOut::OpenStream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, | ||||
|                                u32 num_channels, std::string&& name, | ||||
|                                Stream::ReleaseCallback&& release_callback) { | ||||
|     if (!sink) { | ||||
|         sink = CreateSinkFromID(Settings::values.sink_id.GetValue(), | ||||
|                                 Settings::values.audio_device_id.GetValue()); | ||||
|     } | ||||
|  | ||||
|     return std::make_shared<Stream>( | ||||
|         core_timing, sample_rate, ChannelsToStreamFormat(num_channels), std::move(release_callback), | ||||
|         sink->AcquireSinkStream(sample_rate, num_channels, name), std::move(name)); | ||||
| } | ||||
|  | ||||
| std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, | ||||
|                                                             std::size_t max_count) { | ||||
|     return stream->GetTagsAndReleaseBuffers(max_count); | ||||
| } | ||||
|  | ||||
| std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream) { | ||||
|     return stream->GetTagsAndReleaseBuffers(); | ||||
| } | ||||
|  | ||||
| void AudioOut::StartStream(StreamPtr stream) { | ||||
|     stream->Play(); | ||||
| } | ||||
|  | ||||
| void AudioOut::StopStream(StreamPtr stream) { | ||||
|     stream->Stop(); | ||||
| } | ||||
|  | ||||
| bool AudioOut::QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<s16>&& data) { | ||||
|     return stream->QueueBuffer(std::make_shared<Buffer>(tag, std::move(data))); | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,49 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
|  | ||||
| #include "audio_core/buffer.h" | ||||
| #include "audio_core/sink.h" | ||||
| #include "audio_core/stream.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace Core::Timing { | ||||
| class CoreTiming; | ||||
| } | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| /** | ||||
|  * Represents an audio playback interface, used to open and play audio streams | ||||
|  */ | ||||
| class AudioOut { | ||||
| public: | ||||
|     /// Opens a new audio stream | ||||
|     StreamPtr OpenStream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, u32 num_channels, | ||||
|                          std::string&& name, Stream::ReleaseCallback&& release_callback); | ||||
|  | ||||
|     /// Returns a vector of recently released buffers specified by tag for the specified stream | ||||
|     std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream, std::size_t max_count); | ||||
|  | ||||
|     /// Returns a vector of all recently released buffers specified by tag for the specified stream | ||||
|     std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream); | ||||
|  | ||||
|     /// Starts an audio stream for playback | ||||
|     void StartStream(StreamPtr stream); | ||||
|  | ||||
|     /// Stops an audio stream that is currently playing | ||||
|     void StopStream(StreamPtr stream); | ||||
|  | ||||
|     /// Queues a buffer into the specified audio stream, returns true on success | ||||
|     bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<s16>&& data); | ||||
|  | ||||
| private: | ||||
|     SinkPtr sink; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										81
									
								
								src/audio_core/audio_out_manager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/audio_core/audio_out_manager.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/audio_core.h" | ||||
| #include "audio_core/audio_manager.h" | ||||
| #include "audio_core/audio_out_manager.h" | ||||
| #include "audio_core/out/audio_out.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hle/kernel/k_event.h" | ||||
| #include "core/hle/service/audio/errors.h" | ||||
|  | ||||
| namespace AudioCore::AudioOut { | ||||
|  | ||||
| Manager::Manager(Core::System& system_) : system{system_} { | ||||
|     std::iota(session_ids.begin(), session_ids.end(), 0); | ||||
|     num_free_sessions = MaxOutSessions; | ||||
| } | ||||
|  | ||||
| Result Manager::AcquireSessionId(size_t& session_id) { | ||||
|     if (num_free_sessions == 0) { | ||||
|         LOG_ERROR(Service_Audio, "All 12 Audio Out sessions are in use, cannot create any more"); | ||||
|         return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED; | ||||
|     } | ||||
|     session_id = session_ids[next_session_id]; | ||||
|     next_session_id = (next_session_id + 1) % MaxOutSessions; | ||||
|     num_free_sessions--; | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| void Manager::ReleaseSessionId(const size_t session_id) { | ||||
|     std::scoped_lock l{mutex}; | ||||
|     LOG_DEBUG(Service_Audio, "Freeing AudioOut session {}", session_id); | ||||
|     session_ids[free_session_id] = session_id; | ||||
|     num_free_sessions++; | ||||
|     free_session_id = (free_session_id + 1) % MaxOutSessions; | ||||
|     sessions[session_id].reset(); | ||||
|     applet_resource_user_ids[session_id] = 0; | ||||
| } | ||||
|  | ||||
| Result Manager::LinkToManager() { | ||||
|     std::scoped_lock l{mutex}; | ||||
|     if (!linked_to_manager) { | ||||
|         AudioManager& manager{system.AudioCore().GetAudioManager()}; | ||||
|         manager.SetOutManager(std::bind(&Manager::BufferReleaseAndRegister, this)); | ||||
|         linked_to_manager = true; | ||||
|     } | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| void Manager::Start() { | ||||
|     if (sessions_started) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     std::scoped_lock l{mutex}; | ||||
|     for (auto& session : sessions) { | ||||
|         if (session) { | ||||
|             session->StartSession(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     sessions_started = true; | ||||
| } | ||||
|  | ||||
| void Manager::BufferReleaseAndRegister() { | ||||
|     std::scoped_lock l{mutex}; | ||||
|     for (auto& session : sessions) { | ||||
|         if (session != nullptr) { | ||||
|             session->ReleaseAndRegisterBuffers(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| u32 Manager::GetAudioOutDeviceNames( | ||||
|     std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const { | ||||
|     names.push_back({"DeviceOut"}); | ||||
|     return 1; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioOut | ||||
							
								
								
									
										89
									
								
								src/audio_core/audio_out_manager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/audio_core/audio_out_manager.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <mutex> | ||||
|  | ||||
| #include "audio_core/renderer/audio_device.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace AudioCore::AudioOut { | ||||
| class Out; | ||||
|  | ||||
| constexpr size_t MaxOutSessions = 12; | ||||
| /** | ||||
|  * Manages all audio out sessions. | ||||
|  */ | ||||
| class Manager { | ||||
| public: | ||||
|     explicit Manager(Core::System& system); | ||||
|  | ||||
|     /** | ||||
|      * Acquire a free session id for opening a new audio out. | ||||
|      * | ||||
|      * @param session_id - Output session_id. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result AcquireSessionId(size_t& session_id); | ||||
|  | ||||
|     /** | ||||
|      * Release a session id on close. | ||||
|      * | ||||
|      * @param session_id - Session id to free. | ||||
|      */ | ||||
|     void ReleaseSessionId(size_t session_id); | ||||
|  | ||||
|     /** | ||||
|      * Link this manager to the main audio manager. | ||||
|      * | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result LinkToManager(); | ||||
|  | ||||
|     /** | ||||
|      * Start the audio out manager. | ||||
|      */ | ||||
|     void Start(); | ||||
|  | ||||
|     /** | ||||
|      * Callback function, called by the audio manager when the audio out event is signalled. | ||||
|      */ | ||||
|     void BufferReleaseAndRegister(); | ||||
|  | ||||
|     /** | ||||
|      * Get a list of audio out device names. | ||||
|      * | ||||
|      * @oaram names     - Output container to write names to. | ||||
|      * @return Number of names written. | ||||
|      */ | ||||
|     u32 GetAudioOutDeviceNames( | ||||
|         std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const; | ||||
|  | ||||
|     /// Core system | ||||
|     Core::System& system; | ||||
|     /// Array of session ids | ||||
|     std::array<size_t, MaxOutSessions> session_ids{}; | ||||
|     /// Array of resource user ids | ||||
|     std::array<size_t, MaxOutSessions> applet_resource_user_ids{}; | ||||
|     /// Pointer to each open session | ||||
|     std::array<std::shared_ptr<Out>, MaxOutSessions> sessions{}; | ||||
|     /// The number of free sessions | ||||
|     size_t num_free_sessions{}; | ||||
|     /// The next session id to be taken | ||||
|     size_t next_session_id{}; | ||||
|     /// The next session id to be freed | ||||
|     size_t free_session_id{}; | ||||
|     /// Whether this is linked to the audio manager | ||||
|     bool linked_to_manager{}; | ||||
|     /// Whether the sessions have been started | ||||
|     bool sessions_started{}; | ||||
|     /// Protect state due to audio manager callback | ||||
|     std::recursive_mutex mutex{}; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioOut | ||||
							
								
								
									
										70
									
								
								src/audio_core/audio_render_manager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/audio_core/audio_render_manager.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/audio_render_manager.h" | ||||
| #include "audio_core/common/audio_renderer_parameter.h" | ||||
| #include "audio_core/common/feature_support.h" | ||||
| #include "core/core.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
|  | ||||
| Manager::Manager(Core::System& system_) | ||||
|     : system{system_}, system_manager{std::make_unique<SystemManager>(system)} { | ||||
|     std::iota(session_ids.begin(), session_ids.end(), 0); | ||||
| } | ||||
|  | ||||
| Manager::~Manager() { | ||||
|     Stop(); | ||||
| } | ||||
|  | ||||
| void Manager::Stop() { | ||||
|     system_manager->Stop(); | ||||
| } | ||||
|  | ||||
| SystemManager& Manager::GetSystemManager() { | ||||
|     return *system_manager; | ||||
| } | ||||
|  | ||||
| auto Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) | ||||
|     -> Result { | ||||
|     if (!CheckValidRevision(params.revision)) { | ||||
|         return Service::Audio::ERR_INVALID_REVISION; | ||||
|     } | ||||
|  | ||||
|     out_count = System::GetWorkBufferSize(params); | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| s32 Manager::GetSessionId() { | ||||
|     std::scoped_lock l{session_lock}; | ||||
|     auto session_id{session_ids[session_count]}; | ||||
|  | ||||
|     if (session_id == -1) { | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     session_ids[session_count] = -1; | ||||
|     session_count++; | ||||
|     return session_id; | ||||
| } | ||||
|  | ||||
| void Manager::ReleaseSessionId(const s32 session_id) { | ||||
|     std::scoped_lock l{session_lock}; | ||||
|     session_ids[--session_count] = session_id; | ||||
| } | ||||
|  | ||||
| u32 Manager::GetSessionCount() { | ||||
|     std::scoped_lock l{session_lock}; | ||||
|     return session_count; | ||||
| } | ||||
|  | ||||
| bool Manager::AddSystem(System& system_) { | ||||
|     return system_manager->Add(system_); | ||||
| } | ||||
|  | ||||
| bool Manager::RemoveSystem(System& system_) { | ||||
|     return system_manager->Remove(system_); | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										103
									
								
								src/audio_core/audio_render_manager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/audio_core/audio_render_manager.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <memory> | ||||
| #include <mutex> | ||||
|  | ||||
| #include "audio_core/common/common.h" | ||||
| #include "audio_core/renderer/system_manager.h" | ||||
| #include "core/hle/service/audio/errors.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace AudioCore { | ||||
| struct AudioRendererParameterInternal; | ||||
|  | ||||
| namespace AudioRenderer { | ||||
| /** | ||||
|  * Wrapper for the audio system manager, handles service calls. | ||||
|  */ | ||||
| class Manager { | ||||
| public: | ||||
|     explicit Manager(Core::System& system); | ||||
|     ~Manager(); | ||||
|  | ||||
|     /** | ||||
|      * Stop the manager. | ||||
|      */ | ||||
|     void Stop(); | ||||
|  | ||||
|     /** | ||||
|      * Get the system manager. | ||||
|      * | ||||
|      * @return The system manager. | ||||
|      */ | ||||
|     SystemManager& GetSystemManager(); | ||||
|  | ||||
|     /** | ||||
|      * Get required size for the audio renderer workbuffer. | ||||
|      * | ||||
|      * @param params    - Input parameters with the numbers of voices/mixes/sinks etc. | ||||
|      * @param out_count - Output size of the required workbuffer. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count); | ||||
|  | ||||
|     /** | ||||
|      * Get a new session id. | ||||
|      * | ||||
|      * @return The new session id. -1 if invalid, otherwise 0-MaxRendererSessions. | ||||
|      */ | ||||
|     s32 GetSessionId(); | ||||
|  | ||||
|     /** | ||||
|      * Get the number of currently active sessions. | ||||
|      * | ||||
|      * @return The number of active sessions. | ||||
|      */ | ||||
|     u32 GetSessionCount(); | ||||
|  | ||||
|     /** | ||||
|      * Add a renderer system to the manager. | ||||
|      * The system will be reguarly called to generate commands for the AudioRenderer. | ||||
|      * | ||||
|      * @param system - The system to add. | ||||
|      * @return True if the system was sucessfully added, otherwise false. | ||||
|      */ | ||||
|     bool AddSystem(System& system); | ||||
|  | ||||
|     /** | ||||
|      * Remove a renderer system from the manager. | ||||
|      * | ||||
|      * @param system - The system to remove. | ||||
|      * @return True if the system was sucessfully removed, otherwise false. | ||||
|      */ | ||||
|     bool RemoveSystem(System& system); | ||||
|  | ||||
|     /** | ||||
|      * Free a session id when the system wants to shut down. | ||||
|      * | ||||
|      * @param session_id - The session id to free. | ||||
|      */ | ||||
|     void ReleaseSessionId(s32 session_id); | ||||
|  | ||||
| private: | ||||
|     /// Core system | ||||
|     Core::System& system; | ||||
|     /// Session ids, -1 when in use | ||||
|     std::array<s32, MaxRendererSessions> session_ids{}; | ||||
|     /// Number of active renderers | ||||
|     u32 session_count{}; | ||||
|     /// Lock for interacting with the sessions | ||||
|     std::mutex session_lock{}; | ||||
|     /// Regularly generates commands from the registered systems for the AudioRenderer | ||||
|     std::unique_ptr<SystemManager> system_manager{}; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioRenderer | ||||
| } // namespace AudioCore | ||||
| @@ -1,343 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <limits> | ||||
| #include <optional> | ||||
| #include <vector> | ||||
|  | ||||
| #include "audio_core/audio_out.h" | ||||
| #include "audio_core/audio_renderer.h" | ||||
| #include "audio_core/common.h" | ||||
| #include "audio_core/info_updater.h" | ||||
| #include "audio_core/voice_context.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/settings.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/memory.h" | ||||
|  | ||||
| namespace { | ||||
| [[nodiscard]] static constexpr s16 ClampToS16(s32 value) { | ||||
|     return static_cast<s16>(std::clamp(value, s32{std::numeric_limits<s16>::min()}, | ||||
|                                        s32{std::numeric_limits<s16>::max()})); | ||||
| } | ||||
|  | ||||
| [[nodiscard]] static constexpr s16 Mix2To1(s16 l_channel, s16 r_channel) { | ||||
|     // Mix 50% from left and 50% from right channel | ||||
|     constexpr float l_mix_amount = 50.0f / 100.0f; | ||||
|     constexpr float r_mix_amount = 50.0f / 100.0f; | ||||
|     return ClampToS16(static_cast<s32>((static_cast<float>(l_channel) * l_mix_amount) + | ||||
|                                        (static_cast<float>(r_channel) * r_mix_amount))); | ||||
| } | ||||
|  | ||||
| [[maybe_unused, nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2( | ||||
|     s16 fl_channel, s16 fr_channel, s16 fc_channel, [[maybe_unused]] s16 lf_channel, s16 bl_channel, | ||||
|     s16 br_channel) { | ||||
|     // Front channels are mixed 36.94%, Center channels are mixed to be 26.12% & the back channels | ||||
|     // are mixed to be 36.94% | ||||
|  | ||||
|     constexpr float front_mix_amount = 36.94f / 100.0f; | ||||
|     constexpr float center_mix_amount = 26.12f / 100.0f; | ||||
|     constexpr float back_mix_amount = 36.94f / 100.0f; | ||||
|  | ||||
|     // Mix 50% from left and 50% from right channel | ||||
|     const auto left = front_mix_amount * static_cast<float>(fl_channel) + | ||||
|                       center_mix_amount * static_cast<float>(fc_channel) + | ||||
|                       back_mix_amount * static_cast<float>(bl_channel); | ||||
|  | ||||
|     const auto right = front_mix_amount * static_cast<float>(fr_channel) + | ||||
|                        center_mix_amount * static_cast<float>(fc_channel) + | ||||
|                        back_mix_amount * static_cast<float>(br_channel); | ||||
|  | ||||
|     return {ClampToS16(static_cast<s32>(left)), ClampToS16(static_cast<s32>(right))}; | ||||
| } | ||||
|  | ||||
| [[nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2WithCoefficients( | ||||
|     s16 fl_channel, s16 fr_channel, s16 fc_channel, s16 lf_channel, s16 bl_channel, s16 br_channel, | ||||
|     const std::array<float_le, 4>& coeff) { | ||||
|     const auto left = | ||||
|         static_cast<float>(fl_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] + | ||||
|         static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(bl_channel) * coeff[3]; | ||||
|  | ||||
|     const auto right = | ||||
|         static_cast<float>(fr_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] + | ||||
|         static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(br_channel) * coeff[3]; | ||||
|  | ||||
|     return {ClampToS16(static_cast<s32>(left)), ClampToS16(static_cast<s32>(right))}; | ||||
| } | ||||
|  | ||||
| } // namespace | ||||
|  | ||||
| namespace AudioCore { | ||||
| constexpr s32 NUM_BUFFERS = 2; | ||||
|  | ||||
| AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_, | ||||
|                              AudioCommon::AudioRendererParameter params, | ||||
|                              Stream::ReleaseCallback&& release_callback, | ||||
|                              std::size_t instance_number) | ||||
|     : worker_params{params}, memory_pool_info(params.effect_count + params.voice_count * 4), | ||||
|       voice_context(params.voice_count), effect_context(params.effect_count), mix_context(), | ||||
|       sink_context(params.sink_count), splitter_context(), | ||||
|       voices(params.voice_count), memory{memory_}, | ||||
|       command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context, | ||||
|                         memory), | ||||
|       core_timing{core_timing_} { | ||||
|     behavior_info.SetUserRevision(params.revision); | ||||
|     splitter_context.Initialize(behavior_info, params.splitter_count, | ||||
|                                 params.num_splitter_send_channels); | ||||
|     mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count); | ||||
|     audio_out = std::make_unique<AudioCore::AudioOut>(); | ||||
|     stream = audio_out->OpenStream( | ||||
|         core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS, | ||||
|         fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback)); | ||||
|     process_event = | ||||
|         Core::Timing::CreateEvent(fmt::format("AudioRenderer-Instance{}-Process", instance_number), | ||||
|                                   [this](std::uintptr_t, s64, std::chrono::nanoseconds) { | ||||
|                                       ReleaseAndQueueBuffers(); | ||||
|                                       return std::nullopt; | ||||
|                                   }); | ||||
|     for (s32 i = 0; i < NUM_BUFFERS; ++i) { | ||||
|         QueueMixedBuffer(i); | ||||
|     } | ||||
| } | ||||
|  | ||||
| AudioRenderer::~AudioRenderer() = default; | ||||
|  | ||||
| Result AudioRenderer::Start() { | ||||
|     audio_out->StartStream(stream); | ||||
|     ReleaseAndQueueBuffers(); | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result AudioRenderer::Stop() { | ||||
|     audio_out->StopStream(stream); | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| u32 AudioRenderer::GetSampleRate() const { | ||||
|     return worker_params.sample_rate; | ||||
| } | ||||
|  | ||||
| u32 AudioRenderer::GetSampleCount() const { | ||||
|     return worker_params.sample_count; | ||||
| } | ||||
|  | ||||
| u32 AudioRenderer::GetMixBufferCount() const { | ||||
|     return worker_params.mix_buffer_count; | ||||
| } | ||||
|  | ||||
| Stream::State AudioRenderer::GetStreamState() const { | ||||
|     return stream->GetState(); | ||||
| } | ||||
|  | ||||
| Result AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params, | ||||
|                                           std::vector<u8>& output_params) { | ||||
|     std::scoped_lock lock{mutex}; | ||||
|     InfoUpdater info_updater{input_params, output_params, behavior_info}; | ||||
|  | ||||
|     if (!info_updater.UpdateBehaviorInfo(behavior_info)) { | ||||
|         LOG_ERROR(Audio, "Failed to update behavior info input parameters"); | ||||
|         return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|     } | ||||
|  | ||||
|     if (!info_updater.UpdateMemoryPools(memory_pool_info)) { | ||||
|         LOG_ERROR(Audio, "Failed to update memory pool parameters"); | ||||
|         return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|     } | ||||
|  | ||||
|     if (!info_updater.UpdateVoiceChannelResources(voice_context)) { | ||||
|         LOG_ERROR(Audio, "Failed to update voice channel resource parameters"); | ||||
|         return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|     } | ||||
|  | ||||
|     if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) { | ||||
|         LOG_ERROR(Audio, "Failed to update voice parameters"); | ||||
|         return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|     } | ||||
|  | ||||
|     // TODO(ogniK): Deal with stopped audio renderer but updates still taking place | ||||
|     if (!info_updater.UpdateEffects(effect_context, true)) { | ||||
|         LOG_ERROR(Audio, "Failed to update effect parameters"); | ||||
|         return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|     } | ||||
|  | ||||
|     if (behavior_info.IsSplitterSupported()) { | ||||
|         if (!info_updater.UpdateSplitterInfo(splitter_context)) { | ||||
|             LOG_ERROR(Audio, "Failed to update splitter parameters"); | ||||
|             return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     const auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count, | ||||
|                                                      splitter_context, effect_context); | ||||
|  | ||||
|     if (mix_result.IsError()) { | ||||
|         LOG_ERROR(Audio, "Failed to update mix parameters"); | ||||
|         return mix_result; | ||||
|     } | ||||
|  | ||||
|     // TODO(ogniK): Sinks | ||||
|     if (!info_updater.UpdateSinks(sink_context)) { | ||||
|         LOG_ERROR(Audio, "Failed to update sink parameters"); | ||||
|         return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|     } | ||||
|  | ||||
|     // TODO(ogniK): Performance buffer | ||||
|     if (!info_updater.UpdatePerformanceBuffer()) { | ||||
|         LOG_ERROR(Audio, "Failed to update performance buffer parameters"); | ||||
|         return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|     } | ||||
|  | ||||
|     if (!info_updater.UpdateErrorInfo(behavior_info)) { | ||||
|         LOG_ERROR(Audio, "Failed to update error info"); | ||||
|         return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|     } | ||||
|  | ||||
|     if (behavior_info.IsElapsedFrameCountSupported()) { | ||||
|         if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) { | ||||
|             LOG_ERROR(Audio, "Failed to update renderer info"); | ||||
|             return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|         } | ||||
|     } | ||||
|     // TODO(ogniK): Statistics | ||||
|  | ||||
|     if (!info_updater.WriteOutputHeader()) { | ||||
|         LOG_ERROR(Audio, "Failed to write output header"); | ||||
|         return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|     } | ||||
|  | ||||
|     // TODO(ogniK): Check when all sections are implemented | ||||
|  | ||||
|     if (!info_updater.CheckConsumedSize()) { | ||||
|         LOG_ERROR(Audio, "Audio buffers were not consumed!"); | ||||
|         return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|     } | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { | ||||
|     command_generator.PreCommand(); | ||||
|     // Clear mix buffers before our next operation | ||||
|     command_generator.ClearMixBuffers(); | ||||
|  | ||||
|     // If the splitter is not in use, sort our mixes | ||||
|     if (!splitter_context.UsingSplitter()) { | ||||
|         mix_context.SortInfo(); | ||||
|     } | ||||
|     // Sort our voices | ||||
|     voice_context.SortInfo(); | ||||
|  | ||||
|     // Handle samples | ||||
|     command_generator.GenerateVoiceCommands(); | ||||
|     command_generator.GenerateSubMixCommands(); | ||||
|     command_generator.GenerateFinalMixCommands(); | ||||
|  | ||||
|     command_generator.PostCommand(); | ||||
|     // Base sample size | ||||
|     std::size_t BUFFER_SIZE{worker_params.sample_count}; | ||||
|     // Samples, making sure to clear | ||||
|     std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels(), 0); | ||||
|  | ||||
|     if (sink_context.InUse()) { | ||||
|         const auto stream_channel_count = stream->GetNumChannels(); | ||||
|         const auto buffer_offsets = sink_context.OutputBuffers(); | ||||
|         const auto channel_count = buffer_offsets.size(); | ||||
|         const auto& final_mix = mix_context.GetFinalMixInfo(); | ||||
|         const auto& in_params = final_mix.GetInParams(); | ||||
|         std::vector<std::span<s32>> mix_buffers(channel_count); | ||||
|         for (std::size_t i = 0; i < channel_count; i++) { | ||||
|             mix_buffers[i] = | ||||
|                 command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]); | ||||
|         } | ||||
|  | ||||
|         for (std::size_t i = 0; i < BUFFER_SIZE; i++) { | ||||
|             if (channel_count == 1) { | ||||
|                 const auto sample = ClampToS16(mix_buffers[0][i]); | ||||
|  | ||||
|                 // Place sample in all channels | ||||
|                 for (u32 channel = 0; channel < stream_channel_count; channel++) { | ||||
|                     buffer[i * stream_channel_count + channel] = sample; | ||||
|                 } | ||||
|  | ||||
|                 if (stream_channel_count == 6) { | ||||
|                     // Output stream has a LF channel, mute it! | ||||
|                     buffer[i * stream_channel_count + 3] = 0; | ||||
|                 } | ||||
|  | ||||
|             } else if (channel_count == 2) { | ||||
|                 const auto l_sample = ClampToS16(mix_buffers[0][i]); | ||||
|                 const auto r_sample = ClampToS16(mix_buffers[1][i]); | ||||
|                 if (stream_channel_count == 1) { | ||||
|                     buffer[i * stream_channel_count + 0] = Mix2To1(l_sample, r_sample); | ||||
|                 } else if (stream_channel_count == 2) { | ||||
|                     buffer[i * stream_channel_count + 0] = l_sample; | ||||
|                     buffer[i * stream_channel_count + 1] = r_sample; | ||||
|                 } else if (stream_channel_count == 6) { | ||||
|                     buffer[i * stream_channel_count + 0] = l_sample; | ||||
|                     buffer[i * stream_channel_count + 1] = r_sample; | ||||
|  | ||||
|                     // Combine both left and right channels to the center channel | ||||
|                     buffer[i * stream_channel_count + 2] = Mix2To1(l_sample, r_sample); | ||||
|  | ||||
|                     buffer[i * stream_channel_count + 4] = l_sample; | ||||
|                     buffer[i * stream_channel_count + 5] = r_sample; | ||||
|                 } | ||||
|  | ||||
|             } else if (channel_count == 6) { | ||||
|                 const auto fl_sample = ClampToS16(mix_buffers[0][i]); | ||||
|                 const auto fr_sample = ClampToS16(mix_buffers[1][i]); | ||||
|                 const auto fc_sample = ClampToS16(mix_buffers[2][i]); | ||||
|                 const auto lf_sample = ClampToS16(mix_buffers[3][i]); | ||||
|                 const auto bl_sample = ClampToS16(mix_buffers[4][i]); | ||||
|                 const auto br_sample = ClampToS16(mix_buffers[5][i]); | ||||
|  | ||||
|                 if (stream_channel_count == 1) { | ||||
|                     // Games seem to ignore the center channel half the time, we use the front left | ||||
|                     // and right channel for mixing as that's where majority of the audio goes | ||||
|                     buffer[i * stream_channel_count + 0] = Mix2To1(fl_sample, fr_sample); | ||||
|                 } else if (stream_channel_count == 2) { | ||||
|                     // Mix all channels into 2 channels | ||||
|                     const auto [left, right] = Mix6To2WithCoefficients( | ||||
|                         fl_sample, fr_sample, fc_sample, lf_sample, bl_sample, br_sample, | ||||
|                         sink_context.GetDownmixCoefficients()); | ||||
|                     buffer[i * stream_channel_count + 0] = left; | ||||
|                     buffer[i * stream_channel_count + 1] = right; | ||||
|                 } else if (stream_channel_count == 6) { | ||||
|                     // Pass through | ||||
|                     buffer[i * stream_channel_count + 0] = fl_sample; | ||||
|                     buffer[i * stream_channel_count + 1] = fr_sample; | ||||
|                     buffer[i * stream_channel_count + 2] = fc_sample; | ||||
|                     buffer[i * stream_channel_count + 3] = lf_sample; | ||||
|                     buffer[i * stream_channel_count + 4] = bl_sample; | ||||
|                     buffer[i * stream_channel_count + 5] = br_sample; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     audio_out->QueueBuffer(stream, tag, std::move(buffer)); | ||||
|     elapsed_frame_count++; | ||||
|     voice_context.UpdateStateByDspShared(); | ||||
| } | ||||
|  | ||||
| void AudioRenderer::ReleaseAndQueueBuffers() { | ||||
|     if (!stream->IsPlaying()) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     { | ||||
|         std::scoped_lock lock{mutex}; | ||||
|         const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)}; | ||||
|         for (const auto& tag : released_buffers) { | ||||
|             QueueMixedBuffer(tag); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     const f32 sample_rate = static_cast<f32>(GetSampleRate()); | ||||
|     const f32 sample_count = static_cast<f32>(GetSampleCount()); | ||||
|     const f32 consume_rate = sample_rate / (sample_count * (sample_count / 240)); | ||||
|     const s32 ms = (1000 / static_cast<s32>(consume_rate)) - 1; | ||||
|     const std::chrono::milliseconds next_event_time(std::max(ms / NUM_BUFFERS, 1)); | ||||
|     core_timing.ScheduleEvent(next_event_time, process_event, {}); | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,78 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <memory> | ||||
| #include <mutex> | ||||
| #include <vector> | ||||
|  | ||||
| #include "audio_core/behavior_info.h" | ||||
| #include "audio_core/command_generator.h" | ||||
| #include "audio_core/common.h" | ||||
| #include "audio_core/effect_context.h" | ||||
| #include "audio_core/memory_pool.h" | ||||
| #include "audio_core/mix_context.h" | ||||
| #include "audio_core/sink_context.h" | ||||
| #include "audio_core/splitter_context.h" | ||||
| #include "audio_core/stream.h" | ||||
| #include "audio_core/voice_context.h" | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/swap.h" | ||||
| #include "core/hle/result.h" | ||||
|  | ||||
| namespace Core::Timing { | ||||
| class CoreTiming; | ||||
| } | ||||
|  | ||||
| namespace Core::Memory { | ||||
| class Memory; | ||||
| } | ||||
|  | ||||
| namespace AudioCore { | ||||
| using DSPStateHolder = std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>; | ||||
|  | ||||
| class AudioOut; | ||||
|  | ||||
| class AudioRenderer { | ||||
| public: | ||||
|     AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, | ||||
|                   AudioCommon::AudioRendererParameter params, | ||||
|                   Stream::ReleaseCallback&& release_callback, std::size_t instance_number); | ||||
|     ~AudioRenderer(); | ||||
|  | ||||
|     [[nodiscard]] Result UpdateAudioRenderer(const std::vector<u8>& input_params, | ||||
|                                              std::vector<u8>& output_params); | ||||
|     [[nodiscard]] Result Start(); | ||||
|     [[nodiscard]] Result Stop(); | ||||
|     void QueueMixedBuffer(Buffer::Tag tag); | ||||
|     void ReleaseAndQueueBuffers(); | ||||
|     [[nodiscard]] u32 GetSampleRate() const; | ||||
|     [[nodiscard]] u32 GetSampleCount() const; | ||||
|     [[nodiscard]] u32 GetMixBufferCount() const; | ||||
|     [[nodiscard]] Stream::State GetStreamState() const; | ||||
|  | ||||
| private: | ||||
|     BehaviorInfo behavior_info{}; | ||||
|  | ||||
|     AudioCommon::AudioRendererParameter worker_params; | ||||
|     std::vector<ServerMemoryPoolInfo> memory_pool_info; | ||||
|     VoiceContext voice_context; | ||||
|     EffectContext effect_context; | ||||
|     MixContext mix_context; | ||||
|     SinkContext sink_context; | ||||
|     SplitterContext splitter_context; | ||||
|     std::vector<VoiceState> voices; | ||||
|     std::unique_ptr<AudioOut> audio_out; | ||||
|     StreamPtr stream; | ||||
|     Core::Memory::Memory& memory; | ||||
|     CommandGenerator command_generator; | ||||
|     std::size_t elapsed_frame_count{}; | ||||
|     Core::Timing::CoreTiming& core_timing; | ||||
|     std::shared_ptr<Core::Timing::EventType> process_event; | ||||
|     std::mutex mutex; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,104 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <cstring> | ||||
| #include "audio_core/behavior_info.h" | ||||
| #include "audio_core/common.h" | ||||
| #include "common/logging/log.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {} | ||||
| BehaviorInfo::~BehaviorInfo() = default; | ||||
|  | ||||
| bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) { | ||||
|     if (!AudioCommon::CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) { | ||||
|         LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     OutParams params{}; | ||||
|     std::memcpy(params.errors.data(), errors.data(), sizeof(ErrorInfo) * errors.size()); | ||||
|     params.error_count = static_cast<u32_le>(error_count); | ||||
|     std::memcpy(buffer.data() + offset, ¶ms, sizeof(OutParams)); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| void BehaviorInfo::ClearError() { | ||||
|     error_count = 0; | ||||
| } | ||||
|  | ||||
| void BehaviorInfo::UpdateFlags(u64_le dest_flags) { | ||||
|     flags = dest_flags; | ||||
| } | ||||
|  | ||||
| void BehaviorInfo::SetUserRevision(u32_le revision) { | ||||
|     user_revision = revision; | ||||
| } | ||||
|  | ||||
| u32_le BehaviorInfo::GetUserRevision() const { | ||||
|     return user_revision; | ||||
| } | ||||
|  | ||||
| u32_le BehaviorInfo::GetProcessRevision() const { | ||||
|     return process_revision; | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const { | ||||
|     return AudioCommon::IsRevisionSupported(2, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsSplitterSupported() const { | ||||
|     return AudioCommon::IsRevisionSupported(2, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsLongSizePreDelaySupported() const { | ||||
|     return AudioCommon::IsRevisionSupported(3, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const { | ||||
|     return AudioCommon::IsRevisionSupported(5, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const { | ||||
|     return AudioCommon::IsRevisionSupported(4, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const { | ||||
|     return AudioCommon::IsRevisionSupported(1, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsElapsedFrameCountSupported() const { | ||||
|     return AudioCommon::IsRevisionSupported(5, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const { | ||||
|     return (flags & 1) != 0; | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const { | ||||
|     return AudioCommon::IsRevisionSupported(5, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const { | ||||
|     return AudioCommon::IsRevisionSupported(5, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const { | ||||
|     return AudioCommon::IsRevisionSupported(5, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const { | ||||
|     return AudioCommon::IsRevisionSupported(7, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsSplitterBugFixed() const { | ||||
|     return AudioCommon::IsRevisionSupported(5, user_revision); | ||||
| } | ||||
|  | ||||
| void BehaviorInfo::CopyErrorInfo(BehaviorInfo::OutParams& dst) { | ||||
|     dst.error_count = static_cast<u32>(error_count); | ||||
|     std::copy(errors.begin(), errors.begin() + error_count, dst.errors.begin()); | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,71 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
|  | ||||
| #include <vector> | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/swap.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
| class BehaviorInfo { | ||||
| public: | ||||
|     struct ErrorInfo { | ||||
|         u32_le result{}; | ||||
|         INSERT_PADDING_WORDS(1); | ||||
|         u64_le result_info{}; | ||||
|     }; | ||||
|     static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size"); | ||||
|  | ||||
|     struct InParams { | ||||
|         u32_le revision{}; | ||||
|         u32_le padding{}; | ||||
|         u64_le flags{}; | ||||
|     }; | ||||
|     static_assert(sizeof(InParams) == 0x10, "InParams is an invalid size"); | ||||
|  | ||||
|     struct OutParams { | ||||
|         std::array<ErrorInfo, 10> errors{}; | ||||
|         u32_le error_count{}; | ||||
|         INSERT_PADDING_BYTES(12); | ||||
|     }; | ||||
|     static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size"); | ||||
|  | ||||
|     explicit BehaviorInfo(); | ||||
|     ~BehaviorInfo(); | ||||
|  | ||||
|     bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset); | ||||
|  | ||||
|     void ClearError(); | ||||
|     void UpdateFlags(u64_le dest_flags); | ||||
|     void SetUserRevision(u32_le revision); | ||||
|     [[nodiscard]] u32_le GetUserRevision() const; | ||||
|     [[nodiscard]] u32_le GetProcessRevision() const; | ||||
|  | ||||
|     [[nodiscard]] bool IsAdpcmLoopContextBugFixed() const; | ||||
|     [[nodiscard]] bool IsSplitterSupported() const; | ||||
|     [[nodiscard]] bool IsLongSizePreDelaySupported() const; | ||||
|     [[nodiscard]] bool IsAudioRendererProcessingTimeLimit80PercentSupported() const; | ||||
|     [[nodiscard]] bool IsAudioRendererProcessingTimeLimit75PercentSupported() const; | ||||
|     [[nodiscard]] bool IsAudioRendererProcessingTimeLimit70PercentSupported() const; | ||||
|     [[nodiscard]] bool IsElapsedFrameCountSupported() const; | ||||
|     [[nodiscard]] bool IsMemoryPoolForceMappingEnabled() const; | ||||
|     [[nodiscard]] bool IsFlushVoiceWaveBuffersSupported() const; | ||||
|     [[nodiscard]] bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const; | ||||
|     [[nodiscard]] bool IsVoicePitchAndSrcSkippedSupported() const; | ||||
|     [[nodiscard]] bool IsMixInParameterDirtyOnlyUpdateSupported() const; | ||||
|     [[nodiscard]] bool IsSplitterBugFixed() const; | ||||
|     void CopyErrorInfo(OutParams& dst); | ||||
|  | ||||
| private: | ||||
|     u32_le process_revision{}; | ||||
|     u32_le user_revision{}; | ||||
|     u64_le flags{}; | ||||
|     std::array<ErrorInfo, 10> errors{}; | ||||
|     std::size_t error_count{}; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,44 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
| #include <vector> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| /** | ||||
|  * Represents a buffer of audio samples to be played in an audio stream | ||||
|  */ | ||||
| class Buffer { | ||||
| public: | ||||
|     using Tag = u64; | ||||
|  | ||||
|     Buffer(Tag tag_, std::vector<s16>&& samples_) : tag{tag_}, samples{std::move(samples_)} {} | ||||
|  | ||||
|     /// Returns the raw audio data for the buffer | ||||
|     std::vector<s16>& GetSamples() { | ||||
|         return samples; | ||||
|     } | ||||
|  | ||||
|     /// Returns the raw audio data for the buffer | ||||
|     const std::vector<s16>& GetSamples() const { | ||||
|         return samples; | ||||
|     } | ||||
|  | ||||
|     /// Returns the buffer tag, this is provided by the game to the audout service | ||||
|     Tag GetTag() const { | ||||
|         return tag; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     Tag tag; | ||||
|     std::vector<s16> samples; | ||||
| }; | ||||
|  | ||||
| using BufferPtr = std::shared_ptr<Buffer>; | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,77 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <algorithm> | ||||
|  | ||||
| #include "audio_core/codec.h" | ||||
|  | ||||
| namespace AudioCore::Codec { | ||||
|  | ||||
| std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM_Coeff& coeff, | ||||
|                              ADPCMState& state) { | ||||
|     // GC-ADPCM with scale factor and variable coefficients. | ||||
|     // Frames are 8 bytes long containing 14 samples each. | ||||
|     // Samples are 4 bits (one nibble) long. | ||||
|  | ||||
|     constexpr std::size_t FRAME_LEN = 8; | ||||
|     constexpr std::size_t SAMPLES_PER_FRAME = 14; | ||||
|     static constexpr std::array<int, 16> SIGNED_NIBBLES{ | ||||
|         0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1, | ||||
|     }; | ||||
|  | ||||
|     const std::size_t sample_count = (size / FRAME_LEN) * SAMPLES_PER_FRAME; | ||||
|     const std::size_t ret_size = | ||||
|         sample_count % 2 == 0 ? sample_count : sample_count + 1; // Ensure multiple of two. | ||||
|     std::vector<s16> ret(ret_size); | ||||
|  | ||||
|     int yn1 = state.yn1, yn2 = state.yn2; | ||||
|  | ||||
|     const std::size_t NUM_FRAMES = | ||||
|         (sample_count + (SAMPLES_PER_FRAME - 1)) / SAMPLES_PER_FRAME; // Round up. | ||||
|     for (std::size_t framei = 0; framei < NUM_FRAMES; framei++) { | ||||
|         const int frame_header = data[framei * FRAME_LEN]; | ||||
|         const int scale = 1 << (frame_header & 0xF); | ||||
|         const int idx = (frame_header >> 4) & 0x7; | ||||
|  | ||||
|         // Coefficients are fixed point with 11 bits fractional part. | ||||
|         const int coef1 = coeff[idx * 2 + 0]; | ||||
|         const int coef2 = coeff[idx * 2 + 1]; | ||||
|  | ||||
|         // Decodes an audio sample. One nibble produces one sample. | ||||
|         const auto decode_sample = [&](const int nibble) -> s16 { | ||||
|             const int xn = nibble * scale; | ||||
|             // We first transform everything into 11 bit fixed point, perform the second order | ||||
|             // digital filter, then transform back. | ||||
|             // 0x400 == 0.5 in 11 bit fixed point. | ||||
|             // Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2] | ||||
|             int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11; | ||||
|             // Clamp to output range. | ||||
|             val = std::clamp<s32>(val, -32768, 32767); | ||||
|             // Advance output feedback. | ||||
|             yn2 = yn1; | ||||
|             yn1 = val; | ||||
|             return static_cast<s16>(val); | ||||
|         }; | ||||
|  | ||||
|         std::size_t outputi = framei * SAMPLES_PER_FRAME; | ||||
|         std::size_t datai = framei * FRAME_LEN + 1; | ||||
|         for (std::size_t i = 0; i < SAMPLES_PER_FRAME && outputi < sample_count; i += 2) { | ||||
|             const s16 sample1 = decode_sample(SIGNED_NIBBLES[data[datai] >> 4]); | ||||
|             ret[outputi] = sample1; | ||||
|             outputi++; | ||||
|  | ||||
|             const s16 sample2 = decode_sample(SIGNED_NIBBLES[data[datai] & 0xF]); | ||||
|             ret[outputi] = sample2; | ||||
|             outputi++; | ||||
|  | ||||
|             datai++; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     state.yn1 = static_cast<s16>(yn1); | ||||
|     state.yn2 = static_cast<s16>(yn2); | ||||
|  | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::Codec | ||||
| @@ -1,43 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <vector> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore::Codec { | ||||
|  | ||||
| enum class PcmFormat : u32 { | ||||
|     Invalid = 0, | ||||
|     Int8 = 1, | ||||
|     Int16 = 2, | ||||
|     Int24 = 3, | ||||
|     Int32 = 4, | ||||
|     PcmFloat = 5, | ||||
|     Adpcm = 6, | ||||
| }; | ||||
|  | ||||
| /// See: Codec::DecodeADPCM | ||||
| struct ADPCMState { | ||||
|     // Two historical samples from previous processed buffer, | ||||
|     // required for ADPCM decoding | ||||
|     s16 yn1; ///< y[n-1] | ||||
|     s16 yn2; ///< y[n-2] | ||||
| }; | ||||
|  | ||||
| using ADPCM_Coeff = std::array<s16, 16>; | ||||
|  | ||||
| /** | ||||
|  * @param data Pointer to buffer that contains ADPCM data to decode | ||||
|  * @param size Size of buffer in bytes | ||||
|  * @param coeff ADPCM coefficients | ||||
|  * @param state ADPCM state, this is updated with new state | ||||
|  * @return Decoded stereo signed PCM16 data, sample_count in length | ||||
|  */ | ||||
| std::vector<s16> DecodeADPCM(const u8* data, std::size_t size, const ADPCM_Coeff& coeff, | ||||
|                              ADPCMState& state); | ||||
|  | ||||
| }; // namespace AudioCore::Codec | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,110 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <span> | ||||
| #include "audio_core/common.h" | ||||
| #include "audio_core/voice_context.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace Core::Memory { | ||||
| class Memory; | ||||
| } | ||||
|  | ||||
| namespace AudioCore { | ||||
| class MixContext; | ||||
| class SplitterContext; | ||||
| class ServerSplitterDestinationData; | ||||
| class ServerMixInfo; | ||||
| class EffectContext; | ||||
| class EffectBase; | ||||
| struct AuxInfoDSP; | ||||
| struct I3dl2ReverbParams; | ||||
| struct I3dl2ReverbState; | ||||
| using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>; | ||||
|  | ||||
| class CommandGenerator { | ||||
| public: | ||||
|     explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_, | ||||
|                               VoiceContext& voice_context_, MixContext& mix_context_, | ||||
|                               SplitterContext& splitter_context_, EffectContext& effect_context_, | ||||
|                               Core::Memory::Memory& memory_); | ||||
|     ~CommandGenerator(); | ||||
|  | ||||
|     void ClearMixBuffers(); | ||||
|     void GenerateVoiceCommands(); | ||||
|     void GenerateVoiceCommand(ServerVoiceInfo& voice_info); | ||||
|     void GenerateSubMixCommands(); | ||||
|     void GenerateFinalMixCommands(); | ||||
|     void PreCommand(); | ||||
|     void PostCommand(); | ||||
|  | ||||
|     [[nodiscard]] std::span<s32> GetChannelMixBuffer(s32 channel); | ||||
|     [[nodiscard]] std::span<const s32> GetChannelMixBuffer(s32 channel) const; | ||||
|     [[nodiscard]] std::span<s32> GetMixBuffer(std::size_t index); | ||||
|     [[nodiscard]] std::span<const s32> GetMixBuffer(std::size_t index) const; | ||||
|     [[nodiscard]] std::size_t GetMixChannelBufferOffset(s32 channel) const; | ||||
|  | ||||
|     [[nodiscard]] std::size_t GetTotalMixBufferCount() const; | ||||
|  | ||||
| private: | ||||
|     void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel); | ||||
|     void GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state, | ||||
|                                              s32 mix_buffer_count, s32 channel); | ||||
|     void GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel, | ||||
|                                    s32 node_id); | ||||
|     void GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes, | ||||
|                                  const MixVolumeBuffer& last_mix_volumes, VoiceState& dsp_state, | ||||
|                                  s32 mix_buffer_offset, s32 mix_buffer_count, s32 voice_index, | ||||
|                                  s32 node_id); | ||||
|     void GenerateSubMixCommand(ServerMixInfo& mix_info); | ||||
|     void GenerateMixCommands(ServerMixInfo& mix_info); | ||||
|     void GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, float volume, | ||||
|                             s32 node_id); | ||||
|     void GenerateFinalMixCommand(); | ||||
|     void GenerateBiquadFilterCommand(s32 mix_buffer, const BiquadFilterParameter& params, | ||||
|                                      std::array<s64, 2>& state, std::size_t input_offset, | ||||
|                                      std::size_t output_offset, s32 sample_count, s32 node_id); | ||||
|     void GenerateDepopPrepareCommand(VoiceState& dsp_state, std::size_t mix_buffer_count, | ||||
|                                      std::size_t mix_buffer_offset); | ||||
|     void GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count, | ||||
|                                            std::size_t mix_buffer_offset, s32 sample_rate); | ||||
|     void GenerateEffectCommand(ServerMixInfo& mix_info); | ||||
|     void GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); | ||||
|     void GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); | ||||
|     void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); | ||||
|     [[nodiscard]] ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index); | ||||
|  | ||||
|     s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, | ||||
|                        std::span<const s32> data, u32 sample_count, u32 write_offset, | ||||
|                        u32 write_count); | ||||
|     s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, | ||||
|                       std::span<s32> out_data, u32 sample_count, u32 read_offset, u32 read_count); | ||||
|  | ||||
|     void InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, | ||||
|                                std::vector<u8>& work_buffer); | ||||
|     void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear); | ||||
|     // DSP Code | ||||
|     template <typename T> | ||||
|     s32 DecodePcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset, | ||||
|                   s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset); | ||||
|     s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset, | ||||
|                     s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset); | ||||
|     void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, std::span<s32> output, | ||||
|                                VoiceState& dsp_state, s32 channel, s32 target_sample_rate, | ||||
|                                s32 sample_count, s32 node_id); | ||||
|  | ||||
|     AudioCommon::AudioRendererParameter& worker_params; | ||||
|     VoiceContext& voice_context; | ||||
|     MixContext& mix_context; | ||||
|     SplitterContext& splitter_context; | ||||
|     EffectContext& effect_context; | ||||
|     Core::Memory::Memory& memory; | ||||
|     std::vector<s32> mix_buffer{}; | ||||
|     std::vector<s32> sample_buffer{}; | ||||
|     std::vector<s32> depop_buffer{}; | ||||
|     bool dumping_frame{false}; | ||||
| }; | ||||
| } // namespace AudioCore | ||||
| @@ -1,132 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/swap.h" | ||||
| #include "core/hle/result.h" | ||||
|  | ||||
| namespace AudioCommon { | ||||
| namespace Audren { | ||||
| constexpr Result ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41}; | ||||
| constexpr Result ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43}; | ||||
| } // namespace Audren | ||||
|  | ||||
| constexpr u8 BASE_REVISION = '0'; | ||||
| constexpr u32_le CURRENT_PROCESS_REVISION = | ||||
|     Common::MakeMagic('R', 'E', 'V', static_cast<u8>(BASE_REVISION + 0xA)); | ||||
| constexpr std::size_t MAX_MIX_BUFFERS = 24; | ||||
| constexpr std::size_t MAX_BIQUAD_FILTERS = 2; | ||||
| constexpr std::size_t MAX_CHANNEL_COUNT = 6; | ||||
| constexpr std::size_t MAX_WAVE_BUFFERS = 4; | ||||
| constexpr std::size_t MAX_SAMPLE_HISTORY = 4; | ||||
| constexpr u32 STREAM_SAMPLE_RATE = 48000; | ||||
| constexpr u32 STREAM_NUM_CHANNELS = 2; | ||||
| constexpr s32 NO_SPLITTER = -1; | ||||
| constexpr s32 NO_MIX = 0x7fffffff; | ||||
| constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min(); | ||||
| constexpr s32 FINAL_MIX = 0; | ||||
| constexpr s32 NO_EFFECT_ORDER = -1; | ||||
| constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant | ||||
| // Any size checks seem to take the sample history into account | ||||
| // and our const ends up being 0x3f04, the 4 bytes are most | ||||
| // likely the sample history | ||||
| constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY; | ||||
| constexpr f32 I3DL2REVERB_MAX_LEVEL = 5000.0f; | ||||
| constexpr f32 I3DL2REVERB_MIN_REFLECTION_DURATION = 0.02f; | ||||
| constexpr std::size_t I3DL2REVERB_TAPS = 20; | ||||
| constexpr std::size_t I3DL2REVERB_DELAY_LINE_COUNT = 4; | ||||
| using Fractional = s32; | ||||
|  | ||||
| template <typename T> | ||||
| constexpr Fractional ToFractional(T x) { | ||||
|     return static_cast<Fractional>(x * static_cast<T>(0x4000)); | ||||
| } | ||||
|  | ||||
| constexpr Fractional MultiplyFractional(Fractional lhs, Fractional rhs) { | ||||
|     return static_cast<Fractional>(static_cast<s64>(lhs) * rhs >> 14); | ||||
| } | ||||
|  | ||||
| constexpr s32 FractionalToFixed(Fractional x) { | ||||
|     const auto s = x & (1 << 13); | ||||
|     return static_cast<s32>(x >> 14) + s; | ||||
| } | ||||
|  | ||||
| constexpr s32 CalculateDelaySamples(s32 sample_rate_khz, float time) { | ||||
|     return FractionalToFixed(MultiplyFractional(ToFractional(sample_rate_khz), ToFractional(time))); | ||||
| } | ||||
|  | ||||
| static constexpr u32 VersionFromRevision(u32_le rev) { | ||||
|     // "REV7" -> 7 | ||||
|     return ((rev >> 24) & 0xff) - 0x30; | ||||
| } | ||||
|  | ||||
| static constexpr bool IsRevisionSupported(u32 required, u32_le user_revision) { | ||||
|     const auto base = VersionFromRevision(user_revision); | ||||
|     return required <= base; | ||||
| } | ||||
|  | ||||
| static constexpr bool IsValidRevision(u32_le revision) { | ||||
|     const auto base = VersionFromRevision(revision); | ||||
|     constexpr auto max_rev = VersionFromRevision(CURRENT_PROCESS_REVISION); | ||||
|     return base <= max_rev; | ||||
| } | ||||
|  | ||||
| static constexpr bool CanConsumeBuffer(std::size_t size, std::size_t offset, std::size_t required) { | ||||
|     if (offset > size) { | ||||
|         return false; | ||||
|     } | ||||
|     if (size < required) { | ||||
|         return false; | ||||
|     } | ||||
|     if ((size - offset) < required) { | ||||
|         return false; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| struct UpdateDataSizes { | ||||
|     u32_le behavior{}; | ||||
|     u32_le memory_pool{}; | ||||
|     u32_le voice{}; | ||||
|     u32_le voice_channel_resource{}; | ||||
|     u32_le effect{}; | ||||
|     u32_le mixer{}; | ||||
|     u32_le sink{}; | ||||
|     u32_le performance{}; | ||||
|     u32_le splitter{}; | ||||
|     u32_le render_info{}; | ||||
|     INSERT_PADDING_WORDS(4); | ||||
| }; | ||||
| static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size"); | ||||
|  | ||||
| struct UpdateDataHeader { | ||||
|     u32_le revision{}; | ||||
|     UpdateDataSizes size{}; | ||||
|     u32_le total_size{}; | ||||
| }; | ||||
| static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size"); | ||||
|  | ||||
| struct AudioRendererParameter { | ||||
|     u32_le sample_rate; | ||||
|     u32_le sample_count; | ||||
|     u32_le mix_buffer_count; | ||||
|     u32_le submix_count; | ||||
|     u32_le voice_count; | ||||
|     u32_le sink_count; | ||||
|     u32_le effect_count; | ||||
|     u32_le performance_frame_count; | ||||
|     u8 is_voice_drop_enabled; | ||||
|     u8 unknown_21; | ||||
|     u8 unknown_22; | ||||
|     u8 execution_mode; | ||||
|     u32_le splitter_count; | ||||
|     u32_le num_splitter_send_channels; | ||||
|     u32_le unknown_30; | ||||
|     u32_le revision; | ||||
| }; | ||||
| static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size"); | ||||
|  | ||||
| } // namespace AudioCommon | ||||
							
								
								
									
										60
									
								
								src/audio_core/common/audio_renderer_parameter.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/audio_core/common/audio_renderer_parameter.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <span> | ||||
|  | ||||
| #include "audio_core/renderer/behavior/behavior_info.h" | ||||
| #include "audio_core/renderer/memory/memory_pool_info.h" | ||||
| #include "audio_core/renderer/upsampler/upsampler_manager.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
| /** | ||||
|  * Execution mode of the audio renderer. | ||||
|  * Only Auto is currently supported. | ||||
|  */ | ||||
| enum class ExecutionMode : u8 { | ||||
|     Auto, | ||||
|     Manual, | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Parameters from the game, passed to the audio renderer for initialisation. | ||||
|  */ | ||||
| struct AudioRendererParameterInternal { | ||||
|     /* 0x00 */ u32 sample_rate; | ||||
|     /* 0x04 */ u32 sample_count; | ||||
|     /* 0x08 */ u32 mixes; | ||||
|     /* 0x0C */ u32 sub_mixes; | ||||
|     /* 0x10 */ u32 voices; | ||||
|     /* 0x14 */ u32 sinks; | ||||
|     /* 0x18 */ u32 effects; | ||||
|     /* 0x1C */ u32 perf_frames; | ||||
|     /* 0x20 */ u16 voice_drop_enabled; | ||||
|     /* 0x22 */ u8 rendering_device; | ||||
|     /* 0x23 */ ExecutionMode execution_mode; | ||||
|     /* 0x24 */ u32 splitter_infos; | ||||
|     /* 0x28 */ s32 splitter_destinations; | ||||
|     /* 0x2C */ u32 external_context_size; | ||||
|     /* 0x30 */ u32 revision; | ||||
|     /* 0x34 */ char unk34[0x4]; | ||||
| }; | ||||
| static_assert(sizeof(AudioRendererParameterInternal) == 0x38, | ||||
|               "AudioRendererParameterInternal has the wrong size!"); | ||||
|  | ||||
| /** | ||||
|  * Context for rendering, contains a bunch of useful fields for the command generator. | ||||
|  */ | ||||
| struct AudioRendererSystemContext { | ||||
|     s32 session_id; | ||||
|     s8 channels; | ||||
|     s16 mix_buffer_count; | ||||
|     AudioRenderer::BehaviorInfo* behavior; | ||||
|     std::span<s32> depop_buffer; | ||||
|     AudioRenderer::UpsamplerManager* upsampler_manager; | ||||
|     AudioRenderer::MemoryPoolInfo* memory_pool_info; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										138
									
								
								src/audio_core/common/common.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								src/audio_core/common/common.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <numeric> | ||||
| #include <span> | ||||
|  | ||||
| #include "common/assert.h" | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
| using CpuAddr = std::uintptr_t; | ||||
|  | ||||
| enum class PlayState : u8 { | ||||
|     Started, | ||||
|     Stopped, | ||||
|     Paused, | ||||
| }; | ||||
|  | ||||
| enum class SrcQuality : u8 { | ||||
|     Medium, | ||||
|     High, | ||||
|     Low, | ||||
| }; | ||||
|  | ||||
| enum class SampleFormat : u8 { | ||||
|     Invalid, | ||||
|     PcmInt8, | ||||
|     PcmInt16, | ||||
|     PcmInt24, | ||||
|     PcmInt32, | ||||
|     PcmFloat, | ||||
|     Adpcm, | ||||
| }; | ||||
|  | ||||
| enum class SessionTypes { | ||||
|     AudioIn, | ||||
|     AudioOut, | ||||
|     FinalOutputRecorder, | ||||
| }; | ||||
|  | ||||
| enum class Channels : u32 { | ||||
|     FrontLeft, | ||||
|     FrontRight, | ||||
|     Center, | ||||
|     LFE, | ||||
|     BackLeft, | ||||
|     BackRight, | ||||
| }; | ||||
|  | ||||
| // These are used by Delay, Reverb and I3dl2Reverb prior to Revision 11. | ||||
| enum class OldChannels : u32 { | ||||
|     FrontLeft, | ||||
|     FrontRight, | ||||
|     BackLeft, | ||||
|     BackRight, | ||||
|     Center, | ||||
|     LFE, | ||||
| }; | ||||
|  | ||||
| constexpr u32 BufferCount = 32; | ||||
|  | ||||
| constexpr u32 MaxRendererSessions = 2; | ||||
| constexpr u32 TargetSampleCount = 240; | ||||
| constexpr u32 TargetSampleRate = 48'000; | ||||
| constexpr u32 MaxChannels = 6; | ||||
| constexpr u32 MaxMixBuffers = 24; | ||||
| constexpr u32 MaxWaveBuffers = 4; | ||||
| constexpr s32 LowestVoicePriority = 0xFF; | ||||
| constexpr s32 HighestVoicePriority = 0; | ||||
| constexpr u32 BufferAlignment = 0x40; | ||||
| constexpr u32 WorkbufferAlignment = 0x1000; | ||||
| constexpr s32 FinalMixId = 0; | ||||
| constexpr s32 InvalidDistanceFromFinalMix = std::numeric_limits<s32>::min(); | ||||
| constexpr s32 UnusedSplitterId = -1; | ||||
| constexpr s32 UnusedMixId = std::numeric_limits<s32>::max(); | ||||
| constexpr u32 InvalidNodeId = 0xF0000000; | ||||
| constexpr s32 InvalidProcessOrder = -1; | ||||
| constexpr u32 MaxBiquadFilters = 2; | ||||
| constexpr u32 MaxEffects = 256; | ||||
|  | ||||
| constexpr bool IsChannelCountValid(u16 channel_count) { | ||||
|     return channel_count <= 6 && | ||||
|            (channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6); | ||||
| } | ||||
|  | ||||
| constexpr void UseOldChannelMapping(std::span<s16> inputs, std::span<s16> outputs) { | ||||
|     constexpr auto old_center{static_cast<u32>(OldChannels::Center)}; | ||||
|     constexpr auto new_center{static_cast<u32>(Channels::Center)}; | ||||
|     constexpr auto old_lfe{static_cast<u32>(OldChannels::LFE)}; | ||||
|     constexpr auto new_lfe{static_cast<u32>(Channels::LFE)}; | ||||
|  | ||||
|     auto center{inputs[old_center]}; | ||||
|     auto lfe{inputs[old_lfe]}; | ||||
|     inputs[old_center] = inputs[new_center]; | ||||
|     inputs[old_lfe] = inputs[new_lfe]; | ||||
|     inputs[new_center] = center; | ||||
|     inputs[new_lfe] = lfe; | ||||
|  | ||||
|     center = outputs[old_center]; | ||||
|     lfe = outputs[old_lfe]; | ||||
|     outputs[old_center] = outputs[new_center]; | ||||
|     outputs[old_lfe] = outputs[new_lfe]; | ||||
|     outputs[new_center] = center; | ||||
|     outputs[new_lfe] = lfe; | ||||
| } | ||||
|  | ||||
| constexpr u32 GetSplitterInParamHeaderMagic() { | ||||
|     return Common::MakeMagic('S', 'N', 'D', 'H'); | ||||
| } | ||||
|  | ||||
| constexpr u32 GetSplitterInfoMagic() { | ||||
|     return Common::MakeMagic('S', 'N', 'D', 'I'); | ||||
| } | ||||
|  | ||||
| constexpr u32 GetSplitterSendDataMagic() { | ||||
|     return Common::MakeMagic('S', 'N', 'D', 'D'); | ||||
| } | ||||
|  | ||||
| constexpr size_t GetSampleFormatByteSize(SampleFormat format) { | ||||
|     switch (format) { | ||||
|     case SampleFormat::PcmInt8: | ||||
|         return 1; | ||||
|     case SampleFormat::PcmInt16: | ||||
|         return 2; | ||||
|     case SampleFormat::PcmInt24: | ||||
|         return 3; | ||||
|     case SampleFormat::PcmInt32: | ||||
|     case SampleFormat::PcmFloat: | ||||
|         return 4; | ||||
|     default: | ||||
|         return 2; | ||||
|     } | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										105
									
								
								src/audio_core/common/feature_support.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/audio_core/common/feature_support.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <map> | ||||
| #include <ranges> | ||||
| #include <tuple> | ||||
|  | ||||
| #include "common/assert.h" | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
| constexpr u32 CurrentRevision = 11; | ||||
|  | ||||
| enum class SupportTags { | ||||
|     CommandProcessingTimeEstimatorVersion4, | ||||
|     CommandProcessingTimeEstimatorVersion3, | ||||
|     CommandProcessingTimeEstimatorVersion2, | ||||
|     MultiTapBiquadFilterProcessing, | ||||
|     EffectInfoVer2, | ||||
|     WaveBufferVer2, | ||||
|     BiquadFilterFloatProcessing, | ||||
|     VolumeMixParameterPrecisionQ23, | ||||
|     MixInParameterDirtyOnlyUpdate, | ||||
|     BiquadFilterEffectStateClearBugFix, | ||||
|     VoicePlayedSampleCountResetAtLoopPoint, | ||||
|     VoicePitchAndSrcSkipped, | ||||
|     SplitterBugFix, | ||||
|     FlushVoiceWaveBuffers, | ||||
|     ElapsedFrameCount, | ||||
|     AudioRendererVariadicCommandBufferSize, | ||||
|     PerformanceMetricsDataFormatVersion2, | ||||
|     AudioRendererProcessingTimeLimit80Percent, | ||||
|     AudioRendererProcessingTimeLimit75Percent, | ||||
|     AudioRendererProcessingTimeLimit70Percent, | ||||
|     AdpcmLoopContextBugFix, | ||||
|     Splitter, | ||||
|     LongSizePreDelay, | ||||
|     AudioUsbDeviceOutput, | ||||
|     DeviceApiVersion2, | ||||
|     DelayChannelMappingChange, | ||||
|     ReverbChannelMappingChange, | ||||
|     I3dl2ReverbChannelMappingChange, | ||||
|  | ||||
|     // Not a real tag, just here to get the count. | ||||
|     Size | ||||
| }; | ||||
|  | ||||
| constexpr u32 GetRevisionNum(u32 user_revision) { | ||||
|     if (user_revision >= 0x100) { | ||||
|         user_revision -= Common::MakeMagic('R', 'E', 'V', '0'); | ||||
|         user_revision >>= 24; | ||||
|     } | ||||
|     return user_revision; | ||||
| }; | ||||
|  | ||||
| constexpr bool CheckFeatureSupported(SupportTags tag, u32 user_revision) { | ||||
|     constexpr std::array<std::pair<SupportTags, u32>, static_cast<u32>(SupportTags::Size)> features{ | ||||
|         { | ||||
|             {SupportTags::AudioRendererProcessingTimeLimit70Percent, 1}, | ||||
|             {SupportTags::Splitter, 2}, | ||||
|             {SupportTags::AdpcmLoopContextBugFix, 2}, | ||||
|             {SupportTags::LongSizePreDelay, 3}, | ||||
|             {SupportTags::AudioUsbDeviceOutput, 4}, | ||||
|             {SupportTags::AudioRendererProcessingTimeLimit75Percent, 4}, | ||||
|             {SupportTags::VoicePlayedSampleCountResetAtLoopPoint, 5}, | ||||
|             {SupportTags::VoicePitchAndSrcSkipped, 5}, | ||||
|             {SupportTags::SplitterBugFix, 5}, | ||||
|             {SupportTags::FlushVoiceWaveBuffers, 5}, | ||||
|             {SupportTags::ElapsedFrameCount, 5}, | ||||
|             {SupportTags::AudioRendererProcessingTimeLimit80Percent, 5}, | ||||
|             {SupportTags::AudioRendererVariadicCommandBufferSize, 5}, | ||||
|             {SupportTags::PerformanceMetricsDataFormatVersion2, 5}, | ||||
|             {SupportTags::CommandProcessingTimeEstimatorVersion2, 5}, | ||||
|             {SupportTags::BiquadFilterEffectStateClearBugFix, 6}, | ||||
|             {SupportTags::BiquadFilterFloatProcessing, 7}, | ||||
|             {SupportTags::VolumeMixParameterPrecisionQ23, 7}, | ||||
|             {SupportTags::MixInParameterDirtyOnlyUpdate, 7}, | ||||
|             {SupportTags::WaveBufferVer2, 8}, | ||||
|             {SupportTags::CommandProcessingTimeEstimatorVersion3, 8}, | ||||
|             {SupportTags::EffectInfoVer2, 9}, | ||||
|             {SupportTags::CommandProcessingTimeEstimatorVersion4, 10}, | ||||
|             {SupportTags::MultiTapBiquadFilterProcessing, 10}, | ||||
|             {SupportTags::DelayChannelMappingChange, 11}, | ||||
|             {SupportTags::ReverbChannelMappingChange, 11}, | ||||
|             {SupportTags::I3dl2ReverbChannelMappingChange, 11}, | ||||
|         }}; | ||||
|  | ||||
|     const auto& feature = | ||||
|         std::ranges::find_if(features, [tag](const auto& entry) { return entry.first == tag; }); | ||||
|     if (feature == features.cend()) { | ||||
|         LOG_ERROR(Service_Audio, "Invalid SupportTag {}!", static_cast<u32>(tag)); | ||||
|         return false; | ||||
|     } | ||||
|     user_revision = GetRevisionNum(user_revision); | ||||
|     return (*feature).second <= user_revision; | ||||
| } | ||||
|  | ||||
| constexpr bool CheckValidRevision(u32 user_revision) { | ||||
|     return GetRevisionNum(user_revision) <= CurrentRevision; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										35
									
								
								src/audio_core/common/wave_buffer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/audio_core/common/wave_buffer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| struct WaveBufferVersion1 { | ||||
|     CpuAddr buffer; | ||||
|     u64 buffer_size; | ||||
|     u32 start_offset; | ||||
|     u32 end_offset; | ||||
|     bool loop; | ||||
|     bool stream_ended; | ||||
|     CpuAddr context; | ||||
|     u64 context_size; | ||||
| }; | ||||
|  | ||||
| struct WaveBufferVersion2 { | ||||
|     CpuAddr buffer; | ||||
|     CpuAddr context; | ||||
|     u64 buffer_size; | ||||
|     u64 context_size; | ||||
|     u32 start_offset; | ||||
|     u32 end_offset; | ||||
|     u32 loop_start_offset; | ||||
|     u32 loop_end_offset; | ||||
|     s32 loop_count; | ||||
|     bool loop; | ||||
|     bool stream_ended; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										100
									
								
								src/audio_core/common/workbuffer_allocator.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/audio_core/common/workbuffer_allocator.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <span> | ||||
|  | ||||
| #include "common/alignment.h" | ||||
| #include "common/assert.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
| /** | ||||
|  * Responsible for allocating up a workbuffer into multiple pieces. | ||||
|  * Takes in a buffer and size (it does not own them), and allocates up the buffer via Allocate. | ||||
|  */ | ||||
| class WorkbufferAllocator { | ||||
| public: | ||||
|     explicit WorkbufferAllocator(std::span<u8> buffer_, u64 size_) | ||||
|         : buffer{reinterpret_cast<u64>(buffer_.data())}, size{size_} {} | ||||
|  | ||||
|     /** | ||||
|      * Allocate the given count of T elements, aligned to alignment. | ||||
|      * | ||||
|      * @param count     - The number of elements to allocate. | ||||
|      * @param alignment - The required starting alignment. | ||||
|      * @return Non-owning container of allocated elements. | ||||
|      */ | ||||
|     template <typename T> | ||||
|     std::span<T> Allocate(u64 count, u64 alignment) { | ||||
|         u64 out{0}; | ||||
|         u64 byte_size{count * sizeof(T)}; | ||||
|  | ||||
|         if (byte_size > 0) { | ||||
|             auto current{buffer + offset}; | ||||
|             auto aligned_buffer{Common::AlignUp(current, alignment)}; | ||||
|             if (aligned_buffer + byte_size <= buffer + size) { | ||||
|                 out = aligned_buffer; | ||||
|                 offset = byte_size - buffer + aligned_buffer; | ||||
|             } else { | ||||
|                 LOG_ERROR( | ||||
|                     Service_Audio, | ||||
|                     "Allocated buffer was too small to hold new alloc.\nAllocator size={:08X}, " | ||||
|                     "offset={:08X}.\nAttempting to allocate {:08X} with alignment={:02X}", | ||||
|                     size, offset, byte_size, alignment); | ||||
|                 count = 0; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return std::span<T>(reinterpret_cast<T*>(out), count); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Align the current offset to the given alignment. | ||||
|      * | ||||
|      * @param alignment - The required starting alignment. | ||||
|      */ | ||||
|     void Align(u64 alignment) { | ||||
|         auto current{buffer + offset}; | ||||
|         auto aligned_buffer{Common::AlignUp(current, alignment)}; | ||||
|         offset = 0 - buffer + aligned_buffer; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get the current buffer offset. | ||||
|      * | ||||
|      * @return The current allocating offset. | ||||
|      */ | ||||
|     u64 GetCurrentOffset() const { | ||||
|         return offset; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get the current buffer size. | ||||
|      * | ||||
|      * @return The size of the current buffer. | ||||
|      */ | ||||
|     u64 GetSize() const { | ||||
|         return size; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get the remaining size that can be allocated. | ||||
|      * | ||||
|      * @return The remaining size left in the buffer. | ||||
|      */ | ||||
|     u64 GetRemainingSize() const { | ||||
|         return size - offset; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     /// The buffer into which we are allocating. | ||||
|     u64 buffer; | ||||
|     /// Size of the buffer we're allocating to. | ||||
|     u64 size; | ||||
|     /// Current offset into the buffer, an error will be thrown if it exceeds size. | ||||
|     u64 offset{}; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,249 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <atomic> | ||||
| #include <cstring> | ||||
| #include "audio_core/cubeb_sink.h" | ||||
| #include "audio_core/stream.h" | ||||
| #include "common/assert.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/ring_buffer.h" | ||||
| #include "common/settings.h" | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| #include <objbase.h> | ||||
| #endif | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| class CubebSinkStream final : public SinkStream { | ||||
| public: | ||||
|     CubebSinkStream(cubeb* ctx_, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, | ||||
|                     const std::string& name) | ||||
|         : ctx{ctx_}, num_channels{std::min(num_channels_, 6u)} { | ||||
|  | ||||
|         cubeb_stream_params params{}; | ||||
|         params.rate = sample_rate; | ||||
|         params.channels = num_channels; | ||||
|         params.format = CUBEB_SAMPLE_S16NE; | ||||
|         params.prefs = CUBEB_STREAM_PREF_PERSIST; | ||||
|         switch (num_channels) { | ||||
|         case 1: | ||||
|             params.layout = CUBEB_LAYOUT_MONO; | ||||
|             break; | ||||
|         case 2: | ||||
|             params.layout = CUBEB_LAYOUT_STEREO; | ||||
|             break; | ||||
|         case 6: | ||||
|             params.layout = CUBEB_LAYOUT_3F2_LFE; | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         u32 minimum_latency{}; | ||||
|         if (cubeb_get_min_latency(ctx, ¶ms, &minimum_latency) != CUBEB_OK) { | ||||
|             LOG_CRITICAL(Audio_Sink, "Error getting minimum latency"); | ||||
|         } | ||||
|  | ||||
|         if (cubeb_stream_init(ctx, &stream_backend, name.c_str(), nullptr, nullptr, output_device, | ||||
|                               ¶ms, std::max(512u, minimum_latency), | ||||
|                               &CubebSinkStream::DataCallback, &CubebSinkStream::StateCallback, | ||||
|                               this) != CUBEB_OK) { | ||||
|             LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream"); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (cubeb_stream_start(stream_backend) != CUBEB_OK) { | ||||
|             LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     ~CubebSinkStream() override { | ||||
|         if (!ctx) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (cubeb_stream_stop(stream_backend) != CUBEB_OK) { | ||||
|             LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); | ||||
|         } | ||||
|  | ||||
|         cubeb_stream_destroy(stream_backend); | ||||
|     } | ||||
|  | ||||
|     void EnqueueSamples(u32 source_num_channels, const std::vector<s16>& samples) override { | ||||
|         if (source_num_channels > num_channels) { | ||||
|             // Downsample 6 channels to 2 | ||||
|             ASSERT_MSG(source_num_channels == 6, "Channel count must be 6"); | ||||
|  | ||||
|             std::vector<s16> buf; | ||||
|             buf.reserve(samples.size() * num_channels / source_num_channels); | ||||
|             for (std::size_t i = 0; i < samples.size(); i += source_num_channels) { | ||||
|                 // Downmixing implementation taken from the ATSC standard | ||||
|                 const s16 left{samples[i + 0]}; | ||||
|                 const s16 right{samples[i + 1]}; | ||||
|                 const s16 center{samples[i + 2]}; | ||||
|                 const s16 surround_left{samples[i + 4]}; | ||||
|                 const s16 surround_right{samples[i + 5]}; | ||||
|                 // Not used in the ATSC reference implementation | ||||
|                 [[maybe_unused]] const s16 low_frequency_effects{samples[i + 3]}; | ||||
|  | ||||
|                 constexpr s32 clev{707}; // center mixing level coefficient | ||||
|                 constexpr s32 slev{707}; // surround mixing level coefficient | ||||
|  | ||||
|                 buf.push_back(static_cast<s16>(left + (clev * center / 1000) + | ||||
|                                                (slev * surround_left / 1000))); | ||||
|                 buf.push_back(static_cast<s16>(right + (clev * center / 1000) + | ||||
|                                                (slev * surround_right / 1000))); | ||||
|             } | ||||
|             queue.Push(buf); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         queue.Push(samples); | ||||
|     } | ||||
|  | ||||
|     std::size_t SamplesInQueue(u32 channel_count) const override { | ||||
|         if (!ctx) | ||||
|             return 0; | ||||
|  | ||||
|         return queue.Size() / channel_count; | ||||
|     } | ||||
|  | ||||
|     void Flush() override { | ||||
|         should_flush = true; | ||||
|     } | ||||
|  | ||||
|     u32 GetNumChannels() const { | ||||
|         return num_channels; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     std::vector<std::string> device_list; | ||||
|  | ||||
|     cubeb* ctx{}; | ||||
|     cubeb_stream* stream_backend{}; | ||||
|     u32 num_channels{}; | ||||
|  | ||||
|     Common::RingBuffer<s16, 0x10000> queue; | ||||
|     std::array<s16, 2> last_frame{}; | ||||
|     std::atomic<bool> should_flush{}; | ||||
|  | ||||
|     static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, | ||||
|                              void* output_buffer, long num_frames); | ||||
|     static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state); | ||||
| }; | ||||
|  | ||||
| CubebSink::CubebSink(std::string_view target_device_name) { | ||||
|     // Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows | ||||
| #ifdef _WIN32 | ||||
|     com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); | ||||
| #endif | ||||
|  | ||||
|     if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) { | ||||
|         LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (target_device_name != auto_device_name && !target_device_name.empty()) { | ||||
|         cubeb_device_collection collection; | ||||
|         if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) { | ||||
|             LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported"); | ||||
|         } else { | ||||
|             const auto collection_end{collection.device + collection.count}; | ||||
|             const auto device{ | ||||
|                 std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) { | ||||
|                     return info.friendly_name != nullptr && | ||||
|                            target_device_name == info.friendly_name; | ||||
|                 })}; | ||||
|             if (device != collection_end) { | ||||
|                 output_device = device->devid; | ||||
|             } | ||||
|             cubeb_device_collection_destroy(ctx, &collection); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| CubebSink::~CubebSink() { | ||||
|     if (!ctx) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     for (auto& sink_stream : sink_streams) { | ||||
|         sink_stream.reset(); | ||||
|     } | ||||
|  | ||||
|     cubeb_destroy(ctx); | ||||
|  | ||||
| #ifdef _WIN32 | ||||
|     if (SUCCEEDED(com_init_result)) { | ||||
|         CoUninitialize(); | ||||
|     } | ||||
| #endif | ||||
| } | ||||
|  | ||||
| SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, | ||||
|                                          const std::string& name) { | ||||
|     sink_streams.push_back( | ||||
|         std::make_unique<CubebSinkStream>(ctx, sample_rate, num_channels, output_device, name)); | ||||
|     return *sink_streams.back(); | ||||
| } | ||||
|  | ||||
| long CubebSinkStream::DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data, | ||||
|                                    [[maybe_unused]] const void* input_buffer, void* output_buffer, | ||||
|                                    long num_frames) { | ||||
|     auto* impl = static_cast<CubebSinkStream*>(user_data); | ||||
|     auto* buffer = static_cast<u8*>(output_buffer); | ||||
|  | ||||
|     if (!impl) { | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     const std::size_t num_channels = impl->GetNumChannels(); | ||||
|     const std::size_t samples_to_write = num_channels * num_frames; | ||||
|     const std::size_t samples_written = impl->queue.Pop(buffer, samples_to_write); | ||||
|  | ||||
|     if (samples_written >= num_channels) { | ||||
|         std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16), | ||||
|                     num_channels * sizeof(s16)); | ||||
|     } | ||||
|  | ||||
|     // Fill the rest of the frames with last_frame | ||||
|     for (std::size_t i = samples_written; i < samples_to_write; i += num_channels) { | ||||
|         std::memcpy(buffer + i * sizeof(s16), &impl->last_frame[0], num_channels * sizeof(s16)); | ||||
|     } | ||||
|  | ||||
|     return num_frames; | ||||
| } | ||||
|  | ||||
| void CubebSinkStream::StateCallback([[maybe_unused]] cubeb_stream* stream, | ||||
|                                     [[maybe_unused]] void* user_data, | ||||
|                                     [[maybe_unused]] cubeb_state state) {} | ||||
|  | ||||
| std::vector<std::string> ListCubebSinkDevices() { | ||||
|     std::vector<std::string> device_list; | ||||
|     cubeb* ctx; | ||||
|  | ||||
|     if (cubeb_init(&ctx, "yuzu Device Enumerator", nullptr) != CUBEB_OK) { | ||||
|         LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     cubeb_device_collection collection; | ||||
|     if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) { | ||||
|         LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported"); | ||||
|     } else { | ||||
|         for (std::size_t i = 0; i < collection.count; i++) { | ||||
|             const cubeb_device_info& device = collection.device[i]; | ||||
|             if (device.friendly_name) { | ||||
|                 device_list.emplace_back(device.friendly_name); | ||||
|             } | ||||
|         } | ||||
|         cubeb_device_collection_destroy(ctx, &collection); | ||||
|     } | ||||
|  | ||||
|     cubeb_destroy(ctx); | ||||
|     return device_list; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,35 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <string> | ||||
| #include <vector> | ||||
|  | ||||
| #include <cubeb/cubeb.h> | ||||
|  | ||||
| #include "audio_core/sink.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| class CubebSink final : public Sink { | ||||
| public: | ||||
|     explicit CubebSink(std::string_view device_id); | ||||
|     ~CubebSink() override; | ||||
|  | ||||
|     SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels, | ||||
|                                   const std::string& name) override; | ||||
|  | ||||
| private: | ||||
|     cubeb* ctx{}; | ||||
|     cubeb_devid output_device{}; | ||||
|     std::vector<SinkStreamPtr> sink_streams; | ||||
|  | ||||
| #ifdef _WIN32 | ||||
|     u32 com_init_result = 0; | ||||
| #endif | ||||
| }; | ||||
|  | ||||
| std::vector<std::string> ListCubebSinkDevices(); | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,107 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <cstring> | ||||
| #include "audio_core/delay_line.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
| DelayLineBase::DelayLineBase() = default; | ||||
| DelayLineBase::~DelayLineBase() = default; | ||||
|  | ||||
| void DelayLineBase::Initialize(s32 max_delay_, float* src_buffer) { | ||||
|     buffer = src_buffer; | ||||
|     buffer_end = buffer + max_delay_; | ||||
|     max_delay = max_delay_; | ||||
|     output = buffer; | ||||
|     SetDelay(max_delay_); | ||||
|     Clear(); | ||||
| } | ||||
|  | ||||
| void DelayLineBase::SetDelay(s32 new_delay) { | ||||
|     if (max_delay < new_delay) { | ||||
|         return; | ||||
|     } | ||||
|     delay = new_delay; | ||||
|     input = (buffer + ((output - buffer) + new_delay) % (max_delay + 1)); | ||||
| } | ||||
|  | ||||
| s32 DelayLineBase::GetDelay() const { | ||||
|     return delay; | ||||
| } | ||||
|  | ||||
| s32 DelayLineBase::GetMaxDelay() const { | ||||
|     return max_delay; | ||||
| } | ||||
|  | ||||
| f32 DelayLineBase::TapOut(s32 last_sample) { | ||||
|     const float* ptr = input - (last_sample + 1); | ||||
|     if (ptr < buffer) { | ||||
|         ptr += (max_delay + 1); | ||||
|     } | ||||
|  | ||||
|     return *ptr; | ||||
| } | ||||
|  | ||||
| f32 DelayLineBase::Tick(f32 sample) { | ||||
|     *(input++) = sample; | ||||
|     const auto out_sample = *(output++); | ||||
|  | ||||
|     if (buffer_end < input) { | ||||
|         input = buffer; | ||||
|     } | ||||
|  | ||||
|     if (buffer_end < output) { | ||||
|         output = buffer; | ||||
|     } | ||||
|  | ||||
|     return out_sample; | ||||
| } | ||||
|  | ||||
| float* DelayLineBase::GetInput() { | ||||
|     return input; | ||||
| } | ||||
|  | ||||
| const float* DelayLineBase::GetInput() const { | ||||
|     return input; | ||||
| } | ||||
|  | ||||
| f32 DelayLineBase::GetOutputSample() const { | ||||
|     return *output; | ||||
| } | ||||
|  | ||||
| void DelayLineBase::Clear() { | ||||
|     std::memset(buffer, 0, sizeof(float) * max_delay); | ||||
| } | ||||
|  | ||||
| void DelayLineBase::Reset() { | ||||
|     buffer = nullptr; | ||||
|     buffer_end = nullptr; | ||||
|     max_delay = 0; | ||||
|     input = nullptr; | ||||
|     output = nullptr; | ||||
|     delay = 0; | ||||
| } | ||||
|  | ||||
| DelayLineAllPass::DelayLineAllPass() = default; | ||||
| DelayLineAllPass::~DelayLineAllPass() = default; | ||||
|  | ||||
| void DelayLineAllPass::Initialize(u32 delay_, float coeffcient_, f32* src_buffer) { | ||||
|     DelayLineBase::Initialize(delay_, src_buffer); | ||||
|     SetCoefficient(coeffcient_); | ||||
| } | ||||
|  | ||||
| void DelayLineAllPass::SetCoefficient(float coeffcient_) { | ||||
|     coefficient = coeffcient_; | ||||
| } | ||||
|  | ||||
| f32 DelayLineAllPass::Tick(f32 sample) { | ||||
|     const auto temp = sample - coefficient * *output; | ||||
|     return coefficient * temp + DelayLineBase::Tick(temp); | ||||
| } | ||||
|  | ||||
| void DelayLineAllPass::Reset() { | ||||
|     coefficient = 0.0f; | ||||
|     DelayLineBase::Reset(); | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,49 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| class DelayLineBase { | ||||
| public: | ||||
|     DelayLineBase(); | ||||
|     ~DelayLineBase(); | ||||
|  | ||||
|     void Initialize(s32 max_delay_, float* src_buffer); | ||||
|     void SetDelay(s32 new_delay); | ||||
|     s32 GetDelay() const; | ||||
|     s32 GetMaxDelay() const; | ||||
|     f32 TapOut(s32 last_sample); | ||||
|     f32 Tick(f32 sample); | ||||
|     float* GetInput(); | ||||
|     const float* GetInput() const; | ||||
|     f32 GetOutputSample() const; | ||||
|     void Clear(); | ||||
|     void Reset(); | ||||
|  | ||||
| protected: | ||||
|     float* buffer{nullptr}; | ||||
|     float* buffer_end{nullptr}; | ||||
|     s32 max_delay{}; | ||||
|     float* input{nullptr}; | ||||
|     float* output{nullptr}; | ||||
|     s32 delay{}; | ||||
| }; | ||||
|  | ||||
| class DelayLineAllPass final : public DelayLineBase { | ||||
| public: | ||||
|     DelayLineAllPass(); | ||||
|     ~DelayLineAllPass(); | ||||
|  | ||||
|     void Initialize(u32 delay, float coeffcient_, f32* src_buffer); | ||||
|     void SetCoefficient(float coeffcient_); | ||||
|     f32 Tick(f32 sample); | ||||
|     void Reset(); | ||||
|  | ||||
| private: | ||||
|     float coefficient{}; | ||||
| }; | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										21
									
								
								src/audio_core/device/audio_buffer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/audio_core/device/audio_buffer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| struct AudioBuffer { | ||||
|     /// Timestamp this buffer completed playing. | ||||
|     s64 played_timestamp; | ||||
|     /// Game memory address for these samples. | ||||
|     VAddr samples; | ||||
|     /// Unqiue identifier for this buffer. | ||||
|     u64 tag; | ||||
|     /// Size of the samples buffer. | ||||
|     u64 size; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										304
									
								
								src/audio_core/device/audio_buffers.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										304
									
								
								src/audio_core/device/audio_buffers.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,304 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <mutex> | ||||
| #include <span> | ||||
| #include <vector> | ||||
|  | ||||
| #include "audio_buffer.h" | ||||
| #include "audio_core/device/device_session.h" | ||||
| #include "core/core_timing.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| constexpr s32 BufferAppendLimit = 4; | ||||
|  | ||||
| /** | ||||
|  * A ringbuffer of N audio buffers. | ||||
|  * The buffer contains 3 sections: | ||||
|  *     Appended   - Buffers added to the ring, but have yet to be sent to the audio backend. | ||||
|  *     Registered - Buffers sent to the backend and queued for playback. | ||||
|  *     Released   - Buffers which have been played, and can now be recycled. | ||||
|  * Any others are free/untracked. | ||||
|  * | ||||
|  * @tparam N - Maximum number of buffers in the ring. | ||||
|  */ | ||||
| template <size_t N> | ||||
| class AudioBuffers { | ||||
| public: | ||||
|     explicit AudioBuffers(size_t limit) : append_limit{static_cast<u32>(limit)} {} | ||||
|  | ||||
|     /** | ||||
|      * Append a new audio buffer to the ring. | ||||
|      * | ||||
|      * @param buffer - The new buffer. | ||||
|      */ | ||||
|     void AppendBuffer(AudioBuffer& buffer) { | ||||
|         std::scoped_lock l{lock}; | ||||
|         buffers[appended_index] = buffer; | ||||
|         appended_count++; | ||||
|         appended_index = (appended_index + 1) % append_limit; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Register waiting buffers, up to a maximum of BufferAppendLimit. | ||||
|      * | ||||
|      * @param out_buffers - The buffers which were registered. | ||||
|      */ | ||||
|     void RegisterBuffers(std::vector<AudioBuffer>& out_buffers) { | ||||
|         std::scoped_lock l{lock}; | ||||
|         const s32 to_register{std::min(std::min(appended_count, BufferAppendLimit), | ||||
|                                        BufferAppendLimit - registered_count)}; | ||||
|  | ||||
|         for (s32 i = 0; i < to_register; i++) { | ||||
|             s32 index{appended_index - appended_count}; | ||||
|             if (index < 0) { | ||||
|                 index += N; | ||||
|             } | ||||
|             out_buffers.push_back(buffers[index]); | ||||
|             registered_count++; | ||||
|             registered_index = (registered_index + 1) % append_limit; | ||||
|  | ||||
|             appended_count--; | ||||
|             if (appended_count == 0) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Release a single buffer. Must be already registered. | ||||
|      * | ||||
|      * @param index     - The buffer index to release. | ||||
|      * @param timestamp - The released timestamp for this buffer. | ||||
|      */ | ||||
|     void ReleaseBuffer(s32 index, s64 timestamp) { | ||||
|         std::scoped_lock l{lock}; | ||||
|         buffers[index].played_timestamp = timestamp; | ||||
|  | ||||
|         registered_count--; | ||||
|         released_count++; | ||||
|         released_index = (released_index + 1) % append_limit; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Release all registered buffers. | ||||
|      * | ||||
|      * @param timestamp - The released timestamp for this buffer. | ||||
|      * @return Is the buffer was released. | ||||
|      */ | ||||
|     bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) { | ||||
|         std::scoped_lock l{lock}; | ||||
|         bool buffer_released{false}; | ||||
|         while (registered_count > 0) { | ||||
|             auto index{registered_index - registered_count}; | ||||
|             if (index < 0) { | ||||
|                 index += N; | ||||
|             } | ||||
|  | ||||
|             // Check with the backend if this buffer can be released yet. | ||||
|             if (!session.IsBufferConsumed(buffers[index].tag)) { | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             ReleaseBuffer(index, core_timing.GetGlobalTimeNs().count()); | ||||
|             buffer_released = true; | ||||
|         } | ||||
|  | ||||
|         return buffer_released || registered_count == 0; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get all released buffers. | ||||
|      * | ||||
|      * @param tags - Container to be filled with the released buffers' tags. | ||||
|      * @return The number of buffers released. | ||||
|      */ | ||||
|     u32 GetReleasedBuffers(std::span<u64> tags) { | ||||
|         std::scoped_lock l{lock}; | ||||
|         u32 released{0}; | ||||
|  | ||||
|         while (released_count > 0) { | ||||
|             auto index{released_index - released_count}; | ||||
|             if (index < 0) { | ||||
|                 index += N; | ||||
|             } | ||||
|  | ||||
|             auto& buffer{buffers[index]}; | ||||
|             released_count--; | ||||
|  | ||||
|             auto tag{buffer.tag}; | ||||
|             buffer.played_timestamp = 0; | ||||
|             buffer.samples = 0; | ||||
|             buffer.tag = 0; | ||||
|             buffer.size = 0; | ||||
|  | ||||
|             if (tag == 0) { | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             tags[released++] = tag; | ||||
|  | ||||
|             if (released >= tags.size()) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return released; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get all appended and registered buffers. | ||||
|      * | ||||
|      * @param buffers_flushed - Output vector for the buffers which are released. | ||||
|      * @param max_buffers     - Maximum number of buffers to released. | ||||
|      * @return The number of buffers released. | ||||
|      */ | ||||
|     u32 GetRegisteredAppendedBuffers(std::vector<AudioBuffer>& buffers_flushed, u32 max_buffers) { | ||||
|         std::scoped_lock l{lock}; | ||||
|         if (registered_count + appended_count == 0) { | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         size_t buffers_to_flush{ | ||||
|             std::min(static_cast<u32>(registered_count + appended_count), max_buffers)}; | ||||
|         if (buffers_to_flush == 0) { | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         while (registered_count > 0) { | ||||
|             auto index{registered_index - registered_count}; | ||||
|             if (index < 0) { | ||||
|                 index += N; | ||||
|             } | ||||
|  | ||||
|             buffers_flushed.push_back(buffers[index]); | ||||
|  | ||||
|             registered_count--; | ||||
|             released_count++; | ||||
|             released_index = (released_index + 1) % append_limit; | ||||
|  | ||||
|             if (buffers_flushed.size() >= buffers_to_flush) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         while (appended_count > 0) { | ||||
|             auto index{appended_index - appended_count}; | ||||
|             if (index < 0) { | ||||
|                 index += N; | ||||
|             } | ||||
|  | ||||
|             buffers_flushed.push_back(buffers[index]); | ||||
|  | ||||
|             appended_count--; | ||||
|             released_count++; | ||||
|             released_index = (released_index + 1) % append_limit; | ||||
|  | ||||
|             if (buffers_flushed.size() >= buffers_to_flush) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return static_cast<u32>(buffers_flushed.size()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check if the given tag is in the buffers. | ||||
|      * | ||||
|      * @param tag - Unique tag of the buffer to search for. | ||||
|      * @return True if the buffer is still in the ring, otherwise false. | ||||
|      */ | ||||
|     bool ContainsBuffer(const u64 tag) const { | ||||
|         std::scoped_lock l{lock}; | ||||
|         const auto registered_buffers{appended_count + registered_count + released_count}; | ||||
|  | ||||
|         if (registered_buffers == 0) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         auto index{released_index - released_count}; | ||||
|         if (index < 0) { | ||||
|             index += append_limit; | ||||
|         } | ||||
|  | ||||
|         for (s32 i = 0; i < registered_buffers; i++) { | ||||
|             if (buffers[index].tag == tag) { | ||||
|                 return true; | ||||
|             } | ||||
|             index = (index + 1) % append_limit; | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get the number of active buffers in the ring. | ||||
|      * That is, appended, registered and released buffers. | ||||
|      * | ||||
|      * @return Number of active buffers. | ||||
|      */ | ||||
|     u32 GetAppendedRegisteredCount() const { | ||||
|         std::scoped_lock l{lock}; | ||||
|         return appended_count + registered_count; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get the total number of active buffers in the ring. | ||||
|      * That is, appended, registered and released buffers. | ||||
|      * | ||||
|      * @return Number of active buffers. | ||||
|      */ | ||||
|     u32 GetTotalBufferCount() const { | ||||
|         std::scoped_lock l{lock}; | ||||
|         return static_cast<u32>(appended_count + registered_count + released_count); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Flush all of the currently appended and registered buffers | ||||
|      * | ||||
|      * @param buffers_released - Output count for the number of buffers released. | ||||
|      * @return True if buffers were successfully flushed, otherwise false. | ||||
|      */ | ||||
|     bool FlushBuffers(u32& buffers_released) { | ||||
|         std::scoped_lock l{lock}; | ||||
|         std::vector<AudioBuffer> buffers_flushed{}; | ||||
|  | ||||
|         buffers_released = GetRegisteredAppendedBuffers(buffers_flushed, append_limit); | ||||
|  | ||||
|         if (registered_count > 0) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         if (static_cast<u32>(released_count + appended_count) > append_limit) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     /// Buffer lock | ||||
|     mutable std::recursive_mutex lock{}; | ||||
|     /// The audio buffers | ||||
|     std::array<AudioBuffer, N> buffers{}; | ||||
|     /// Current released index | ||||
|     s32 released_index{}; | ||||
|     /// Number of released buffers | ||||
|     s32 released_count{}; | ||||
|     /// Current registered index | ||||
|     s32 registered_index{}; | ||||
|     /// Number of registered buffers | ||||
|     s32 registered_count{}; | ||||
|     /// Current appended index | ||||
|     s32 appended_index{}; | ||||
|     /// Number of appended buffers | ||||
|     s32 appended_count{}; | ||||
|     /// Maximum number of buffers (default 32) | ||||
|     u32 append_limit{}; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										114
									
								
								src/audio_core/device/device_session.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/audio_core/device/device_session.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/audio_core.h" | ||||
| #include "audio_core/audio_manager.h" | ||||
| #include "audio_core/device/audio_buffer.h" | ||||
| #include "audio_core/device/device_session.h" | ||||
| #include "audio_core/sink/sink_stream.h" | ||||
| #include "core/core.h" | ||||
| #include "core/memory.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| DeviceSession::DeviceSession(Core::System& system_) : system{system_} {} | ||||
|  | ||||
| DeviceSession::~DeviceSession() { | ||||
|     Finalize(); | ||||
| } | ||||
|  | ||||
| Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_format_, | ||||
|                                  u16 channel_count_, size_t session_id_, u32 handle_, | ||||
|                                  u64 applet_resource_user_id_, Sink::StreamType type_) { | ||||
|     if (stream) { | ||||
|         Finalize(); | ||||
|     } | ||||
|     name = fmt::format("{}-{}", name_, session_id_); | ||||
|     type = type_; | ||||
|     sample_format = sample_format_; | ||||
|     channel_count = channel_count_; | ||||
|     session_id = session_id_; | ||||
|     handle = handle_; | ||||
|     applet_resource_user_id = applet_resource_user_id_; | ||||
|  | ||||
|     if (type == Sink::StreamType::In) { | ||||
|         sink = &system.AudioCore().GetInputSink(); | ||||
|     } else { | ||||
|         sink = &system.AudioCore().GetOutputSink(); | ||||
|     } | ||||
|     stream = sink->AcquireSinkStream(system, channel_count, name, type); | ||||
|     initialized = true; | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| void DeviceSession::Finalize() { | ||||
|     if (initialized) { | ||||
|         Stop(); | ||||
|         sink->CloseStream(stream); | ||||
|         stream = nullptr; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void DeviceSession::Start() { | ||||
|     stream->SetPlayedSampleCount(played_sample_count); | ||||
|     stream->Start(); | ||||
| } | ||||
|  | ||||
| void DeviceSession::Stop() { | ||||
|     if (stream) { | ||||
|         played_sample_count = stream->GetPlayedSampleCount(); | ||||
|         stream->Stop(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const { | ||||
|     auto& memory{system.Memory()}; | ||||
|  | ||||
|     for (size_t i = 0; i < buffers.size(); i++) { | ||||
|         Sink::SinkBuffer new_buffer{ | ||||
|             .frames = buffers[i].size / (channel_count * sizeof(s16)), | ||||
|             .frames_played = 0, | ||||
|             .tag = buffers[i].tag, | ||||
|             .consumed = false, | ||||
|         }; | ||||
|  | ||||
|         if (type == Sink::StreamType::In) { | ||||
|             std::vector<s16> samples{}; | ||||
|             stream->AppendBuffer(new_buffer, samples); | ||||
|         } else { | ||||
|             std::vector<s16> samples(buffers[i].size / sizeof(s16)); | ||||
|             memory.ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size); | ||||
|             stream->AppendBuffer(new_buffer, samples); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const { | ||||
|     if (type == Sink::StreamType::In) { | ||||
|         auto& memory{system.Memory()}; | ||||
|         auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))}; | ||||
|         memory.WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size); | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool DeviceSession::IsBufferConsumed(u64 tag) const { | ||||
|     if (stream) { | ||||
|         return stream->IsBufferConsumed(tag); | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| void DeviceSession::SetVolume(f32 volume) const { | ||||
|     if (stream) { | ||||
|         stream->SetSystemVolume(volume); | ||||
|     } | ||||
| } | ||||
|  | ||||
| u64 DeviceSession::GetPlayedSampleCount() const { | ||||
|     if (stream) { | ||||
|         return stream->GetPlayedSampleCount(); | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										126
									
								
								src/audio_core/device/device_session.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/audio_core/device/device_session.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <span> | ||||
|  | ||||
| #include "audio_core/common/common.h" | ||||
| #include "audio_core/sink/sink.h" | ||||
| #include "core/hle/service/audio/errors.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace AudioCore { | ||||
| namespace Sink { | ||||
| class SinkStream; | ||||
| struct SinkBuffer; | ||||
| } // namespace Sink | ||||
|  | ||||
| struct AudioBuffer; | ||||
|  | ||||
| /** | ||||
|  * Represents an input or output device stream for audio in and audio out (not used for render). | ||||
|  **/ | ||||
| class DeviceSession { | ||||
| public: | ||||
|     explicit DeviceSession(Core::System& system); | ||||
|     ~DeviceSession(); | ||||
|  | ||||
|     /** | ||||
|      * Initialize this device session. | ||||
|      * | ||||
|      * @param name                    - Name of this device. | ||||
|      * @param sample_format           - Sample format for this device's output. | ||||
|      * @param channel_count           - Number of channels for this device (2 or 6). | ||||
|      * @param session_id              - This session's id. | ||||
|      * @param handle                  - Handle for this device session (unused). | ||||
|      * @param applet_resource_user_id - Applet resource user id for this device session (unused). | ||||
|      * @param type                    - Type of this stream (Render, In, Out). | ||||
|      * @return Result code for this call. | ||||
|      */ | ||||
|     Result Initialize(std::string_view name, SampleFormat sample_format, u16 channel_count, | ||||
|                       size_t session_id, u32 handle, u64 applet_resource_user_id, | ||||
|                       Sink::StreamType type); | ||||
|  | ||||
|     /** | ||||
|      * Finalize this device session. | ||||
|      */ | ||||
|     void Finalize(); | ||||
|  | ||||
|     /** | ||||
|      * Append audio buffers to this device session to be played back. | ||||
|      * | ||||
|      * @param buffers - The buffers to play. | ||||
|      */ | ||||
|     void AppendBuffers(std::span<AudioBuffer> buffers) const; | ||||
|  | ||||
|     /** | ||||
|      * (Audio In only) Pop samples from the backend, and write them back to this buffer's address. | ||||
|      * | ||||
|      * @param buffer - The buffer to write to. | ||||
|      */ | ||||
|     void ReleaseBuffer(AudioBuffer& buffer) const; | ||||
|  | ||||
|     /** | ||||
|      * Check if the buffer for the given tag has been consumed by the backend. | ||||
|      * | ||||
|      * @param tag - Unqiue tag of the buffer to check. | ||||
|      * @return true if the buffer has been consumed, otherwise false. | ||||
|      */ | ||||
|     bool IsBufferConsumed(u64 tag) const; | ||||
|  | ||||
|     /** | ||||
|      * Start this device session, starting the backend stream. | ||||
|      */ | ||||
|     void Start(); | ||||
|  | ||||
|     /** | ||||
|      * Stop this device session, stopping the backend stream. | ||||
|      */ | ||||
|     void Stop(); | ||||
|  | ||||
|     /** | ||||
|      * Set this device session's volume. | ||||
|      * | ||||
|      * @param volume - New volume for this session. | ||||
|      */ | ||||
|     void SetVolume(f32 volume) const; | ||||
|  | ||||
|     /** | ||||
|      * Get this device session's total played sample count. | ||||
|      * | ||||
|      * @return Samples played by this session. | ||||
|      */ | ||||
|     u64 GetPlayedSampleCount() const; | ||||
|  | ||||
| private: | ||||
|     /// System | ||||
|     Core::System& system; | ||||
|     /// Output sink this device will use | ||||
|     Sink::Sink* sink{}; | ||||
|     /// The backend stream for this device session to send samples to | ||||
|     Sink::SinkStream* stream{}; | ||||
|     /// Name of this device session | ||||
|     std::string name{}; | ||||
|     /// Type of this device session (render/in/out) | ||||
|     Sink::StreamType type{}; | ||||
|     /// Sample format for this device. | ||||
|     SampleFormat sample_format{SampleFormat::PcmInt16}; | ||||
|     /// Channel count for this device session | ||||
|     u16 channel_count{}; | ||||
|     /// Session id of this device session | ||||
|     size_t session_id{}; | ||||
|     /// Handle of this device session | ||||
|     u32 handle{}; | ||||
|     /// Applet resource user id of this device session | ||||
|     u64 applet_resource_user_id{}; | ||||
|     /// Total number of samples played by this device session | ||||
|     u64 played_sample_count{}; | ||||
|     /// Is this session initialised? | ||||
|     bool initialized{}; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,320 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <algorithm> | ||||
| #include "audio_core/effect_context.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
| namespace { | ||||
| bool ValidChannelCountForEffect(s32 channel_count) { | ||||
|     return channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6; | ||||
| } | ||||
| } // namespace | ||||
|  | ||||
| EffectContext::EffectContext(std::size_t effect_count_) : effect_count(effect_count_) { | ||||
|     effects.reserve(effect_count); | ||||
|     std::generate_n(std::back_inserter(effects), effect_count, | ||||
|                     [] { return std::make_unique<EffectStubbed>(); }); | ||||
| } | ||||
| EffectContext::~EffectContext() = default; | ||||
|  | ||||
| std::size_t EffectContext::GetCount() const { | ||||
|     return effect_count; | ||||
| } | ||||
|  | ||||
| EffectBase* EffectContext::GetInfo(std::size_t i) { | ||||
|     return effects.at(i).get(); | ||||
| } | ||||
|  | ||||
| EffectBase* EffectContext::RetargetEffect(std::size_t i, EffectType effect) { | ||||
|     switch (effect) { | ||||
|     case EffectType::Invalid: | ||||
|         effects[i] = std::make_unique<EffectStubbed>(); | ||||
|         break; | ||||
|     case EffectType::BufferMixer: | ||||
|         effects[i] = std::make_unique<EffectBufferMixer>(); | ||||
|         break; | ||||
|     case EffectType::Aux: | ||||
|         effects[i] = std::make_unique<EffectAuxInfo>(); | ||||
|         break; | ||||
|     case EffectType::Delay: | ||||
|         effects[i] = std::make_unique<EffectDelay>(); | ||||
|         break; | ||||
|     case EffectType::Reverb: | ||||
|         effects[i] = std::make_unique<EffectReverb>(); | ||||
|         break; | ||||
|     case EffectType::I3dl2Reverb: | ||||
|         effects[i] = std::make_unique<EffectI3dl2Reverb>(); | ||||
|         break; | ||||
|     case EffectType::BiquadFilter: | ||||
|         effects[i] = std::make_unique<EffectBiquadFilter>(); | ||||
|         break; | ||||
|     default: | ||||
|         ASSERT_MSG(false, "Unimplemented effect {}", effect); | ||||
|         effects[i] = std::make_unique<EffectStubbed>(); | ||||
|     } | ||||
|     return GetInfo(i); | ||||
| } | ||||
|  | ||||
| const EffectBase* EffectContext::GetInfo(std::size_t i) const { | ||||
|     return effects.at(i).get(); | ||||
| } | ||||
|  | ||||
| EffectStubbed::EffectStubbed() : EffectBase(EffectType::Invalid) {} | ||||
| EffectStubbed::~EffectStubbed() = default; | ||||
|  | ||||
| void EffectStubbed::Update([[maybe_unused]] EffectInfo::InParams& in_params) {} | ||||
| void EffectStubbed::UpdateForCommandGeneration() {} | ||||
|  | ||||
| EffectBase::EffectBase(EffectType effect_type_) : effect_type(effect_type_) {} | ||||
| EffectBase::~EffectBase() = default; | ||||
|  | ||||
| UsageState EffectBase::GetUsage() const { | ||||
|     return usage; | ||||
| } | ||||
|  | ||||
| EffectType EffectBase::GetType() const { | ||||
|     return effect_type; | ||||
| } | ||||
|  | ||||
| bool EffectBase::IsEnabled() const { | ||||
|     return enabled; | ||||
| } | ||||
|  | ||||
| s32 EffectBase::GetMixID() const { | ||||
|     return mix_id; | ||||
| } | ||||
|  | ||||
| s32 EffectBase::GetProcessingOrder() const { | ||||
|     return processing_order; | ||||
| } | ||||
|  | ||||
| std::vector<u8>& EffectBase::GetWorkBuffer() { | ||||
|     return work_buffer; | ||||
| } | ||||
|  | ||||
| const std::vector<u8>& EffectBase::GetWorkBuffer() const { | ||||
|     return work_buffer; | ||||
| } | ||||
|  | ||||
| EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {} | ||||
| EffectI3dl2Reverb::~EffectI3dl2Reverb() = default; | ||||
|  | ||||
| void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) { | ||||
|     auto& params = GetParams(); | ||||
|     const auto* reverb_params = reinterpret_cast<I3dl2ReverbParams*>(in_params.raw.data()); | ||||
|     if (!ValidChannelCountForEffect(reverb_params->max_channels)) { | ||||
|         ASSERT_MSG(false, "Invalid reverb max channel count {}", reverb_params->max_channels); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto last_status = params.status; | ||||
|     mix_id = in_params.mix_id; | ||||
|     processing_order = in_params.processing_order; | ||||
|     params = *reverb_params; | ||||
|     if (!ValidChannelCountForEffect(reverb_params->channel_count)) { | ||||
|         params.channel_count = params.max_channels; | ||||
|     } | ||||
|     enabled = in_params.is_enabled; | ||||
|     if (last_status != ParameterStatus::Updated) { | ||||
|         params.status = last_status; | ||||
|     } | ||||
|  | ||||
|     if (in_params.is_new || skipped) { | ||||
|         usage = UsageState::Initialized; | ||||
|         params.status = ParameterStatus::Initialized; | ||||
|         skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; | ||||
|         if (!skipped) { | ||||
|             auto& cur_work_buffer = GetWorkBuffer(); | ||||
|             // Has two buffers internally | ||||
|             cur_work_buffer.resize(in_params.buffer_size * 2); | ||||
|             std::fill(cur_work_buffer.begin(), cur_work_buffer.end(), 0); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void EffectI3dl2Reverb::UpdateForCommandGeneration() { | ||||
|     if (enabled) { | ||||
|         usage = UsageState::Running; | ||||
|     } else { | ||||
|         usage = UsageState::Stopped; | ||||
|     } | ||||
|     GetParams().status = ParameterStatus::Updated; | ||||
| } | ||||
|  | ||||
| I3dl2ReverbState& EffectI3dl2Reverb::GetState() { | ||||
|     return state; | ||||
| } | ||||
|  | ||||
| const I3dl2ReverbState& EffectI3dl2Reverb::GetState() const { | ||||
|     return state; | ||||
| } | ||||
|  | ||||
| EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {} | ||||
| EffectBiquadFilter::~EffectBiquadFilter() = default; | ||||
|  | ||||
| void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) { | ||||
|     auto& params = GetParams(); | ||||
|     const auto* biquad_params = reinterpret_cast<BiquadFilterParams*>(in_params.raw.data()); | ||||
|     mix_id = in_params.mix_id; | ||||
|     processing_order = in_params.processing_order; | ||||
|     params = *biquad_params; | ||||
|     enabled = in_params.is_enabled; | ||||
| } | ||||
|  | ||||
| void EffectBiquadFilter::UpdateForCommandGeneration() { | ||||
|     if (enabled) { | ||||
|         usage = UsageState::Running; | ||||
|     } else { | ||||
|         usage = UsageState::Stopped; | ||||
|     } | ||||
|     GetParams().status = ParameterStatus::Updated; | ||||
| } | ||||
|  | ||||
| EffectAuxInfo::EffectAuxInfo() : EffectGeneric(EffectType::Aux) {} | ||||
| EffectAuxInfo::~EffectAuxInfo() = default; | ||||
|  | ||||
| void EffectAuxInfo::Update(EffectInfo::InParams& in_params) { | ||||
|     const auto* aux_params = reinterpret_cast<AuxInfo*>(in_params.raw.data()); | ||||
|     mix_id = in_params.mix_id; | ||||
|     processing_order = in_params.processing_order; | ||||
|     GetParams() = *aux_params; | ||||
|     enabled = in_params.is_enabled; | ||||
|  | ||||
|     if (in_params.is_new || skipped) { | ||||
|         skipped = aux_params->send_buffer_info == 0 || aux_params->return_buffer_info == 0; | ||||
|         if (skipped) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // There's two AuxInfos which are an identical size, the first one is managed by the cpu, | ||||
|         // the second is managed by the dsp. All we care about is managing the DSP one | ||||
|         send_info = aux_params->send_buffer_info + sizeof(AuxInfoDSP); | ||||
|         send_buffer = aux_params->send_buffer_info + (sizeof(AuxInfoDSP) * 2); | ||||
|  | ||||
|         recv_info = aux_params->return_buffer_info + sizeof(AuxInfoDSP); | ||||
|         recv_buffer = aux_params->return_buffer_info + (sizeof(AuxInfoDSP) * 2); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void EffectAuxInfo::UpdateForCommandGeneration() { | ||||
|     if (enabled) { | ||||
|         usage = UsageState::Running; | ||||
|     } else { | ||||
|         usage = UsageState::Stopped; | ||||
|     } | ||||
| } | ||||
|  | ||||
| VAddr EffectAuxInfo::GetSendInfo() const { | ||||
|     return send_info; | ||||
| } | ||||
|  | ||||
| VAddr EffectAuxInfo::GetSendBuffer() const { | ||||
|     return send_buffer; | ||||
| } | ||||
|  | ||||
| VAddr EffectAuxInfo::GetRecvInfo() const { | ||||
|     return recv_info; | ||||
| } | ||||
|  | ||||
| VAddr EffectAuxInfo::GetRecvBuffer() const { | ||||
|     return recv_buffer; | ||||
| } | ||||
|  | ||||
| EffectDelay::EffectDelay() : EffectGeneric(EffectType::Delay) {} | ||||
| EffectDelay::~EffectDelay() = default; | ||||
|  | ||||
| void EffectDelay::Update(EffectInfo::InParams& in_params) { | ||||
|     const auto* delay_params = reinterpret_cast<DelayParams*>(in_params.raw.data()); | ||||
|     auto& params = GetParams(); | ||||
|     if (!ValidChannelCountForEffect(delay_params->max_channels)) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto last_status = params.status; | ||||
|     mix_id = in_params.mix_id; | ||||
|     processing_order = in_params.processing_order; | ||||
|     params = *delay_params; | ||||
|     if (!ValidChannelCountForEffect(delay_params->channels)) { | ||||
|         params.channels = params.max_channels; | ||||
|     } | ||||
|     enabled = in_params.is_enabled; | ||||
|  | ||||
|     if (last_status != ParameterStatus::Updated) { | ||||
|         params.status = last_status; | ||||
|     } | ||||
|  | ||||
|     if (in_params.is_new || skipped) { | ||||
|         usage = UsageState::Initialized; | ||||
|         params.status = ParameterStatus::Initialized; | ||||
|         skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void EffectDelay::UpdateForCommandGeneration() { | ||||
|     if (enabled) { | ||||
|         usage = UsageState::Running; | ||||
|     } else { | ||||
|         usage = UsageState::Stopped; | ||||
|     } | ||||
|     GetParams().status = ParameterStatus::Updated; | ||||
| } | ||||
|  | ||||
| EffectBufferMixer::EffectBufferMixer() : EffectGeneric(EffectType::BufferMixer) {} | ||||
| EffectBufferMixer::~EffectBufferMixer() = default; | ||||
|  | ||||
| void EffectBufferMixer::Update(EffectInfo::InParams& in_params) { | ||||
|     mix_id = in_params.mix_id; | ||||
|     processing_order = in_params.processing_order; | ||||
|     GetParams() = *reinterpret_cast<BufferMixerParams*>(in_params.raw.data()); | ||||
|     enabled = in_params.is_enabled; | ||||
| } | ||||
|  | ||||
| void EffectBufferMixer::UpdateForCommandGeneration() { | ||||
|     if (enabled) { | ||||
|         usage = UsageState::Running; | ||||
|     } else { | ||||
|         usage = UsageState::Stopped; | ||||
|     } | ||||
| } | ||||
|  | ||||
| EffectReverb::EffectReverb() : EffectGeneric(EffectType::Reverb) {} | ||||
| EffectReverb::~EffectReverb() = default; | ||||
|  | ||||
| void EffectReverb::Update(EffectInfo::InParams& in_params) { | ||||
|     const auto* reverb_params = reinterpret_cast<ReverbParams*>(in_params.raw.data()); | ||||
|     auto& params = GetParams(); | ||||
|     if (!ValidChannelCountForEffect(reverb_params->max_channels)) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto last_status = params.status; | ||||
|     mix_id = in_params.mix_id; | ||||
|     processing_order = in_params.processing_order; | ||||
|     params = *reverb_params; | ||||
|     if (!ValidChannelCountForEffect(reverb_params->channels)) { | ||||
|         params.channels = params.max_channels; | ||||
|     } | ||||
|     enabled = in_params.is_enabled; | ||||
|  | ||||
|     if (last_status != ParameterStatus::Updated) { | ||||
|         params.status = last_status; | ||||
|     } | ||||
|  | ||||
|     if (in_params.is_new || skipped) { | ||||
|         usage = UsageState::Initialized; | ||||
|         params.status = ParameterStatus::Initialized; | ||||
|         skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void EffectReverb::UpdateForCommandGeneration() { | ||||
|     if (enabled) { | ||||
|         usage = UsageState::Running; | ||||
|     } else { | ||||
|         usage = UsageState::Stopped; | ||||
|     } | ||||
|     GetParams().status = ParameterStatus::Updated; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,349 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| #include "audio_core/common.h" | ||||
| #include "audio_core/delay_line.h" | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/swap.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
| enum class EffectType : u8 { | ||||
|     Invalid = 0, | ||||
|     BufferMixer = 1, | ||||
|     Aux = 2, | ||||
|     Delay = 3, | ||||
|     Reverb = 4, | ||||
|     I3dl2Reverb = 5, | ||||
|     BiquadFilter = 6, | ||||
| }; | ||||
|  | ||||
| enum class UsageStatus : u8 { | ||||
|     Invalid = 0, | ||||
|     New = 1, | ||||
|     Initialized = 2, | ||||
|     Used = 3, | ||||
|     Removed = 4, | ||||
| }; | ||||
|  | ||||
| enum class UsageState { | ||||
|     Invalid = 0, | ||||
|     Initialized = 1, | ||||
|     Running = 2, | ||||
|     Stopped = 3, | ||||
| }; | ||||
|  | ||||
| enum class ParameterStatus : u8 { | ||||
|     Initialized = 0, | ||||
|     Updating = 1, | ||||
|     Updated = 2, | ||||
| }; | ||||
|  | ||||
| struct BufferMixerParams { | ||||
|     std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input{}; | ||||
|     std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output{}; | ||||
|     std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> volume{}; | ||||
|     s32_le count{}; | ||||
| }; | ||||
| static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size"); | ||||
|  | ||||
| struct AuxInfoDSP { | ||||
|     u32_le read_offset{}; | ||||
|     u32_le write_offset{}; | ||||
|     u32_le remaining{}; | ||||
|     INSERT_PADDING_WORDS(13); | ||||
| }; | ||||
| static_assert(sizeof(AuxInfoDSP) == 0x40, "AuxInfoDSP is an invalid size"); | ||||
|  | ||||
| struct AuxInfo { | ||||
|     std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input_mix_buffers{}; | ||||
|     std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output_mix_buffers{}; | ||||
|     u32_le count{}; | ||||
|     s32_le sample_rate{}; | ||||
|     s32_le sample_count{}; | ||||
|     s32_le mix_buffer_count{}; | ||||
|     u64_le send_buffer_info{}; | ||||
|     u64_le send_buffer_base{}; | ||||
|  | ||||
|     u64_le return_buffer_info{}; | ||||
|     u64_le return_buffer_base{}; | ||||
| }; | ||||
| static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size"); | ||||
|  | ||||
| struct I3dl2ReverbParams { | ||||
|     std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{}; | ||||
|     std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{}; | ||||
|     u16_le max_channels{}; | ||||
|     u16_le channel_count{}; | ||||
|     INSERT_PADDING_BYTES(1); | ||||
|     u32_le sample_rate{}; | ||||
|     f32 room_hf{}; | ||||
|     f32 hf_reference{}; | ||||
|     f32 decay_time{}; | ||||
|     f32 hf_decay_ratio{}; | ||||
|     f32 room{}; | ||||
|     f32 reflection{}; | ||||
|     f32 reverb{}; | ||||
|     f32 diffusion{}; | ||||
|     f32 reflection_delay{}; | ||||
|     f32 reverb_delay{}; | ||||
|     f32 density{}; | ||||
|     f32 dry_gain{}; | ||||
|     ParameterStatus status{}; | ||||
|     INSERT_PADDING_BYTES(3); | ||||
| }; | ||||
| static_assert(sizeof(I3dl2ReverbParams) == 0x4c, "I3dl2ReverbParams is an invalid size"); | ||||
|  | ||||
| struct BiquadFilterParams { | ||||
|     std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{}; | ||||
|     std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{}; | ||||
|     std::array<s16_le, 3> numerator; | ||||
|     std::array<s16_le, 2> denominator; | ||||
|     s8 channel_count{}; | ||||
|     ParameterStatus status{}; | ||||
| }; | ||||
| static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size"); | ||||
|  | ||||
| struct DelayParams { | ||||
|     std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{}; | ||||
|     std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{}; | ||||
|     u16_le max_channels{}; | ||||
|     u16_le channels{}; | ||||
|     s32_le max_delay{}; | ||||
|     s32_le delay{}; | ||||
|     s32_le sample_rate{}; | ||||
|     s32_le gain{}; | ||||
|     s32_le feedback_gain{}; | ||||
|     s32_le out_gain{}; | ||||
|     s32_le dry_gain{}; | ||||
|     s32_le channel_spread{}; | ||||
|     s32_le low_pass{}; | ||||
|     ParameterStatus status{}; | ||||
|     INSERT_PADDING_BYTES(3); | ||||
| }; | ||||
| static_assert(sizeof(DelayParams) == 0x38, "DelayParams is an invalid size"); | ||||
|  | ||||
| struct ReverbParams { | ||||
|     std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{}; | ||||
|     std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{}; | ||||
|     u16_le max_channels{}; | ||||
|     u16_le channels{}; | ||||
|     s32_le sample_rate{}; | ||||
|     s32_le mode0{}; | ||||
|     s32_le mode0_gain{}; | ||||
|     s32_le pre_delay{}; | ||||
|     s32_le mode1{}; | ||||
|     s32_le mode1_gain{}; | ||||
|     s32_le decay{}; | ||||
|     s32_le hf_decay_ratio{}; | ||||
|     s32_le coloration{}; | ||||
|     s32_le reverb_gain{}; | ||||
|     s32_le out_gain{}; | ||||
|     s32_le dry_gain{}; | ||||
|     ParameterStatus status{}; | ||||
|     INSERT_PADDING_BYTES(3); | ||||
| }; | ||||
| static_assert(sizeof(ReverbParams) == 0x44, "ReverbParams is an invalid size"); | ||||
|  | ||||
| class EffectInfo { | ||||
| public: | ||||
|     struct InParams { | ||||
|         EffectType type{}; | ||||
|         u8 is_new{}; | ||||
|         u8 is_enabled{}; | ||||
|         INSERT_PADDING_BYTES(1); | ||||
|         s32_le mix_id{}; | ||||
|         u64_le buffer_address{}; | ||||
|         u64_le buffer_size{}; | ||||
|         s32_le processing_order{}; | ||||
|         INSERT_PADDING_BYTES(4); | ||||
|         union { | ||||
|             std::array<u8, 0xa0> raw; | ||||
|         }; | ||||
|     }; | ||||
|     static_assert(sizeof(InParams) == 0xc0, "InParams is an invalid size"); | ||||
|  | ||||
|     struct OutParams { | ||||
|         UsageStatus status{}; | ||||
|         INSERT_PADDING_BYTES(15); | ||||
|     }; | ||||
|     static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size"); | ||||
| }; | ||||
|  | ||||
| struct AuxAddress { | ||||
|     VAddr send_dsp_info{}; | ||||
|     VAddr send_buffer_base{}; | ||||
|     VAddr return_dsp_info{}; | ||||
|     VAddr return_buffer_base{}; | ||||
| }; | ||||
|  | ||||
| class EffectBase { | ||||
| public: | ||||
|     explicit EffectBase(EffectType effect_type_); | ||||
|     virtual ~EffectBase(); | ||||
|  | ||||
|     virtual void Update(EffectInfo::InParams& in_params) = 0; | ||||
|     virtual void UpdateForCommandGeneration() = 0; | ||||
|     [[nodiscard]] UsageState GetUsage() const; | ||||
|     [[nodiscard]] EffectType GetType() const; | ||||
|     [[nodiscard]] bool IsEnabled() const; | ||||
|     [[nodiscard]] s32 GetMixID() const; | ||||
|     [[nodiscard]] s32 GetProcessingOrder() const; | ||||
|     [[nodiscard]] std::vector<u8>& GetWorkBuffer(); | ||||
|     [[nodiscard]] const std::vector<u8>& GetWorkBuffer() const; | ||||
|  | ||||
| protected: | ||||
|     UsageState usage{UsageState::Invalid}; | ||||
|     EffectType effect_type{}; | ||||
|     s32 mix_id{}; | ||||
|     s32 processing_order{}; | ||||
|     bool enabled = false; | ||||
|     std::vector<u8> work_buffer{}; | ||||
| }; | ||||
|  | ||||
| template <typename T> | ||||
| class EffectGeneric : public EffectBase { | ||||
| public: | ||||
|     explicit EffectGeneric(EffectType effect_type_) : EffectBase(effect_type_) {} | ||||
|  | ||||
|     T& GetParams() { | ||||
|         return internal_params; | ||||
|     } | ||||
|  | ||||
|     const T& GetParams() const { | ||||
|         return internal_params; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     T internal_params{}; | ||||
| }; | ||||
|  | ||||
| class EffectStubbed : public EffectBase { | ||||
| public: | ||||
|     explicit EffectStubbed(); | ||||
|     ~EffectStubbed() override; | ||||
|  | ||||
|     void Update(EffectInfo::InParams& in_params) override; | ||||
|     void UpdateForCommandGeneration() override; | ||||
| }; | ||||
|  | ||||
| struct I3dl2ReverbState { | ||||
|     f32 lowpass_0{}; | ||||
|     f32 lowpass_1{}; | ||||
|     f32 lowpass_2{}; | ||||
|  | ||||
|     DelayLineBase early_delay_line{}; | ||||
|     std::array<u32, AudioCommon::I3DL2REVERB_TAPS> early_tap_steps{}; | ||||
|     f32 early_gain{}; | ||||
|     f32 late_gain{}; | ||||
|  | ||||
|     u32 early_to_late_taps{}; | ||||
|     std::array<DelayLineBase, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fdn_delay_line{}; | ||||
|     std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line0{}; | ||||
|     std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line1{}; | ||||
|     f32 last_reverb_echo{}; | ||||
|     DelayLineBase center_delay_line{}; | ||||
|     std::array<std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>, 3> lpf_coefficients{}; | ||||
|     std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> shelf_filter{}; | ||||
|     f32 dry_gain{}; | ||||
| }; | ||||
|  | ||||
| class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> { | ||||
| public: | ||||
|     explicit EffectI3dl2Reverb(); | ||||
|     ~EffectI3dl2Reverb() override; | ||||
|  | ||||
|     void Update(EffectInfo::InParams& in_params) override; | ||||
|     void UpdateForCommandGeneration() override; | ||||
|  | ||||
|     I3dl2ReverbState& GetState(); | ||||
|     const I3dl2ReverbState& GetState() const; | ||||
|  | ||||
| private: | ||||
|     bool skipped = false; | ||||
|     I3dl2ReverbState state{}; | ||||
| }; | ||||
|  | ||||
| class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> { | ||||
| public: | ||||
|     explicit EffectBiquadFilter(); | ||||
|     ~EffectBiquadFilter() override; | ||||
|  | ||||
|     void Update(EffectInfo::InParams& in_params) override; | ||||
|     void UpdateForCommandGeneration() override; | ||||
| }; | ||||
|  | ||||
| class EffectAuxInfo : public EffectGeneric<AuxInfo> { | ||||
| public: | ||||
|     explicit EffectAuxInfo(); | ||||
|     ~EffectAuxInfo() override; | ||||
|  | ||||
|     void Update(EffectInfo::InParams& in_params) override; | ||||
|     void UpdateForCommandGeneration() override; | ||||
|     [[nodiscard]] VAddr GetSendInfo() const; | ||||
|     [[nodiscard]] VAddr GetSendBuffer() const; | ||||
|     [[nodiscard]] VAddr GetRecvInfo() const; | ||||
|     [[nodiscard]] VAddr GetRecvBuffer() const; | ||||
|  | ||||
| private: | ||||
|     VAddr send_info{}; | ||||
|     VAddr send_buffer{}; | ||||
|     VAddr recv_info{}; | ||||
|     VAddr recv_buffer{}; | ||||
|     bool skipped = false; | ||||
|     AuxAddress addresses{}; | ||||
| }; | ||||
|  | ||||
| class EffectDelay : public EffectGeneric<DelayParams> { | ||||
| public: | ||||
|     explicit EffectDelay(); | ||||
|     ~EffectDelay() override; | ||||
|  | ||||
|     void Update(EffectInfo::InParams& in_params) override; | ||||
|     void UpdateForCommandGeneration() override; | ||||
|  | ||||
| private: | ||||
|     bool skipped = false; | ||||
| }; | ||||
|  | ||||
| class EffectBufferMixer : public EffectGeneric<BufferMixerParams> { | ||||
| public: | ||||
|     explicit EffectBufferMixer(); | ||||
|     ~EffectBufferMixer() override; | ||||
|  | ||||
|     void Update(EffectInfo::InParams& in_params) override; | ||||
|     void UpdateForCommandGeneration() override; | ||||
| }; | ||||
|  | ||||
| class EffectReverb : public EffectGeneric<ReverbParams> { | ||||
| public: | ||||
|     explicit EffectReverb(); | ||||
|     ~EffectReverb() override; | ||||
|  | ||||
|     void Update(EffectInfo::InParams& in_params) override; | ||||
|     void UpdateForCommandGeneration() override; | ||||
|  | ||||
| private: | ||||
|     bool skipped = false; | ||||
| }; | ||||
|  | ||||
| class EffectContext { | ||||
| public: | ||||
|     explicit EffectContext(std::size_t effect_count_); | ||||
|     ~EffectContext(); | ||||
|  | ||||
|     [[nodiscard]] std::size_t GetCount() const; | ||||
|     [[nodiscard]] EffectBase* GetInfo(std::size_t i); | ||||
|     [[nodiscard]] EffectBase* RetargetEffect(std::size_t i, EffectType effect); | ||||
|     [[nodiscard]] const EffectBase* GetInfo(std::size_t i) const; | ||||
|  | ||||
| private: | ||||
|     std::size_t effect_count{}; | ||||
|     std::vector<std::unique_ptr<EffectBase>> effects; | ||||
| }; | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										100
									
								
								src/audio_core/in/audio_in.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/audio_core/in/audio_in.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/audio_in_manager.h" | ||||
| #include "audio_core/in/audio_in.h" | ||||
| #include "core/hle/kernel/k_event.h" | ||||
|  | ||||
| namespace AudioCore::AudioIn { | ||||
|  | ||||
| In::In(Core::System& system_, Manager& manager_, Kernel::KEvent* event_, size_t session_id_) | ||||
|     : manager{manager_}, parent_mutex{manager.mutex}, event{event_}, system{system_, event, | ||||
|                                                                             session_id_} {} | ||||
|  | ||||
| void In::Free() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     manager.ReleaseSessionId(system.GetSessionId()); | ||||
| } | ||||
|  | ||||
| System& In::GetSystem() { | ||||
|     return system; | ||||
| } | ||||
|  | ||||
| AudioIn::State In::GetState() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.GetState(); | ||||
| } | ||||
|  | ||||
| Result In::StartSystem() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.Start(); | ||||
| } | ||||
|  | ||||
| void In::StartSession() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     system.StartSession(); | ||||
| } | ||||
|  | ||||
| Result In::StopSystem() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.Stop(); | ||||
| } | ||||
|  | ||||
| Result In::AppendBuffer(const AudioInBuffer& buffer, u64 tag) { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|  | ||||
|     if (system.AppendBuffer(buffer, tag)) { | ||||
|         return ResultSuccess; | ||||
|     } | ||||
|     return Service::Audio::ERR_BUFFER_COUNT_EXCEEDED; | ||||
| } | ||||
|  | ||||
| void In::ReleaseAndRegisterBuffers() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     if (system.GetState() == State::Started) { | ||||
|         system.ReleaseBuffers(); | ||||
|         system.RegisterBuffers(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool In::FlushAudioInBuffers() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.FlushAudioInBuffers(); | ||||
| } | ||||
|  | ||||
| u32 In::GetReleasedBuffers(std::span<u64> tags) { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.GetReleasedBuffers(tags); | ||||
| } | ||||
|  | ||||
| Kernel::KReadableEvent& In::GetBufferEvent() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return event->GetReadableEvent(); | ||||
| } | ||||
|  | ||||
| f32 In::GetVolume() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.GetVolume(); | ||||
| } | ||||
|  | ||||
| void In::SetVolume(f32 volume) { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     system.SetVolume(volume); | ||||
| } | ||||
|  | ||||
| bool In::ContainsAudioBuffer(u64 tag) { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.ContainsAudioBuffer(tag); | ||||
| } | ||||
|  | ||||
| u32 In::GetBufferCount() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.GetBufferCount(); | ||||
| } | ||||
|  | ||||
| u64 In::GetPlayedSampleCount() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.GetPlayedSampleCount(); | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioIn | ||||
							
								
								
									
										147
									
								
								src/audio_core/in/audio_in.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								src/audio_core/in/audio_in.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,147 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <mutex> | ||||
|  | ||||
| #include "audio_core/in/audio_in_system.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace Kernel { | ||||
| class KEvent; | ||||
| class KReadableEvent; | ||||
| } // namespace Kernel | ||||
|  | ||||
| namespace AudioCore::AudioIn { | ||||
| class Manager; | ||||
|  | ||||
| /** | ||||
|  * Interface between the service and audio in system. Mainly responsible for forwarding service | ||||
|  * calls to the system. | ||||
|  */ | ||||
| class In { | ||||
| public: | ||||
|     explicit In(Core::System& system, Manager& manager, Kernel::KEvent* event, size_t session_id); | ||||
|  | ||||
|     /** | ||||
|      * Free this audio in from the audio in manager. | ||||
|      */ | ||||
|     void Free(); | ||||
|  | ||||
|     /** | ||||
|      * Get this audio in's system. | ||||
|      */ | ||||
|     System& GetSystem(); | ||||
|  | ||||
|     /** | ||||
|      * Get the current state. | ||||
|      * | ||||
|      * @return Started or Stopped. | ||||
|      */ | ||||
|     AudioIn::State GetState(); | ||||
|  | ||||
|     /** | ||||
|      * Start the system | ||||
|      * | ||||
|      * @return Result code | ||||
|      */ | ||||
|     Result StartSystem(); | ||||
|  | ||||
|     /** | ||||
|      * Start the system's device session. | ||||
|      */ | ||||
|     void StartSession(); | ||||
|  | ||||
|     /** | ||||
|      * Stop the system. | ||||
|      * | ||||
|      * @return Result code | ||||
|      */ | ||||
|     Result StopSystem(); | ||||
|  | ||||
|     /** | ||||
|      * Append a new buffer to the system, the buffer event will be signalled when it is filled. | ||||
|      * | ||||
|      * @param buffer - The new buffer to append. | ||||
|      * @param tag    - Unique tag for this buffer. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result AppendBuffer(const AudioInBuffer& buffer, u64 tag); | ||||
|  | ||||
|     /** | ||||
|      * Release all completed buffers, and register any appended. | ||||
|      */ | ||||
|     void ReleaseAndRegisterBuffers(); | ||||
|  | ||||
|     /** | ||||
|      * Flush all buffers. | ||||
|      */ | ||||
|     bool FlushAudioInBuffers(); | ||||
|  | ||||
|     /** | ||||
|      * Get all of the currently released buffers. | ||||
|      * | ||||
|      * @param tags - Output container for the buffer tags which were released. | ||||
|      * @return The number of buffers released. | ||||
|      */ | ||||
|     u32 GetReleasedBuffers(std::span<u64> tags); | ||||
|  | ||||
|     /** | ||||
|      * Get the buffer event for this audio in, this event will be signalled when a buffer is filled. | ||||
|      * | ||||
|      * @return The buffer event. | ||||
|      */ | ||||
|     Kernel::KReadableEvent& GetBufferEvent(); | ||||
|  | ||||
|     /** | ||||
|      * Get the current system volume. | ||||
|      * | ||||
|      * @return The current volume. | ||||
|      */ | ||||
|     f32 GetVolume(); | ||||
|  | ||||
|     /** | ||||
|      * Set the system volume. | ||||
|      * | ||||
|      * @param volume - The volume to set. | ||||
|      */ | ||||
|     void SetVolume(f32 volume); | ||||
|  | ||||
|     /** | ||||
|      * Check if a buffer is in the system. | ||||
|      * | ||||
|      * @param tag - The tag to search for. | ||||
|      * @return True if the buffer is in the system, otherwise false. | ||||
|      */ | ||||
|     bool ContainsAudioBuffer(u64 tag); | ||||
|  | ||||
|     /** | ||||
|      * Get the maximum number of buffers. | ||||
|      * | ||||
|      * @return The maximum number of buffers. | ||||
|      */ | ||||
|     u32 GetBufferCount(); | ||||
|  | ||||
|     /** | ||||
|      * Get the total played sample count for this audio in. | ||||
|      * | ||||
|      * @return The played sample count. | ||||
|      */ | ||||
|     u64 GetPlayedSampleCount(); | ||||
|  | ||||
| private: | ||||
|     /// The AudioIn::Manager this audio in is registered with | ||||
|     Manager& manager; | ||||
|     /// Manager's mutex | ||||
|     std::recursive_mutex& parent_mutex; | ||||
|     /// Buffer event, signalled when buffers are ready to be released | ||||
|     Kernel::KEvent* event; | ||||
|     /// Main audio in system | ||||
|     System system; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioIn | ||||
							
								
								
									
										213
									
								
								src/audio_core/in/audio_in_system.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								src/audio_core/in/audio_in_system.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,213 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <mutex> | ||||
| #include "audio_core/audio_event.h" | ||||
| #include "audio_core/audio_manager.h" | ||||
| #include "audio_core/in/audio_in_system.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/core.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/hle/kernel/k_event.h" | ||||
|  | ||||
| namespace AudioCore::AudioIn { | ||||
|  | ||||
| System::System(Core::System& system_, Kernel::KEvent* event_, const size_t session_id_) | ||||
|     : system{system_}, buffer_event{event_}, | ||||
|       session_id{session_id_}, session{std::make_unique<DeviceSession>(system_)} {} | ||||
|  | ||||
| System::~System() { | ||||
|     Finalize(); | ||||
| } | ||||
|  | ||||
| void System::Finalize() { | ||||
|     Stop(); | ||||
|     session->Finalize(); | ||||
|     buffer_event->GetWritableEvent().Signal(); | ||||
| } | ||||
|  | ||||
| void System::StartSession() { | ||||
|     session->Start(); | ||||
| } | ||||
|  | ||||
| size_t System::GetSessionId() const { | ||||
|     return session_id; | ||||
| } | ||||
|  | ||||
| std::string_view System::GetDefaultDeviceName() { | ||||
|     return "BuiltInHeadset"; | ||||
| } | ||||
|  | ||||
| std::string_view System::GetDefaultUacDeviceName() { | ||||
|     return "Uac"; | ||||
| } | ||||
|  | ||||
| Result System::IsConfigValid(const std::string_view device_name, | ||||
|                              const AudioInParameter& in_params) { | ||||
|     if ((device_name.size() > 0) && | ||||
|         (device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) { | ||||
|         return Service::Audio::ERR_INVALID_DEVICE_NAME; | ||||
|     } | ||||
|  | ||||
|     if (in_params.sample_rate != TargetSampleRate && in_params.sample_rate > 0) { | ||||
|         return Service::Audio::ERR_INVALID_SAMPLE_RATE; | ||||
|     } | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result System::Initialize(std::string& device_name, const AudioInParameter& in_params, | ||||
|                           const u32 handle_, const u64 applet_resource_user_id_) { | ||||
|     auto result{IsConfigValid(device_name, in_params)}; | ||||
|     if (result.IsError()) { | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     handle = handle_; | ||||
|     applet_resource_user_id = applet_resource_user_id_; | ||||
|     if (device_name.empty() || device_name[0] == '\0') { | ||||
|         name = std::string(GetDefaultDeviceName()); | ||||
|     } else { | ||||
|         name = std::move(device_name); | ||||
|     } | ||||
|  | ||||
|     sample_rate = TargetSampleRate; | ||||
|     sample_format = SampleFormat::PcmInt16; | ||||
|     channel_count = in_params.channel_count <= 2 ? 2 : 6; | ||||
|     volume = 1.0f; | ||||
|     is_uac = name == "Uac"; | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result System::Start() { | ||||
|     if (state != State::Stopped) { | ||||
|         return Service::Audio::ERR_OPERATION_FAILED; | ||||
|     } | ||||
|  | ||||
|     session->Initialize(name, sample_format, channel_count, session_id, handle, | ||||
|                         applet_resource_user_id, Sink::StreamType::In); | ||||
|     session->SetVolume(volume); | ||||
|     session->Start(); | ||||
|     state = State::Started; | ||||
|  | ||||
|     std::vector<AudioBuffer> buffers_to_flush{}; | ||||
|     buffers.RegisterBuffers(buffers_to_flush); | ||||
|     session->AppendBuffers(buffers_to_flush); | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result System::Stop() { | ||||
|     if (state == State::Started) { | ||||
|         session->Stop(); | ||||
|         session->SetVolume(0.0f); | ||||
|         state = State::Stopped; | ||||
|     } | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) { | ||||
|     if (buffers.GetTotalBufferCount() == BufferCount) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     AudioBuffer new_buffer{ | ||||
|         .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; | ||||
|  | ||||
|     buffers.AppendBuffer(new_buffer); | ||||
|     RegisterBuffers(); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| void System::RegisterBuffers() { | ||||
|     if (state == State::Started) { | ||||
|         std::vector<AudioBuffer> registered_buffers{}; | ||||
|         buffers.RegisterBuffers(registered_buffers); | ||||
|         session->AppendBuffers(registered_buffers); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void System::ReleaseBuffers() { | ||||
|     bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session)}; | ||||
|  | ||||
|     if (signal) { | ||||
|         // Signal if any buffer was released, or if none are registered, we need more. | ||||
|         buffer_event->GetWritableEvent().Signal(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| u32 System::GetReleasedBuffers(std::span<u64> tags) { | ||||
|     return buffers.GetReleasedBuffers(tags); | ||||
| } | ||||
|  | ||||
| bool System::FlushAudioInBuffers() { | ||||
|     if (state != State::Started) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     u32 buffers_released{}; | ||||
|     buffers.FlushBuffers(buffers_released); | ||||
|  | ||||
|     if (buffers_released > 0) { | ||||
|         buffer_event->GetWritableEvent().Signal(); | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| u16 System::GetChannelCount() const { | ||||
|     return channel_count; | ||||
| } | ||||
|  | ||||
| u32 System::GetSampleRate() const { | ||||
|     return sample_rate; | ||||
| } | ||||
|  | ||||
| SampleFormat System::GetSampleFormat() const { | ||||
|     return sample_format; | ||||
| } | ||||
|  | ||||
| State System::GetState() { | ||||
|     switch (state) { | ||||
|     case State::Started: | ||||
|     case State::Stopped: | ||||
|         return state; | ||||
|     default: | ||||
|         LOG_ERROR(Service_Audio, "AudioIn invalid state!"); | ||||
|         state = State::Stopped; | ||||
|         break; | ||||
|     } | ||||
|     return state; | ||||
| } | ||||
|  | ||||
| std::string System::GetName() const { | ||||
|     return name; | ||||
| } | ||||
|  | ||||
| f32 System::GetVolume() const { | ||||
|     return volume; | ||||
| } | ||||
|  | ||||
| void System::SetVolume(const f32 volume_) { | ||||
|     volume = volume_; | ||||
|     session->SetVolume(volume_); | ||||
| } | ||||
|  | ||||
| bool System::ContainsAudioBuffer(const u64 tag) { | ||||
|     return buffers.ContainsBuffer(tag); | ||||
| } | ||||
|  | ||||
| u32 System::GetBufferCount() { | ||||
|     return buffers.GetAppendedRegisteredCount(); | ||||
| } | ||||
|  | ||||
| u64 System::GetPlayedSampleCount() const { | ||||
|     return session->GetPlayedSampleCount(); | ||||
| } | ||||
|  | ||||
| bool System::IsUac() const { | ||||
|     return is_uac; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioIn | ||||
							
								
								
									
										275
									
								
								src/audio_core/in/audio_in_system.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								src/audio_core/in/audio_in_system.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,275 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <atomic> | ||||
| #include <memory> | ||||
| #include <span> | ||||
| #include <string> | ||||
|  | ||||
| #include "audio_core/common/common.h" | ||||
| #include "audio_core/device/audio_buffers.h" | ||||
| #include "audio_core/device/device_session.h" | ||||
| #include "core/hle/service/audio/errors.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace Kernel { | ||||
| class KEvent; | ||||
| } | ||||
|  | ||||
| namespace AudioCore::AudioIn { | ||||
|  | ||||
| constexpr SessionTypes SessionType = SessionTypes::AudioIn; | ||||
|  | ||||
| struct AudioInParameter { | ||||
|     /* 0x0 */ s32_le sample_rate; | ||||
|     /* 0x4 */ u16_le channel_count; | ||||
|     /* 0x6 */ u16_le reserved; | ||||
| }; | ||||
| static_assert(sizeof(AudioInParameter) == 0x8, "AudioInParameter is an invalid size"); | ||||
|  | ||||
| struct AudioInParameterInternal { | ||||
|     /* 0x0 */ u32_le sample_rate; | ||||
|     /* 0x4 */ u32_le channel_count; | ||||
|     /* 0x8 */ u32_le sample_format; | ||||
|     /* 0xC */ u32_le state; | ||||
| }; | ||||
| static_assert(sizeof(AudioInParameterInternal) == 0x10, | ||||
|               "AudioInParameterInternal is an invalid size"); | ||||
|  | ||||
| struct AudioInBuffer { | ||||
|     /* 0x00 */ AudioInBuffer* next; | ||||
|     /* 0x08 */ VAddr samples; | ||||
|     /* 0x10 */ u64 capacity; | ||||
|     /* 0x18 */ u64 size; | ||||
|     /* 0x20 */ u64 offset; | ||||
| }; | ||||
| static_assert(sizeof(AudioInBuffer) == 0x28, "AudioInBuffer is an invalid size"); | ||||
|  | ||||
| enum class State { | ||||
|     Started, | ||||
|     Stopped, | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Controls and drives audio input. | ||||
|  */ | ||||
| class System { | ||||
| public: | ||||
|     explicit System(Core::System& system, Kernel::KEvent* event, size_t session_id); | ||||
|     ~System(); | ||||
|  | ||||
|     /** | ||||
|      * Get the default audio input device name. | ||||
|      * | ||||
|      * @return The default audio input device name. | ||||
|      */ | ||||
|     std::string_view GetDefaultDeviceName(); | ||||
|  | ||||
|     /** | ||||
|      * Get the default USB audio input device name. | ||||
|      * This is preferred over non-USB as some games refuse to work with the BuiltInHeadset | ||||
|      * (e.g Let's Sing). | ||||
|      * | ||||
|      * @return The default USB audio input device name. | ||||
|      */ | ||||
|     std::string_view GetDefaultUacDeviceName(); | ||||
|  | ||||
|     /** | ||||
|      * Is the given initialize config valid? | ||||
|      * | ||||
|      * @param device_name - The name of the requested input device. | ||||
|      * @param in_params   - Input parameters, see AudioInParameter. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params); | ||||
|  | ||||
|     /** | ||||
|      * Initialize this system. | ||||
|      * | ||||
|      * @param device_name             - The name of the requested input device. | ||||
|      * @param in_params               - Input parameters, see AudioInParameter. | ||||
|      * @param handle                  - Unused. | ||||
|      * @param applet_resource_user_id - Unused. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result Initialize(std::string& device_name, const AudioInParameter& in_params, u32 handle, | ||||
|                       u64 applet_resource_user_id); | ||||
|  | ||||
|     /** | ||||
|      * Start this system. | ||||
|      * | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result Start(); | ||||
|  | ||||
|     /** | ||||
|      * Stop this system. | ||||
|      * | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result Stop(); | ||||
|  | ||||
|     /** | ||||
|      * Finalize this system. | ||||
|      */ | ||||
|     void Finalize(); | ||||
|  | ||||
|     /** | ||||
|      * Start this system's device session. | ||||
|      */ | ||||
|     void StartSession(); | ||||
|  | ||||
|     /** | ||||
|      * Get this system's id. | ||||
|      */ | ||||
|     size_t GetSessionId() const; | ||||
|  | ||||
|     /** | ||||
|      * Append a new buffer to the device. | ||||
|      * | ||||
|      * @param buffer - New buffer to append. | ||||
|      * @param tag    - Unique tag of the buffer. | ||||
|      * @return True if the buffer was appended, otherwise false. | ||||
|      */ | ||||
|     bool AppendBuffer(const AudioInBuffer& buffer, u64 tag); | ||||
|  | ||||
|     /** | ||||
|      * Register all appended buffers. | ||||
|      */ | ||||
|     void RegisterBuffers(); | ||||
|  | ||||
|     /** | ||||
|      * Release all registered buffers. | ||||
|      */ | ||||
|     void ReleaseBuffers(); | ||||
|  | ||||
|     /** | ||||
|      * Get all released buffers. | ||||
|      * | ||||
|      * @param tags - Container to be filled with the released buffers' tags. | ||||
|      * @return The number of buffers released. | ||||
|      */ | ||||
|     u32 GetReleasedBuffers(std::span<u64> tags); | ||||
|  | ||||
|     /** | ||||
|      * Flush all appended and registered buffers. | ||||
|      * | ||||
|      * @return True if buffers were successfully flushed, otherwise false. | ||||
|      */ | ||||
|     bool FlushAudioInBuffers(); | ||||
|  | ||||
|     /** | ||||
|      * Get this system's current channel count. | ||||
|      * | ||||
|      * @return The channel count. | ||||
|      */ | ||||
|     u16 GetChannelCount() const; | ||||
|  | ||||
|     /** | ||||
|      * Get this system's current sample rate. | ||||
|      * | ||||
|      * @return The sample rate. | ||||
|      */ | ||||
|     u32 GetSampleRate() const; | ||||
|  | ||||
|     /** | ||||
|      * Get this system's current sample format. | ||||
|      * | ||||
|      * @return The sample format. | ||||
|      */ | ||||
|     SampleFormat GetSampleFormat() const; | ||||
|  | ||||
|     /** | ||||
|      * Get this system's current state. | ||||
|      * | ||||
|      * @return The current state. | ||||
|      */ | ||||
|     State GetState(); | ||||
|  | ||||
|     /** | ||||
|      * Get this system's name. | ||||
|      * | ||||
|      * @return The system's name. | ||||
|      */ | ||||
|     std::string GetName() const; | ||||
|  | ||||
|     /** | ||||
|      * Get this system's current volume. | ||||
|      * | ||||
|      * @return The system's current volume. | ||||
|      */ | ||||
|     f32 GetVolume() const; | ||||
|  | ||||
|     /** | ||||
|      * Set this system's current volume. | ||||
|      * | ||||
|      * @param The new volume. | ||||
|      */ | ||||
|     void SetVolume(f32 volume); | ||||
|  | ||||
|     /** | ||||
|      * Does the system contain this buffer? | ||||
|      * | ||||
|      * @param tag - Unique tag to search for. | ||||
|      * @return True if the buffer is in the system, otherwise false. | ||||
|      */ | ||||
|     bool ContainsAudioBuffer(u64 tag); | ||||
|  | ||||
|     /** | ||||
|      * Get the maximum number of usable buffers (default 32). | ||||
|      * | ||||
|      * @return The number of buffers. | ||||
|      */ | ||||
|     u32 GetBufferCount(); | ||||
|  | ||||
|     /** | ||||
|      * Get the total number of samples played by this system. | ||||
|      * | ||||
|      * @return The number of samples. | ||||
|      */ | ||||
|     u64 GetPlayedSampleCount() const; | ||||
|  | ||||
|     /** | ||||
|      * Is this system using a USB device? | ||||
|      * | ||||
|      * @return True if using a USB device, otherwise false. | ||||
|      */ | ||||
|     bool IsUac() const; | ||||
|  | ||||
| private: | ||||
|     /// Core system | ||||
|     Core::System& system; | ||||
|     /// (Unused) | ||||
|     u32 handle{}; | ||||
|     /// (Unused) | ||||
|     u64 applet_resource_user_id{}; | ||||
|     /// Buffer event, signalled when a buffer is ready | ||||
|     Kernel::KEvent* buffer_event; | ||||
|     /// Session id of this system | ||||
|     size_t session_id{}; | ||||
|     /// Device session for this system | ||||
|     std::unique_ptr<DeviceSession> session; | ||||
|     /// Audio buffers in use by this system | ||||
|     AudioBuffers<BufferCount> buffers{BufferCount}; | ||||
|     /// Sample rate of this system | ||||
|     u32 sample_rate{}; | ||||
|     /// Sample format of this system | ||||
|     SampleFormat sample_format{SampleFormat::PcmInt16}; | ||||
|     /// Channel count of this system | ||||
|     u16 channel_count{}; | ||||
|     /// State of this system | ||||
|     std::atomic<State> state{State::Stopped}; | ||||
|     /// Name of this system | ||||
|     std::string name{}; | ||||
|     /// Volume of this system | ||||
|     f32 volume{1.0f}; | ||||
|     /// Is this system's device USB? | ||||
|     bool is_uac{false}; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioIn | ||||
| @@ -1,511 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/behavior_info.h" | ||||
| #include "audio_core/effect_context.h" | ||||
| #include "audio_core/info_updater.h" | ||||
| #include "audio_core/memory_pool.h" | ||||
| #include "audio_core/mix_context.h" | ||||
| #include "audio_core/sink_context.h" | ||||
| #include "audio_core/splitter_context.h" | ||||
| #include "audio_core/voice_context.h" | ||||
| #include "common/logging/log.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| InfoUpdater::InfoUpdater(const std::vector<u8>& in_params_, std::vector<u8>& out_params_, | ||||
|                          BehaviorInfo& behavior_info_) | ||||
|     : in_params(in_params_), out_params(out_params_), behavior_info(behavior_info_) { | ||||
|     ASSERT( | ||||
|         AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader))); | ||||
|     std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader)); | ||||
|     output_header.total_size = sizeof(AudioCommon::UpdateDataHeader); | ||||
| } | ||||
|  | ||||
| InfoUpdater::~InfoUpdater() = default; | ||||
|  | ||||
| bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) { | ||||
|     if (input_header.size.behavior != sizeof(BehaviorInfo::InParams)) { | ||||
|         LOG_ERROR(Audio, "Behavior info is an invalid size, expecting 0x{:X} but got 0x{:X}", | ||||
|                   sizeof(BehaviorInfo::InParams), input_header.size.behavior); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, | ||||
|                                        sizeof(BehaviorInfo::InParams))) { | ||||
|         LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     BehaviorInfo::InParams behavior_in{}; | ||||
|     std::memcpy(&behavior_in, in_params.data() + input_offset, sizeof(BehaviorInfo::InParams)); | ||||
|     input_offset += sizeof(BehaviorInfo::InParams); | ||||
|  | ||||
|     // Make sure it's an audio revision we can actually support | ||||
|     if (!AudioCommon::IsValidRevision(behavior_in.revision)) { | ||||
|         LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", behavior_in.revision); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // Make sure that our behavior info revision matches the input | ||||
|     if (in_behavior_info.GetUserRevision() != behavior_in.revision) { | ||||
|         LOG_ERROR(Audio, | ||||
|                   "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}", | ||||
|                   in_behavior_info.GetUserRevision(), behavior_in.revision); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // Update behavior info flags | ||||
|     in_behavior_info.ClearError(); | ||||
|     in_behavior_info.UpdateFlags(behavior_in.flags); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool InfoUpdater::UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info) { | ||||
|     const auto memory_pool_count = memory_pool_info.size(); | ||||
|     const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count; | ||||
|     const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count; | ||||
|  | ||||
|     if (input_header.size.memory_pool != total_memory_pool_in) { | ||||
|         LOG_ERROR(Audio, "Memory pools are an invalid size, expecting 0x{:X} but got 0x{:X}", | ||||
|                   total_memory_pool_in, input_header.size.memory_pool); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_memory_pool_in)) { | ||||
|         LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     std::vector<ServerMemoryPoolInfo::InParams> mempool_in(memory_pool_count); | ||||
|     std::vector<ServerMemoryPoolInfo::OutParams> mempool_out(memory_pool_count); | ||||
|  | ||||
|     std::memcpy(mempool_in.data(), in_params.data() + input_offset, total_memory_pool_in); | ||||
|     input_offset += total_memory_pool_in; | ||||
|  | ||||
|     // Update our memory pools | ||||
|     for (std::size_t i = 0; i < memory_pool_count; i++) { | ||||
|         if (!memory_pool_info[i].Update(mempool_in[i], mempool_out[i])) { | ||||
|             LOG_ERROR(Audio, "Failed to update memory pool {}!", i); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, | ||||
|                                        sizeof(BehaviorInfo::InParams))) { | ||||
|         LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     std::memcpy(out_params.data() + output_offset, mempool_out.data(), total_memory_pool_out); | ||||
|     output_offset += total_memory_pool_out; | ||||
|     output_header.size.memory_pool = static_cast<u32>(total_memory_pool_out); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) { | ||||
|     const auto voice_count = voice_context.GetVoiceCount(); | ||||
|     const auto voice_size = voice_count * sizeof(VoiceChannelResource::InParams); | ||||
|     std::vector<VoiceChannelResource::InParams> resources_in(voice_count); | ||||
|  | ||||
|     if (input_header.size.voice_channel_resource != voice_size) { | ||||
|         LOG_ERROR(Audio, "VoiceChannelResource is an invalid size, expecting 0x{:X} but got 0x{:X}", | ||||
|                   voice_size, input_header.size.voice_channel_resource); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_size)) { | ||||
|         LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     std::memcpy(resources_in.data(), in_params.data() + input_offset, voice_size); | ||||
|     input_offset += voice_size; | ||||
|  | ||||
|     // Update our channel resources | ||||
|     for (std::size_t i = 0; i < voice_count; i++) { | ||||
|         // Grab our channel resource | ||||
|         auto& resource = voice_context.GetChannelResource(i); | ||||
|         resource.Update(resources_in[i]); | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool InfoUpdater::UpdateVoices(VoiceContext& voice_context, | ||||
|                                [[maybe_unused]] std::vector<ServerMemoryPoolInfo>& memory_pool_info, | ||||
|                                [[maybe_unused]] VAddr audio_codec_dsp_addr) { | ||||
|     const auto voice_count = voice_context.GetVoiceCount(); | ||||
|     std::vector<VoiceInfo::InParams> voice_in(voice_count); | ||||
|     std::vector<VoiceInfo::OutParams> voice_out(voice_count); | ||||
|  | ||||
|     const auto voice_in_size = voice_count * sizeof(VoiceInfo::InParams); | ||||
|     const auto voice_out_size = voice_count * sizeof(VoiceInfo::OutParams); | ||||
|  | ||||
|     if (input_header.size.voice != voice_in_size) { | ||||
|         LOG_ERROR(Audio, "Voices are an invalid size, expecting 0x{:X} but got 0x{:X}", | ||||
|                   voice_in_size, input_header.size.voice); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_in_size)) { | ||||
|         LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     std::memcpy(voice_in.data(), in_params.data() + input_offset, voice_in_size); | ||||
|     input_offset += voice_in_size; | ||||
|  | ||||
|     // Set all voices to not be in use | ||||
|     for (std::size_t i = 0; i < voice_count; i++) { | ||||
|         voice_context.GetInfo(i).GetInParams().in_use = false; | ||||
|     } | ||||
|  | ||||
|     // Update our voices | ||||
|     for (std::size_t i = 0; i < voice_count; i++) { | ||||
|         auto& voice_in_params = voice_in[i]; | ||||
|         const auto channel_count = static_cast<std::size_t>(voice_in_params.channel_count); | ||||
|         // Skip if it's not currently in use | ||||
|         if (!voice_in_params.is_in_use) { | ||||
|             continue; | ||||
|         } | ||||
|         // Voice states for each channel | ||||
|         std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> voice_states{}; | ||||
|         ASSERT(static_cast<std::size_t>(voice_in_params.id) < voice_count); | ||||
|  | ||||
|         // Grab our current voice info | ||||
|         auto& voice_info = voice_context.GetInfo(static_cast<std::size_t>(voice_in_params.id)); | ||||
|  | ||||
|         ASSERT(channel_count <= AudioCommon::MAX_CHANNEL_COUNT); | ||||
|  | ||||
|         // Get all our channel voice states | ||||
|         for (std::size_t channel = 0; channel < channel_count; channel++) { | ||||
|             voice_states[channel] = | ||||
|                 &voice_context.GetState(voice_in_params.voice_channel_resource_ids[channel]); | ||||
|         } | ||||
|  | ||||
|         if (voice_in_params.is_new) { | ||||
|             // Default our values for our voice | ||||
|             voice_info.Initialize(); | ||||
|  | ||||
|             // Zero out our voice states | ||||
|             for (std::size_t channel = 0; channel < channel_count; channel++) { | ||||
|                 std::memset(voice_states[channel], 0, sizeof(VoiceState)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Update our voice | ||||
|         voice_info.UpdateParameters(voice_in_params, behavior_info); | ||||
|         // TODO(ogniK): Handle mapping errors with behavior info based on in params response | ||||
|  | ||||
|         // Update our wave buffers | ||||
|         voice_info.UpdateWaveBuffers(voice_in_params, voice_states, behavior_info); | ||||
|         voice_info.WriteOutStatus(voice_out[i], voice_in_params, voice_states); | ||||
|     } | ||||
|  | ||||
|     if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, voice_out_size)) { | ||||
|         LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||||
|         return false; | ||||
|     } | ||||
|     std::memcpy(out_params.data() + output_offset, voice_out.data(), voice_out_size); | ||||
|     output_offset += voice_out_size; | ||||
|     output_header.size.voice = static_cast<u32>(voice_out_size); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) { | ||||
|     const auto effect_count = effect_context.GetCount(); | ||||
|     std::vector<EffectInfo::InParams> effect_in(effect_count); | ||||
|     std::vector<EffectInfo::OutParams> effect_out(effect_count); | ||||
|  | ||||
|     const auto total_effect_in = effect_count * sizeof(EffectInfo::InParams); | ||||
|     const auto total_effect_out = effect_count * sizeof(EffectInfo::OutParams); | ||||
|  | ||||
|     if (input_header.size.effect != total_effect_in) { | ||||
|         LOG_ERROR(Audio, "Effects are an invalid size, expecting 0x{:X} but got 0x{:X}", | ||||
|                   total_effect_in, input_header.size.effect); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_effect_in)) { | ||||
|         LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     std::memcpy(effect_in.data(), in_params.data() + input_offset, total_effect_in); | ||||
|     input_offset += total_effect_in; | ||||
|  | ||||
|     // Update effects | ||||
|     for (std::size_t i = 0; i < effect_count; i++) { | ||||
|         auto* info = effect_context.GetInfo(i); | ||||
|         if (effect_in[i].type != info->GetType()) { | ||||
|             info = effect_context.RetargetEffect(i, effect_in[i].type); | ||||
|         } | ||||
|  | ||||
|         info->Update(effect_in[i]); | ||||
|  | ||||
|         if ((!is_active && info->GetUsage() != UsageState::Initialized) || | ||||
|             info->GetUsage() == UsageState::Stopped) { | ||||
|             effect_out[i].status = UsageStatus::Removed; | ||||
|         } else { | ||||
|             effect_out[i].status = UsageStatus::Used; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_effect_out)) { | ||||
|         LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     std::memcpy(out_params.data() + output_offset, effect_out.data(), total_effect_out); | ||||
|     output_offset += total_effect_out; | ||||
|     output_header.size.effect = static_cast<u32>(total_effect_out); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) { | ||||
|     std::size_t start_offset = input_offset; | ||||
|     std::size_t bytes_read{}; | ||||
|     // Update splitter context | ||||
|     if (!splitter_context.Update(in_params, input_offset, bytes_read)) { | ||||
|         LOG_ERROR(Audio, "Failed to update splitter context!"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     const auto consumed = input_offset - start_offset; | ||||
|  | ||||
|     if (input_header.size.splitter != consumed) { | ||||
|         LOG_ERROR(Audio, "Splitters is an invalid size, expecting 0x{:X} but got 0x{:X}", | ||||
|                   bytes_read, input_header.size.splitter); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| Result InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count, | ||||
|                                 SplitterContext& splitter_context, EffectContext& effect_context) { | ||||
|     std::vector<MixInfo::InParams> mix_in_params; | ||||
|  | ||||
|     if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) { | ||||
|         // If we're not dirty, get ALL mix in parameters | ||||
|         const auto context_mix_count = mix_context.GetCount(); | ||||
|         const auto total_mix_in = context_mix_count * sizeof(MixInfo::InParams); | ||||
|         if (input_header.size.mixer != total_mix_in) { | ||||
|             LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}", | ||||
|                       total_mix_in, input_header.size.mixer); | ||||
|             return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|         } | ||||
|  | ||||
|         if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_mix_in)) { | ||||
|             LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||||
|             return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|         } | ||||
|  | ||||
|         mix_in_params.resize(context_mix_count); | ||||
|         std::memcpy(mix_in_params.data(), in_params.data() + input_offset, total_mix_in); | ||||
|  | ||||
|         input_offset += total_mix_in; | ||||
|     } else { | ||||
|         // Only update the "dirty" mixes | ||||
|         MixInfo::DirtyHeader dirty_header{}; | ||||
|         if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, | ||||
|                                            sizeof(MixInfo::DirtyHeader))) { | ||||
|             LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||||
|             return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|         } | ||||
|  | ||||
|         std::memcpy(&dirty_header, in_params.data() + input_offset, sizeof(MixInfo::DirtyHeader)); | ||||
|         input_offset += sizeof(MixInfo::DirtyHeader); | ||||
|  | ||||
|         const auto total_mix_in = | ||||
|             dirty_header.mixer_count * sizeof(MixInfo::InParams) + sizeof(MixInfo::DirtyHeader); | ||||
|  | ||||
|         if (input_header.size.mixer != total_mix_in) { | ||||
|             LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}", | ||||
|                       total_mix_in, input_header.size.mixer); | ||||
|             return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|         } | ||||
|  | ||||
|         if (dirty_header.mixer_count != 0) { | ||||
|             mix_in_params.resize(dirty_header.mixer_count); | ||||
|             std::memcpy(mix_in_params.data(), in_params.data() + input_offset, | ||||
|                         mix_in_params.size() * sizeof(MixInfo::InParams)); | ||||
|             input_offset += mix_in_params.size() * sizeof(MixInfo::InParams); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Get our total input count | ||||
|     const auto mix_count = mix_in_params.size(); | ||||
|  | ||||
|     if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) { | ||||
|         // Only verify our buffer count if we're not dirty | ||||
|         std::size_t total_buffer_count{}; | ||||
|         for (std::size_t i = 0; i < mix_count; i++) { | ||||
|             const auto& in = mix_in_params[i]; | ||||
|             total_buffer_count += in.buffer_count; | ||||
|             if (static_cast<std::size_t>(in.dest_mix_id) > mix_count && | ||||
|                 in.dest_mix_id != AudioCommon::NO_MIX && in.mix_id != AudioCommon::FINAL_MIX) { | ||||
|                 LOG_ERROR( | ||||
|                     Audio, | ||||
|                     "Invalid mix destination, mix_id={:X}, dest_mix_id={:X}, mix_buffer_count={:X}", | ||||
|                     in.mix_id, in.dest_mix_id, mix_buffer_count); | ||||
|                 return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (total_buffer_count > mix_buffer_count) { | ||||
|             LOG_ERROR(Audio, | ||||
|                       "Too many mix buffers used! mix_buffer_count={:X}, requesting_buffers={:X}", | ||||
|                       mix_buffer_count, total_buffer_count); | ||||
|             return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (mix_buffer_count == 0) { | ||||
|         LOG_ERROR(Audio, "No mix buffers!"); | ||||
|         return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | ||||
|     } | ||||
|  | ||||
|     bool should_sort = false; | ||||
|     for (std::size_t i = 0; i < mix_count; i++) { | ||||
|         const auto& mix_in = mix_in_params[i]; | ||||
|         std::size_t target_mix{}; | ||||
|         if (behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) { | ||||
|             target_mix = mix_in.mix_id; | ||||
|         } else { | ||||
|             // Non dirty supported games just use i instead of the actual mix_id | ||||
|             target_mix = i; | ||||
|         } | ||||
|         auto& mix_info = mix_context.GetInfo(target_mix); | ||||
|         auto& mix_info_params = mix_info.GetInParams(); | ||||
|         if (mix_info_params.in_use != mix_in.in_use) { | ||||
|             mix_info_params.in_use = mix_in.in_use; | ||||
|             mix_info.ResetEffectProcessingOrder(); | ||||
|             should_sort = true; | ||||
|         } | ||||
|  | ||||
|         if (mix_in.in_use) { | ||||
|             should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info, | ||||
|                                            splitter_context, effect_context); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (should_sort && behavior_info.IsSplitterSupported()) { | ||||
|         // Sort our splitter data | ||||
|         if (!mix_context.TsortInfo(splitter_context)) { | ||||
|             return AudioCommon::Audren::ERR_SPLITTER_SORT_FAILED; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // TODO(ogniK): Sort when splitter is suppoorted | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| bool InfoUpdater::UpdateSinks(SinkContext& sink_context) { | ||||
|     const auto sink_count = sink_context.GetCount(); | ||||
|     std::vector<SinkInfo::InParams> sink_in_params(sink_count); | ||||
|     const auto total_sink_in = sink_count * sizeof(SinkInfo::InParams); | ||||
|  | ||||
|     if (input_header.size.sink != total_sink_in) { | ||||
|         LOG_ERROR(Audio, "Sinks are an invalid size, expecting 0x{:X} but got 0x{:X}", | ||||
|                   total_sink_in, input_header.size.effect); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_sink_in)) { | ||||
|         LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     std::memcpy(sink_in_params.data(), in_params.data() + input_offset, total_sink_in); | ||||
|     input_offset += total_sink_in; | ||||
|  | ||||
|     // TODO(ogniK): Properly update sinks | ||||
|     if (!sink_in_params.empty()) { | ||||
|         sink_context.UpdateMainSink(sink_in_params[0]); | ||||
|     } | ||||
|  | ||||
|     output_header.size.sink = static_cast<u32>(0x20 * sink_count); | ||||
|     output_offset += 0x20 * sink_count; | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool InfoUpdater::UpdatePerformanceBuffer() { | ||||
|     output_header.size.performance = 0x10; | ||||
|     output_offset += 0x10; | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool InfoUpdater::UpdateErrorInfo([[maybe_unused]] BehaviorInfo& in_behavior_info) { | ||||
|     const auto total_beahvior_info_out = sizeof(BehaviorInfo::OutParams); | ||||
|  | ||||
|     if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_beahvior_info_out)) { | ||||
|         LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     BehaviorInfo::OutParams behavior_info_out{}; | ||||
|     behavior_info.CopyErrorInfo(behavior_info_out); | ||||
|  | ||||
|     std::memcpy(out_params.data() + output_offset, &behavior_info_out, total_beahvior_info_out); | ||||
|     output_offset += total_beahvior_info_out; | ||||
|     output_header.size.behavior = total_beahvior_info_out; | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| struct RendererInfo { | ||||
|     u64_le elasped_frame_count{}; | ||||
|     INSERT_PADDING_WORDS(2); | ||||
| }; | ||||
| static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size"); | ||||
|  | ||||
| bool InfoUpdater::UpdateRendererInfo(std::size_t elapsed_frame_count) { | ||||
|     const auto total_renderer_info_out = sizeof(RendererInfo); | ||||
|     if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_renderer_info_out)) { | ||||
|         LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||||
|         return false; | ||||
|     } | ||||
|     RendererInfo out{}; | ||||
|     out.elasped_frame_count = elapsed_frame_count; | ||||
|     std::memcpy(out_params.data() + output_offset, &out, total_renderer_info_out); | ||||
|     output_offset += total_renderer_info_out; | ||||
|     output_header.size.render_info = total_renderer_info_out; | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool InfoUpdater::CheckConsumedSize() const { | ||||
|     if (output_offset != out_params.size()) { | ||||
|         LOG_ERROR(Audio, "Output is not consumed! Consumed {}, but requires {}. {} bytes remaining", | ||||
|                   output_offset, out_params.size(), out_params.size() - output_offset); | ||||
|         return false; | ||||
|     } | ||||
|     /*if (input_offset != in_params.size()) { | ||||
|         LOG_ERROR(Audio, "Input is not consumed!"); | ||||
|         return false; | ||||
|     }*/ | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool InfoUpdater::WriteOutputHeader() { | ||||
|     if (!AudioCommon::CanConsumeBuffer(out_params.size(), 0, | ||||
|                                        sizeof(AudioCommon::UpdateDataHeader))) { | ||||
|         LOG_ERROR(Audio, "Buffer is an invalid size!"); | ||||
|         return false; | ||||
|     } | ||||
|     output_header.revision = AudioCommon::CURRENT_PROCESS_REVISION; | ||||
|     const auto& sz = output_header.size; | ||||
|     output_header.total_size += sz.behavior + sz.memory_pool + sz.voice + | ||||
|                                 sz.voice_channel_resource + sz.effect + sz.mixer + sz.sink + | ||||
|                                 sz.performance + sz.splitter + sz.render_info; | ||||
|  | ||||
|     std::memcpy(out_params.data(), &output_header, sizeof(AudioCommon::UpdateDataHeader)); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,57 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <vector> | ||||
| #include "audio_core/common.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| class BehaviorInfo; | ||||
| class ServerMemoryPoolInfo; | ||||
| class VoiceContext; | ||||
| class EffectContext; | ||||
| class MixContext; | ||||
| class SinkContext; | ||||
| class SplitterContext; | ||||
|  | ||||
| class InfoUpdater { | ||||
| public: | ||||
|     // TODO(ogniK): Pass process handle when we support it | ||||
|     InfoUpdater(const std::vector<u8>& in_params_, std::vector<u8>& out_params_, | ||||
|                 BehaviorInfo& behavior_info_); | ||||
|     ~InfoUpdater(); | ||||
|  | ||||
|     bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info); | ||||
|     bool UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info); | ||||
|     bool UpdateVoiceChannelResources(VoiceContext& voice_context); | ||||
|     bool UpdateVoices(VoiceContext& voice_context, | ||||
|                       std::vector<ServerMemoryPoolInfo>& memory_pool_info, | ||||
|                       VAddr audio_codec_dsp_addr); | ||||
|     bool UpdateEffects(EffectContext& effect_context, bool is_active); | ||||
|     bool UpdateSplitterInfo(SplitterContext& splitter_context); | ||||
|     Result UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count, | ||||
|                        SplitterContext& splitter_context, EffectContext& effect_context); | ||||
|     bool UpdateSinks(SinkContext& sink_context); | ||||
|     bool UpdatePerformanceBuffer(); | ||||
|     bool UpdateErrorInfo(BehaviorInfo& in_behavior_info); | ||||
|     bool UpdateRendererInfo(std::size_t elapsed_frame_count); | ||||
|     bool CheckConsumedSize() const; | ||||
|  | ||||
|     bool WriteOutputHeader(); | ||||
|  | ||||
| private: | ||||
|     const std::vector<u8>& in_params; | ||||
|     std::vector<u8>& out_params; | ||||
|     BehaviorInfo& behavior_info; | ||||
|  | ||||
|     AudioCommon::UpdateDataHeader input_header{}; | ||||
|     AudioCommon::UpdateDataHeader output_header{}; | ||||
|  | ||||
|     std::size_t input_offset{sizeof(AudioCommon::UpdateDataHeader)}; | ||||
|     std::size_t output_offset{sizeof(AudioCommon::UpdateDataHeader)}; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,60 +0,0 @@ | ||||
|  | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/memory_pool.h" | ||||
| #include "common/logging/log.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default; | ||||
| ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default; | ||||
|  | ||||
| bool ServerMemoryPoolInfo::Update(const InParams& in_params, OutParams& out_params) { | ||||
|     // Our state does not need to be changed | ||||
|     if (in_params.state != State::RequestAttach && in_params.state != State::RequestDetach) { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     // Address or size is null | ||||
|     if (in_params.address == 0 || in_params.size == 0) { | ||||
|         LOG_ERROR(Audio, "Memory pool address or size is zero! address={:X}, size={:X}", | ||||
|                   in_params.address, in_params.size); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // Address or size is not aligned | ||||
|     if ((in_params.address % 0x1000) != 0 || (in_params.size % 0x1000) != 0) { | ||||
|         LOG_ERROR(Audio, "Memory pool address or size is not aligned! address={:X}, size={:X}", | ||||
|                   in_params.address, in_params.size); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (in_params.state == State::RequestAttach) { | ||||
|         cpu_address = in_params.address; | ||||
|         size = in_params.size; | ||||
|         used = true; | ||||
|         out_params.state = State::Attached; | ||||
|     } else { | ||||
|         // Unexpected address | ||||
|         if (cpu_address != in_params.address) { | ||||
|             LOG_ERROR(Audio, "Memory pool address differs! Expecting {:X} but address is {:X}", | ||||
|                       cpu_address, in_params.address); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         if (size != in_params.size) { | ||||
|             LOG_ERROR(Audio, "Memory pool size differs! Expecting {:X} but size is {:X}", size, | ||||
|                       in_params.size); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         cpu_address = 0; | ||||
|         size = 0; | ||||
|         used = false; | ||||
|         out_params.state = State::Detached; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,51 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/swap.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| class ServerMemoryPoolInfo { | ||||
| public: | ||||
|     ServerMemoryPoolInfo(); | ||||
|     ~ServerMemoryPoolInfo(); | ||||
|  | ||||
|     enum class State : u32_le { | ||||
|         Invalid = 0x0, | ||||
|         Aquired = 0x1, | ||||
|         RequestDetach = 0x2, | ||||
|         Detached = 0x3, | ||||
|         RequestAttach = 0x4, | ||||
|         Attached = 0x5, | ||||
|         Released = 0x6, | ||||
|     }; | ||||
|  | ||||
|     struct InParams { | ||||
|         u64_le address{}; | ||||
|         u64_le size{}; | ||||
|         State state{}; | ||||
|         INSERT_PADDING_WORDS(3); | ||||
|     }; | ||||
|     static_assert(sizeof(InParams) == 0x20, "InParams are an invalid size"); | ||||
|  | ||||
|     struct OutParams { | ||||
|         State state{}; | ||||
|         INSERT_PADDING_WORDS(3); | ||||
|     }; | ||||
|     static_assert(sizeof(OutParams) == 0x10, "OutParams are an invalid size"); | ||||
|  | ||||
|     bool Update(const InParams& in_params, OutParams& out_params); | ||||
|  | ||||
| private: | ||||
|     // There's another entry here which is the DSP address, however since we're not talking to the | ||||
|     // DSP we can just use the same address provided by the guest without needing to remap | ||||
|     u64_le cpu_address{}; | ||||
|     u64_le size{}; | ||||
|     bool used{}; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,297 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <algorithm> | ||||
|  | ||||
| #include "audio_core/behavior_info.h" | ||||
| #include "audio_core/common.h" | ||||
| #include "audio_core/effect_context.h" | ||||
| #include "audio_core/mix_context.h" | ||||
| #include "audio_core/splitter_context.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
| MixContext::MixContext() = default; | ||||
| MixContext::~MixContext() = default; | ||||
|  | ||||
| void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count, | ||||
|                             std::size_t effect_count) { | ||||
|     info_count = mix_count; | ||||
|     infos.resize(info_count); | ||||
|     auto& final_mix = GetInfo(AudioCommon::FINAL_MIX); | ||||
|     final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX; | ||||
|     sorted_info.reserve(infos.size()); | ||||
|     for (auto& info : infos) { | ||||
|         sorted_info.push_back(&info); | ||||
|     } | ||||
|  | ||||
|     for (auto& info : infos) { | ||||
|         info.SetEffectCount(effect_count); | ||||
|     } | ||||
|  | ||||
|     // Only initialize our edge matrix and node states if splitters are supported | ||||
|     if (behavior_info.IsSplitterSupported()) { | ||||
|         node_states.Initialize(mix_count); | ||||
|         edge_matrix.Initialize(mix_count); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void MixContext::UpdateDistancesFromFinalMix() { | ||||
|     // Set all distances to be invalid | ||||
|     for (std::size_t i = 0; i < info_count; i++) { | ||||
|         GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX; | ||||
|     } | ||||
|  | ||||
|     for (std::size_t i = 0; i < info_count; i++) { | ||||
|         auto& info = GetInfo(i); | ||||
|         auto& in_params = info.GetInParams(); | ||||
|         // Populate our sorted info | ||||
|         sorted_info[i] = &info; | ||||
|  | ||||
|         if (!in_params.in_use) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         auto mix_id = in_params.mix_id; | ||||
|         // Needs to be referenced out of scope | ||||
|         s32 distance_to_final_mix{AudioCommon::FINAL_MIX}; | ||||
|         for (; distance_to_final_mix < static_cast<s32>(info_count); distance_to_final_mix++) { | ||||
|             if (mix_id == AudioCommon::FINAL_MIX) { | ||||
|                 // If we're at the final mix, we're done | ||||
|                 break; | ||||
|             } else if (mix_id == AudioCommon::NO_MIX) { | ||||
|                 // If we have no more mix ids, we're done | ||||
|                 distance_to_final_mix = AudioCommon::NO_FINAL_MIX; | ||||
|                 break; | ||||
|             } else { | ||||
|                 const auto& dest_mix = GetInfo(mix_id); | ||||
|                 const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance; | ||||
|  | ||||
|                 if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) { | ||||
|                     // If our current mix isn't pointing to a final mix, follow through | ||||
|                     mix_id = dest_mix.GetInParams().dest_mix_id; | ||||
|                 } else { | ||||
|                     // Our current mix + 1 = final distance | ||||
|                     distance_to_final_mix = dest_mix_distance + 1; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // If we're out of range for our distance, mark it as no final mix | ||||
|         if (distance_to_final_mix >= static_cast<s32>(info_count)) { | ||||
|             distance_to_final_mix = AudioCommon::NO_FINAL_MIX; | ||||
|         } | ||||
|  | ||||
|         in_params.final_mix_distance = distance_to_final_mix; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void MixContext::CalcMixBufferOffset() { | ||||
|     s32 offset{}; | ||||
|     for (std::size_t i = 0; i < info_count; i++) { | ||||
|         auto& info = GetSortedInfo(i); | ||||
|         auto& in_params = info.GetInParams(); | ||||
|         if (in_params.in_use) { | ||||
|             // Only update if in use | ||||
|             in_params.buffer_offset = offset; | ||||
|             offset += in_params.buffer_count; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void MixContext::SortInfo() { | ||||
|     // Get the distance to the final mix | ||||
|     UpdateDistancesFromFinalMix(); | ||||
|  | ||||
|     // Sort based on the distance to the final mix | ||||
|     std::sort(sorted_info.begin(), sorted_info.end(), | ||||
|               [](const ServerMixInfo* lhs, const ServerMixInfo* rhs) { | ||||
|                   return lhs->GetInParams().final_mix_distance > | ||||
|                          rhs->GetInParams().final_mix_distance; | ||||
|               }); | ||||
|  | ||||
|     // Calculate the mix buffer offset | ||||
|     CalcMixBufferOffset(); | ||||
| } | ||||
|  | ||||
| bool MixContext::TsortInfo(SplitterContext& splitter_context) { | ||||
|     // If we're not using mixes, just calculate the mix buffer offset | ||||
|     if (!splitter_context.UsingSplitter()) { | ||||
|         CalcMixBufferOffset(); | ||||
|         return true; | ||||
|     } | ||||
|     // Sort our node states | ||||
|     if (!node_states.Tsort(edge_matrix)) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // Get our sorted list | ||||
|     const auto sorted_list = node_states.GetIndexList(); | ||||
|     std::size_t info_id{}; | ||||
|     for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) { | ||||
|         // Set our sorted info | ||||
|         sorted_info[info_id++] = &GetInfo(*itr); | ||||
|     } | ||||
|  | ||||
|     // Calculate the mix buffer offset | ||||
|     CalcMixBufferOffset(); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| std::size_t MixContext::GetCount() const { | ||||
|     return info_count; | ||||
| } | ||||
|  | ||||
| ServerMixInfo& MixContext::GetInfo(std::size_t i) { | ||||
|     ASSERT(i < info_count); | ||||
|     return infos.at(i); | ||||
| } | ||||
|  | ||||
| const ServerMixInfo& MixContext::GetInfo(std::size_t i) const { | ||||
|     ASSERT(i < info_count); | ||||
|     return infos.at(i); | ||||
| } | ||||
|  | ||||
| ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) { | ||||
|     ASSERT(i < info_count); | ||||
|     return *sorted_info.at(i); | ||||
| } | ||||
|  | ||||
| const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const { | ||||
|     ASSERT(i < info_count); | ||||
|     return *sorted_info.at(i); | ||||
| } | ||||
|  | ||||
| ServerMixInfo& MixContext::GetFinalMixInfo() { | ||||
|     return infos.at(AudioCommon::FINAL_MIX); | ||||
| } | ||||
|  | ||||
| const ServerMixInfo& MixContext::GetFinalMixInfo() const { | ||||
|     return infos.at(AudioCommon::FINAL_MIX); | ||||
| } | ||||
|  | ||||
| EdgeMatrix& MixContext::GetEdgeMatrix() { | ||||
|     return edge_matrix; | ||||
| } | ||||
|  | ||||
| const EdgeMatrix& MixContext::GetEdgeMatrix() const { | ||||
|     return edge_matrix; | ||||
| } | ||||
|  | ||||
| ServerMixInfo::ServerMixInfo() { | ||||
|     Cleanup(); | ||||
| } | ||||
| ServerMixInfo::~ServerMixInfo() = default; | ||||
|  | ||||
| const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const { | ||||
|     return in_params; | ||||
| } | ||||
|  | ||||
| ServerMixInfo::InParams& ServerMixInfo::GetInParams() { | ||||
|     return in_params; | ||||
| } | ||||
|  | ||||
| bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, | ||||
|                            BehaviorInfo& behavior_info, SplitterContext& splitter_context, | ||||
|                            EffectContext& effect_context) { | ||||
|     in_params.volume = mix_in.volume; | ||||
|     in_params.sample_rate = mix_in.sample_rate; | ||||
|     in_params.buffer_count = mix_in.buffer_count; | ||||
|     in_params.in_use = mix_in.in_use; | ||||
|     in_params.mix_id = mix_in.mix_id; | ||||
|     in_params.node_id = mix_in.node_id; | ||||
|     for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) { | ||||
|         std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(), | ||||
|                   in_params.mix_volume[i].begin()); | ||||
|     } | ||||
|  | ||||
|     bool require_sort = false; | ||||
|  | ||||
|     if (behavior_info.IsSplitterSupported()) { | ||||
|         require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context); | ||||
|     } else { | ||||
|         in_params.dest_mix_id = mix_in.dest_mix_id; | ||||
|         in_params.splitter_id = AudioCommon::NO_SPLITTER; | ||||
|     } | ||||
|  | ||||
|     ResetEffectProcessingOrder(); | ||||
|     const auto effect_count = effect_context.GetCount(); | ||||
|     for (std::size_t i = 0; i < effect_count; i++) { | ||||
|         auto* effect_info = effect_context.GetInfo(i); | ||||
|         if (effect_info->GetMixID() == in_params.mix_id) { | ||||
|             effect_processing_order[effect_info->GetProcessingOrder()] = static_cast<s32>(i); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // TODO(ogniK): Update effect processing order | ||||
|     return require_sort; | ||||
| } | ||||
|  | ||||
| bool ServerMixInfo::HasAnyConnection() const { | ||||
|     return in_params.splitter_id != AudioCommon::NO_SPLITTER || | ||||
|            in_params.mix_id != AudioCommon::NO_MIX; | ||||
| } | ||||
|  | ||||
| void ServerMixInfo::Cleanup() { | ||||
|     in_params.volume = 0.0f; | ||||
|     in_params.sample_rate = 0; | ||||
|     in_params.buffer_count = 0; | ||||
|     in_params.in_use = false; | ||||
|     in_params.mix_id = AudioCommon::NO_MIX; | ||||
|     in_params.node_id = 0; | ||||
|     in_params.buffer_offset = 0; | ||||
|     in_params.dest_mix_id = AudioCommon::NO_MIX; | ||||
|     in_params.splitter_id = AudioCommon::NO_SPLITTER; | ||||
|     std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size()); | ||||
| } | ||||
|  | ||||
| void ServerMixInfo::SetEffectCount(std::size_t count) { | ||||
|     effect_processing_order.resize(count); | ||||
|     ResetEffectProcessingOrder(); | ||||
| } | ||||
|  | ||||
| void ServerMixInfo::ResetEffectProcessingOrder() { | ||||
|     for (auto& order : effect_processing_order) { | ||||
|         order = AudioCommon::NO_EFFECT_ORDER; | ||||
|     } | ||||
| } | ||||
|  | ||||
| s32 ServerMixInfo::GetEffectOrder(std::size_t i) const { | ||||
|     return effect_processing_order.at(i); | ||||
| } | ||||
|  | ||||
| bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, | ||||
|                                      SplitterContext& splitter_context) { | ||||
|     // Mixes are identical | ||||
|     if (in_params.dest_mix_id == mix_in.dest_mix_id && | ||||
|         in_params.splitter_id == mix_in.splitter_id && | ||||
|         ((in_params.splitter_id == AudioCommon::NO_SPLITTER) || | ||||
|          !splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) { | ||||
|         return false; | ||||
|     } | ||||
|     // Remove current edges for mix id | ||||
|     edge_matrix.RemoveEdges(in_params.mix_id); | ||||
|     if (mix_in.dest_mix_id != AudioCommon::NO_MIX) { | ||||
|         // If we have a valid destination mix id, set our edge matrix | ||||
|         edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id); | ||||
|     } else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) { | ||||
|         // Recurse our splitter linked and set our edges | ||||
|         auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id); | ||||
|         const auto length = splitter_info.GetLength(); | ||||
|         for (s32 i = 0; i < length; i++) { | ||||
|             const auto* splitter_destination = | ||||
|                 splitter_context.GetDestinationData(mix_in.splitter_id, i); | ||||
|             if (splitter_destination == nullptr) { | ||||
|                 continue; | ||||
|             } | ||||
|             if (splitter_destination->ValidMixId()) { | ||||
|                 edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     in_params.dest_mix_id = mix_in.dest_mix_id; | ||||
|     in_params.splitter_id = mix_in.splitter_id; | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore | ||||
| @@ -1,113 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <vector> | ||||
| #include "audio_core/common.h" | ||||
| #include "audio_core/splitter_context.h" | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
| class BehaviorInfo; | ||||
| class EffectContext; | ||||
|  | ||||
| class MixInfo { | ||||
| public: | ||||
|     struct DirtyHeader { | ||||
|         u32_le magic{}; | ||||
|         u32_le mixer_count{}; | ||||
|         INSERT_PADDING_BYTES(0x18); | ||||
|     }; | ||||
|     static_assert(sizeof(DirtyHeader) == 0x20, "MixInfo::DirtyHeader is an invalid size"); | ||||
|  | ||||
|     struct InParams { | ||||
|         float_le volume{}; | ||||
|         s32_le sample_rate{}; | ||||
|         s32_le buffer_count{}; | ||||
|         bool in_use{}; | ||||
|         INSERT_PADDING_BYTES(3); | ||||
|         s32_le mix_id{}; | ||||
|         s32_le effect_count{}; | ||||
|         u32_le node_id{}; | ||||
|         INSERT_PADDING_WORDS(2); | ||||
|         std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS> | ||||
|             mix_volume{}; | ||||
|         s32_le dest_mix_id{}; | ||||
|         s32_le splitter_id{}; | ||||
|         INSERT_PADDING_WORDS(1); | ||||
|     }; | ||||
|     static_assert(sizeof(MixInfo::InParams) == 0x930, "MixInfo::InParams is an invalid size"); | ||||
| }; | ||||
|  | ||||
| class ServerMixInfo { | ||||
| public: | ||||
|     struct InParams { | ||||
|         float volume{}; | ||||
|         s32 sample_rate{}; | ||||
|         s32 buffer_count{}; | ||||
|         bool in_use{}; | ||||
|         s32 mix_id{}; | ||||
|         u32 node_id{}; | ||||
|         std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS> | ||||
|             mix_volume{}; | ||||
|         s32 dest_mix_id{}; | ||||
|         s32 splitter_id{}; | ||||
|         s32 buffer_offset{}; | ||||
|         s32 final_mix_distance{}; | ||||
|     }; | ||||
|     ServerMixInfo(); | ||||
|     ~ServerMixInfo(); | ||||
|  | ||||
|     [[nodiscard]] const ServerMixInfo::InParams& GetInParams() const; | ||||
|     [[nodiscard]] ServerMixInfo::InParams& GetInParams(); | ||||
|  | ||||
|     bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, | ||||
|                 BehaviorInfo& behavior_info, SplitterContext& splitter_context, | ||||
|                 EffectContext& effect_context); | ||||
|     [[nodiscard]] bool HasAnyConnection() const; | ||||
|     void Cleanup(); | ||||
|     void SetEffectCount(std::size_t count); | ||||
|     void ResetEffectProcessingOrder(); | ||||
|     [[nodiscard]] s32 GetEffectOrder(std::size_t i) const; | ||||
|  | ||||
| private: | ||||
|     std::vector<s32> effect_processing_order; | ||||
|     InParams in_params{}; | ||||
|     bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, | ||||
|                           SplitterContext& splitter_context); | ||||
| }; | ||||
|  | ||||
| class MixContext { | ||||
| public: | ||||
|     MixContext(); | ||||
|     ~MixContext(); | ||||
|  | ||||
|     void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count, | ||||
|                     std::size_t effect_count); | ||||
|     void SortInfo(); | ||||
|     bool TsortInfo(SplitterContext& splitter_context); | ||||
|  | ||||
|     [[nodiscard]] std::size_t GetCount() const; | ||||
|     [[nodiscard]] ServerMixInfo& GetInfo(std::size_t i); | ||||
|     [[nodiscard]] const ServerMixInfo& GetInfo(std::size_t i) const; | ||||
|     [[nodiscard]] ServerMixInfo& GetSortedInfo(std::size_t i); | ||||
|     [[nodiscard]] const ServerMixInfo& GetSortedInfo(std::size_t i) const; | ||||
|     [[nodiscard]] ServerMixInfo& GetFinalMixInfo(); | ||||
|     [[nodiscard]] const ServerMixInfo& GetFinalMixInfo() const; | ||||
|     [[nodiscard]] EdgeMatrix& GetEdgeMatrix(); | ||||
|     [[nodiscard]] const EdgeMatrix& GetEdgeMatrix() const; | ||||
|  | ||||
| private: | ||||
|     void CalcMixBufferOffset(); | ||||
|     void UpdateDistancesFromFinalMix(); | ||||
|  | ||||
|     NodeStates node_states{}; | ||||
|     EdgeMatrix edge_matrix{}; | ||||
|     std::size_t info_count{}; | ||||
|     std::vector<ServerMixInfo> infos{}; | ||||
|     std::vector<ServerMixInfo*> sorted_info{}; | ||||
| }; | ||||
| } // namespace AudioCore | ||||
| @@ -1,32 +0,0 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "audio_core/sink.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
|  | ||||
| class NullSink final : public Sink { | ||||
| public: | ||||
|     explicit NullSink(std::string_view) {} | ||||
|     ~NullSink() override = default; | ||||
|  | ||||
|     SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/, | ||||
|                                   const std::string& /*name*/) override { | ||||
|         return null_sink_stream; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     struct NullSinkStreamImpl final : SinkStream { | ||||
|         void EnqueueSamples(u32 /*num_channels*/, const std::vector<s16>& /*samples*/) override {} | ||||
|  | ||||
|         std::size_t SamplesInQueue(u32 /*num_channels*/) const override { | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         void Flush() override {} | ||||
|     } null_sink_stream; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										100
									
								
								src/audio_core/out/audio_out.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/audio_core/out/audio_out.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/audio_out_manager.h" | ||||
| #include "audio_core/out/audio_out.h" | ||||
| #include "core/hle/kernel/k_event.h" | ||||
|  | ||||
| namespace AudioCore::AudioOut { | ||||
|  | ||||
| Out::Out(Core::System& system_, Manager& manager_, Kernel::KEvent* event_, size_t session_id_) | ||||
|     : manager{manager_}, parent_mutex{manager.mutex}, event{event_}, system{system_, event, | ||||
|                                                                             session_id_} {} | ||||
|  | ||||
| void Out::Free() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     manager.ReleaseSessionId(system.GetSessionId()); | ||||
| } | ||||
|  | ||||
| System& Out::GetSystem() { | ||||
|     return system; | ||||
| } | ||||
|  | ||||
| AudioOut::State Out::GetState() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.GetState(); | ||||
| } | ||||
|  | ||||
| Result Out::StartSystem() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.Start(); | ||||
| } | ||||
|  | ||||
| void Out::StartSession() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     system.StartSession(); | ||||
| } | ||||
|  | ||||
| Result Out::StopSystem() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.Stop(); | ||||
| } | ||||
|  | ||||
| Result Out::AppendBuffer(const AudioOutBuffer& buffer, const u64 tag) { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|  | ||||
|     if (system.AppendBuffer(buffer, tag)) { | ||||
|         return ResultSuccess; | ||||
|     } | ||||
|     return Service::Audio::ERR_BUFFER_COUNT_EXCEEDED; | ||||
| } | ||||
|  | ||||
| void Out::ReleaseAndRegisterBuffers() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     if (system.GetState() == State::Started) { | ||||
|         system.ReleaseBuffers(); | ||||
|         system.RegisterBuffers(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool Out::FlushAudioOutBuffers() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.FlushAudioOutBuffers(); | ||||
| } | ||||
|  | ||||
| u32 Out::GetReleasedBuffers(std::span<u64> tags) { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.GetReleasedBuffers(tags); | ||||
| } | ||||
|  | ||||
| Kernel::KReadableEvent& Out::GetBufferEvent() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return event->GetReadableEvent(); | ||||
| } | ||||
|  | ||||
| f32 Out::GetVolume() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.GetVolume(); | ||||
| } | ||||
|  | ||||
| void Out::SetVolume(const f32 volume) { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     system.SetVolume(volume); | ||||
| } | ||||
|  | ||||
| bool Out::ContainsAudioBuffer(const u64 tag) { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.ContainsAudioBuffer(tag); | ||||
| } | ||||
|  | ||||
| u32 Out::GetBufferCount() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.GetBufferCount(); | ||||
| } | ||||
|  | ||||
| u64 Out::GetPlayedSampleCount() { | ||||
|     std::scoped_lock l{parent_mutex}; | ||||
|     return system.GetPlayedSampleCount(); | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioOut | ||||
							
								
								
									
										147
									
								
								src/audio_core/out/audio_out.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								src/audio_core/out/audio_out.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,147 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <mutex> | ||||
|  | ||||
| #include "audio_core/out/audio_out_system.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace Kernel { | ||||
| class KEvent; | ||||
| class KReadableEvent; | ||||
| } // namespace Kernel | ||||
|  | ||||
| namespace AudioCore::AudioOut { | ||||
| class Manager; | ||||
|  | ||||
| /** | ||||
|  * Interface between the service and audio out system. Mainly responsible for forwarding service | ||||
|  * calls to the system. | ||||
|  */ | ||||
| class Out { | ||||
| public: | ||||
|     explicit Out(Core::System& system, Manager& manager, Kernel::KEvent* event, size_t session_id); | ||||
|  | ||||
|     /** | ||||
|      * Free this audio out from the audio out manager. | ||||
|      */ | ||||
|     void Free(); | ||||
|  | ||||
|     /** | ||||
|      * Get this audio out's system. | ||||
|      */ | ||||
|     System& GetSystem(); | ||||
|  | ||||
|     /** | ||||
|      * Get the current state. | ||||
|      * | ||||
|      * @return Started or Stopped. | ||||
|      */ | ||||
|     AudioOut::State GetState(); | ||||
|  | ||||
|     /** | ||||
|      * Start the system | ||||
|      * | ||||
|      * @return Result code | ||||
|      */ | ||||
|     Result StartSystem(); | ||||
|  | ||||
|     /** | ||||
|      * Start the system's device session. | ||||
|      */ | ||||
|     void StartSession(); | ||||
|  | ||||
|     /** | ||||
|      * Stop the system. | ||||
|      * | ||||
|      * @return Result code | ||||
|      */ | ||||
|     Result StopSystem(); | ||||
|  | ||||
|     /** | ||||
|      * Append a new buffer to the system, the buffer event will be signalled when it is filled. | ||||
|      * | ||||
|      * @param buffer - The new buffer to append. | ||||
|      * @param tag    - Unique tag for this buffer. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result AppendBuffer(const AudioOutBuffer& buffer, u64 tag); | ||||
|  | ||||
|     /** | ||||
|      * Release all completed buffers, and register any appended. | ||||
|      */ | ||||
|     void ReleaseAndRegisterBuffers(); | ||||
|  | ||||
|     /** | ||||
|      * Flush all buffers. | ||||
|      */ | ||||
|     bool FlushAudioOutBuffers(); | ||||
|  | ||||
|     /** | ||||
|      * Get all of the currently released buffers. | ||||
|      * | ||||
|      * @param tags - Output container for the buffer tags which were released. | ||||
|      * @return The number of buffers released. | ||||
|      */ | ||||
|     u32 GetReleasedBuffers(std::span<u64> tags); | ||||
|  | ||||
|     /** | ||||
|      * Get the buffer event for this audio out, this event will be signalled when a buffer is | ||||
|      * filled. | ||||
|      * @return The buffer event. | ||||
|      */ | ||||
|     Kernel::KReadableEvent& GetBufferEvent(); | ||||
|  | ||||
|     /** | ||||
|      * Get the current system volume. | ||||
|      * | ||||
|      * @return The current volume. | ||||
|      */ | ||||
|     f32 GetVolume(); | ||||
|  | ||||
|     /** | ||||
|      * Set the system volume. | ||||
|      * | ||||
|      * @param volume - The volume to set. | ||||
|      */ | ||||
|     void SetVolume(f32 volume); | ||||
|  | ||||
|     /** | ||||
|      * Check if a buffer is in the system. | ||||
|      * | ||||
|      * @param tag - The tag to search for. | ||||
|      * @return True if the buffer is in the system, otherwise false. | ||||
|      */ | ||||
|     bool ContainsAudioBuffer(u64 tag); | ||||
|  | ||||
|     /** | ||||
|      * Get the maximum number of buffers. | ||||
|      * | ||||
|      * @return The maximum number of buffers. | ||||
|      */ | ||||
|     u32 GetBufferCount(); | ||||
|  | ||||
|     /** | ||||
|      * Get the total played sample count for this audio out. | ||||
|      * | ||||
|      * @return The played sample count. | ||||
|      */ | ||||
|     u64 GetPlayedSampleCount(); | ||||
|  | ||||
| private: | ||||
|     /// The AudioOut::Manager this audio out is registered with | ||||
|     Manager& manager; | ||||
|     /// Manager's mutex | ||||
|     std::recursive_mutex& parent_mutex; | ||||
|     /// Buffer event, signalled when buffers are ready to be released | ||||
|     Kernel::KEvent* event; | ||||
|     /// Main audio out system | ||||
|     System system; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioOut | ||||
							
								
								
									
										207
									
								
								src/audio_core/out/audio_out_system.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								src/audio_core/out/audio_out_system.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,207 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <mutex> | ||||
|  | ||||
| #include "audio_core/audio_event.h" | ||||
| #include "audio_core/audio_manager.h" | ||||
| #include "audio_core/out/audio_out_system.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/core.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/hle/kernel/k_event.h" | ||||
|  | ||||
| namespace AudioCore::AudioOut { | ||||
|  | ||||
| System::System(Core::System& system_, Kernel::KEvent* event_, size_t session_id_) | ||||
|     : system{system_}, buffer_event{event_}, | ||||
|       session_id{session_id_}, session{std::make_unique<DeviceSession>(system_)} {} | ||||
|  | ||||
| System::~System() { | ||||
|     Finalize(); | ||||
| } | ||||
|  | ||||
| void System::Finalize() { | ||||
|     Stop(); | ||||
|     session->Finalize(); | ||||
|     buffer_event->GetWritableEvent().Signal(); | ||||
| } | ||||
|  | ||||
| std::string_view System::GetDefaultOutputDeviceName() { | ||||
|     return "DeviceOut"; | ||||
| } | ||||
|  | ||||
| Result System::IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) { | ||||
|     if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) { | ||||
|         return Service::Audio::ERR_INVALID_DEVICE_NAME; | ||||
|     } | ||||
|  | ||||
|     if (in_params.sample_rate != TargetSampleRate && in_params.sample_rate > 0) { | ||||
|         return Service::Audio::ERR_INVALID_SAMPLE_RATE; | ||||
|     } | ||||
|  | ||||
|     if (in_params.channel_count == 0 || in_params.channel_count == 2 || | ||||
|         in_params.channel_count == 6) { | ||||
|         return ResultSuccess; | ||||
|     } | ||||
|  | ||||
|     return Service::Audio::ERR_INVALID_CHANNEL_COUNT; | ||||
| } | ||||
|  | ||||
| Result System::Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle_, | ||||
|                           u64& applet_resource_user_id_) { | ||||
|     auto result = IsConfigValid(device_name, in_params); | ||||
|     if (result.IsError()) { | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     handle = handle_; | ||||
|     applet_resource_user_id = applet_resource_user_id_; | ||||
|     if (device_name.empty() || device_name[0] == '\0') { | ||||
|         name = std::string(GetDefaultOutputDeviceName()); | ||||
|     } else { | ||||
|         name = std::move(device_name); | ||||
|     } | ||||
|  | ||||
|     sample_rate = TargetSampleRate; | ||||
|     sample_format = SampleFormat::PcmInt16; | ||||
|     channel_count = in_params.channel_count <= 2 ? 2 : 6; | ||||
|     volume = 1.0f; | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| void System::StartSession() { | ||||
|     session->Start(); | ||||
| } | ||||
|  | ||||
| size_t System::GetSessionId() const { | ||||
|     return session_id; | ||||
| } | ||||
|  | ||||
| Result System::Start() { | ||||
|     if (state != State::Stopped) { | ||||
|         return Service::Audio::ERR_OPERATION_FAILED; | ||||
|     } | ||||
|  | ||||
|     session->Initialize(name, sample_format, channel_count, session_id, handle, | ||||
|                         applet_resource_user_id, Sink::StreamType::Out); | ||||
|     session->SetVolume(volume); | ||||
|     session->Start(); | ||||
|     state = State::Started; | ||||
|  | ||||
|     std::vector<AudioBuffer> buffers_to_flush{}; | ||||
|     buffers.RegisterBuffers(buffers_to_flush); | ||||
|     session->AppendBuffers(buffers_to_flush); | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result System::Stop() { | ||||
|     if (state == State::Started) { | ||||
|         session->Stop(); | ||||
|         session->SetVolume(0.0f); | ||||
|         state = State::Stopped; | ||||
|     } | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) { | ||||
|     if (buffers.GetTotalBufferCount() == BufferCount) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     AudioBuffer new_buffer{ | ||||
|         .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; | ||||
|  | ||||
|     buffers.AppendBuffer(new_buffer); | ||||
|     RegisterBuffers(); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| void System::RegisterBuffers() { | ||||
|     if (state == State::Started) { | ||||
|         std::vector<AudioBuffer> registered_buffers{}; | ||||
|         buffers.RegisterBuffers(registered_buffers); | ||||
|         session->AppendBuffers(registered_buffers); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void System::ReleaseBuffers() { | ||||
|     bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session)}; | ||||
|     if (signal) { | ||||
|         // Signal if any buffer was released, or if none are registered, we need more. | ||||
|         buffer_event->GetWritableEvent().Signal(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| u32 System::GetReleasedBuffers(std::span<u64> tags) { | ||||
|     return buffers.GetReleasedBuffers(tags); | ||||
| } | ||||
|  | ||||
| bool System::FlushAudioOutBuffers() { | ||||
|     if (state != State::Started) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     u32 buffers_released{}; | ||||
|     buffers.FlushBuffers(buffers_released); | ||||
|  | ||||
|     if (buffers_released > 0) { | ||||
|         buffer_event->GetWritableEvent().Signal(); | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| u16 System::GetChannelCount() const { | ||||
|     return channel_count; | ||||
| } | ||||
|  | ||||
| u32 System::GetSampleRate() const { | ||||
|     return sample_rate; | ||||
| } | ||||
|  | ||||
| SampleFormat System::GetSampleFormat() const { | ||||
|     return sample_format; | ||||
| } | ||||
|  | ||||
| State System::GetState() { | ||||
|     switch (state) { | ||||
|     case State::Started: | ||||
|     case State::Stopped: | ||||
|         return state; | ||||
|     default: | ||||
|         LOG_ERROR(Service_Audio, "AudioOut invalid state!"); | ||||
|         state = State::Stopped; | ||||
|         break; | ||||
|     } | ||||
|     return state; | ||||
| } | ||||
|  | ||||
| std::string System::GetName() const { | ||||
|     return name; | ||||
| } | ||||
|  | ||||
| f32 System::GetVolume() const { | ||||
|     return volume; | ||||
| } | ||||
|  | ||||
| void System::SetVolume(const f32 volume_) { | ||||
|     volume = volume_; | ||||
|     session->SetVolume(volume_); | ||||
| } | ||||
|  | ||||
| bool System::ContainsAudioBuffer(const u64 tag) { | ||||
|     return buffers.ContainsBuffer(tag); | ||||
| } | ||||
|  | ||||
| u32 System::GetBufferCount() { | ||||
|     return buffers.GetAppendedRegisteredCount(); | ||||
| } | ||||
|  | ||||
| u64 System::GetPlayedSampleCount() const { | ||||
|     return session->GetPlayedSampleCount(); | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioOut | ||||
							
								
								
									
										257
									
								
								src/audio_core/out/audio_out_system.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										257
									
								
								src/audio_core/out/audio_out_system.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,257 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <atomic> | ||||
| #include <memory> | ||||
| #include <span> | ||||
| #include <string> | ||||
|  | ||||
| #include "audio_core/common/common.h" | ||||
| #include "audio_core/device/audio_buffers.h" | ||||
| #include "audio_core/device/device_session.h" | ||||
| #include "core/hle/service/audio/errors.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace Kernel { | ||||
| class KEvent; | ||||
| } | ||||
|  | ||||
| namespace AudioCore::AudioOut { | ||||
|  | ||||
| constexpr SessionTypes SessionType = SessionTypes::AudioOut; | ||||
|  | ||||
| struct AudioOutParameter { | ||||
|     /* 0x0 */ s32_le sample_rate; | ||||
|     /* 0x4 */ u16_le channel_count; | ||||
|     /* 0x6 */ u16_le reserved; | ||||
| }; | ||||
| static_assert(sizeof(AudioOutParameter) == 0x8, "AudioOutParameter is an invalid size"); | ||||
|  | ||||
| struct AudioOutParameterInternal { | ||||
|     /* 0x0 */ u32_le sample_rate; | ||||
|     /* 0x4 */ u32_le channel_count; | ||||
|     /* 0x8 */ u32_le sample_format; | ||||
|     /* 0xC */ u32_le state; | ||||
| }; | ||||
| static_assert(sizeof(AudioOutParameterInternal) == 0x10, | ||||
|               "AudioOutParameterInternal is an invalid size"); | ||||
|  | ||||
| struct AudioOutBuffer { | ||||
|     /* 0x00 */ AudioOutBuffer* next; | ||||
|     /* 0x08 */ VAddr samples; | ||||
|     /* 0x10 */ u64 capacity; | ||||
|     /* 0x18 */ u64 size; | ||||
|     /* 0x20 */ u64 offset; | ||||
| }; | ||||
| static_assert(sizeof(AudioOutBuffer) == 0x28, "AudioOutBuffer is an invalid size"); | ||||
|  | ||||
| enum class State { | ||||
|     Started, | ||||
|     Stopped, | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Controls and drives audio output. | ||||
|  */ | ||||
| class System { | ||||
| public: | ||||
|     explicit System(Core::System& system, Kernel::KEvent* event, size_t session_id); | ||||
|     ~System(); | ||||
|  | ||||
|     /** | ||||
|      * Get the default audio output device name. | ||||
|      * | ||||
|      * @return The default audio output device name. | ||||
|      */ | ||||
|     std::string_view GetDefaultOutputDeviceName(); | ||||
|  | ||||
|     /** | ||||
|      * Is the given initialize config valid? | ||||
|      * | ||||
|      * @param device_name - The name of the requested output device. | ||||
|      * @param in_params   - Input parameters, see AudioOutParameter. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params); | ||||
|  | ||||
|     /** | ||||
|      * Initialize this system. | ||||
|      * | ||||
|      * @param device_name             - The name of the requested output device. | ||||
|      * @param in_params               - Input parameters, see AudioOutParameter. | ||||
|      * @param handle                  - Unused. | ||||
|      * @param applet_resource_user_id - Unused. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle, | ||||
|                       u64& applet_resource_user_id); | ||||
|  | ||||
|     /** | ||||
|      * Start this system. | ||||
|      * | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result Start(); | ||||
|  | ||||
|     /** | ||||
|      * Stop this system. | ||||
|      * | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result Stop(); | ||||
|  | ||||
|     /** | ||||
|      * Finalize this system. | ||||
|      */ | ||||
|     void Finalize(); | ||||
|  | ||||
|     /** | ||||
|      * Start this system's device session. | ||||
|      */ | ||||
|     void StartSession(); | ||||
|  | ||||
|     /** | ||||
|      * Get this system's id. | ||||
|      */ | ||||
|     size_t GetSessionId() const; | ||||
|  | ||||
|     /** | ||||
|      * Append a new buffer to the device. | ||||
|      * | ||||
|      * @param buffer - New buffer to append. | ||||
|      * @param tag    - Unique tag of the buffer. | ||||
|      * @return True if the buffer was appended, otherwise false. | ||||
|      */ | ||||
|     bool AppendBuffer(const AudioOutBuffer& buffer, u64 tag); | ||||
|  | ||||
|     /** | ||||
|      * Register all appended buffers. | ||||
|      */ | ||||
|     void RegisterBuffers(); | ||||
|  | ||||
|     /** | ||||
|      * Release all registered buffers. | ||||
|      */ | ||||
|     void ReleaseBuffers(); | ||||
|  | ||||
|     /** | ||||
|      * Get all released buffers. | ||||
|      * | ||||
|      * @param tags - Container to be filled with the released buffers' tags. | ||||
|      * @return The number of buffers released. | ||||
|      */ | ||||
|     u32 GetReleasedBuffers(std::span<u64> tags); | ||||
|  | ||||
|     /** | ||||
|      * Flush all appended and registered buffers. | ||||
|      * | ||||
|      * @return True if buffers were successfully flushed, otherwise false. | ||||
|      */ | ||||
|     bool FlushAudioOutBuffers(); | ||||
|  | ||||
|     /** | ||||
|      * Get this system's current channel count. | ||||
|      * | ||||
|      * @return The channel count. | ||||
|      */ | ||||
|     u16 GetChannelCount() const; | ||||
|  | ||||
|     /** | ||||
|      * Get this system's current sample rate. | ||||
|      * | ||||
|      * @return The sample rate. | ||||
|      */ | ||||
|     u32 GetSampleRate() const; | ||||
|  | ||||
|     /** | ||||
|      * Get this system's current sample format. | ||||
|      * | ||||
|      * @return The sample format. | ||||
|      */ | ||||
|     SampleFormat GetSampleFormat() const; | ||||
|  | ||||
|     /** | ||||
|      * Get this system's current state. | ||||
|      * | ||||
|      * @return The current state. | ||||
|      */ | ||||
|     State GetState(); | ||||
|  | ||||
|     /** | ||||
|      * Get this system's name. | ||||
|      * | ||||
|      * @return The system's name. | ||||
|      */ | ||||
|     std::string GetName() const; | ||||
|  | ||||
|     /** | ||||
|      * Get this system's current volume. | ||||
|      * | ||||
|      * @return The system's current volume. | ||||
|      */ | ||||
|     f32 GetVolume() const; | ||||
|  | ||||
|     /** | ||||
|      * Set this system's current volume. | ||||
|      * | ||||
|      * @param The new volume. | ||||
|      */ | ||||
|     void SetVolume(f32 volume); | ||||
|  | ||||
|     /** | ||||
|      * Does the system contain this buffer? | ||||
|      * | ||||
|      * @param tag - Unique tag to search for. | ||||
|      * @return True if the buffer is in the system, otherwise false. | ||||
|      */ | ||||
|     bool ContainsAudioBuffer(u64 tag); | ||||
|  | ||||
|     /** | ||||
|      * Get the maximum number of usable buffers (default 32). | ||||
|      * | ||||
|      * @return The number of buffers. | ||||
|      */ | ||||
|     u32 GetBufferCount(); | ||||
|  | ||||
|     /** | ||||
|      * Get the total number of samples played by this system. | ||||
|      * | ||||
|      * @return The number of samples. | ||||
|      */ | ||||
|     u64 GetPlayedSampleCount() const; | ||||
|  | ||||
| private: | ||||
|     /// Core system | ||||
|     Core::System& system; | ||||
|     /// (Unused) | ||||
|     u32 handle{}; | ||||
|     /// (Unused) | ||||
|     u64 applet_resource_user_id{}; | ||||
|     /// Buffer event, signalled when a buffer is ready | ||||
|     Kernel::KEvent* buffer_event; | ||||
|     /// Session id of this system | ||||
|     size_t session_id{}; | ||||
|     /// Device session for this system | ||||
|     std::unique_ptr<DeviceSession> session; | ||||
|     /// Audio buffers in use by this system | ||||
|     AudioBuffers<BufferCount> buffers{BufferCount}; | ||||
|     /// Sample rate of this system | ||||
|     u32 sample_rate{}; | ||||
|     /// Sample format of this system | ||||
|     SampleFormat sample_format{SampleFormat::PcmInt16}; | ||||
|     /// Channel count of this system | ||||
|     u16 channel_count{}; | ||||
|     /// State of this system | ||||
|     std::atomic<State> state{State::Stopped}; | ||||
|     /// Name of this system | ||||
|     std::string name{}; | ||||
|     /// Volume of this system | ||||
|     f32 volume{1.0f}; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioOut | ||||
							
								
								
									
										118
									
								
								src/audio_core/renderer/adsp/adsp.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/audio_core/renderer/adsp/adsp.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/renderer/adsp/adsp.h" | ||||
| #include "audio_core/renderer/adsp/command_buffer.h" | ||||
| #include "audio_core/sink/sink.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/core.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/core_timing_util.h" | ||||
| #include "core/memory.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer::ADSP { | ||||
|  | ||||
| ADSP::ADSP(Core::System& system_, Sink::Sink& sink_) | ||||
|     : system{system_}, memory{system.Memory()}, sink{sink_} {} | ||||
|  | ||||
| ADSP::~ADSP() { | ||||
|     ClearCommandBuffers(); | ||||
| } | ||||
|  | ||||
| State ADSP::GetState() const { | ||||
|     if (running) { | ||||
|         return State::Started; | ||||
|     } | ||||
|     return State::Stopped; | ||||
| } | ||||
|  | ||||
| AudioRenderer_Mailbox* ADSP::GetRenderMailbox() { | ||||
|     return &render_mailbox; | ||||
| } | ||||
|  | ||||
| void ADSP::ClearRemainCount(const u32 session_id) { | ||||
|     render_mailbox.ClearRemainCount(session_id); | ||||
| } | ||||
|  | ||||
| u64 ADSP::GetSignalledTick() const { | ||||
|     return render_mailbox.GetSignalledTick(); | ||||
| } | ||||
|  | ||||
| u64 ADSP::GetTimeTaken() const { | ||||
|     return render_mailbox.GetRenderTimeTaken(); | ||||
| } | ||||
|  | ||||
| u64 ADSP::GetRenderTimeTaken(const u32 session_id) { | ||||
|     return render_mailbox.GetCommandBuffer(session_id).render_time_taken; | ||||
| } | ||||
|  | ||||
| u32 ADSP::GetRemainCommandCount(const u32 session_id) const { | ||||
|     return render_mailbox.GetRemainCommandCount(session_id); | ||||
| } | ||||
|  | ||||
| void ADSP::SendCommandBuffer(const u32 session_id, CommandBuffer& command_buffer) { | ||||
|     render_mailbox.SetCommandBuffer(session_id, command_buffer); | ||||
| } | ||||
|  | ||||
| u64 ADSP::GetRenderingStartTick(const u32 session_id) { | ||||
|     return render_mailbox.GetSignalledTick() + | ||||
|            render_mailbox.GetCommandBuffer(session_id).render_time_taken; | ||||
| } | ||||
|  | ||||
| bool ADSP::Start() { | ||||
|     if (running) { | ||||
|         return running; | ||||
|     } | ||||
|  | ||||
|     running = true; | ||||
|     systems_active++; | ||||
|     audio_renderer = std::make_unique<AudioRenderer>(system); | ||||
|     audio_renderer->Start(&render_mailbox); | ||||
|     render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_InitializeOK); | ||||
|     if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) { | ||||
|         LOG_ERROR( | ||||
|             Service_Audio, | ||||
|             "Host Audio Renderer -- Failed to receive initialize message response from ADSP!"); | ||||
|     } | ||||
|     return running; | ||||
| } | ||||
|  | ||||
| void ADSP::Stop() { | ||||
|     systems_active--; | ||||
|     if (running && systems_active == 0) { | ||||
|         { | ||||
|             std::scoped_lock l{mailbox_lock}; | ||||
|             render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Shutdown); | ||||
|             if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_Shutdown) { | ||||
|                 LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown " | ||||
|                                          "message response from ADSP!"); | ||||
|             } | ||||
|         } | ||||
|         audio_renderer->Stop(); | ||||
|         running = false; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void ADSP::Signal() { | ||||
|     const auto signalled_tick{system.CoreTiming().GetClockTicks()}; | ||||
|     render_mailbox.SetSignalledTick(signalled_tick); | ||||
|     render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Render); | ||||
| } | ||||
|  | ||||
| void ADSP::Wait() { | ||||
|     std::scoped_lock l{mailbox_lock}; | ||||
|     auto response{render_mailbox.HostWaitMessage()}; | ||||
|     if (response != RenderMessage::AudioRenderer_RenderResponse) { | ||||
|         LOG_ERROR(Service_Audio, "Invalid ADSP response message, expected 0x{:02X}, got 0x{:02X}", | ||||
|                   static_cast<u32>(RenderMessage::AudioRenderer_RenderResponse), | ||||
|                   static_cast<u32>(response)); | ||||
|     } | ||||
|  | ||||
|     ClearCommandBuffers(); | ||||
| } | ||||
|  | ||||
| void ADSP::ClearCommandBuffers() { | ||||
|     render_mailbox.ClearCommandBuffers(); | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer::ADSP | ||||
							
								
								
									
										173
									
								
								src/audio_core/renderer/adsp/adsp.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								src/audio_core/renderer/adsp/adsp.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,173 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
| #include <mutex> | ||||
|  | ||||
| #include "audio_core/renderer/adsp/audio_renderer.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace Core { | ||||
| namespace Memory { | ||||
| class Memory; | ||||
| } | ||||
| class System; | ||||
| } // namespace Core | ||||
|  | ||||
| namespace AudioCore { | ||||
| namespace Sink { | ||||
| class Sink; | ||||
| } | ||||
|  | ||||
| namespace AudioRenderer::ADSP { | ||||
| struct CommandBuffer; | ||||
|  | ||||
| enum class State { | ||||
|     Started, | ||||
|     Stopped, | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Represents the ADSP embedded within the audio sysmodule. | ||||
|  * This is a 32-bit Linux4Tegra kernel from nVidia, which is launched with the sysmodule on boot. | ||||
|  * | ||||
|  * The kernel will run apps you program for it, Nintendo have the following: | ||||
|  * | ||||
|  * Gmix - Responsible for mixing final audio and sending it out to hardware. This is last place all | ||||
|  *        audio samples end up, and we skip it entirely, since we have very different backends and | ||||
|  *        mixing is implicitly handled by the OS (but also due to lack of research/simplicity). | ||||
|  * | ||||
|  * AudioRenderer - Receives command lists generated by the audio render | ||||
|  *                 system, processes them, and sends the samples to Gmix. | ||||
|  * | ||||
|  * OpusDecoder - Contains libopus, and controls processing Opus audio and sends it to Gmix. | ||||
|  *               Not much research done here, TODO if needed. | ||||
|  * | ||||
|  * We only implement the AudioRenderer for now. | ||||
|  * | ||||
|  * Communication for the apps is done through mailboxes, and some shared memory. | ||||
|  */ | ||||
| class ADSP { | ||||
| public: | ||||
|     explicit ADSP(Core::System& system, Sink::Sink& sink); | ||||
|     ~ADSP(); | ||||
|  | ||||
|     /** | ||||
|      * Start the ADSP. | ||||
|      * | ||||
|      * @return True if started or already running, otherwise false. | ||||
|      */ | ||||
|     bool Start(); | ||||
|  | ||||
|     /** | ||||
|      * Stop the ADSP. | ||||
|      * | ||||
|      * @return True if started or already running, otherwise false. | ||||
|      */ | ||||
|     void Stop(); | ||||
|  | ||||
|     /** | ||||
|      * Get the ADSP's state. | ||||
|      * | ||||
|      * @return Started or Stopped. | ||||
|      */ | ||||
|     State GetState() const; | ||||
|  | ||||
|     /** | ||||
|      * Get the AudioRenderer mailbox to communicate with it. | ||||
|      * | ||||
|      * @return The AudioRenderer mailbox. | ||||
|      */ | ||||
|     AudioRenderer_Mailbox* GetRenderMailbox(); | ||||
|  | ||||
|     /** | ||||
|      * Get the tick the ADSP was signalled. | ||||
|      * | ||||
|      * @return The tick the ADSP was signalled. | ||||
|      */ | ||||
|     u64 GetSignalledTick() const; | ||||
|  | ||||
|     /** | ||||
|      * Get the total time it took for the ADSP to run the last command lists (both command lists). | ||||
|      * | ||||
|      * @return The tick the ADSP was signalled. | ||||
|      */ | ||||
|     u64 GetTimeTaken() const; | ||||
|  | ||||
|     /** | ||||
|      * Get the last time a given command list took to run. | ||||
|      * | ||||
|      * @param session_id - The session id to check (0 or 1). | ||||
|      * @return The time it took. | ||||
|      */ | ||||
|     u64 GetRenderTimeTaken(u32 session_id); | ||||
|  | ||||
|     /** | ||||
|      * Clear the remaining command count for a given session. | ||||
|      * | ||||
|      * @param session_id - The session id to check (0 or 1). | ||||
|      */ | ||||
|     void ClearRemainCount(u32 session_id); | ||||
|  | ||||
|     /** | ||||
|      * Get the remaining number of commands left to process for a command list. | ||||
|      * | ||||
|      * @param session_id - The session id to check (0 or 1). | ||||
|      * @return The number of commands remaining. | ||||
|      */ | ||||
|     u32 GetRemainCommandCount(u32 session_id) const; | ||||
|  | ||||
|     /** | ||||
|      * Get the last tick a command list started processing. | ||||
|      * | ||||
|      * @param session_id - The session id to check (0 or 1). | ||||
|      * @return The last tick the given command list started. | ||||
|      */ | ||||
|     u64 GetRenderingStartTick(u32 session_id); | ||||
|  | ||||
|     /** | ||||
|      * Set a command buffer to be processed. | ||||
|      * | ||||
|      * @param session_id     - The session id to check (0 or 1). | ||||
|      * @param command_buffer - The command buffer to process. | ||||
|      */ | ||||
|     void SendCommandBuffer(u32 session_id, CommandBuffer& command_buffer); | ||||
|  | ||||
|     /** | ||||
|      * Clear the command buffers (does not clear the time taken or the remaining command count) | ||||
|      */ | ||||
|     void ClearCommandBuffers(); | ||||
|  | ||||
|     /** | ||||
|      * Signal the AudioRenderer to begin processing. | ||||
|      */ | ||||
|     void Signal(); | ||||
|  | ||||
|     /** | ||||
|      * Wait for the AudioRenderer to finish processing. | ||||
|      */ | ||||
|     void Wait(); | ||||
|  | ||||
| private: | ||||
|     /// Core system | ||||
|     Core::System& system; | ||||
|     /// Core memory | ||||
|     Core::Memory::Memory& memory; | ||||
|     /// Number of systems active, used to prevent accidental shutdowns | ||||
|     u8 systems_active{0}; | ||||
|     /// ADSP running state | ||||
|     std::atomic<bool> running{false}; | ||||
|     /// Output sink used by the ADSP | ||||
|     Sink::Sink& sink; | ||||
|     /// AudioRenderer app | ||||
|     std::unique_ptr<AudioRenderer> audio_renderer{}; | ||||
|     /// Communication for the AudioRenderer | ||||
|     AudioRenderer_Mailbox render_mailbox{}; | ||||
|     /// Mailbox lock ffor the render mailbox | ||||
|     std::mutex mailbox_lock; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioRenderer::ADSP | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										226
									
								
								src/audio_core/renderer/adsp/audio_renderer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								src/audio_core/renderer/adsp/audio_renderer.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,226 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <array> | ||||
| #include <chrono> | ||||
|  | ||||
| #include "audio_core/audio_core.h" | ||||
| #include "audio_core/common/common.h" | ||||
| #include "audio_core/renderer/adsp/audio_renderer.h" | ||||
| #include "audio_core/sink/sink.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/microprofile.h" | ||||
| #include "common/thread.h" | ||||
| #include "core/core.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/core_timing_util.h" | ||||
|  | ||||
| MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97)); | ||||
|  | ||||
| namespace AudioCore::AudioRenderer::ADSP { | ||||
|  | ||||
| void AudioRenderer_Mailbox::HostSendMessage(RenderMessage message_) { | ||||
|     adsp_messages.enqueue(message_); | ||||
|     adsp_event.Set(); | ||||
| } | ||||
|  | ||||
| RenderMessage AudioRenderer_Mailbox::HostWaitMessage() { | ||||
|     host_event.Wait(); | ||||
|     RenderMessage msg{RenderMessage::Invalid}; | ||||
|     if (!host_messages.try_dequeue(msg)) { | ||||
|         LOG_ERROR(Service_Audio, "Failed to dequeue host message!"); | ||||
|     } | ||||
|     return msg; | ||||
| } | ||||
|  | ||||
| void AudioRenderer_Mailbox::ADSPSendMessage(const RenderMessage message_) { | ||||
|     host_messages.enqueue(message_); | ||||
|     host_event.Set(); | ||||
| } | ||||
|  | ||||
| RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() { | ||||
|     adsp_event.Wait(); | ||||
|     RenderMessage msg{RenderMessage::Invalid}; | ||||
|     if (!adsp_messages.try_dequeue(msg)) { | ||||
|         LOG_ERROR(Service_Audio, "Failed to dequeue ADSP message!"); | ||||
|     } | ||||
|     return msg; | ||||
| } | ||||
|  | ||||
| CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) { | ||||
|     return command_buffers[session_id]; | ||||
| } | ||||
|  | ||||
| void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, CommandBuffer& buffer) { | ||||
|     command_buffers[session_id] = buffer; | ||||
| } | ||||
|  | ||||
| u64 AudioRenderer_Mailbox::GetRenderTimeTaken() const { | ||||
|     return command_buffers[0].render_time_taken + command_buffers[1].render_time_taken; | ||||
| } | ||||
|  | ||||
| u64 AudioRenderer_Mailbox::GetSignalledTick() const { | ||||
|     return signalled_tick; | ||||
| } | ||||
|  | ||||
| void AudioRenderer_Mailbox::SetSignalledTick(const u64 tick) { | ||||
|     signalled_tick = tick; | ||||
| } | ||||
|  | ||||
| void AudioRenderer_Mailbox::ClearRemainCount(const u32 session_id) { | ||||
|     command_buffers[session_id].remaining_command_count = 0; | ||||
| } | ||||
|  | ||||
| u32 AudioRenderer_Mailbox::GetRemainCommandCount(const u32 session_id) const { | ||||
|     return command_buffers[session_id].remaining_command_count; | ||||
| } | ||||
|  | ||||
| void AudioRenderer_Mailbox::ClearCommandBuffers() { | ||||
|     command_buffers[0].buffer = 0; | ||||
|     command_buffers[0].size = 0; | ||||
|     command_buffers[0].reset_buffers = false; | ||||
|     command_buffers[1].buffer = 0; | ||||
|     command_buffers[1].size = 0; | ||||
|     command_buffers[1].reset_buffers = false; | ||||
| } | ||||
|  | ||||
| AudioRenderer::AudioRenderer(Core::System& system_) | ||||
|     : system{system_}, sink{system.AudioCore().GetOutputSink()} { | ||||
|     CreateSinkStreams(); | ||||
| } | ||||
|  | ||||
| AudioRenderer::~AudioRenderer() { | ||||
|     Stop(); | ||||
|     for (auto& stream : streams) { | ||||
|         if (stream) { | ||||
|             sink.CloseStream(stream); | ||||
|         } | ||||
|         stream = nullptr; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) { | ||||
|     if (running) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     mailbox = mailbox_; | ||||
|     thread = std::thread(&AudioRenderer::ThreadFunc, this); | ||||
|     for (auto& stream : streams) { | ||||
|         stream->Start(); | ||||
|     } | ||||
|     running = true; | ||||
| } | ||||
|  | ||||
| void AudioRenderer::Stop() { | ||||
|     if (!running) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     for (auto& stream : streams) { | ||||
|         stream->Stop(); | ||||
|     } | ||||
|     thread.join(); | ||||
|     running = false; | ||||
| } | ||||
|  | ||||
| void AudioRenderer::CreateSinkStreams() { | ||||
|     u32 channels{sink.GetDeviceChannels()}; | ||||
|     for (u32 i = 0; i < MaxRendererSessions; i++) { | ||||
|         std::string name{fmt::format("ADSP_RenderStream-{}", i)}; | ||||
|         streams[i] = | ||||
|             sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void AudioRenderer::ThreadFunc() { | ||||
|     constexpr char name[]{"yuzu:AudioRenderer"}; | ||||
|     MicroProfileOnThreadCreate(name); | ||||
|     Common::SetCurrentThreadName(name); | ||||
|     Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical); | ||||
|     if (mailbox->ADSPWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) { | ||||
|         LOG_ERROR(Service_Audio, | ||||
|                   "ADSP Audio Renderer -- Failed to receive initialize message from host!"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK); | ||||
|  | ||||
|     constexpr u64 max_process_time{2'304'000ULL}; | ||||
|  | ||||
|     while (true) { | ||||
|         auto message{mailbox->ADSPWaitMessage()}; | ||||
|         switch (message) { | ||||
|         case RenderMessage::AudioRenderer_Shutdown: | ||||
|             mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_Shutdown); | ||||
|             return; | ||||
|  | ||||
|         case RenderMessage::AudioRenderer_Render: { | ||||
|             std::array<bool, MaxRendererSessions> buffers_reset{}; | ||||
|             std::array<u64, MaxRendererSessions> render_times_taken{}; | ||||
|             const auto start_time{system.CoreTiming().GetClockTicks()}; | ||||
|  | ||||
|             for (u32 index = 0; index < 2; index++) { | ||||
|                 auto& command_buffer{mailbox->GetCommandBuffer(index)}; | ||||
|                 auto& command_list_processor{command_list_processors[index]}; | ||||
|  | ||||
|                 // Check this buffer is valid, as it may not be used. | ||||
|                 if (command_buffer.buffer != 0) { | ||||
|                     // If there are no remaining commands (from the previous list), | ||||
|                     // this is a new command list, initalize it. | ||||
|                     if (command_buffer.remaining_command_count == 0) { | ||||
|                         command_list_processor.Initialize(system, command_buffer.buffer, | ||||
|                                                           command_buffer.size, streams[index]); | ||||
|                     } | ||||
|  | ||||
|                     if (command_buffer.reset_buffers && !buffers_reset[index]) { | ||||
|                         streams[index]->ClearQueue(); | ||||
|                         buffers_reset[index] = true; | ||||
|                     } | ||||
|  | ||||
|                     u64 max_time{max_process_time}; | ||||
|                     if (index == 1 && command_buffer.applet_resource_user_id == | ||||
|                                           mailbox->GetCommandBuffer(0).applet_resource_user_id) { | ||||
|                         max_time = max_process_time - | ||||
|                                    Core::Timing::CyclesToNs(render_times_taken[0]).count(); | ||||
|                         if (render_times_taken[0] > max_process_time) { | ||||
|                             max_time = 0; | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     max_time = std::min(command_buffer.time_limit, max_time); | ||||
|                     command_list_processor.SetProcessTimeMax(max_time); | ||||
|  | ||||
|                     // Process the command list | ||||
|                     { | ||||
|                         MICROPROFILE_SCOPE(Audio_Renderer); | ||||
|                         render_times_taken[index] = | ||||
|                             command_list_processor.Process(index) - start_time; | ||||
|                     } | ||||
|  | ||||
|                     if (index == 0) { | ||||
|                         auto stream{command_list_processor.GetOutputSinkStream()}; | ||||
|                         system.AudioCore().SetStreamQueue(stream->GetQueueSize()); | ||||
|                     } | ||||
|  | ||||
|                     const auto end_time{system.CoreTiming().GetClockTicks()}; | ||||
|  | ||||
|                     command_buffer.remaining_command_count = | ||||
|                         command_list_processor.GetRemainingCommandCount(); | ||||
|                     command_buffer.render_time_taken = end_time - start_time; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse); | ||||
|         } break; | ||||
|  | ||||
|         default: | ||||
|             LOG_WARNING(Service_Audio, | ||||
|                         "ADSP AudioRenderer received an invalid message, msg={:02X}!", | ||||
|                         static_cast<u32>(message)); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer::ADSP | ||||
							
								
								
									
										203
									
								
								src/audio_core/renderer/adsp/audio_renderer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								src/audio_core/renderer/adsp/audio_renderer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,203 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <memory> | ||||
| #include <thread> | ||||
|  | ||||
| #include "audio_core/renderer/adsp/command_buffer.h" | ||||
| #include "audio_core/renderer/adsp/command_list_processor.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/reader_writer_queue.h" | ||||
| #include "common/thread.h" | ||||
|  | ||||
| namespace Core { | ||||
| namespace Timing { | ||||
| struct EventType; | ||||
| } | ||||
| class System; | ||||
| } // namespace Core | ||||
|  | ||||
| namespace AudioCore { | ||||
| namespace Sink { | ||||
| class Sink; | ||||
| } | ||||
|  | ||||
| namespace AudioRenderer::ADSP { | ||||
|  | ||||
| enum class RenderMessage { | ||||
|     /* 0x00 */ Invalid, | ||||
|     /* 0x01 */ AudioRenderer_MapUnmap_Map, | ||||
|     /* 0x02 */ AudioRenderer_MapUnmap_MapResponse, | ||||
|     /* 0x03 */ AudioRenderer_MapUnmap_Unmap, | ||||
|     /* 0x04 */ AudioRenderer_MapUnmap_UnmapResponse, | ||||
|     /* 0x05 */ AudioRenderer_MapUnmap_InvalidateCache, | ||||
|     /* 0x06 */ AudioRenderer_MapUnmap_InvalidateCacheResponse, | ||||
|     /* 0x07 */ AudioRenderer_MapUnmap_Shutdown, | ||||
|     /* 0x08 */ AudioRenderer_MapUnmap_ShutdownResponse, | ||||
|     /* 0x16 */ AudioRenderer_InitializeOK = 0x16, | ||||
|     /* 0x20 */ AudioRenderer_RenderResponse = 0x20, | ||||
|     /* 0x2A */ AudioRenderer_Render = 0x2A, | ||||
|     /* 0x34 */ AudioRenderer_Shutdown = 0x34, | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * A mailbox for the AudioRenderer, allowing communication between the host and the AudioRenderer | ||||
|  * running on the ADSP. | ||||
|  */ | ||||
| class AudioRenderer_Mailbox { | ||||
| public: | ||||
|     /** | ||||
|      * Send a message from the host to the AudioRenderer. | ||||
|      * | ||||
|      * @param message_ - The message to send to the AudioRenderer. | ||||
|      */ | ||||
|     void HostSendMessage(RenderMessage message); | ||||
|  | ||||
|     /** | ||||
|      * Host wait for a message from the AudioRenderer. | ||||
|      * | ||||
|      * @return The message returned from the AudioRenderer. | ||||
|      */ | ||||
|     RenderMessage HostWaitMessage(); | ||||
|  | ||||
|     /** | ||||
|      * Send a message from the AudioRenderer to the host. | ||||
|      * | ||||
|      * @param message_ - The message to send to the host. | ||||
|      */ | ||||
|     void ADSPSendMessage(RenderMessage message); | ||||
|  | ||||
|     /** | ||||
|      * AudioRenderer wait for a message from the host. | ||||
|      * | ||||
|      * @return The message returned from the AudioRenderer. | ||||
|      */ | ||||
|     RenderMessage ADSPWaitMessage(); | ||||
|  | ||||
|     /** | ||||
|      * Get the command buffer with the given session id (0 or 1). | ||||
|      * | ||||
|      * @param session_id - The session id to get (0 or 1). | ||||
|      * @return The command buffer. | ||||
|      */ | ||||
|     CommandBuffer& GetCommandBuffer(s32 session_id); | ||||
|  | ||||
|     /** | ||||
|      * Set the command buffer with the given session id (0 or 1). | ||||
|      * | ||||
|      * @param session_id - The session id to get (0 or 1). | ||||
|      * @param buffer     - The command buffer to set. | ||||
|      */ | ||||
|     void SetCommandBuffer(u32 session_id, CommandBuffer& buffer); | ||||
|  | ||||
|     /** | ||||
|      * Get the total render time taken for the last command lists sent. | ||||
|      * | ||||
|      * @return Total render time taken for the last command lists. | ||||
|      */ | ||||
|     u64 GetRenderTimeTaken() const; | ||||
|  | ||||
|     /** | ||||
|      * Get the tick the AudioRenderer was signalled. | ||||
|      * | ||||
|      * @return The tick the AudioRenderer was signalled. | ||||
|      */ | ||||
|     u64 GetSignalledTick() const; | ||||
|  | ||||
|     /** | ||||
|      * Set the tick the AudioRenderer was signalled. | ||||
|      * | ||||
|      * @param tick - The tick the AudioRenderer was signalled. | ||||
|      */ | ||||
|     void SetSignalledTick(u64 tick); | ||||
|  | ||||
|     /** | ||||
|      * Clear the remaining command count. | ||||
|      * | ||||
|      * @param session_id - Index for which command list to clear (0 or 1). | ||||
|      */ | ||||
|     void ClearRemainCount(u32 session_id); | ||||
|  | ||||
|     /** | ||||
|      * Get the remaining command count for a given command list. | ||||
|      * | ||||
|      * @param session_id - Index for which command list to clear (0 or 1). | ||||
|      * @return The remaining command count. | ||||
|      */ | ||||
|     u32 GetRemainCommandCount(u32 session_id) const; | ||||
|  | ||||
|     /** | ||||
|      * Clear the command buffers (does not clear the time taken or the remaining command count). | ||||
|      */ | ||||
|     void ClearCommandBuffers(); | ||||
|  | ||||
| private: | ||||
|     /// Host signalling event | ||||
|     Common::Event host_event{}; | ||||
|     /// AudioRenderer signalling event | ||||
|     Common::Event adsp_event{}; | ||||
|     /// Host message queue | ||||
|  | ||||
|     Common::ReaderWriterQueue<RenderMessage> host_messages{}; | ||||
|     /// AudioRenderer message queue | ||||
|  | ||||
|     Common::ReaderWriterQueue<RenderMessage> adsp_messages{}; | ||||
|     /// Command buffers | ||||
|  | ||||
|     std::array<CommandBuffer, MaxRendererSessions> command_buffers{}; | ||||
|     /// Tick the AudioRnederer was signalled | ||||
|     u64 signalled_tick{}; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * The AudioRenderer application running on the ADSP. | ||||
|  */ | ||||
| class AudioRenderer { | ||||
| public: | ||||
|     explicit AudioRenderer(Core::System& system); | ||||
|     ~AudioRenderer(); | ||||
|  | ||||
|     /** | ||||
|      * Start the AudioRenderer. | ||||
|      * | ||||
|      * @param The mailbox to use for this session. | ||||
|      */ | ||||
|     void Start(AudioRenderer_Mailbox* mailbox); | ||||
|  | ||||
|     /** | ||||
|      * Stop the AudioRenderer. | ||||
|      */ | ||||
|     void Stop(); | ||||
|  | ||||
| private: | ||||
|     /** | ||||
|      * Main AudioRenderer thread, responsible for processing the command lists. | ||||
|      */ | ||||
|     void ThreadFunc(); | ||||
|  | ||||
|     /** | ||||
|      * Creates the streams which will receive the processed samples. | ||||
|      */ | ||||
|     void CreateSinkStreams(); | ||||
|  | ||||
|     /// Core system | ||||
|     Core::System& system; | ||||
|     /// Main thread | ||||
|     std::thread thread{}; | ||||
|     /// The current state | ||||
|     std::atomic<bool> running{}; | ||||
|     /// The active mailbox | ||||
|     AudioRenderer_Mailbox* mailbox{}; | ||||
|     /// The command lists to process | ||||
|     std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{}; | ||||
|     /// The output sink the AudioRenderer will use | ||||
|     Sink::Sink& sink; | ||||
|     /// The streams which will receive the processed samples | ||||
|     std::array<Sink::SinkStream*, MaxRendererSessions> streams; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioRenderer::ADSP | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										21
									
								
								src/audio_core/renderer/adsp/command_buffer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/audio_core/renderer/adsp/command_buffer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "audio_core/common/common.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer::ADSP { | ||||
|  | ||||
| struct CommandBuffer { | ||||
|     CpuAddr buffer; | ||||
|     u64 size; | ||||
|     u64 time_limit; | ||||
|     u32 remaining_command_count; | ||||
|     bool reset_buffers; | ||||
|     u64 applet_resource_user_id; | ||||
|     u64 render_time_taken; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer::ADSP | ||||
							
								
								
									
										109
									
								
								src/audio_core/renderer/adsp/command_list_processor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/audio_core/renderer/adsp/command_list_processor.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| #include "audio_core/renderer/adsp/command_list_processor.h" | ||||
| #include "audio_core/renderer/command/command_list_header.h" | ||||
| #include "audio_core/renderer/command/commands.h" | ||||
| #include "common/settings.h" | ||||
| #include "core/core.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/core_timing_util.h" | ||||
| #include "core/memory.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer::ADSP { | ||||
|  | ||||
| void CommandListProcessor::Initialize(Core::System& system_, CpuAddr buffer, u64 size, | ||||
|                                       Sink::SinkStream* stream_) { | ||||
|     system = &system_; | ||||
|     memory = &system->Memory(); | ||||
|     stream = stream_; | ||||
|     header = reinterpret_cast<CommandListHeader*>(buffer); | ||||
|     commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader)); | ||||
|     commands_buffer_size = size; | ||||
|     command_count = header->command_count; | ||||
|     sample_count = header->sample_count; | ||||
|     target_sample_rate = header->sample_rate; | ||||
|     mix_buffers = header->samples_buffer; | ||||
|     buffer_count = header->buffer_count; | ||||
|     processed_command_count = 0; | ||||
| } | ||||
|  | ||||
| void CommandListProcessor::SetProcessTimeMax(const u64 time) { | ||||
|     max_process_time = time; | ||||
| } | ||||
|  | ||||
| u32 CommandListProcessor::GetRemainingCommandCount() const { | ||||
|     return command_count - processed_command_count; | ||||
| } | ||||
|  | ||||
| void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) { | ||||
|     commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader)); | ||||
|     commands_buffer_size = size; | ||||
| } | ||||
|  | ||||
| Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const { | ||||
|     return stream; | ||||
| } | ||||
|  | ||||
| u64 CommandListProcessor::Process(u32 session_id) { | ||||
|     const auto start_time_{system->CoreTiming().GetClockTicks()}; | ||||
|     const auto command_base{CpuAddr(commands)}; | ||||
|  | ||||
|     if (processed_command_count > 0) { | ||||
|         current_processing_time += start_time_ - end_time; | ||||
|     } else { | ||||
|         start_time = start_time_; | ||||
|         current_processing_time = 0; | ||||
|     } | ||||
|  | ||||
|     std::string dump{fmt::format("\nSession {}\n", session_id)}; | ||||
|  | ||||
|     for (u32 index = 0; index < command_count; index++) { | ||||
|         auto& command{*reinterpret_cast<ICommand*>(commands)}; | ||||
|  | ||||
|         if (command.magic != 0xCAFEBABE) { | ||||
|             LOG_ERROR(Service_Audio, "Command has invalid magic! Expected 0xCAFEBABE, got {:08X}", | ||||
|                       command.magic); | ||||
|             return system->CoreTiming().GetClockTicks() - start_time_; | ||||
|         } | ||||
|  | ||||
|         auto current_offset{CpuAddr(commands) - command_base}; | ||||
|  | ||||
|         if (current_offset + command.size > commands_buffer_size) { | ||||
|             LOG_ERROR(Service_Audio, | ||||
|                       "Command exceeded command buffer, buffer size {:08X}, command ends at {:08X}", | ||||
|                       commands_buffer_size, | ||||
|                       CpuAddr(commands) + command.size - sizeof(CommandListHeader)); | ||||
|             return system->CoreTiming().GetClockTicks() - start_time_; | ||||
|         } | ||||
|  | ||||
|         if (Settings::values.dump_audio_commands) { | ||||
|             command.Dump(*this, dump); | ||||
|         } | ||||
|  | ||||
|         if (!command.Verify(*this)) { | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         if (command.enabled) { | ||||
|             command.Process(*this); | ||||
|         } else { | ||||
|             dump += fmt::format("\tDisabled!\n"); | ||||
|         } | ||||
|  | ||||
|         processed_command_count++; | ||||
|         commands += command.size; | ||||
|     } | ||||
|  | ||||
|     if (Settings::values.dump_audio_commands && dump != last_dump) { | ||||
|         LOG_WARNING(Service_Audio, "{}", dump); | ||||
|         last_dump = dump; | ||||
|     } | ||||
|  | ||||
|     end_time = system->CoreTiming().GetClockTicks(); | ||||
|     return end_time - start_time_; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer::ADSP | ||||
							
								
								
									
										118
									
								
								src/audio_core/renderer/adsp/command_list_processor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/audio_core/renderer/adsp/command_list_processor.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <span> | ||||
|  | ||||
| #include "audio_core/common/common.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace Core { | ||||
| namespace Memory { | ||||
| class Memory; | ||||
| } | ||||
| class System; | ||||
| } // namespace Core | ||||
|  | ||||
| namespace AudioCore { | ||||
| namespace Sink { | ||||
| class SinkStream; | ||||
| } | ||||
|  | ||||
| namespace AudioRenderer { | ||||
| struct CommandListHeader; | ||||
|  | ||||
| namespace ADSP { | ||||
|  | ||||
| /** | ||||
|  * A processor for command lists given to the AudioRenderer. | ||||
|  */ | ||||
| class CommandListProcessor { | ||||
| public: | ||||
|     /** | ||||
|      * Initialize the processor. | ||||
|      * | ||||
|      * @param system_ - The core system. | ||||
|      * @param buffer  - The command buffer to process. | ||||
|      * @param size    - The size of the buffer. | ||||
|      * @param stream_ - The stream to be used for sending the samples. | ||||
|      */ | ||||
|     void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream); | ||||
|  | ||||
|     /** | ||||
|      * Set the maximum processing time for this command list. | ||||
|      * | ||||
|      * @param time - The maximum process time. | ||||
|      */ | ||||
|     void SetProcessTimeMax(u64 time); | ||||
|  | ||||
|     /** | ||||
|      * Get the remaining command count for this list. | ||||
|      * | ||||
|      * @return The remaining command count. | ||||
|      */ | ||||
|     u32 GetRemainingCommandCount() const; | ||||
|  | ||||
|     /** | ||||
|      * Set the command buffer. | ||||
|      * | ||||
|      * @param buffer - The buffer to use. | ||||
|      * @param size   - The size of the buffer. | ||||
|      */ | ||||
|     void SetBuffer(CpuAddr buffer, u64 size); | ||||
|  | ||||
|     /** | ||||
|      * Get the stream for this command list. | ||||
|      * | ||||
|      * @return The stream associated with this command list. | ||||
|      */ | ||||
|     Sink::SinkStream* GetOutputSinkStream() const; | ||||
|  | ||||
|     /** | ||||
|      * Process the command list. | ||||
|      * | ||||
|      * @param index - Index of the current command list. | ||||
|      * @return The time taken to process. | ||||
|      */ | ||||
|     u64 Process(u32 session_id); | ||||
|  | ||||
|     /// Core system | ||||
|     Core::System* system{}; | ||||
|     /// Core memory | ||||
|     Core::Memory::Memory* memory{}; | ||||
|     /// Stream for the processed samples | ||||
|     Sink::SinkStream* stream{}; | ||||
|     /// Header info for this command list | ||||
|     CommandListHeader* header{}; | ||||
|     /// The command buffer | ||||
|     u8* commands{}; | ||||
|     /// The command buffer size | ||||
|     u64 commands_buffer_size{}; | ||||
|     /// The maximum processing time alloted | ||||
|     u64 max_process_time{}; | ||||
|     /// The number of commands in the buffer | ||||
|     u32 command_count{}; | ||||
|     /// The target sample count for output | ||||
|     u32 sample_count{}; | ||||
|     /// The target sample rate for output | ||||
|     u32 target_sample_rate{}; | ||||
|     /// The mixing buffers used by the commands | ||||
|     std::span<s32> mix_buffers{}; | ||||
|     /// The number of mix buffers | ||||
|     u32 buffer_count{}; | ||||
|     /// The number of processed commands so far | ||||
|     u32 processed_command_count{}; | ||||
|     /// The processing start time of this list | ||||
|     u64 start_time{}; | ||||
|     /// The current processing time for this list | ||||
|     u64 current_processing_time{}; | ||||
|     /// The end processing time for this list | ||||
|     u64 end_time{}; | ||||
|     /// Last command list string generated, used for dumping audio commands to console | ||||
|     std::string last_dump{}; | ||||
| }; | ||||
|  | ||||
| } // namespace ADSP | ||||
| } // namespace AudioRenderer | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										52
									
								
								src/audio_core/renderer/audio_device.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/audio_core/renderer/audio_device.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/audio_core.h" | ||||
| #include "audio_core/common/feature_support.h" | ||||
| #include "audio_core/renderer/audio_device.h" | ||||
| #include "audio_core/sink/sink.h" | ||||
| #include "core/core.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
|  | ||||
| AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_, | ||||
|                          const u32 revision) | ||||
|     : output_sink{system.AudioCore().GetOutputSink()}, | ||||
|       applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {} | ||||
|  | ||||
| u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, | ||||
|                                      const size_t max_count) { | ||||
|     std::span<AudioDeviceName> names{}; | ||||
|  | ||||
|     if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) { | ||||
|         names = usb_device_names; | ||||
|     } else { | ||||
|         names = device_names; | ||||
|     } | ||||
|  | ||||
|     u32 out_count{static_cast<u32>(std::min(max_count, names.size()))}; | ||||
|     for (u32 i = 0; i < out_count; i++) { | ||||
|         out_buffer.push_back(names[i]); | ||||
|     } | ||||
|     return out_count; | ||||
| } | ||||
|  | ||||
| u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, | ||||
|                                            const size_t max_count) { | ||||
|     u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))}; | ||||
|  | ||||
|     for (u32 i = 0; i < out_count; i++) { | ||||
|         out_buffer.push_back(output_device_names[i]); | ||||
|     } | ||||
|     return out_count; | ||||
| } | ||||
|  | ||||
| void AudioDevice::SetDeviceVolumes(const f32 volume) { | ||||
|     output_sink.SetDeviceVolume(volume); | ||||
| } | ||||
|  | ||||
| f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) { | ||||
|     return output_sink.GetDeviceVolume(); | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										88
									
								
								src/audio_core/renderer/audio_device.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/audio_core/renderer/audio_device.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <span> | ||||
|  | ||||
| #include "audio_core/audio_render_manager.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace AudioCore { | ||||
| namespace Sink { | ||||
| class Sink; | ||||
| } | ||||
|  | ||||
| namespace AudioRenderer { | ||||
| /** | ||||
|  * An interface to an output audio device available to the Switch. | ||||
|  */ | ||||
| class AudioDevice { | ||||
| public: | ||||
|     struct AudioDeviceName { | ||||
|         std::array<char, 0x100> name; | ||||
|  | ||||
|         AudioDeviceName(const char* name_) { | ||||
|             std::strncpy(name.data(), name_, name.size()); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     std::array<AudioDeviceName, 4> usb_device_names{"AudioStereoJackOutput", | ||||
|                                                     "AudioBuiltInSpeakerOutput", "AudioTvOutput", | ||||
|                                                     "AudioUsbDeviceOutput"}; | ||||
|     std::array<AudioDeviceName, 3> device_names{"AudioStereoJackOutput", | ||||
|                                                 "AudioBuiltInSpeakerOutput", "AudioTvOutput"}; | ||||
|     std::array<AudioDeviceName, 3> output_device_names{"AudioBuiltInSpeakerOutput", "AudioTvOutput", | ||||
|                                                        "AudioExternalOutput"}; | ||||
|  | ||||
|     explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision); | ||||
|  | ||||
|     /** | ||||
|      * Get a list of the available output devices. | ||||
|      * | ||||
|      * @param out_buffer - Output buffer to write the available device names. | ||||
|      * @param max_count  - Maximum number of devices to write (count of out_buffer). | ||||
|      * @return Number of device names written. | ||||
|      */ | ||||
|     u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); | ||||
|  | ||||
|     /** | ||||
|      * Get a list of the available output devices. | ||||
|      * Different to above somehow... | ||||
|      * | ||||
|      * @param out_buffer - Output buffer to write the available device names. | ||||
|      * @param max_count  - Maximum number of devices to write (count of out_buffer). | ||||
|      * @return Number of device names written. | ||||
|      */ | ||||
|     u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); | ||||
|  | ||||
|     /** | ||||
|      * Set the volume of all streams in the backend sink. | ||||
|      * | ||||
|      * @param volume - Volume to set. | ||||
|      */ | ||||
|     void SetDeviceVolumes(f32 volume); | ||||
|  | ||||
|     /** | ||||
|      * Get the volume for a given device name. | ||||
|      * Note: This is not fully implemented, we only assume 1 device for all streams. | ||||
|      * | ||||
|      * @param name - Name of the device to check. Unused. | ||||
|      * @return Volume of the device. | ||||
|      */ | ||||
|     f32 GetDeviceVolume(std::string_view name); | ||||
|  | ||||
| private: | ||||
|     /// Backend output sink for the device | ||||
|     Sink::Sink& output_sink; | ||||
|     /// Resource id this device is used for | ||||
|     const u64 applet_resource_user_id; | ||||
|     /// User audio renderer revision | ||||
|     const u32 user_revision; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioRenderer | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										67
									
								
								src/audio_core/renderer/audio_renderer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/audio_core/renderer/audio_renderer.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/audio_render_manager.h" | ||||
| #include "audio_core/common/audio_renderer_parameter.h" | ||||
| #include "audio_core/renderer/audio_renderer.h" | ||||
| #include "audio_core/renderer/system_manager.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hle/kernel/k_transfer_memory.h" | ||||
| #include "core/hle/service/audio/errors.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
|  | ||||
| Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event) | ||||
|     : core{system_}, manager{manager_}, system{system_, rendered_event} {} | ||||
|  | ||||
| Result Renderer::Initialize(const AudioRendererParameterInternal& params, | ||||
|                             Kernel::KTransferMemory* transfer_memory, | ||||
|                             const u64 transfer_memory_size, const u32 process_handle, | ||||
|                             const u64 applet_resource_user_id, const s32 session_id) { | ||||
|     if (params.execution_mode == ExecutionMode::Auto) { | ||||
|         if (!manager.AddSystem(system)) { | ||||
|             LOG_ERROR(Service_Audio, | ||||
|                       "Both Audio Render sessions are in use, cannot create any more"); | ||||
|             return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED; | ||||
|         } | ||||
|         system_registered = true; | ||||
|     } | ||||
|  | ||||
|     initialized = true; | ||||
|     system.Initialize(params, transfer_memory, transfer_memory_size, process_handle, | ||||
|                       applet_resource_user_id, session_id); | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| void Renderer::Finalize() { | ||||
|     auto session_id{system.GetSessionId()}; | ||||
|  | ||||
|     system.Finalize(); | ||||
|  | ||||
|     if (system_registered) { | ||||
|         manager.RemoveSystem(system); | ||||
|         system_registered = false; | ||||
|     } | ||||
|  | ||||
|     manager.ReleaseSessionId(session_id); | ||||
| } | ||||
|  | ||||
| System& Renderer::GetSystem() { | ||||
|     return system; | ||||
| } | ||||
|  | ||||
| void Renderer::Start() { | ||||
|     system.Start(); | ||||
| } | ||||
|  | ||||
| void Renderer::Stop() { | ||||
|     system.Stop(); | ||||
| } | ||||
|  | ||||
| Result Renderer::RequestUpdate(std::span<const u8> input, std::span<u8> performance, | ||||
|                                std::span<u8> output) { | ||||
|     return system.Update(input, performance, output); | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										97
									
								
								src/audio_core/renderer/audio_renderer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/audio_core/renderer/audio_renderer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <span> | ||||
|  | ||||
| #include "audio_core/renderer/system.h" | ||||
| #include "core/hle/service/audio/errors.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| namespace Kernel { | ||||
| class KTransferMemory; | ||||
| } | ||||
|  | ||||
| namespace AudioCore { | ||||
| struct AudioRendererParameterInternal; | ||||
|  | ||||
| namespace AudioRenderer { | ||||
| class Manager; | ||||
|  | ||||
| /** | ||||
|  * Audio Renderer, wraps the main audio system and is mainly responsible for handling service calls. | ||||
|  */ | ||||
| class Renderer { | ||||
| public: | ||||
|     explicit Renderer(Core::System& system, Manager& manager, Kernel::KEvent* rendered_event); | ||||
|  | ||||
|     /** | ||||
|      * Initialize the renderer. | ||||
|      * Registers the system with the AudioRenderer::Manager, allocates workbuffers and initializes | ||||
|      * everything to a default state. | ||||
|      * | ||||
|      * @param params                  - Input parameters to initialize the system with. | ||||
|      * @param transfer_memory         - Game-supplied memory for all workbuffers. Unused. | ||||
|      * @param transfer_memory_size    - Size of the transfer memory. Unused. | ||||
|      * @param process_handle          - Process handle, also used for memory. Unused. | ||||
|      * @param applet_resource_user_id - Applet id for this renderer. Unused. | ||||
|      * @param session_id              - Session id of this renderer. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result Initialize(const AudioRendererParameterInternal& params, | ||||
|                       Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size, | ||||
|                       u32 process_handle, u64 applet_resource_user_id, s32 session_id); | ||||
|  | ||||
|     /** | ||||
|      * Finalize the renderer for shutdown. | ||||
|      */ | ||||
|     void Finalize(); | ||||
|  | ||||
|     /** | ||||
|      * Get the renderer's system. | ||||
|      * | ||||
|      * @return Reference to the system. | ||||
|      */ | ||||
|     System& GetSystem(); | ||||
|  | ||||
|     /** | ||||
|      * Start the renderer. | ||||
|      */ | ||||
|     void Start(); | ||||
|  | ||||
|     /** | ||||
|      * Stop the renderer. | ||||
|      */ | ||||
|     void Stop(); | ||||
|  | ||||
|     /** | ||||
|      * Update the audio renderer with new information. | ||||
|      * Called via RequestUpdate from the AudRen:U service. | ||||
|      * | ||||
|      * @param input       - Input buffer containing the new data. | ||||
|      * @param performance - Optional performance buffer for outputting performance metrics. | ||||
|      * @param output      - Output data from the renderer. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result RequestUpdate(std::span<const u8> input, std::span<u8> performance, | ||||
|                          std::span<u8> output); | ||||
|  | ||||
| private: | ||||
|     /// System core | ||||
|     Core::System& core; | ||||
|     /// Manager this renderer is registered with | ||||
|     Manager& manager; | ||||
|     /// Is the audio renderer initialized? | ||||
|     bool initialized{}; | ||||
|     /// Is the system registered with the manager? | ||||
|     bool system_registered{}; | ||||
|     /// Audio render system, main driver of audio rendering | ||||
|     System system; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioRenderer | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										191
									
								
								src/audio_core/renderer/behavior/behavior_info.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								src/audio_core/renderer/behavior/behavior_info.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/common/feature_support.h" | ||||
| #include "audio_core/renderer/behavior/behavior_info.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
|  | ||||
| BehaviorInfo::BehaviorInfo() : process_revision{CurrentRevision} {} | ||||
|  | ||||
| u32 BehaviorInfo::GetProcessRevisionNum() const { | ||||
|     return process_revision; | ||||
| } | ||||
|  | ||||
| u32 BehaviorInfo::GetProcessRevision() const { | ||||
|     return Common::MakeMagic('R', 'E', 'V', | ||||
|                              static_cast<char>(static_cast<u8>('0') + process_revision)); | ||||
| } | ||||
|  | ||||
| u32 BehaviorInfo::GetUserRevisionNum() const { | ||||
|     return user_revision; | ||||
| } | ||||
|  | ||||
| u32 BehaviorInfo::GetUserRevision() const { | ||||
|     return Common::MakeMagic('R', 'E', 'V', | ||||
|                              static_cast<char>(static_cast<u8>('0') + user_revision)); | ||||
| } | ||||
|  | ||||
| void BehaviorInfo::SetUserLibRevision(const u32 user_revision_) { | ||||
|     user_revision = GetRevisionNum(user_revision_); | ||||
| } | ||||
|  | ||||
| void BehaviorInfo::ClearError() { | ||||
|     error_count = 0; | ||||
| } | ||||
|  | ||||
| void BehaviorInfo::AppendError(ErrorInfo& error) { | ||||
|     LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}", | ||||
|               error.error_code.raw, error.address); | ||||
|     if (error_count < MaxErrors) { | ||||
|         errors[error_count++] = error; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) { | ||||
|     auto error_count_{std::min(error_count, MaxErrors)}; | ||||
|     std::memset(out_errors.data(), 0, MaxErrors * sizeof(ErrorInfo)); | ||||
|  | ||||
|     for (size_t i = 0; i < error_count_; i++) { | ||||
|         out_errors[i] = errors[i]; | ||||
|     } | ||||
|     out_count = error_count_; | ||||
| } | ||||
|  | ||||
| void BehaviorInfo::UpdateFlags(const Flags flags_) { | ||||
|     flags = flags_; | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsMemoryForceMappingEnabled() const { | ||||
|     return flags.IsMemoryForceMappingEnabled; | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const { | ||||
|     return CheckFeatureSupported(SupportTags::AdpcmLoopContextBugFix, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsSplitterSupported() const { | ||||
|     return CheckFeatureSupported(SupportTags::Splitter, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsSplitterBugFixed() const { | ||||
|     return CheckFeatureSupported(SupportTags::SplitterBugFix, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsEffectInfoVersion2Supported() const { | ||||
|     return CheckFeatureSupported(SupportTags::EffectInfoVer2, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsVariadicCommandBufferSizeSupported() const { | ||||
|     return CheckFeatureSupported(SupportTags::AudioRendererVariadicCommandBufferSize, | ||||
|                                  user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsWaveBufferVer2Supported() const { | ||||
|     return CheckFeatureSupported(SupportTags::WaveBufferVer2, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsLongSizePreDelaySupported() const { | ||||
|     return CheckFeatureSupported(SupportTags::LongSizePreDelay, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion2Supported() const { | ||||
|     return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion2, | ||||
|                                  user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion3Supported() const { | ||||
|     return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion3, | ||||
|                                  user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion4Supported() const { | ||||
|     return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4, | ||||
|                                  user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion5Supported() const { | ||||
|     return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4, | ||||
|                                  user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const { | ||||
|     return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit70Percent, | ||||
|                                  user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const { | ||||
|     return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit75Percent, | ||||
|                                  user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const { | ||||
|     return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit80Percent, | ||||
|                                  user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const { | ||||
|     return CheckFeatureSupported(SupportTags::FlushVoiceWaveBuffers, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsElapsedFrameCountSupported() const { | ||||
|     return CheckFeatureSupported(SupportTags::ElapsedFrameCount, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsPerformanceMetricsDataFormatVersion2Supported() const { | ||||
|     return CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision); | ||||
| } | ||||
|  | ||||
| size_t BehaviorInfo::GetPerformanceMetricsDataFormat() const { | ||||
|     if (CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision)) { | ||||
|         return 2; | ||||
|     } | ||||
|     return 1; | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const { | ||||
|     return CheckFeatureSupported(SupportTags::VoicePitchAndSrcSkipped, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const { | ||||
|     return CheckFeatureSupported(SupportTags::VoicePlayedSampleCountResetAtLoopPoint, | ||||
|                                  user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsBiquadFilterEffectStateClearBugFixed() const { | ||||
|     return CheckFeatureSupported(SupportTags::BiquadFilterEffectStateClearBugFix, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsVolumeMixParameterPrecisionQ23Supported() const { | ||||
|     return CheckFeatureSupported(SupportTags::VolumeMixParameterPrecisionQ23, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::UseBiquadFilterFloatProcessing() const { | ||||
|     return CheckFeatureSupported(SupportTags::BiquadFilterFloatProcessing, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const { | ||||
|     return CheckFeatureSupported(SupportTags::MixInParameterDirtyOnlyUpdate, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::UseMultiTapBiquadFilterProcessing() const { | ||||
|     return CheckFeatureSupported(SupportTags::MultiTapBiquadFilterProcessing, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsDeviceApiVersion2Supported() const { | ||||
|     return CheckFeatureSupported(SupportTags::DeviceApiVersion2, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsDelayChannelMappingChanged() const { | ||||
|     return CheckFeatureSupported(SupportTags::DelayChannelMappingChange, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsReverbChannelMappingChanged() const { | ||||
|     return CheckFeatureSupported(SupportTags::ReverbChannelMappingChange, user_revision); | ||||
| } | ||||
|  | ||||
| bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const { | ||||
|     return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision); | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										376
									
								
								src/audio_core/renderer/behavior/behavior_info.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										376
									
								
								src/audio_core/renderer/behavior/behavior_info.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,376 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <span> | ||||
|  | ||||
| #include "audio_core/common/common.h" | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/service/audio/errors.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
| /** | ||||
|  * Holds host and user revisions, checks whether render features can be enabled, and reports errors. | ||||
|  */ | ||||
| class BehaviorInfo { | ||||
|     static constexpr u32 MaxErrors = 10; | ||||
|  | ||||
| public: | ||||
|     struct ErrorInfo { | ||||
|         /* 0x00 */ Result error_code{0}; | ||||
|         /* 0x04 */ u32 unk_04; | ||||
|         /* 0x08 */ CpuAddr address; | ||||
|     }; | ||||
|     static_assert(sizeof(ErrorInfo) == 0x10, "BehaviorInfo::ErrorInfo has the wrong size!"); | ||||
|  | ||||
|     struct Flags { | ||||
|         u64 IsMemoryForceMappingEnabled : 1; | ||||
|     }; | ||||
|  | ||||
|     struct InParameter { | ||||
|         /* 0x00 */ u32 revision; | ||||
|         /* 0x08 */ Flags flags; | ||||
|     }; | ||||
|     static_assert(sizeof(InParameter) == 0x10, "BehaviorInfo::InParameter has the wrong size!"); | ||||
|  | ||||
|     struct OutStatus { | ||||
|         /* 0x00 */ std::array<ErrorInfo, MaxErrors> errors; | ||||
|         /* 0xA0 */ u32 error_count; | ||||
|         /* 0xA4 */ char unkA4[0xC]; | ||||
|     }; | ||||
|     static_assert(sizeof(OutStatus) == 0xB0, "BehaviorInfo::OutStatus has the wrong size!"); | ||||
|  | ||||
|     BehaviorInfo(); | ||||
|  | ||||
|     /** | ||||
|      * Get the host revision as a number. | ||||
|      * | ||||
|      * @return The host revision. | ||||
|      */ | ||||
|     u32 GetProcessRevisionNum() const; | ||||
|  | ||||
|     /** | ||||
|      * Get the host revision in chars, e.g REV8. | ||||
|      * Rev 10 and higher use the ascii characters above 9. | ||||
|      * E.g: | ||||
|      *     Rev 10 = REV: | ||||
|      *     Rev 11 = REV; | ||||
|      * | ||||
|      * @return The host revision. | ||||
|      */ | ||||
|     u32 GetProcessRevision() const; | ||||
|  | ||||
|     /** | ||||
|      * Get the user revision as a number. | ||||
|      * | ||||
|      * @return The user revision. | ||||
|      */ | ||||
|     u32 GetUserRevisionNum() const; | ||||
|  | ||||
|     /** | ||||
|      * Get the user revision in chars, e.g REV8. | ||||
|      * Rev 10 and higher use the ascii characters above 9. REV: REV; etc. | ||||
|      * | ||||
|      * @return The user revision. | ||||
|      */ | ||||
|     u32 GetUserRevision() const; | ||||
|  | ||||
|     /** | ||||
|      * Set the user revision. | ||||
|      * | ||||
|      * @param user_revision - The user's revision. | ||||
|      */ | ||||
|     void SetUserLibRevision(u32 user_revision); | ||||
|  | ||||
|     /** | ||||
|      * Clear the current error count. | ||||
|      */ | ||||
|     void ClearError(); | ||||
|  | ||||
|     /** | ||||
|      * Append an error to the error list. | ||||
|      * | ||||
|      * @param error - The new error. | ||||
|      */ | ||||
|     void AppendError(ErrorInfo& error); | ||||
|  | ||||
|     /** | ||||
|      * Copy errors to the given output container. | ||||
|      * | ||||
|      * @param out_errors - Output container to receive the errors. | ||||
|      * @param out_count  - The number of errors written. | ||||
|      */ | ||||
|     void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count); | ||||
|  | ||||
|     /** | ||||
|      * Update the behaviour flags. | ||||
|      * | ||||
|      * @param flags - New flags to use. | ||||
|      */ | ||||
|     void UpdateFlags(Flags flags); | ||||
|  | ||||
|     /** | ||||
|      * Check if memory pools can be forcibly mapped. | ||||
|      * | ||||
|      * @return True if enabled, otherwise false. | ||||
|      */ | ||||
|     bool IsMemoryForceMappingEnabled() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if the ADPCM context bug is fixed. | ||||
|      * The ADPCM context was not being sent to the AudioRenderer, leading to incorrect scaling being | ||||
|      * used. | ||||
|      * | ||||
|      * @return True if fixed, otherwise false. | ||||
|      */ | ||||
|     bool IsAdpcmLoopContextBugFixed() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if the splitter is supported. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsSplitterSupported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if the splitter bug is fixed. | ||||
|      * Update is given the wrong number of splitter destinations, leading to invalid data | ||||
|      * being processed. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsSplitterBugFixed() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if effects version 2 are supported. | ||||
|      * This gives support for returning effect states from the AudioRenderer, currently only used | ||||
|      * for Limiter statistics. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsEffectInfoVersion2Supported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if a variadic command buffer is supported. | ||||
|      * As of Rev 5 with the added optional performance metric logging, the command | ||||
|      * buffer can be a variable size, so take that into account for calcualting its size. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsVariadicCommandBufferSizeSupported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if wave buffers version 2 are supported. | ||||
|      * See WaveBufferVersion1 and WaveBufferVersion2. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsWaveBufferVer2Supported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if long size pre delay is supported. | ||||
|      * This allows a longer initial delay time for the Reverb command. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsLongSizePreDelaySupported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if the command time estimator version 2 is supported. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsCommandProcessingTimeEstimatorVersion2Supported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if the command time estimator version 3 is supported. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsCommandProcessingTimeEstimatorVersion3Supported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if the command time estimator version 4 is supported. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsCommandProcessingTimeEstimatorVersion4Supported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if the command time estimator version 5 is supported. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsCommandProcessingTimeEstimatorVersion5Supported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if the AudioRenderer can use up to 70% of the allocated processing timeslice. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsAudioRendererProcessingTimeLimit70PercentSupported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if the AudioRenderer can use up to 75% of the allocated processing timeslice. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsAudioRendererProcessingTimeLimit75PercentSupported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if the AudioRenderer can use up to 80% of the allocated processing timeslice. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsAudioRendererProcessingTimeLimit80PercentSupported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if voice flushing is supported | ||||
|      * This allowws low-priority voices to be dropped if the AudioRenderer is running behind. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsFlushVoiceWaveBuffersSupported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if counting the number of elapsed frames is supported. | ||||
|      * This adds extra output to RequestUpdate, returning the number of times the AudioRenderer | ||||
|      * processed a command list. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsElapsedFrameCountSupported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if performance metrics version 2 are supported. | ||||
|      * This adds extra output to RequestUpdate, returning the number of times the AudioRenderer | ||||
|      * (Unused?). | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsPerformanceMetricsDataFormatVersion2Supported() const; | ||||
|  | ||||
|     /** | ||||
|      * Get the supported performance metrics version. | ||||
|      * Version 2 logs some extra fields in output, such as number of voices dropped, | ||||
|      * processing start time, if the AudioRenderer exceeded its time, etc. | ||||
|      * | ||||
|      * @return Version supported, either 1 or 2. | ||||
|      */ | ||||
|     size_t GetPerformanceMetricsDataFormat() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if skipping voice pitch and sample rate conversion is supported. | ||||
|      * This speeds up the data source commands by skipping resampling if unwanted. | ||||
|      * See AudioCore::AudioRenderer::DecodeFromWaveBuffers | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsVoicePitchAndSrcSkippedSupported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if resetting played sample count at loop points is supported. | ||||
|      * This resets the number of samples played in a voice state when a loop point is reached. | ||||
|      * See AudioCore::AudioRenderer::DecodeFromWaveBuffers | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if the clear state bug for biquad filters is fixed. | ||||
|      * The biquad state was not marked as needing re-initialisation when the effect was updated, it | ||||
|      * was only initialized once with a new effect. | ||||
|      * | ||||
|      * @return True if fixed, otherwise false. | ||||
|      */ | ||||
|     bool IsBiquadFilterEffectStateClearBugFixed() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if Q23 precision is supported for fixed point. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsVolumeMixParameterPrecisionQ23Supported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if float processing for biuad filters is supported. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool UseBiquadFilterFloatProcessing() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if dirty-only mix updates are supported. | ||||
|      * This saves a lot of buffer size as mixes can be large and not change much. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsMixInParameterDirtyOnlyUpdateSupported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if multi-tap biquad filters are supported. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool UseMultiTapBiquadFilterProcessing() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if device api version 2 is supported. | ||||
|      * In the SDK but not in any sysmodule? Not sure, left here for completeness anyway. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsDeviceApiVersion2Supported() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if new channel mappings are used for Delay commands. | ||||
|      * Older commands used: | ||||
|      *   front left/front right/back left/back right/center/lfe | ||||
|      * Whereas everywhere else in the code uses: | ||||
|      *   front left/front right/center/lfe/back left/back right | ||||
|      * This corrects that and makes everything standardised. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsDelayChannelMappingChanged() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if new channel mappings are used for Reverb commands. | ||||
|      * Older commands used: | ||||
|      *   front left/front right/back left/back right/center/lfe | ||||
|      * Whereas everywhere else in the code uses: | ||||
|      *   front left/front right/center/lfe/back left/back right | ||||
|      * This corrects that and makes everything standardised. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsReverbChannelMappingChanged() const; | ||||
|  | ||||
|     /** | ||||
|      * Check if new channel mappings are used for I3dl2Reverb commands. | ||||
|      * Older commands used: | ||||
|      *   front left/front right/back left/back right/center/lfe | ||||
|      * Whereas everywhere else in the code uses: | ||||
|      *   front left/front right/center/lfe/back left/back right | ||||
|      * This corrects that and makes everything standardised. | ||||
|      * | ||||
|      * @return True if supported, otherwise false. | ||||
|      */ | ||||
|     bool IsI3dl2ReverbChannelMappingChanged() const; | ||||
|  | ||||
|     /// Host version | ||||
|     u32 process_revision; | ||||
|     /// User version | ||||
|     u32 user_revision{}; | ||||
|     /// Behaviour flags | ||||
|     Flags flags{}; | ||||
|     /// Errors generated and reported during Update | ||||
|     std::array<ErrorInfo, MaxErrors> errors{}; | ||||
|     /// Error count | ||||
|     u32 error_count{}; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										539
									
								
								src/audio_core/renderer/behavior/info_updater.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										539
									
								
								src/audio_core/renderer/behavior/info_updater.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,539 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/common/feature_support.h" | ||||
| #include "audio_core/renderer/behavior/behavior_info.h" | ||||
| #include "audio_core/renderer/behavior/info_updater.h" | ||||
| #include "audio_core/renderer/effect/effect_context.h" | ||||
| #include "audio_core/renderer/effect/effect_reset.h" | ||||
| #include "audio_core/renderer/memory/memory_pool_info.h" | ||||
| #include "audio_core/renderer/mix/mix_context.h" | ||||
| #include "audio_core/renderer/performance/performance_manager.h" | ||||
| #include "audio_core/renderer/sink/circular_buffer_sink_info.h" | ||||
| #include "audio_core/renderer/sink/device_sink_info.h" | ||||
| #include "audio_core/renderer/sink/sink_context.h" | ||||
| #include "audio_core/renderer/splitter/splitter_context.h" | ||||
| #include "audio_core/renderer/voice/voice_context.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
|  | ||||
| InfoUpdater::InfoUpdater(std::span<const u8> input_, std::span<u8> output_, | ||||
|                          const u32 process_handle_, BehaviorInfo& behaviour_) | ||||
|     : input{input_.data() + sizeof(UpdateDataHeader)}, | ||||
|       input_origin{input_}, output{output_.data() + sizeof(UpdateDataHeader)}, | ||||
|       output_origin{output_}, in_header{reinterpret_cast<const UpdateDataHeader*>( | ||||
|                                   input_origin.data())}, | ||||
|       out_header{reinterpret_cast<UpdateDataHeader*>(output_origin.data())}, | ||||
|       expected_input_size{input_.size()}, expected_output_size{output_.size()}, | ||||
|       process_handle{process_handle_}, behaviour{behaviour_} { | ||||
|     std::construct_at<UpdateDataHeader>(out_header, behaviour.GetProcessRevision()); | ||||
| } | ||||
|  | ||||
| Result InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) { | ||||
|     const auto voice_count{voice_context.GetCount()}; | ||||
|     std::span<const VoiceChannelResource::InParameter> in_params{ | ||||
|         reinterpret_cast<const VoiceChannelResource::InParameter*>(input), voice_count}; | ||||
|  | ||||
|     for (u32 i = 0; i < voice_count; i++) { | ||||
|         auto& resource{voice_context.GetChannelResource(i)}; | ||||
|         resource.in_use = in_params[i].in_use; | ||||
|         if (in_params[i].in_use) { | ||||
|             resource.mix_volumes = in_params[i].mix_volumes; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     const auto consumed_input_size{voice_count * | ||||
|                                    static_cast<u32>(sizeof(VoiceChannelResource::InParameter))}; | ||||
|     if (consumed_input_size != in_header->voice_resources_size) { | ||||
|         LOG_ERROR(Service_Audio, | ||||
|                   "Consumed an incorrect voice resource size, header size={}, consumed={}", | ||||
|                   in_header->voice_resources_size, consumed_input_size); | ||||
|         return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|     } | ||||
|  | ||||
|     input += consumed_input_size; | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result InfoUpdater::UpdateVoices(VoiceContext& voice_context, | ||||
|                                  std::span<MemoryPoolInfo> memory_pools, | ||||
|                                  const u32 memory_pool_count) { | ||||
|     const PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, | ||||
|                                  behaviour.IsMemoryForceMappingEnabled()); | ||||
|     const auto voice_count{voice_context.GetCount()}; | ||||
|     std::span<const VoiceInfo::InParameter> in_params{ | ||||
|         reinterpret_cast<const VoiceInfo::InParameter*>(input), voice_count}; | ||||
|     std::span<VoiceInfo::OutStatus> out_params{reinterpret_cast<VoiceInfo::OutStatus*>(output), | ||||
|                                                voice_count}; | ||||
|  | ||||
|     for (u32 i = 0; i < voice_count; i++) { | ||||
|         auto& voice_info{voice_context.GetInfo(i)}; | ||||
|         voice_info.in_use = false; | ||||
|     } | ||||
|  | ||||
|     u32 new_voice_count{0}; | ||||
|  | ||||
|     for (u32 i = 0; i < voice_count; i++) { | ||||
|         const auto& in_param{in_params[i]}; | ||||
|         std::array<VoiceState*, MaxChannels> voice_states{}; | ||||
|  | ||||
|         if (!in_param.in_use) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         auto& voice_info{voice_context.GetInfo(in_param.id)}; | ||||
|  | ||||
|         for (u32 channel = 0; channel < in_param.channel_count; channel++) { | ||||
|             voice_states[channel] = &voice_context.GetState(in_param.channel_resource_ids[channel]); | ||||
|         } | ||||
|  | ||||
|         if (in_param.is_new) { | ||||
|             voice_info.Initialize(); | ||||
|  | ||||
|             for (u32 channel = 0; channel < in_param.channel_count; channel++) { | ||||
|                 std::memset(voice_states[channel], 0, sizeof(VoiceState)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         BehaviorInfo::ErrorInfo update_error{}; | ||||
|         voice_info.UpdateParameters(update_error, in_param, pool_mapper, behaviour); | ||||
|  | ||||
|         if (!update_error.error_code.IsSuccess()) { | ||||
|             behaviour.AppendError(update_error); | ||||
|         } | ||||
|  | ||||
|         std::array<std::array<BehaviorInfo::ErrorInfo, 2>, MaxWaveBuffers> wavebuffer_errors{}; | ||||
|         voice_info.UpdateWaveBuffers(wavebuffer_errors, MaxWaveBuffers * 2, in_param, voice_states, | ||||
|                                      pool_mapper, behaviour); | ||||
|  | ||||
|         for (auto& wavebuffer_error : wavebuffer_errors) { | ||||
|             for (auto& error : wavebuffer_error) { | ||||
|                 if (error.error_code.IsError()) { | ||||
|                     behaviour.AppendError(error); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         voice_info.WriteOutStatus(out_params[i], in_param, voice_states); | ||||
|         new_voice_count += in_param.channel_count; | ||||
|     } | ||||
|  | ||||
|     auto consumed_input_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::InParameter))}; | ||||
|     auto consumed_output_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::OutStatus))}; | ||||
|     if (consumed_input_size != in_header->voices_size) { | ||||
|         LOG_ERROR(Service_Audio, "Consumed an incorrect voices size, header size={}, consumed={}", | ||||
|                   in_header->voices_size, consumed_input_size); | ||||
|         return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|     } | ||||
|  | ||||
|     out_header->voices_size = consumed_output_size; | ||||
|     out_header->size += consumed_output_size; | ||||
|     input += consumed_input_size; | ||||
|     output += consumed_output_size; | ||||
|  | ||||
|     voice_context.SetActiveCount(new_voice_count); | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result InfoUpdater::UpdateEffects(EffectContext& effect_context, const bool renderer_active, | ||||
|                                   std::span<MemoryPoolInfo> memory_pools, | ||||
|                                   const u32 memory_pool_count) { | ||||
|     if (behaviour.IsEffectInfoVersion2Supported()) { | ||||
|         return UpdateEffectsVersion2(effect_context, renderer_active, memory_pools, | ||||
|                                      memory_pool_count); | ||||
|     } else { | ||||
|         return UpdateEffectsVersion1(effect_context, renderer_active, memory_pools, | ||||
|                                      memory_pool_count); | ||||
|     } | ||||
| } | ||||
|  | ||||
| Result InfoUpdater::UpdateEffectsVersion1(EffectContext& effect_context, const bool renderer_active, | ||||
|                                           std::span<MemoryPoolInfo> memory_pools, | ||||
|                                           const u32 memory_pool_count) { | ||||
|     PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, | ||||
|                            behaviour.IsMemoryForceMappingEnabled()); | ||||
|  | ||||
|     const auto effect_count{effect_context.GetCount()}; | ||||
|  | ||||
|     std::span<const EffectInfoBase::InParameterVersion1> in_params{ | ||||
|         reinterpret_cast<const EffectInfoBase::InParameterVersion1*>(input), effect_count}; | ||||
|     std::span<EffectInfoBase::OutStatusVersion1> out_params{ | ||||
|         reinterpret_cast<EffectInfoBase::OutStatusVersion1*>(output), effect_count}; | ||||
|  | ||||
|     for (u32 i = 0; i < effect_count; i++) { | ||||
|         auto effect_info{&effect_context.GetInfo(i)}; | ||||
|         if (effect_info->GetType() != in_params[i].type) { | ||||
|             effect_info->ForceUnmapBuffers(pool_mapper); | ||||
|             ResetEffect(effect_info, in_params[i].type); | ||||
|         } | ||||
|  | ||||
|         BehaviorInfo::ErrorInfo error_info{}; | ||||
|         effect_info->Update(error_info, in_params[i], pool_mapper); | ||||
|         if (error_info.error_code.IsError()) { | ||||
|             behaviour.AppendError(error_info); | ||||
|         } | ||||
|  | ||||
|         effect_info->StoreStatus(out_params[i], renderer_active); | ||||
|     } | ||||
|  | ||||
|     auto consumed_input_size{effect_count * | ||||
|                              static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion1))}; | ||||
|     auto consumed_output_size{effect_count * | ||||
|                               static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion1))}; | ||||
|     if (consumed_input_size != in_header->effects_size) { | ||||
|         LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}", | ||||
|                   in_header->effects_size, consumed_input_size); | ||||
|         return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|     } | ||||
|  | ||||
|     out_header->effects_size = consumed_output_size; | ||||
|     out_header->size += consumed_output_size; | ||||
|     input += consumed_input_size; | ||||
|     output += consumed_output_size; | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result InfoUpdater::UpdateEffectsVersion2(EffectContext& effect_context, const bool renderer_active, | ||||
|                                           std::span<MemoryPoolInfo> memory_pools, | ||||
|                                           const u32 memory_pool_count) { | ||||
|     PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, | ||||
|                            behaviour.IsMemoryForceMappingEnabled()); | ||||
|  | ||||
|     const auto effect_count{effect_context.GetCount()}; | ||||
|  | ||||
|     std::span<const EffectInfoBase::InParameterVersion2> in_params{ | ||||
|         reinterpret_cast<const EffectInfoBase::InParameterVersion2*>(input), effect_count}; | ||||
|     std::span<EffectInfoBase::OutStatusVersion2> out_params{ | ||||
|         reinterpret_cast<EffectInfoBase::OutStatusVersion2*>(output), effect_count}; | ||||
|  | ||||
|     for (u32 i = 0; i < effect_count; i++) { | ||||
|         auto effect_info{&effect_context.GetInfo(i)}; | ||||
|         if (effect_info->GetType() != in_params[i].type) { | ||||
|             effect_info->ForceUnmapBuffers(pool_mapper); | ||||
|             ResetEffect(effect_info, in_params[i].type); | ||||
|         } | ||||
|  | ||||
|         BehaviorInfo::ErrorInfo error_info{}; | ||||
|         effect_info->Update(error_info, in_params[i], pool_mapper); | ||||
|  | ||||
|         if (error_info.error_code.IsError()) { | ||||
|             behaviour.AppendError(error_info); | ||||
|         } | ||||
|  | ||||
|         effect_info->StoreStatus(out_params[i], renderer_active); | ||||
|  | ||||
|         if (in_params[i].is_new) { | ||||
|             effect_info->InitializeResultState(effect_context.GetDspSharedResultState(i)); | ||||
|             effect_info->InitializeResultState(effect_context.GetResultState(i)); | ||||
|         } | ||||
|         effect_info->UpdateResultState(out_params[i].result_state, | ||||
|                                        effect_context.GetResultState(i)); | ||||
|     } | ||||
|  | ||||
|     auto consumed_input_size{effect_count * | ||||
|                              static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion2))}; | ||||
|     auto consumed_output_size{effect_count * | ||||
|                               static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion2))}; | ||||
|     if (consumed_input_size != in_header->effects_size) { | ||||
|         LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}", | ||||
|                   in_header->effects_size, consumed_input_size); | ||||
|         return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|     } | ||||
|  | ||||
|     out_header->effects_size = consumed_output_size; | ||||
|     out_header->size += consumed_output_size; | ||||
|     input += consumed_input_size; | ||||
|     output += consumed_output_size; | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result InfoUpdater::UpdateMixes(MixContext& mix_context, const u32 mix_buffer_count, | ||||
|                                 EffectContext& effect_context, SplitterContext& splitter_context) { | ||||
|     s32 mix_count{0}; | ||||
|     u32 consumed_input_size{0}; | ||||
|  | ||||
|     if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) { | ||||
|         auto in_dirty_params{reinterpret_cast<const MixInfo::InDirtyParameter*>(input)}; | ||||
|         mix_count = in_dirty_params->count; | ||||
|         input += sizeof(MixInfo::InDirtyParameter); | ||||
|         consumed_input_size = static_cast<u32>(sizeof(MixInfo::InDirtyParameter) + | ||||
|                                                mix_count * sizeof(MixInfo::InParameter)); | ||||
|     } else { | ||||
|         mix_count = mix_context.GetCount(); | ||||
|         consumed_input_size = static_cast<u32>(mix_count * sizeof(MixInfo::InParameter)); | ||||
|     } | ||||
|  | ||||
|     if (mix_buffer_count == 0) { | ||||
|         return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|     } | ||||
|  | ||||
|     std::span<const MixInfo::InParameter> in_params{ | ||||
|         reinterpret_cast<const MixInfo::InParameter*>(input), static_cast<size_t>(mix_count)}; | ||||
|  | ||||
|     u32 total_buffer_count{0}; | ||||
|     for (s32 i = 0; i < mix_count; i++) { | ||||
|         const auto& params{in_params[i]}; | ||||
|  | ||||
|         if (params.in_use) { | ||||
|             total_buffer_count += params.buffer_count; | ||||
|             if (params.dest_mix_id > static_cast<s32>(mix_context.GetCount()) && | ||||
|                 params.dest_mix_id != UnusedMixId && params.mix_id != FinalMixId) { | ||||
|                 return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (total_buffer_count > mix_buffer_count) { | ||||
|         return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|     } | ||||
|  | ||||
|     bool mix_dirty{false}; | ||||
|     for (s32 i = 0; i < mix_count; i++) { | ||||
|         const auto& params{in_params[i]}; | ||||
|  | ||||
|         s32 mix_id{i}; | ||||
|         if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) { | ||||
|             mix_id = params.mix_id; | ||||
|         } | ||||
|  | ||||
|         auto mix_info{mix_context.GetInfo(mix_id)}; | ||||
|         if (mix_info->in_use != params.in_use) { | ||||
|             mix_info->in_use = params.in_use; | ||||
|             if (!params.in_use) { | ||||
|                 mix_info->ClearEffectProcessingOrder(); | ||||
|             } | ||||
|             mix_dirty = true; | ||||
|         } | ||||
|  | ||||
|         if (params.in_use) { | ||||
|             mix_dirty |= mix_info->Update(mix_context.GetEdgeMatrix(), params, effect_context, | ||||
|                                           splitter_context, behaviour); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (mix_dirty) { | ||||
|         if (behaviour.IsSplitterSupported() && splitter_context.UsingSplitter()) { | ||||
|             if (!mix_context.TSortInfo(splitter_context)) { | ||||
|                 return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|             } | ||||
|         } else { | ||||
|             mix_context.SortInfo(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (consumed_input_size != in_header->mix_size) { | ||||
|         LOG_ERROR(Service_Audio, "Consumed an incorrect mixes size, header size={}, consumed={}", | ||||
|                   in_header->mix_size, consumed_input_size); | ||||
|         return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|     } | ||||
|  | ||||
|     input += mix_count * sizeof(MixInfo::InParameter); | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result InfoUpdater::UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools, | ||||
|                                 const u32 memory_pool_count) { | ||||
|     PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, | ||||
|                            behaviour.IsMemoryForceMappingEnabled()); | ||||
|  | ||||
|     std::span<const SinkInfoBase::InParameter> in_params{ | ||||
|         reinterpret_cast<const SinkInfoBase::InParameter*>(input), memory_pool_count}; | ||||
|     std::span<SinkInfoBase::OutStatus> out_params{ | ||||
|         reinterpret_cast<SinkInfoBase::OutStatus*>(output), memory_pool_count}; | ||||
|  | ||||
|     const auto sink_count{sink_context.GetCount()}; | ||||
|  | ||||
|     for (u32 i = 0; i < sink_count; i++) { | ||||
|         const auto& params{in_params[i]}; | ||||
|         auto sink_info{sink_context.GetInfo(i)}; | ||||
|  | ||||
|         if (sink_info->GetType() != params.type) { | ||||
|             sink_info->CleanUp(); | ||||
|             switch (params.type) { | ||||
|             case SinkInfoBase::Type::Invalid: | ||||
|                 std::construct_at<SinkInfoBase>(reinterpret_cast<SinkInfoBase*>(sink_info)); | ||||
|                 break; | ||||
|             case SinkInfoBase::Type::DeviceSink: | ||||
|                 std::construct_at<DeviceSinkInfo>(reinterpret_cast<DeviceSinkInfo*>(sink_info)); | ||||
|                 break; | ||||
|             case SinkInfoBase::Type::CircularBufferSink: | ||||
|                 std::construct_at<CircularBufferSinkInfo>( | ||||
|                     reinterpret_cast<CircularBufferSinkInfo*>(sink_info)); | ||||
|                 break; | ||||
|             default: | ||||
|                 LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(params.type)); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         BehaviorInfo::ErrorInfo error_info{}; | ||||
|         sink_info->Update(error_info, out_params[i], params, pool_mapper); | ||||
|  | ||||
|         if (error_info.error_code.IsError()) { | ||||
|             behaviour.AppendError(error_info); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     const auto consumed_input_size{sink_count * | ||||
|                                    static_cast<u32>(sizeof(SinkInfoBase::InParameter))}; | ||||
|     const auto consumed_output_size{sink_count * static_cast<u32>(sizeof(SinkInfoBase::OutStatus))}; | ||||
|     if (consumed_input_size != in_header->sinks_size) { | ||||
|         LOG_ERROR(Service_Audio, "Consumed an incorrect sinks size, header size={}, consumed={}", | ||||
|                   in_header->sinks_size, consumed_input_size); | ||||
|         return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|     } | ||||
|  | ||||
|     input += consumed_input_size; | ||||
|     output += consumed_output_size; | ||||
|     out_header->sinks_size = consumed_output_size; | ||||
|     out_header->size += consumed_output_size; | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result InfoUpdater::UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools, | ||||
|                                       const u32 memory_pool_count) { | ||||
|     PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, | ||||
|                            behaviour.IsMemoryForceMappingEnabled()); | ||||
|     std::span<const MemoryPoolInfo::InParameter> in_params{ | ||||
|         reinterpret_cast<const MemoryPoolInfo::InParameter*>(input), memory_pool_count}; | ||||
|     std::span<MemoryPoolInfo::OutStatus> out_params{ | ||||
|         reinterpret_cast<MemoryPoolInfo::OutStatus*>(output), memory_pool_count}; | ||||
|  | ||||
|     for (size_t i = 0; i < memory_pool_count; i++) { | ||||
|         auto state{pool_mapper.Update(memory_pools[i], in_params[i], out_params[i])}; | ||||
|         if (state != MemoryPoolInfo::ResultState::Success && | ||||
|             state != MemoryPoolInfo::ResultState::BadParam && | ||||
|             state != MemoryPoolInfo::ResultState::MapFailed && | ||||
|             state != MemoryPoolInfo::ResultState::InUse) { | ||||
|             LOG_WARNING(Service_Audio, "Invalid ResultState from updating memory pools"); | ||||
|             return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     const auto consumed_input_size{memory_pool_count * | ||||
|                                    static_cast<u32>(sizeof(MemoryPoolInfo::InParameter))}; | ||||
|     const auto consumed_output_size{memory_pool_count * | ||||
|                                     static_cast<u32>(sizeof(MemoryPoolInfo::OutStatus))}; | ||||
|     if (consumed_input_size != in_header->memory_pool_size) { | ||||
|         LOG_ERROR(Service_Audio, | ||||
|                   "Consumed an incorrect memory pool size, header size={}, consumed={}", | ||||
|                   in_header->memory_pool_size, consumed_input_size); | ||||
|         return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|     } | ||||
|  | ||||
|     input += consumed_input_size; | ||||
|     output += consumed_output_size; | ||||
|     out_header->memory_pool_size = consumed_output_size; | ||||
|     out_header->size += consumed_output_size; | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result InfoUpdater::UpdatePerformanceBuffer(std::span<u8> performance_output, | ||||
|                                             const u64 performance_output_size, | ||||
|                                             PerformanceManager* performance_manager) { | ||||
|     auto in_params{reinterpret_cast<const PerformanceManager::InParameter*>(input)}; | ||||
|     auto out_params{reinterpret_cast<PerformanceManager::OutStatus*>(output)}; | ||||
|  | ||||
|     if (performance_manager != nullptr) { | ||||
|         out_params->history_size = | ||||
|             performance_manager->CopyHistories(performance_output.data(), performance_output_size); | ||||
|         performance_manager->SetDetailTarget(in_params->target_node_id); | ||||
|     } else { | ||||
|         out_params->history_size = 0; | ||||
|     } | ||||
|  | ||||
|     const auto consumed_input_size{static_cast<u32>(sizeof(PerformanceManager::InParameter))}; | ||||
|     const auto consumed_output_size{static_cast<u32>(sizeof(PerformanceManager::OutStatus))}; | ||||
|     if (consumed_input_size != in_header->performance_buffer_size) { | ||||
|         LOG_ERROR(Service_Audio, | ||||
|                   "Consumed an incorrect performance size, header size={}, consumed={}", | ||||
|                   in_header->performance_buffer_size, consumed_input_size); | ||||
|         return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|     } | ||||
|  | ||||
|     input += consumed_input_size; | ||||
|     output += consumed_output_size; | ||||
|     out_header->performance_buffer_size = consumed_output_size; | ||||
|     out_header->size += consumed_output_size; | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) { | ||||
|     const auto in_params{reinterpret_cast<const BehaviorInfo::InParameter*>(input)}; | ||||
|  | ||||
|     if (!CheckValidRevision(in_params->revision)) { | ||||
|         return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|     } | ||||
|  | ||||
|     if (in_params->revision != behaviour_.GetUserRevision()) { | ||||
|         return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|     } | ||||
|  | ||||
|     behaviour_.ClearError(); | ||||
|     behaviour_.UpdateFlags(in_params->flags); | ||||
|  | ||||
|     if (in_header->behaviour_size != sizeof(BehaviorInfo::InParameter)) { | ||||
|         return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|     } | ||||
|  | ||||
|     input += sizeof(BehaviorInfo::InParameter); | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result InfoUpdater::UpdateErrorInfo(BehaviorInfo& behaviour_) { | ||||
|     auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)}; | ||||
|     behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count); | ||||
|  | ||||
|     const auto consumed_output_size{static_cast<u32>(sizeof(BehaviorInfo::OutStatus))}; | ||||
|  | ||||
|     output += consumed_output_size; | ||||
|     out_header->behaviour_size = consumed_output_size; | ||||
|     out_header->size += consumed_output_size; | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) { | ||||
|     u32 consumed_size{0}; | ||||
|     if (!splitter_context.Update(input, consumed_size)) { | ||||
|         return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|     } | ||||
|  | ||||
|     input += consumed_size; | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result InfoUpdater::UpdateRendererInfo(const u64 elapsed_frames) { | ||||
|     struct RenderInfo { | ||||
|         /* 0x00 */ u64 frames_elapsed; | ||||
|         /* 0x08 */ char unk08[0x8]; | ||||
|     }; | ||||
|     static_assert(sizeof(RenderInfo) == 0x10, "RenderInfo has the wrong size!"); | ||||
|  | ||||
|     auto out_params{reinterpret_cast<RenderInfo*>(output)}; | ||||
|     out_params->frames_elapsed = elapsed_frames; | ||||
|  | ||||
|     const auto consumed_output_size{static_cast<u32>(sizeof(RenderInfo))}; | ||||
|  | ||||
|     output += consumed_output_size; | ||||
|     out_header->render_info_size = consumed_output_size; | ||||
|     out_header->size += consumed_output_size; | ||||
|  | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| Result InfoUpdater::CheckConsumedSize() { | ||||
|     if (CpuAddr(input) - CpuAddr(input_origin.data()) != expected_input_size) { | ||||
|         return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|     } else if (CpuAddr(output) - CpuAddr(output_origin.data()) != expected_output_size) { | ||||
|         return Service::Audio::ERR_INVALID_UPDATE_DATA; | ||||
|     } | ||||
|     return ResultSuccess; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										205
									
								
								src/audio_core/renderer/behavior/info_updater.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								src/audio_core/renderer/behavior/info_updater.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,205 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <span> | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/service/audio/errors.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
| class BehaviorInfo; | ||||
| class VoiceContext; | ||||
| class MixContext; | ||||
| class SinkContext; | ||||
| class SplitterContext; | ||||
| class EffectContext; | ||||
| class MemoryPoolInfo; | ||||
| class PerformanceManager; | ||||
|  | ||||
| class InfoUpdater { | ||||
|     struct UpdateDataHeader { | ||||
|         explicit UpdateDataHeader(u32 revision_) : revision{revision_} {} | ||||
|  | ||||
|         /* 0x00 */ u32 revision; | ||||
|         /* 0x04 */ u32 behaviour_size{}; | ||||
|         /* 0x08 */ u32 memory_pool_size{}; | ||||
|         /* 0x0C */ u32 voices_size{}; | ||||
|         /* 0x10 */ u32 voice_resources_size{}; | ||||
|         /* 0x14 */ u32 effects_size{}; | ||||
|         /* 0x18 */ u32 mix_size{}; | ||||
|         /* 0x1C */ u32 sinks_size{}; | ||||
|         /* 0x20 */ u32 performance_buffer_size{}; | ||||
|         /* 0x24 */ char unk24[4]; | ||||
|         /* 0x28 */ u32 render_info_size{}; | ||||
|         /* 0x2C */ char unk2C[0x10]; | ||||
|         /* 0x3C */ u32 size{sizeof(UpdateDataHeader)}; | ||||
|     }; | ||||
|     static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has the wrong size!"); | ||||
|  | ||||
| public: | ||||
|     explicit InfoUpdater(std::span<const u8> input, std::span<u8> output, u32 process_handle, | ||||
|                          BehaviorInfo& behaviour); | ||||
|  | ||||
|     /** | ||||
|      * Update the voice channel resources. | ||||
|      * | ||||
|      * @param voice_context - Voice context to update. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result UpdateVoiceChannelResources(VoiceContext& voice_context); | ||||
|  | ||||
|     /** | ||||
|      * Update voices. | ||||
|      * | ||||
|      * @param voice_context     - Voice context to update. | ||||
|      * @param memory_pools      - Memory pools to use for these voices. | ||||
|      * @param memory_pool_count - Number of memory pools. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result UpdateVoices(VoiceContext& voice_context, std::span<MemoryPoolInfo> memory_pools, | ||||
|                         u32 memory_pool_count); | ||||
|  | ||||
|     /** | ||||
|      * Update effects. | ||||
|      * | ||||
|      * @param effect_context    - Effect context to update. | ||||
|      * @param renderer_active   - Whether the AudioRenderer is active. | ||||
|      * @param memory_pools      - Memory pools to use for these voices. | ||||
|      * @param memory_pool_count - Number of memory pools. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result UpdateEffects(EffectContext& effect_context, bool renderer_active, | ||||
|                          std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count); | ||||
|  | ||||
|     /** | ||||
|      * Update mixes. | ||||
|      * | ||||
|      * @param mix_context       - Mix context to update. | ||||
|      * @param mix_buffer_count  - Number of mix buffers. | ||||
|      * @param effect_context    - Effect context to update effort order. | ||||
|      * @param splitter_context  - Splitter context for the mixes. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result UpdateMixes(MixContext& mix_context, u32 mix_buffer_count, EffectContext& effect_context, | ||||
|                        SplitterContext& splitter_context); | ||||
|  | ||||
|     /** | ||||
|      * Update sinks. | ||||
|      * | ||||
|      * @param sink_context      - Sink context to update. | ||||
|      * @param memory_pools      - Memory pools to use for these voices. | ||||
|      * @param memory_pool_count - Number of memory pools. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools, | ||||
|                        u32 memory_pool_count); | ||||
|  | ||||
|     /** | ||||
|      * Update memory pools. | ||||
|      * | ||||
|      * @param memory_pools      - Memory pools to use for these voices. | ||||
|      * @param memory_pool_count - Number of memory pools. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count); | ||||
|  | ||||
|     /** | ||||
|      * Update the performance buffer. | ||||
|      * | ||||
|      * @param output              - Output buffer for performance metrics. | ||||
|      * @param output_size         - Output buffer size. | ||||
|      * @param performance_manager - Performance manager.. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result UpdatePerformanceBuffer(std::span<u8> output, u64 output_size, | ||||
|                                    PerformanceManager* performance_manager); | ||||
|  | ||||
|     /** | ||||
|      * Update behaviour. | ||||
|      * | ||||
|      * @param behaviour - Behaviour to update. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result UpdateBehaviorInfo(BehaviorInfo& behaviour); | ||||
|  | ||||
|     /** | ||||
|      * Update errors. | ||||
|      * | ||||
|      * @param behaviour - Behaviour to update. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result UpdateErrorInfo(BehaviorInfo& behaviour); | ||||
|  | ||||
|     /** | ||||
|      * Update splitter. | ||||
|      * | ||||
|      * @param splitter_context - Splitter context to update. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result UpdateSplitterInfo(SplitterContext& splitter_context); | ||||
|  | ||||
|     /** | ||||
|      * Update renderer info. | ||||
|      * | ||||
|      * @param elapsed_frames - Number of elapsed frames. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result UpdateRendererInfo(u64 elapsed_frames); | ||||
|  | ||||
|     /** | ||||
|      * Check that the input.output sizes match their expected values. | ||||
|      * | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result CheckConsumedSize(); | ||||
|  | ||||
| private: | ||||
|     /** | ||||
|      * Update effects version 1. | ||||
|      * | ||||
|      * @param effect_context    - Effect context to update. | ||||
|      * @param renderer_active   - Is the AudioRenderer active? | ||||
|      * @param memory_pools      - Memory pools to use for these voices. | ||||
|      * @param memory_pool_count - Number of memory pools. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result UpdateEffectsVersion1(EffectContext& effect_context, bool renderer_active, | ||||
|                                  std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count); | ||||
|  | ||||
|     /** | ||||
|      * Update effects version 2. | ||||
|      * | ||||
|      * @param effect_context    - Effect context to update. | ||||
|      * @param renderer_active   - Is the AudioRenderer active? | ||||
|      * @param memory_pools      - Memory pools to use for these voices. | ||||
|      * @param memory_pool_count - Number of memory pools. | ||||
|      * @return Result code. | ||||
|      */ | ||||
|     Result UpdateEffectsVersion2(EffectContext& effect_context, bool renderer_active, | ||||
|                                  std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count); | ||||
|  | ||||
|     /// Input buffer | ||||
|     u8 const* input; | ||||
|     /// Input buffer start | ||||
|     std::span<const u8> input_origin; | ||||
|     /// Output buffer start | ||||
|     u8* output; | ||||
|     /// Output buffer start | ||||
|     std::span<u8> output_origin; | ||||
|     /// Input header | ||||
|     const UpdateDataHeader* in_header; | ||||
|     /// Output header | ||||
|     UpdateDataHeader* out_header; | ||||
|     /// Expected input size, see CheckConsumedSize | ||||
|     u64 expected_input_size; | ||||
|     /// Expected output size, see CheckConsumedSize | ||||
|     u64 expected_output_size; | ||||
|     /// Unused | ||||
|     u32 process_handle; | ||||
|     /// Behaviour | ||||
|     BehaviorInfo& behaviour; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										714
									
								
								src/audio_core/renderer/command/command_buffer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										714
									
								
								src/audio_core/renderer/command/command_buffer.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,714 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/renderer/behavior/behavior_info.h" | ||||
| #include "audio_core/renderer/command/command_buffer.h" | ||||
| #include "audio_core/renderer/command/command_list_header.h" | ||||
| #include "audio_core/renderer/command/command_processing_time_estimator.h" | ||||
| #include "audio_core/renderer/effect/biquad_filter.h" | ||||
| #include "audio_core/renderer/effect/delay.h" | ||||
| #include "audio_core/renderer/effect/reverb.h" | ||||
| #include "audio_core/renderer/memory/memory_pool_info.h" | ||||
| #include "audio_core/renderer/mix/mix_info.h" | ||||
| #include "audio_core/renderer/sink/circular_buffer_sink_info.h" | ||||
| #include "audio_core/renderer/sink/device_sink_info.h" | ||||
| #include "audio_core/renderer/sink/sink_info_base.h" | ||||
| #include "audio_core/renderer/voice/voice_info.h" | ||||
| #include "audio_core/renderer/voice/voice_state.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
|  | ||||
| template <typename T, CommandId Id> | ||||
| T& CommandBuffer::GenerateStart(const s32 node_id) { | ||||
|     if (size + sizeof(T) >= command_list.size_bytes()) { | ||||
|         LOG_ERROR( | ||||
|             Service_Audio, | ||||
|             "Attempting to write commands beyond the end of allocated command buffer memory!"); | ||||
|         UNREACHABLE(); | ||||
|     } | ||||
|  | ||||
|     auto& cmd{*std::construct_at<T>(reinterpret_cast<T*>(&command_list[size]))}; | ||||
|  | ||||
|     cmd.magic = CommandMagic; | ||||
|     cmd.enabled = true; | ||||
|     cmd.type = Id; | ||||
|     cmd.size = sizeof(T); | ||||
|     cmd.node_id = node_id; | ||||
|  | ||||
|     return cmd; | ||||
| } | ||||
|  | ||||
| template <typename T> | ||||
| void CommandBuffer::GenerateEnd(T& cmd) { | ||||
|     cmd.estimated_process_time = time_estimator->Estimate(cmd); | ||||
|     estimated_process_time += cmd.estimated_process_time; | ||||
|     size += sizeof(T); | ||||
|     count++; | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GeneratePcmInt16Version1Command(const s32 node_id, | ||||
|                                                     const MemoryPoolInfo& memory_pool_, | ||||
|                                                     VoiceInfo& voice_info, | ||||
|                                                     const VoiceState& voice_state, | ||||
|                                                     const s16 buffer_count, const s8 channel) { | ||||
|     auto& cmd{ | ||||
|         GenerateStart<PcmInt16DataSourceVersion1Command, CommandId::DataSourcePcmInt16Version1>( | ||||
|             node_id)}; | ||||
|  | ||||
|     cmd.src_quality = voice_info.src_quality; | ||||
|     cmd.output_index = buffer_count + channel; | ||||
|     cmd.flags = voice_info.flags & 3; | ||||
|     cmd.sample_rate = voice_info.sample_rate; | ||||
|     cmd.pitch = voice_info.pitch; | ||||
|     cmd.channel_index = channel; | ||||
|     cmd.channel_count = voice_info.channel_count; | ||||
|  | ||||
|     for (u32 i = 0; i < MaxWaveBuffers; i++) { | ||||
|         voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); | ||||
|     } | ||||
|  | ||||
|     cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); | ||||
|  | ||||
|     GenerateEnd<PcmInt16DataSourceVersion1Command>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GeneratePcmInt16Version2Command(const s32 node_id, VoiceInfo& voice_info, | ||||
|                                                     const VoiceState& voice_state, | ||||
|                                                     const s16 buffer_count, const s8 channel) { | ||||
|     auto& cmd{ | ||||
|         GenerateStart<PcmInt16DataSourceVersion2Command, CommandId::DataSourcePcmInt16Version2>( | ||||
|             node_id)}; | ||||
|  | ||||
|     cmd.src_quality = voice_info.src_quality; | ||||
|     cmd.output_index = buffer_count + channel; | ||||
|     cmd.flags = voice_info.flags & 3; | ||||
|     cmd.sample_rate = voice_info.sample_rate; | ||||
|     cmd.pitch = voice_info.pitch; | ||||
|     cmd.channel_index = channel; | ||||
|     cmd.channel_count = voice_info.channel_count; | ||||
|  | ||||
|     for (u32 i = 0; i < MaxWaveBuffers; i++) { | ||||
|         voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); | ||||
|     } | ||||
|  | ||||
|     cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); | ||||
|  | ||||
|     GenerateEnd<PcmInt16DataSourceVersion2Command>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GeneratePcmFloatVersion1Command(const s32 node_id, | ||||
|                                                     const MemoryPoolInfo& memory_pool_, | ||||
|                                                     VoiceInfo& voice_info, | ||||
|                                                     const VoiceState& voice_state, | ||||
|                                                     const s16 buffer_count, const s8 channel) { | ||||
|     auto& cmd{ | ||||
|         GenerateStart<PcmFloatDataSourceVersion1Command, CommandId::DataSourcePcmFloatVersion1>( | ||||
|             node_id)}; | ||||
|  | ||||
|     cmd.src_quality = voice_info.src_quality; | ||||
|     cmd.output_index = buffer_count + channel; | ||||
|     cmd.flags = voice_info.flags & 3; | ||||
|     cmd.sample_rate = voice_info.sample_rate; | ||||
|     cmd.pitch = voice_info.pitch; | ||||
|     cmd.channel_index = channel; | ||||
|     cmd.channel_count = voice_info.channel_count; | ||||
|  | ||||
|     for (u32 i = 0; i < MaxWaveBuffers; i++) { | ||||
|         voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); | ||||
|     } | ||||
|  | ||||
|     cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); | ||||
|  | ||||
|     GenerateEnd<PcmFloatDataSourceVersion1Command>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GeneratePcmFloatVersion2Command(const s32 node_id, VoiceInfo& voice_info, | ||||
|                                                     const VoiceState& voice_state, | ||||
|                                                     const s16 buffer_count, const s8 channel) { | ||||
|     auto& cmd{ | ||||
|         GenerateStart<PcmFloatDataSourceVersion2Command, CommandId::DataSourcePcmFloatVersion2>( | ||||
|             node_id)}; | ||||
|  | ||||
|     cmd.src_quality = voice_info.src_quality; | ||||
|     cmd.output_index = buffer_count + channel; | ||||
|     cmd.flags = voice_info.flags & 3; | ||||
|     cmd.sample_rate = voice_info.sample_rate; | ||||
|     cmd.pitch = voice_info.pitch; | ||||
|     cmd.channel_index = channel; | ||||
|     cmd.channel_count = voice_info.channel_count; | ||||
|  | ||||
|     for (u32 i = 0; i < MaxWaveBuffers; i++) { | ||||
|         voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); | ||||
|     } | ||||
|  | ||||
|     cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); | ||||
|  | ||||
|     GenerateEnd<PcmFloatDataSourceVersion2Command>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateAdpcmVersion1Command(const s32 node_id, | ||||
|                                                  const MemoryPoolInfo& memory_pool_, | ||||
|                                                  VoiceInfo& voice_info, | ||||
|                                                  const VoiceState& voice_state, | ||||
|                                                  const s16 buffer_count, const s8 channel) { | ||||
|     auto& cmd{ | ||||
|         GenerateStart<AdpcmDataSourceVersion1Command, CommandId::DataSourceAdpcmVersion1>(node_id)}; | ||||
|  | ||||
|     cmd.src_quality = voice_info.src_quality; | ||||
|     cmd.output_index = buffer_count + channel; | ||||
|     cmd.flags = voice_info.flags & 3; | ||||
|     cmd.sample_rate = voice_info.sample_rate; | ||||
|     cmd.pitch = voice_info.pitch; | ||||
|  | ||||
|     for (u32 i = 0; i < MaxWaveBuffers; i++) { | ||||
|         voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); | ||||
|     } | ||||
|  | ||||
|     cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); | ||||
|     cmd.data_address = voice_info.data_address.GetReference(true); | ||||
|     cmd.data_size = voice_info.data_address.GetSize(); | ||||
|  | ||||
|     GenerateEnd<AdpcmDataSourceVersion1Command>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateAdpcmVersion2Command(const s32 node_id, VoiceInfo& voice_info, | ||||
|                                                  const VoiceState& voice_state, | ||||
|                                                  const s16 buffer_count, const s8 channel) { | ||||
|     auto& cmd{ | ||||
|         GenerateStart<AdpcmDataSourceVersion2Command, CommandId::DataSourceAdpcmVersion2>(node_id)}; | ||||
|  | ||||
|     cmd.src_quality = voice_info.src_quality; | ||||
|     cmd.output_index = buffer_count + channel; | ||||
|     cmd.flags = voice_info.flags & 3; | ||||
|     cmd.sample_rate = voice_info.sample_rate; | ||||
|     cmd.pitch = voice_info.pitch; | ||||
|     cmd.channel_index = channel; | ||||
|     cmd.channel_count = voice_info.channel_count; | ||||
|  | ||||
|     for (u32 i = 0; i < MaxWaveBuffers; i++) { | ||||
|         voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); | ||||
|     } | ||||
|  | ||||
|     cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); | ||||
|     cmd.data_address = voice_info.data_address.GetReference(true); | ||||
|     cmd.data_size = voice_info.data_address.GetSize(); | ||||
|  | ||||
|     GenerateEnd<AdpcmDataSourceVersion2Command>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateVolumeCommand(const s32 node_id, const s16 buffer_offset, | ||||
|                                           const s16 input_index, const f32 volume, | ||||
|                                           const u8 precision) { | ||||
|     auto& cmd{GenerateStart<VolumeCommand, CommandId::Volume>(node_id)}; | ||||
|  | ||||
|     cmd.precision = precision; | ||||
|     cmd.input_index = buffer_offset + input_index; | ||||
|     cmd.output_index = buffer_offset + input_index; | ||||
|     cmd.volume = volume; | ||||
|  | ||||
|     GenerateEnd<VolumeCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateVolumeRampCommand(const s32 node_id, VoiceInfo& voice_info, | ||||
|                                               const s16 buffer_count, const u8 precision) { | ||||
|     auto& cmd{GenerateStart<VolumeRampCommand, CommandId::VolumeRamp>(node_id)}; | ||||
|  | ||||
|     cmd.input_index = buffer_count; | ||||
|     cmd.output_index = buffer_count; | ||||
|     cmd.prev_volume = voice_info.prev_volume; | ||||
|     cmd.volume = voice_info.volume; | ||||
|     cmd.precision = precision; | ||||
|  | ||||
|     GenerateEnd<VolumeRampCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info, | ||||
|                                                 const VoiceState& voice_state, | ||||
|                                                 const s16 buffer_count, const s8 channel, | ||||
|                                                 const u32 biquad_index, | ||||
|                                                 const bool use_float_processing) { | ||||
|     auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)}; | ||||
|  | ||||
|     cmd.input = buffer_count + channel; | ||||
|     cmd.output = buffer_count + channel; | ||||
|  | ||||
|     cmd.biquad = voice_info.biquads[biquad_index]; | ||||
|  | ||||
|     cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()), | ||||
|                                        MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); | ||||
|  | ||||
|     cmd.needs_init = !voice_info.biquad_initialized[biquad_index]; | ||||
|     cmd.use_float_processing = use_float_processing; | ||||
|  | ||||
|     GenerateEnd<BiquadFilterCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBase& effect_info, | ||||
|                                                 const s16 buffer_offset, const s8 channel, | ||||
|                                                 const bool needs_init, | ||||
|                                                 const bool use_float_processing) { | ||||
|     auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)}; | ||||
|  | ||||
|     const auto& parameter{ | ||||
|         *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||||
|     const auto state{ | ||||
|         reinterpret_cast<VoiceState::BiquadFilterState*>(effect_info.GetStateBuffer())}; | ||||
|  | ||||
|     cmd.input = buffer_offset + parameter.inputs[channel]; | ||||
|     cmd.output = buffer_offset + parameter.outputs[channel]; | ||||
|  | ||||
|     cmd.biquad.b = parameter.b; | ||||
|     cmd.biquad.a = parameter.a; | ||||
|  | ||||
|     cmd.state = memory_pool->Translate(CpuAddr(state), | ||||
|                                        MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); | ||||
|  | ||||
|     cmd.needs_init = needs_init; | ||||
|     cmd.use_float_processing = use_float_processing; | ||||
|  | ||||
|     GenerateEnd<BiquadFilterCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateMixCommand(const s32 node_id, const s16 input_index, | ||||
|                                        const s16 output_index, const s16 buffer_offset, | ||||
|                                        const f32 volume, const u8 precision) { | ||||
|     auto& cmd{GenerateStart<MixCommand, CommandId::Mix>(node_id)}; | ||||
|  | ||||
|     cmd.input_index = input_index; | ||||
|     cmd.output_index = output_index; | ||||
|     cmd.volume = volume; | ||||
|     cmd.precision = precision; | ||||
|  | ||||
|     GenerateEnd<MixCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateMixRampCommand(const s32 node_id, | ||||
|                                            [[maybe_unused]] const s16 buffer_count, | ||||
|                                            const s16 input_index, const s16 output_index, | ||||
|                                            const f32 volume, const f32 prev_volume, | ||||
|                                            const CpuAddr prev_samples, const u8 precision) { | ||||
|     if (volume == 0.0f && prev_volume == 0.0f) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto& cmd{GenerateStart<MixRampCommand, CommandId::MixRamp>(node_id)}; | ||||
|  | ||||
|     cmd.input_index = input_index; | ||||
|     cmd.output_index = output_index; | ||||
|     cmd.prev_volume = prev_volume; | ||||
|     cmd.volume = volume; | ||||
|     cmd.previous_sample = prev_samples; | ||||
|     cmd.precision = precision; | ||||
|  | ||||
|     GenerateEnd<MixRampCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateMixRampGroupedCommand(const s32 node_id, const s16 buffer_count, | ||||
|                                                   const s16 input_index, s16 output_index, | ||||
|                                                   std::span<const f32> volumes, | ||||
|                                                   std::span<const f32> prev_volumes, | ||||
|                                                   const CpuAddr prev_samples, const u8 precision) { | ||||
|     auto& cmd{GenerateStart<MixRampGroupedCommand, CommandId::MixRampGrouped>(node_id)}; | ||||
|  | ||||
|     cmd.buffer_count = buffer_count; | ||||
|  | ||||
|     for (s32 i = 0; i < buffer_count; i++) { | ||||
|         cmd.inputs[i] = input_index; | ||||
|         cmd.outputs[i] = output_index++; | ||||
|         cmd.prev_volumes[i] = prev_volumes[i]; | ||||
|         cmd.volumes[i] = volumes[i]; | ||||
|     } | ||||
|  | ||||
|     cmd.previous_samples = prev_samples; | ||||
|     cmd.precision = precision; | ||||
|  | ||||
|     GenerateEnd<MixRampGroupedCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateDepopPrepareCommand(const s32 node_id, const VoiceState& voice_state, | ||||
|                                                 std::span<const s32> buffer, const s16 buffer_count, | ||||
|                                                 s16 buffer_offset, const bool was_playing) { | ||||
|     auto& cmd{GenerateStart<DepopPrepareCommand, CommandId::DepopPrepare>(node_id)}; | ||||
|  | ||||
|     cmd.enabled = was_playing; | ||||
|  | ||||
|     for (u32 i = 0; i < MaxMixBuffers; i++) { | ||||
|         cmd.inputs[i] = buffer_offset++; | ||||
|     } | ||||
|  | ||||
|     cmd.previous_samples = memory_pool->Translate(CpuAddr(voice_state.previous_samples.data()), | ||||
|                                                   MaxMixBuffers * sizeof(s32)); | ||||
|     cmd.buffer_count = buffer_count; | ||||
|     cmd.depop_buffer = memory_pool->Translate(CpuAddr(buffer.data()), buffer_count * sizeof(s32)); | ||||
|  | ||||
|     GenerateEnd<DepopPrepareCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateDepopForMixBuffersCommand(const s32 node_id, const MixInfo& mix_info, | ||||
|                                                       std::span<const s32> depop_buffer) { | ||||
|     auto& cmd{GenerateStart<DepopForMixBuffersCommand, CommandId::DepopForMixBuffers>(node_id)}; | ||||
|  | ||||
|     cmd.input = mix_info.buffer_offset; | ||||
|     cmd.count = mix_info.buffer_count; | ||||
|     cmd.decay = mix_info.sample_rate == TargetSampleRate ? 0.96218872f : 0.94369507f; | ||||
|     cmd.depop_buffer = | ||||
|         memory_pool->Translate(CpuAddr(depop_buffer.data()), mix_info.buffer_count * sizeof(s32)); | ||||
|  | ||||
|     GenerateEnd<DepopForMixBuffersCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateDelayCommand(const s32 node_id, EffectInfoBase& effect_info, | ||||
|                                          const s16 buffer_offset) { | ||||
|     auto& cmd{GenerateStart<DelayCommand, CommandId::Delay>(node_id)}; | ||||
|  | ||||
|     const auto& parameter{ | ||||
|         *reinterpret_cast<DelayInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||||
|     const auto state{effect_info.GetStateBuffer()}; | ||||
|  | ||||
|     if (IsChannelCountValid(parameter.channel_count)) { | ||||
|         const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(DelayInfo::State))}; | ||||
|         if (state_buffer) { | ||||
|             for (s16 channel = 0; channel < parameter.channel_count; channel++) { | ||||
|                 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; | ||||
|                 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; | ||||
|             } | ||||
|  | ||||
|             if (!behavior->IsDelayChannelMappingChanged() && parameter.channel_count == 6) { | ||||
|                 UseOldChannelMapping(cmd.inputs, cmd.outputs); | ||||
|             } | ||||
|  | ||||
|             cmd.parameter = parameter; | ||||
|             cmd.effect_enabled = effect_info.IsEnabled(); | ||||
|             cmd.state = state_buffer; | ||||
|             cmd.workbuffer = effect_info.GetWorkbuffer(-1); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     GenerateEnd<DelayCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateUpsampleCommand(const s32 node_id, const s16 buffer_offset, | ||||
|                                             UpsamplerInfo& upsampler_info, const u32 input_count, | ||||
|                                             std::span<const s8> inputs, const s16 buffer_count, | ||||
|                                             const u32 sample_count_, const u32 sample_rate_) { | ||||
|     auto& cmd{GenerateStart<UpsampleCommand, CommandId::Upsample>(node_id)}; | ||||
|  | ||||
|     cmd.samples_buffer = memory_pool->Translate(upsampler_info.samples_pos, | ||||
|                                                 upsampler_info.sample_count * sizeof(s32)); | ||||
|     cmd.inputs = memory_pool->Translate(CpuAddr(upsampler_info.inputs.data()), MaxChannels); | ||||
|     cmd.buffer_count = buffer_count; | ||||
|     cmd.unk_20 = 0; | ||||
|     cmd.source_sample_count = sample_count_; | ||||
|     cmd.source_sample_rate = sample_rate_; | ||||
|  | ||||
|     upsampler_info.input_count = input_count; | ||||
|     for (u32 i = 0; i < input_count; i++) { | ||||
|         upsampler_info.inputs[i] = buffer_offset + inputs[i]; | ||||
|     } | ||||
|  | ||||
|     cmd.upsampler_info = memory_pool->Translate(CpuAddr(&upsampler_info), sizeof(UpsamplerInfo)); | ||||
|  | ||||
|     GenerateEnd<UpsampleCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateDownMix6chTo2chCommand(const s32 node_id, std::span<const s8> inputs, | ||||
|                                                    const s16 buffer_offset, | ||||
|                                                    std::span<const f32> downmix_coeff) { | ||||
|     auto& cmd{GenerateStart<DownMix6chTo2chCommand, CommandId::DownMix6chTo2ch>(node_id)}; | ||||
|  | ||||
|     for (u32 i = 0; i < MaxChannels; i++) { | ||||
|         cmd.inputs[i] = buffer_offset + inputs[i]; | ||||
|         cmd.outputs[i] = buffer_offset + inputs[i]; | ||||
|     } | ||||
|  | ||||
|     for (u32 i = 0; i < 4; i++) { | ||||
|         cmd.down_mix_coeff[i] = downmix_coeff[i]; | ||||
|     } | ||||
|  | ||||
|     GenerateEnd<DownMix6chTo2chCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateAuxCommand(const s32 node_id, EffectInfoBase& effect_info, | ||||
|                                        const s16 input_index, const s16 output_index, | ||||
|                                        const s16 buffer_offset, const u32 update_count, | ||||
|                                        const u32 count_max, const u32 write_offset) { | ||||
|     auto& cmd{GenerateStart<AuxCommand, CommandId::Aux>(node_id)}; | ||||
|  | ||||
|     if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) { | ||||
|         cmd.input = buffer_offset + input_index; | ||||
|         cmd.output = buffer_offset + output_index; | ||||
|         cmd.send_buffer_info = effect_info.GetSendBufferInfo(); | ||||
|         cmd.send_buffer = effect_info.GetSendBuffer(); | ||||
|         cmd.return_buffer_info = effect_info.GetReturnBufferInfo(); | ||||
|         cmd.return_buffer = effect_info.GetReturnBuffer(); | ||||
|         cmd.count_max = count_max; | ||||
|         cmd.write_offset = write_offset; | ||||
|         cmd.update_count = update_count; | ||||
|         cmd.effect_enabled = effect_info.IsEnabled(); | ||||
|     } | ||||
|  | ||||
|     GenerateEnd<AuxCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateDeviceSinkCommand(const s32 node_id, const s16 buffer_offset, | ||||
|                                               SinkInfoBase& sink_info, const u32 session_id, | ||||
|                                               std::span<s32> samples_buffer) { | ||||
|     auto& cmd{GenerateStart<DeviceSinkCommand, CommandId::DeviceSink>(node_id)}; | ||||
|     const auto& parameter{ | ||||
|         *reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())}; | ||||
|     auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())}; | ||||
|  | ||||
|     cmd.session_id = session_id; | ||||
|  | ||||
|     if (state.upsampler_info != nullptr) { | ||||
|         const auto size_{state.upsampler_info->sample_count * parameter.input_count}; | ||||
|         const auto size_bytes{size_ * sizeof(s32)}; | ||||
|         const auto addr{memory_pool->Translate(state.upsampler_info->samples_pos, size_bytes)}; | ||||
|         cmd.sample_buffer = {reinterpret_cast<s32*>(addr), | ||||
|                              parameter.input_count * state.upsampler_info->sample_count}; | ||||
|     } else { | ||||
|         cmd.sample_buffer = samples_buffer; | ||||
|     } | ||||
|  | ||||
|     cmd.input_count = parameter.input_count; | ||||
|     for (u32 i = 0; i < parameter.input_count; i++) { | ||||
|         cmd.inputs[i] = buffer_offset + parameter.inputs[i]; | ||||
|     } | ||||
|  | ||||
|     GenerateEnd<DeviceSinkCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateCircularBufferSinkCommand(const s32 node_id, SinkInfoBase& sink_info, | ||||
|                                                       const s16 buffer_offset) { | ||||
|     auto& cmd{GenerateStart<CircularBufferSinkCommand, CommandId::CircularBufferSink>(node_id)}; | ||||
|     const auto& parameter{*reinterpret_cast<CircularBufferSinkInfo::CircularBufferInParameter*>( | ||||
|         sink_info.GetParameter())}; | ||||
|     auto state{ | ||||
|         *reinterpret_cast<CircularBufferSinkInfo::CircularBufferState*>(sink_info.GetState())}; | ||||
|  | ||||
|     cmd.input_count = parameter.input_count; | ||||
|     for (u32 i = 0; i < parameter.input_count; i++) { | ||||
|         cmd.inputs[i] = buffer_offset + parameter.inputs[i]; | ||||
|     } | ||||
|  | ||||
|     cmd.address = state.address_info.GetReference(true); | ||||
|     cmd.size = parameter.size; | ||||
|     cmd.pos = state.current_pos; | ||||
|  | ||||
|     GenerateEnd<CircularBufferSinkCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateReverbCommand(const s32 node_id, EffectInfoBase& effect_info, | ||||
|                                           const s16 buffer_offset, | ||||
|                                           const bool long_size_pre_delay_supported) { | ||||
|     auto& cmd{GenerateStart<ReverbCommand, CommandId::Reverb>(node_id)}; | ||||
|  | ||||
|     const auto& parameter{ | ||||
|         *reinterpret_cast<ReverbInfo::ParameterVersion2*>(effect_info.GetParameter())}; | ||||
|     const auto state{effect_info.GetStateBuffer()}; | ||||
|  | ||||
|     if (IsChannelCountValid(parameter.channel_count)) { | ||||
|         const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(ReverbInfo::State))}; | ||||
|         if (state_buffer) { | ||||
|             for (s16 channel = 0; channel < parameter.channel_count; channel++) { | ||||
|                 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; | ||||
|                 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; | ||||
|             } | ||||
|  | ||||
|             if (!behavior->IsReverbChannelMappingChanged() && parameter.channel_count == 6) { | ||||
|                 UseOldChannelMapping(cmd.inputs, cmd.outputs); | ||||
|             } | ||||
|  | ||||
|             cmd.parameter = parameter; | ||||
|             cmd.effect_enabled = effect_info.IsEnabled(); | ||||
|             cmd.state = state_buffer; | ||||
|             cmd.workbuffer = effect_info.GetWorkbuffer(-1); | ||||
|             cmd.long_size_pre_delay_supported = long_size_pre_delay_supported; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     GenerateEnd<ReverbCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateI3dl2ReverbCommand(const s32 node_id, EffectInfoBase& effect_info, | ||||
|                                                const s16 buffer_offset) { | ||||
|     auto& cmd{GenerateStart<I3dl2ReverbCommand, CommandId::I3dl2Reverb>(node_id)}; | ||||
|  | ||||
|     const auto& parameter{ | ||||
|         *reinterpret_cast<I3dl2ReverbInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||||
|     const auto state{effect_info.GetStateBuffer()}; | ||||
|  | ||||
|     if (IsChannelCountValid(parameter.channel_count)) { | ||||
|         const auto state_buffer{ | ||||
|             memory_pool->Translate(CpuAddr(state), sizeof(I3dl2ReverbInfo::State))}; | ||||
|         if (state_buffer) { | ||||
|             for (s16 channel = 0; channel < parameter.channel_count; channel++) { | ||||
|                 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; | ||||
|                 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; | ||||
|             } | ||||
|  | ||||
|             if (!behavior->IsI3dl2ReverbChannelMappingChanged() && parameter.channel_count == 6) { | ||||
|                 UseOldChannelMapping(cmd.inputs, cmd.outputs); | ||||
|             } | ||||
|  | ||||
|             cmd.parameter = parameter; | ||||
|             cmd.effect_enabled = effect_info.IsEnabled(); | ||||
|             cmd.state = state_buffer; | ||||
|             cmd.workbuffer = effect_info.GetWorkbuffer(-1); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     GenerateEnd<I3dl2ReverbCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GeneratePerformanceCommand(const s32 node_id, const PerformanceState state, | ||||
|                                                const PerformanceEntryAddresses& entry_addresses) { | ||||
|     auto& cmd{GenerateStart<PerformanceCommand, CommandId::Performance>(node_id)}; | ||||
|  | ||||
|     cmd.state = state; | ||||
|     cmd.entry_address = entry_addresses; | ||||
|  | ||||
|     GenerateEnd<PerformanceCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateClearMixCommand(const s32 node_id) { | ||||
|     auto& cmd{GenerateStart<ClearMixBufferCommand, CommandId::ClearMixBuffer>(node_id)}; | ||||
|     GenerateEnd<ClearMixBufferCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateCopyMixBufferCommand(const s32 node_id, EffectInfoBase& effect_info, | ||||
|                                                  const s16 buffer_offset, const s8 channel) { | ||||
|     auto& cmd{GenerateStart<CopyMixBufferCommand, CommandId::CopyMixBuffer>(node_id)}; | ||||
|  | ||||
|     const auto& parameter{ | ||||
|         *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||||
|     cmd.input_index = buffer_offset + parameter.inputs[channel]; | ||||
|     cmd.output_index = buffer_offset + parameter.outputs[channel]; | ||||
|  | ||||
|     GenerateEnd<CopyMixBufferCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateLightLimiterCommand( | ||||
|     const s32 node_id, const s16 buffer_offset, | ||||
|     const LightLimiterInfo::ParameterVersion1& parameter, const LightLimiterInfo::State& state, | ||||
|     const bool enabled, const CpuAddr workbuffer) { | ||||
|     auto& cmd{GenerateStart<LightLimiterVersion1Command, CommandId::LightLimiterVersion1>(node_id)}; | ||||
|  | ||||
|     if (IsChannelCountValid(parameter.channel_count)) { | ||||
|         const auto state_buffer{ | ||||
|             memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))}; | ||||
|         if (state_buffer) { | ||||
|             for (s8 channel = 0; channel < parameter.channel_count; channel++) { | ||||
|                 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; | ||||
|                 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; | ||||
|             } | ||||
|  | ||||
|             std::memcpy(&cmd.parameter, ¶meter, sizeof(LightLimiterInfo::ParameterVersion1)); | ||||
|             cmd.effect_enabled = enabled; | ||||
|             cmd.state = state_buffer; | ||||
|             cmd.workbuffer = workbuffer; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     GenerateEnd<LightLimiterVersion1Command>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateLightLimiterCommand( | ||||
|     const s32 node_id, const s16 buffer_offset, | ||||
|     const LightLimiterInfo::ParameterVersion2& parameter, | ||||
|     const LightLimiterInfo::StatisticsInternal& statistics, const LightLimiterInfo::State& state, | ||||
|     const bool enabled, const CpuAddr workbuffer) { | ||||
|     auto& cmd{GenerateStart<LightLimiterVersion2Command, CommandId::LightLimiterVersion2>(node_id)}; | ||||
|     if (IsChannelCountValid(parameter.channel_count)) { | ||||
|         const auto state_buffer{ | ||||
|             memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))}; | ||||
|         if (state_buffer) { | ||||
|             for (s8 channel = 0; channel < parameter.channel_count; channel++) { | ||||
|                 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; | ||||
|                 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; | ||||
|             } | ||||
|  | ||||
|             cmd.parameter = parameter; | ||||
|             cmd.effect_enabled = enabled; | ||||
|             cmd.state = state_buffer; | ||||
|             if (cmd.parameter.statistics_enabled) { | ||||
|                 cmd.result_state = memory_pool->Translate( | ||||
|                     CpuAddr(&statistics), sizeof(LightLimiterInfo::StatisticsInternal)); | ||||
|             } else { | ||||
|                 cmd.result_state = 0; | ||||
|             } | ||||
|             cmd.workbuffer = workbuffer; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     GenerateEnd<LightLimiterVersion2Command>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateMultitapBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info, | ||||
|                                                         const VoiceState& voice_state, | ||||
|                                                         const s16 buffer_count, const s8 channel) { | ||||
|     auto& cmd{GenerateStart<MultiTapBiquadFilterCommand, CommandId::MultiTapBiquadFilter>(node_id)}; | ||||
|  | ||||
|     cmd.input = buffer_count + channel; | ||||
|     cmd.output = buffer_count + channel; | ||||
|     cmd.biquads = voice_info.biquads; | ||||
|  | ||||
|     cmd.states[0] = | ||||
|         memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()), | ||||
|                                MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); | ||||
|     cmd.states[1] = | ||||
|         memory_pool->Translate(CpuAddr(voice_state.biquad_states[1].data()), | ||||
|                                MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); | ||||
|  | ||||
|     cmd.needs_init[0] = !voice_info.biquad_initialized[0]; | ||||
|     cmd.needs_init[1] = !voice_info.biquad_initialized[1]; | ||||
|     cmd.filter_tap_count = MaxBiquadFilters; | ||||
|  | ||||
|     GenerateEnd<MultiTapBiquadFilterCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateCaptureCommand(const s32 node_id, EffectInfoBase& effect_info, | ||||
|                                            const s16 input_index, const s16 output_index, | ||||
|                                            const s16 buffer_offset, const u32 update_count, | ||||
|                                            const u32 count_max, const u32 write_offset) { | ||||
|     auto& cmd{GenerateStart<CaptureCommand, CommandId::Capture>(node_id)}; | ||||
|  | ||||
|     if (effect_info.GetSendBuffer()) { | ||||
|         cmd.input = buffer_offset + input_index; | ||||
|         cmd.output = buffer_offset + output_index; | ||||
|         cmd.send_buffer_info = effect_info.GetSendBufferInfo(); | ||||
|         cmd.send_buffer = effect_info.GetSendBuffer(); | ||||
|         cmd.count_max = count_max; | ||||
|         cmd.write_offset = write_offset; | ||||
|         cmd.update_count = update_count; | ||||
|         cmd.effect_enabled = effect_info.IsEnabled(); | ||||
|     } | ||||
|  | ||||
|     GenerateEnd<CaptureCommand>(cmd); | ||||
| } | ||||
|  | ||||
| void CommandBuffer::GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, | ||||
|                                               s32 node_id) { | ||||
|     auto& cmd{GenerateStart<CompressorCommand, CommandId::Compressor>(node_id)}; | ||||
|  | ||||
|     auto& parameter{ | ||||
|         *reinterpret_cast<CompressorInfo::ParameterVersion2*>(effect_info.GetParameter())}; | ||||
|     auto state{reinterpret_cast<CompressorInfo::State*>(effect_info.GetStateBuffer())}; | ||||
|  | ||||
|     if (IsChannelCountValid(parameter.channel_count)) { | ||||
|         auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(CompressorInfo::State))}; | ||||
|         if (state_buffer) { | ||||
|             for (u16 channel = 0; channel < parameter.channel_count; channel++) { | ||||
|                 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; | ||||
|                 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; | ||||
|             } | ||||
|             cmd.parameter = parameter; | ||||
|             cmd.workbuffer = state_buffer; | ||||
|             cmd.enabled = effect_info.IsEnabled(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     GenerateEnd<CompressorCommand>(cmd); | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										466
									
								
								src/audio_core/renderer/command/command_buffer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										466
									
								
								src/audio_core/renderer/command/command_buffer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,466 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <span> | ||||
|  | ||||
| #include "audio_core/renderer/command/commands.h" | ||||
| #include "audio_core/renderer/effect/light_limiter.h" | ||||
| #include "audio_core/renderer/performance/performance_manager.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
| struct UpsamplerInfo; | ||||
| struct VoiceState; | ||||
| class EffectInfoBase; | ||||
| class ICommandProcessingTimeEstimator; | ||||
| class MixInfo; | ||||
| class MemoryPoolInfo; | ||||
| class SinkInfoBase; | ||||
| class VoiceInfo; | ||||
|  | ||||
| /** | ||||
|  * Utility functions to generate and add commands into the current command list. | ||||
|  */ | ||||
| class CommandBuffer { | ||||
| public: | ||||
|     /** | ||||
|      * Generate a PCM s16 version 1 command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id      - Node id of the voice this command is generated for. | ||||
|      * @param memory_pool  - Memory pool for translating buffer addresses to the DSP. | ||||
|      * @param voice_info   - The voice info this command is generated from. | ||||
|      * @param voice_state  - The voice state the DSP will use for this command. | ||||
|      * @param buffer_count - Number of mix buffers in use, | ||||
|      *                       data will be read into this index + channel. | ||||
|      * @param channel      - Channel index for this command. | ||||
|      */ | ||||
|     void GeneratePcmInt16Version1Command(s32 node_id, const MemoryPoolInfo& memory_pool, | ||||
|                                          VoiceInfo& voice_info, const VoiceState& voice_state, | ||||
|                                          s16 buffer_count, s8 channel); | ||||
|  | ||||
|     /** | ||||
|      * Generate a PCM s16 version 2 command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id      - Node id of the voice this command is generated for. | ||||
|      * @param voice_info   - The voice info this command is generated from. | ||||
|      * @param voice_state  - The voice state the DSP will use for this command. | ||||
|      * @param buffer_count - Number of mix buffers in use, | ||||
|      *                       data will be read into this index + channel. | ||||
|      * @param channel      - Channel index for this command. | ||||
|      */ | ||||
|     void GeneratePcmInt16Version2Command(s32 node_id, VoiceInfo& voice_info, | ||||
|                                          const VoiceState& voice_state, s16 buffer_count, | ||||
|                                          s8 channel); | ||||
|  | ||||
|     /** | ||||
|      * Generate a PCM f32 version 1 command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id      - Node id of the voice this command is generated for. | ||||
|      * @param memory_pool  - Memory pool for translating buffer addresses to the DSP. | ||||
|      * @param voice_info   - The voice info this command is generated from. | ||||
|      * @param voice_state  - The voice state the DSP will use for this command. | ||||
|      * @param buffer_count - Number of mix buffers in use, | ||||
|      *                       data will be read into this index + channel. | ||||
|      * @param channel      - Channel index for this command. | ||||
|      */ | ||||
|     void GeneratePcmFloatVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool, | ||||
|                                          VoiceInfo& voice_info, const VoiceState& voice_state, | ||||
|                                          s16 buffer_count, s8 channel); | ||||
|  | ||||
|     /** | ||||
|      * Generate a PCM f32 version 2 command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id      - Node id of the voice this command is generated for. | ||||
|      * @param voice_info   - The voice info this command is generated from. | ||||
|      * @param voice_state  - The voice state the DSP will use for this command. | ||||
|      * @param buffer_count - Number of mix buffers in use, | ||||
|      *                       data will be read into this index + channel. | ||||
|      * @param channel      - Channel index for this command. | ||||
|      */ | ||||
|     void GeneratePcmFloatVersion2Command(s32 node_id, VoiceInfo& voice_info, | ||||
|                                          const VoiceState& voice_state, s16 buffer_count, | ||||
|                                          s8 channel); | ||||
|  | ||||
|     /** | ||||
|      * Generate an ADPCM version 1 command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id      - Node id of the voice this command is generated for. | ||||
|      * @param memory_pool  - Memory pool for translating buffer addresses to the DSP. | ||||
|      * @param voice_info   - The voice info this command is generated from. | ||||
|      * @param voice_state  - The voice state the DSP will use for this command. | ||||
|      * @param buffer_count - Number of mix buffers in use, | ||||
|      *                       data will be read into this index + channel. | ||||
|      * @param channel      - Channel index for this command. | ||||
|      */ | ||||
|     void GenerateAdpcmVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool, | ||||
|                                       VoiceInfo& voice_info, const VoiceState& voice_state, | ||||
|                                       s16 buffer_count, s8 channel); | ||||
|  | ||||
|     /** | ||||
|      * Generate an ADPCM version 2 command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id      - Node id of the voice this command is generated for. | ||||
|      * @param voice_info   - The voice info this command is generated from. | ||||
|      * @param voice_state  - The voice state the DSP will use for this command. | ||||
|      * @param buffer_count - Number of mix buffers in use, | ||||
|      *                       data will be read into this index + channel. | ||||
|      * @param channel      - Channel index for this command. | ||||
|      */ | ||||
|     void GenerateAdpcmVersion2Command(s32 node_id, VoiceInfo& voice_info, | ||||
|                                       const VoiceState& voice_state, s16 buffer_count, s8 channel); | ||||
|  | ||||
|     /** | ||||
|      * Generate a volume command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id       - Node id of the voice this command is generated for. | ||||
|      * @param buffer_offset - Base mix buffer index to generate this command at. | ||||
|      * @param input_index   - Channel index and mix buffer offset for this command. | ||||
|      * @param volume        - Mix volume added to the input samples. | ||||
|      * @param precision     - Number of decimal bits for fixed point operations. | ||||
|      */ | ||||
|     void GenerateVolumeCommand(s32 node_id, s16 buffer_offset, s16 input_index, f32 volume, | ||||
|                                u8 precision); | ||||
|  | ||||
|     /** | ||||
|      * Generate a volume ramp command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id      - Node id of the voice this command is generated for. | ||||
|      * @param voice_info   - The voice info this command takes its volumes from. | ||||
|      * @param buffer_count - Number of active mix buffers, command will generate at this index. | ||||
|      * @param precision    - Number of decimal bits for fixed point operations. | ||||
|      */ | ||||
|     void GenerateVolumeRampCommand(s32 node_id, VoiceInfo& voice_info, s16 buffer_count, | ||||
|                                    u8 precision); | ||||
|  | ||||
|     /** | ||||
|      * Generate a biquad filter command from a voice, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id              - Node id of the voice this command is generated for. | ||||
|      * @param voice_info           - The voice info this command takes biquad parameters from. | ||||
|      * @param voice_state          - Used by the AudioRenderer to track previous samples. | ||||
|      * @param buffer_count         - Number of active mix buffers, | ||||
|      *                               command will generate at this index + channel. | ||||
|      * @param channel              - Channel index for this filter to work on. | ||||
|      * @param biquad_index         - Which biquad filter to use for this command (0-1). | ||||
|      * @param use_float_processing - Should int or float processing be used? | ||||
|      */ | ||||
|     void GenerateBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info, | ||||
|                                      const VoiceState& voice_state, s16 buffer_count, s8 channel, | ||||
|                                      u32 biquad_index, bool use_float_processing); | ||||
|  | ||||
|     /** | ||||
|      * Generate a biquad filter effect command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id              - Node id of the voice this command is generated for. | ||||
|      * @param effect_info          - The effect info this command takes biquad parameters from. | ||||
|      * @param buffer_offset        - Mix buffer offset this command will use, | ||||
|      *                               command will generate at this index + channel. | ||||
|      * @param channel              - Channel index for this filter to work on. | ||||
|      * @param needs_init           - True if the biquad state needs initialisation. | ||||
|      * @param use_float_processing - Should int or float processing be used? | ||||
|      */ | ||||
|     void GenerateBiquadFilterCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset, | ||||
|                                      s8 channel, bool needs_init, bool use_float_processing); | ||||
|  | ||||
|     /** | ||||
|      * Generate a mix command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id       - Node id of the voice this command is generated for. | ||||
|      * @param input_index   - Input mix buffer index for this command. | ||||
|      *                        Added to the buffer offset. | ||||
|      * @param output_index  - Output mix buffer index for this command. | ||||
|      *                        Added to the buffer offset. | ||||
|      * @param buffer_offset - Mix buffer offset this command will use. | ||||
|      * @param volume        - Volume to be applied to the input. | ||||
|      * @param precision     - Number of decimal bits for fixed point operations. | ||||
|      */ | ||||
|     void GenerateMixCommand(s32 node_id, s16 input_index, s16 output_index, s16 buffer_offset, | ||||
|                             f32 volume, u8 precision); | ||||
|  | ||||
|     /** | ||||
|      * Generate a mix ramp command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id      - Node id of the voice this command is generated for. | ||||
|      * @param buffer_count - Number of active mix buffers. | ||||
|      * @param input_index  - Input mix buffer index for this command. | ||||
|      *                       Added to buffer_count. | ||||
|      * @param output_index - Output mix buffer index for this command. | ||||
|      *                       Added to buffer_count. | ||||
|      * @param volume       - Current mix volume used for calculating the ramp. | ||||
|      * @param prev_volume  - Previous mix volume, used for calculating the ramp, | ||||
|      *                       also applied to the input. | ||||
|      * @param precision    - Number of decimal bits for fixed point operations. | ||||
|      */ | ||||
|     void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index, | ||||
|                                 f32 volume, f32 prev_volume, CpuAddr prev_samples, u8 precision); | ||||
|  | ||||
|     /** | ||||
|      * Generate a mix ramp grouped command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id      - Node id of the voice this command is generated for. | ||||
|      * @param buffer_count - Number of active mix buffers. | ||||
|      * @param input_index  - Input mix buffer index for this command. | ||||
|      *                       Added to buffer_count. | ||||
|      * @param output_index - Output mix buffer index for this command. | ||||
|      *                       Added to buffer_count. | ||||
|      * @param volumes      - Current mix volumes used for calculating the ramp. | ||||
|      * @param prev_volumes - Previous mix volumes, used for calculating the ramp, | ||||
|      *                       also applied to the input. | ||||
|      * @param precision    - Number of decimal bits for fixed point operations. | ||||
|      */ | ||||
|     void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index, | ||||
|                                        s16 output_index, std::span<const f32> volumes, | ||||
|                                        std::span<const f32> prev_volumes, CpuAddr prev_samples, | ||||
|                                        u8 precision); | ||||
|  | ||||
|     /** | ||||
|      * Generate a depop prepare command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id       - Node id of the voice this command is generated for. | ||||
|      * @param voice_state   - State to track the previous depop samples for each mix buffer. | ||||
|      * @param buffer        - State to track the current depop samples for each mix buffer. | ||||
|      * @param buffer_count  - Number of active mix buffers. | ||||
|      * @param buffer_offset - Base mix buffer index to generate the channel depops at. | ||||
|      * @param was_playing   - Command only needs to work if the voice was previously playing. | ||||
|      */ | ||||
|     void GenerateDepopPrepareCommand(s32 node_id, const VoiceState& voice_state, | ||||
|                                      std::span<const s32> buffer, s16 buffer_count, | ||||
|                                      s16 buffer_offset, bool was_playing); | ||||
|  | ||||
|     /** | ||||
|      * Generate a depop command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id      - Node id of the voice this command is generated for. | ||||
|      * @param mix_info     - Mix info to get the buffer count and base offsets from. | ||||
|      * @param depop_buffer - Buffer of current depop sample values to be added to the input | ||||
|      *                       channels. | ||||
|      */ | ||||
|     void GenerateDepopForMixBuffersCommand(s32 node_id, const MixInfo& mix_info, | ||||
|                                            std::span<const s32> depop_buffer); | ||||
|  | ||||
|     /** | ||||
|      * Generate a delay command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id       - Node id of the voice this command is generated for. | ||||
|      * @param effect_info   - Delay effect info to generate this command from. | ||||
|      * @param buffer_offset - Base mix buffer offset to apply the apply the delay. | ||||
|      */ | ||||
|     void GenerateDelayCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset); | ||||
|  | ||||
|     /** | ||||
|      * Generate an upsample command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id        - Node id of the voice this command is generated for. | ||||
|      * @param buffer_offset  - Base mix buffer offset to upsample. | ||||
|      * @param upsampler_info - Upsampler info to control the upsampling. | ||||
|      * @param input_count    - Number of input channels to upsample. | ||||
|      * @param inputs         - Input mix buffer indexes. | ||||
|      * @param buffer_count   - Number of active mix buffers. | ||||
|      * @param sample_count   - Source sample count of the input. | ||||
|      * @param sample_rate    - Source sample rate of the input. | ||||
|      */ | ||||
|     void GenerateUpsampleCommand(s32 node_id, s16 buffer_offset, UpsamplerInfo& upsampler_info, | ||||
|                                  u32 input_count, std::span<const s8> inputs, s16 buffer_count, | ||||
|                                  u32 sample_count, u32 sample_rate); | ||||
|  | ||||
|     /** | ||||
|      * Generate a downmix 6 -> 2 command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id       - Node id of the voice this command is generated for. | ||||
|      * @param inputs        - Input mix buffer indexes. | ||||
|      * @param buffer_offset - Base mix buffer offset of the channels to downmix. | ||||
|      * @param downmix_coeff - Downmixing coefficients. | ||||
|      */ | ||||
|     void GenerateDownMix6chTo2chCommand(s32 node_id, std::span<const s8> inputs, s16 buffer_offset, | ||||
|                                         std::span<const f32> downmix_coeff); | ||||
|  | ||||
|     /** | ||||
|      * Generate an aux buffer command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id       - Node id of the voice this command is generated for. | ||||
|      * @param effect_info   - Aux effect info to generate this command from. | ||||
|      * @param input_index   - Input mix buffer index for this command. | ||||
|      *                        Added to buffer_offset. | ||||
|      * @param output_index  - Output mix buffer index for this command. | ||||
|      *                        Added to buffer_offset. | ||||
|      * @param buffer_offset - Base mix buffer offset to use. | ||||
|      * @param update_count  - Number of samples to write back to the game as updated, can be 0. | ||||
|      * @param count_max     - Maximum number of samples to read or write. | ||||
|      * @param write_offset  - Current read or write offset within the buffer. | ||||
|      */ | ||||
|     void GenerateAuxCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index, | ||||
|                             s16 output_index, s16 buffer_offset, u32 update_count, u32 count_max, | ||||
|                             u32 write_offset); | ||||
|  | ||||
|     /** | ||||
|      * Generate a device sink command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id       - Node id of the voice this command is generated for. | ||||
|      * @param buffer_offset - Base mix buffer offset to use. | ||||
|      * @param sink_info     - The sink_info to generate this command from. | ||||
|      * @session_id          - System session id this command is generated from. | ||||
|      * @samples_buffer      - The buffer to be sent to the sink if upsampling is not used. | ||||
|      */ | ||||
|     void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info, | ||||
|                                    u32 session_id, std::span<s32> samples_buffer); | ||||
|  | ||||
|     /** | ||||
|      * Generate a circular buffer sink command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id       - Node id of the voice this command is generated for. | ||||
|      * @param sink_info     - The sink_info to generate this command from. | ||||
|      * @param buffer_offset - Base mix buffer offset to use. | ||||
|      */ | ||||
|     void GenerateCircularBufferSinkCommand(s32 node_id, SinkInfoBase& sink_info, s16 buffer_offset); | ||||
|  | ||||
|     /** | ||||
|      * Generate a reverb command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id                       - Node id of the voice this command is generated for. | ||||
|      * @param effect_info                   - Reverb effect info to generate this command from. | ||||
|      * @param buffer_offset                 - Base mix buffer offset to use. | ||||
|      * @param long_size_pre_delay_supported - Should a longer pre-delay time be used before reverb | ||||
|      *                                        begins? | ||||
|      */ | ||||
|     void GenerateReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset, | ||||
|                                bool long_size_pre_delay_supported); | ||||
|  | ||||
|     /** | ||||
|      * Generate an I3DL2 reverb command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id                       - Node id of the voice this command is generated for. | ||||
|      * @param effect_info                   - I3DL2Reverb effect info to generate this command from. | ||||
|      * @param buffer_offset                 - Base mix buffer offset to use. | ||||
|      */ | ||||
|     void GenerateI3dl2ReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset); | ||||
|  | ||||
|     /** | ||||
|      * Generate a performance command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id         - Node id of the voice this command is generated for. | ||||
|      * @param state           - State of the performance. | ||||
|      * @param entry_addresses - The addresses to be filled in by the AudioRenderer. | ||||
|      */ | ||||
|     void GeneratePerformanceCommand(s32 node_id, PerformanceState state, | ||||
|                                     const PerformanceEntryAddresses& entry_addresses); | ||||
|  | ||||
|     /** | ||||
|      * Generate a clear mix command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id         - Node id of the voice this command is generated for. | ||||
|      */ | ||||
|     void GenerateClearMixCommand(s32 node_id); | ||||
|  | ||||
|     /** | ||||
|      * Generate a copy mix command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id       - Node id of the voice this command is generated for. | ||||
|      * @param effect_info   - BiquadFilter effect info to generate this command from. | ||||
|      * @param buffer_offset - Base mix buffer offset to use. | ||||
|      * @param channel       - Index to the effect's parameters input indexes for this command. | ||||
|      */ | ||||
|     void GenerateCopyMixBufferCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset, | ||||
|                                       s8 channel); | ||||
|  | ||||
|     /** | ||||
|      * Generate a light limiter version 1 command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id       - Node id of the voice this command is generated for. | ||||
|      * @param buffer_offset - Base mix buffer offset to use. | ||||
|      * @param parameter     - Effect parameter to generate from. | ||||
|      * @param state         - State used by the AudioRenderer between commands. | ||||
|      * @param enabled       - Is this command enabled? | ||||
|      * @param workbuffer    - Game-supplied memory for the state. | ||||
|      */ | ||||
|     void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset, | ||||
|                                      const LightLimiterInfo::ParameterVersion1& parameter, | ||||
|                                      const LightLimiterInfo::State& state, bool enabled, | ||||
|                                      CpuAddr workbuffer); | ||||
|  | ||||
|     /** | ||||
|      * Generate a light limiter version 2 command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id       - Node id of the voice this command is generated for. | ||||
|      * @param buffer_offset - Base mix buffer offset to use. | ||||
|      * @param parameter     - Effect parameter to generate from. | ||||
|      * @param statistics    - Statistics reported by the AudioRenderer on the limiter's state. | ||||
|      * @param state         - State used by the AudioRenderer between commands. | ||||
|      * @param enabled       - Is this command enabled? | ||||
|      * @param workbuffer    - Game-supplied memory for the state. | ||||
|      */ | ||||
|     void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset, | ||||
|                                      const LightLimiterInfo::ParameterVersion2& parameter, | ||||
|                                      const LightLimiterInfo::StatisticsInternal& statistics, | ||||
|                                      const LightLimiterInfo::State& state, bool enabled, | ||||
|                                      CpuAddr workbuffer); | ||||
|  | ||||
|     /** | ||||
|      * Generate a multitap biquad filter command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id      - Node id of the voice this command is generated for. | ||||
|      * @param voice_info   - The voice info this command takes biquad parameters from. | ||||
|      * @param voice_state  - Used by the AudioRenderer to track previous samples. | ||||
|      * @param buffer_count - Number of active mix buffers, | ||||
|      *                       command will generate at this index + channel. | ||||
|      * @param channel      - Channel index for this filter to work on. | ||||
|      */ | ||||
|     void GenerateMultitapBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info, | ||||
|                                              const VoiceState& voice_state, s16 buffer_count, | ||||
|                                              s8 channel); | ||||
|  | ||||
|     /** | ||||
|      * Generate a capture command, adding it to the command list. | ||||
|      * | ||||
|      * @param node_id       - Node id of the voice this command is generated for. | ||||
|      * @param effect_info   - Capture effect info to generate this command from. | ||||
|      * @param input_index   - Input mix buffer index for this command. | ||||
|      *                        Added to buffer_offset. | ||||
|      * @param output_index  - Output mix buffer index for this command (unused). | ||||
|      *                        Added to buffer_offset. | ||||
|      * @param buffer_offset - Base mix buffer offset to use. | ||||
|      * @param update_count  - Number of samples to write back to the game as updated, can be 0. | ||||
|      * @param count_max     - Maximum number of samples to read or write. | ||||
|      * @param write_offset  - Current read or write offset within the buffer. | ||||
|      */ | ||||
|     void GenerateCaptureCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index, | ||||
|                                 s16 output_index, s16 buffer_offset, u32 update_count, | ||||
|                                 u32 count_max, u32 write_offset); | ||||
|  | ||||
|     /** | ||||
|      * Generate a compressor command, adding it to the command list. | ||||
|      * | ||||
|      * @param buffer_offset - Base mix buffer offset to use. | ||||
|      * @param effect_info   - Capture effect info to generate this command from. | ||||
|      * @param node_id       - Node id of the voice this command is generated for. | ||||
|      */ | ||||
|     void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); | ||||
|  | ||||
|     /// Command list buffer generated commands will be added to | ||||
|     std::span<u8> command_list{}; | ||||
|     /// Input sample count, unused | ||||
|     u32 sample_count{}; | ||||
|     /// Input sample rate, unused | ||||
|     u32 sample_rate{}; | ||||
|     /// Current size of the command buffer | ||||
|     u64 size{}; | ||||
|     /// Current number of commands added | ||||
|     u32 count{}; | ||||
|     /// Current estimated processing time for all commands | ||||
|     u32 estimated_process_time{}; | ||||
|     /// Used for mapping buffers for the AudioRenderer | ||||
|     MemoryPoolInfo* memory_pool{}; | ||||
|     /// Used for estimating command process times | ||||
|     ICommandProcessingTimeEstimator* time_estimator{}; | ||||
|     /// Used to check which rendering features are currently enabled | ||||
|     BehaviorInfo* behavior{}; | ||||
|  | ||||
| private: | ||||
|     template <typename T, CommandId Id> | ||||
|     T& GenerateStart(const s32 node_id); | ||||
|     template <typename T> | ||||
|     void GenerateEnd(T& cmd); | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										796
									
								
								src/audio_core/renderer/command/command_generator.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										796
									
								
								src/audio_core/renderer/command/command_generator.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,796 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/common/audio_renderer_parameter.h" | ||||
| #include "audio_core/renderer/behavior/behavior_info.h" | ||||
| #include "audio_core/renderer/command/command_buffer.h" | ||||
| #include "audio_core/renderer/command/command_generator.h" | ||||
| #include "audio_core/renderer/command/command_list_header.h" | ||||
| #include "audio_core/renderer/effect/aux_.h" | ||||
| #include "audio_core/renderer/effect/biquad_filter.h" | ||||
| #include "audio_core/renderer/effect/buffer_mixer.h" | ||||
| #include "audio_core/renderer/effect/capture.h" | ||||
| #include "audio_core/renderer/effect/effect_context.h" | ||||
| #include "audio_core/renderer/effect/light_limiter.h" | ||||
| #include "audio_core/renderer/mix/mix_context.h" | ||||
| #include "audio_core/renderer/performance/detail_aspect.h" | ||||
| #include "audio_core/renderer/performance/entry_aspect.h" | ||||
| #include "audio_core/renderer/sink/device_sink_info.h" | ||||
| #include "audio_core/renderer/sink/sink_context.h" | ||||
| #include "audio_core/renderer/splitter/splitter_context.h" | ||||
| #include "audio_core/renderer/voice/voice_context.h" | ||||
| #include "common/alignment.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
|  | ||||
| CommandGenerator::CommandGenerator(CommandBuffer& command_buffer_, | ||||
|                                    const CommandListHeader& command_list_header_, | ||||
|                                    const AudioRendererSystemContext& render_context_, | ||||
|                                    VoiceContext& voice_context_, MixContext& mix_context_, | ||||
|                                    EffectContext& effect_context_, SinkContext& sink_context_, | ||||
|                                    SplitterContext& splitter_context_, | ||||
|                                    PerformanceManager* performance_manager_) | ||||
|     : command_buffer{command_buffer_}, command_header{command_list_header_}, | ||||
|       render_context{render_context_}, voice_context{voice_context_}, mix_context{mix_context_}, | ||||
|       effect_context{effect_context_}, sink_context{sink_context_}, | ||||
|       splitter_context{splitter_context_}, performance_manager{performance_manager_} { | ||||
|     command_buffer.GenerateClearMixCommand(InvalidNodeId); | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateDataSourceCommand(VoiceInfo& voice_info, | ||||
|                                                  const VoiceState& voice_state, const s8 channel) { | ||||
|     if (voice_info.mix_id == UnusedMixId) { | ||||
|         if (voice_info.splitter_id != UnusedSplitterId) { | ||||
|             auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, 0)}; | ||||
|             u32 dest_id{0}; | ||||
|             while (destination != nullptr) { | ||||
|                 if (destination->IsConfigured()) { | ||||
|                     auto mix_id{destination->GetMixId()}; | ||||
|                     if (mix_id < mix_context.GetCount()) { | ||||
|                         auto mix_info{mix_context.GetInfo(mix_id)}; | ||||
|                         command_buffer.GenerateDepopPrepareCommand( | ||||
|                             voice_info.node_id, voice_state, render_context.depop_buffer, | ||||
|                             mix_info->buffer_count, mix_info->buffer_offset, | ||||
|                             voice_info.was_playing); | ||||
|                     } | ||||
|                 } | ||||
|                 dest_id++; | ||||
|                 destination = splitter_context.GetDesintationData(voice_info.splitter_id, dest_id); | ||||
|             } | ||||
|         } | ||||
|     } else { | ||||
|         auto mix_info{mix_context.GetInfo(voice_info.mix_id)}; | ||||
|         command_buffer.GenerateDepopPrepareCommand( | ||||
|             voice_info.node_id, voice_state, render_context.depop_buffer, mix_info->buffer_count, | ||||
|             mix_info->buffer_offset, voice_info.was_playing); | ||||
|     } | ||||
|  | ||||
|     if (voice_info.was_playing) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (render_context.behavior->IsWaveBufferVer2Supported()) { | ||||
|         switch (voice_info.sample_format) { | ||||
|         case SampleFormat::PcmInt16: | ||||
|             command_buffer.GeneratePcmInt16Version2Command( | ||||
|                 voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count, | ||||
|                 channel); | ||||
|             break; | ||||
|         case SampleFormat::PcmFloat: | ||||
|             command_buffer.GeneratePcmFloatVersion2Command( | ||||
|                 voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count, | ||||
|                 channel); | ||||
|             break; | ||||
|         case SampleFormat::Adpcm: | ||||
|             command_buffer.GenerateAdpcmVersion2Command(voice_info.node_id, voice_info, voice_state, | ||||
|                                                         render_context.mix_buffer_count, channel); | ||||
|             break; | ||||
|         default: | ||||
|             LOG_ERROR(Service_Audio, "Invalid SampleFormat {}", | ||||
|                       static_cast<u32>(voice_info.sample_format)); | ||||
|             break; | ||||
|         } | ||||
|     } else { | ||||
|         switch (voice_info.sample_format) { | ||||
|         case SampleFormat::PcmInt16: | ||||
|             command_buffer.GeneratePcmInt16Version1Command( | ||||
|                 voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state, | ||||
|                 render_context.mix_buffer_count, channel); | ||||
|             break; | ||||
|         case SampleFormat::PcmFloat: | ||||
|             command_buffer.GeneratePcmFloatVersion1Command( | ||||
|                 voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state, | ||||
|                 render_context.mix_buffer_count, channel); | ||||
|             break; | ||||
|         case SampleFormat::Adpcm: | ||||
|             command_buffer.GenerateAdpcmVersion1Command( | ||||
|                 voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state, | ||||
|                 render_context.mix_buffer_count, channel); | ||||
|             break; | ||||
|         default: | ||||
|             LOG_ERROR(Service_Audio, "Invalid SampleFormat {}", | ||||
|                       static_cast<u32>(voice_info.sample_format)); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateVoiceMixCommand(std::span<const f32> mix_volumes, | ||||
|                                                std::span<const f32> prev_mix_volumes, | ||||
|                                                const VoiceState& voice_state, s16 output_index, | ||||
|                                                const s16 buffer_count, const s16 input_index, | ||||
|                                                const s32 node_id) { | ||||
|     u8 precision{15}; | ||||
|     if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { | ||||
|         precision = 23; | ||||
|     } | ||||
|  | ||||
|     if (buffer_count > 8) { | ||||
|         const auto prev_samples{render_context.memory_pool_info->Translate( | ||||
|             CpuAddr(voice_state.previous_samples.data()), buffer_count * sizeof(s32))}; | ||||
|         command_buffer.GenerateMixRampGroupedCommand(node_id, buffer_count, input_index, | ||||
|                                                      output_index, mix_volumes, prev_mix_volumes, | ||||
|                                                      prev_samples, precision); | ||||
|     } else { | ||||
|         for (s16 i = 0; i < buffer_count; i++, output_index++) { | ||||
|             const auto prev_samples{render_context.memory_pool_info->Translate( | ||||
|                 CpuAddr(&voice_state.previous_samples[i]), sizeof(s32))}; | ||||
|  | ||||
|             command_buffer.GenerateMixRampCommand(node_id, buffer_count, input_index, output_index, | ||||
|                                                   mix_volumes[i], prev_mix_volumes[i], prev_samples, | ||||
|                                                   precision); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, | ||||
|                                                            const VoiceState& voice_state, | ||||
|                                                            const s16 buffer_count, const s8 channel, | ||||
|                                                            const s32 node_id) { | ||||
|     const bool both_biquads_enabled{voice_info.biquads[0].enabled && voice_info.biquads[1].enabled}; | ||||
|     const auto use_float_processing{render_context.behavior->UseBiquadFilterFloatProcessing()}; | ||||
|  | ||||
|     if (both_biquads_enabled && render_context.behavior->UseMultiTapBiquadFilterProcessing() && | ||||
|         use_float_processing) { | ||||
|         command_buffer.GenerateMultitapBiquadFilterCommand(node_id, voice_info, voice_state, | ||||
|                                                            buffer_count, channel); | ||||
|     } else { | ||||
|         for (u32 i = 0; i < MaxBiquadFilters; i++) { | ||||
|             if (voice_info.biquads[i].enabled) { | ||||
|                 command_buffer.GenerateBiquadFilterCommand(node_id, voice_info, voice_state, | ||||
|                                                            buffer_count, channel, i, | ||||
|                                                            use_float_processing); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateVoiceCommand(VoiceInfo& voice_info) { | ||||
|     u8 precision{15}; | ||||
|     if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { | ||||
|         precision = 23; | ||||
|     } | ||||
|  | ||||
|     for (s8 channel = 0; channel < voice_info.channel_count; channel++) { | ||||
|         const auto resource_id{voice_info.channel_resource_ids[channel]}; | ||||
|         auto& voice_state{voice_context.GetDspSharedState(resource_id)}; | ||||
|         auto& channel_resource{voice_context.GetChannelResource(resource_id)}; | ||||
|  | ||||
|         PerformanceDetailType detail_type{PerformanceDetailType::Invalid}; | ||||
|         switch (voice_info.sample_format) { | ||||
|         case SampleFormat::PcmInt16: | ||||
|             detail_type = PerformanceDetailType::Unk1; | ||||
|             break; | ||||
|         case SampleFormat::PcmFloat: | ||||
|             detail_type = PerformanceDetailType::Unk10; | ||||
|             break; | ||||
|         default: | ||||
|             detail_type = PerformanceDetailType::Unk2; | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         DetailAspect data_source_detail(*this, PerformanceEntryType::Voice, voice_info.node_id, | ||||
|                                         detail_type); | ||||
|         GenerateDataSourceCommand(voice_info, voice_state, channel); | ||||
|  | ||||
|         if (data_source_detail.initialized) { | ||||
|             command_buffer.GeneratePerformanceCommand(data_source_detail.node_id, | ||||
|                                                       PerformanceState::Stop, | ||||
|                                                       data_source_detail.performance_entry_address); | ||||
|         } | ||||
|  | ||||
|         if (voice_info.was_playing) { | ||||
|             voice_info.prev_volume = 0.0f; | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         if (!voice_info.HasAnyConnection()) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         DetailAspect biquad_detail_aspect(*this, PerformanceEntryType::Voice, voice_info.node_id, | ||||
|                                           PerformanceDetailType::Unk4); | ||||
|         GenerateBiquadFilterCommandForVoice( | ||||
|             voice_info, voice_state, render_context.mix_buffer_count, channel, voice_info.node_id); | ||||
|  | ||||
|         if (biquad_detail_aspect.initialized) { | ||||
|             command_buffer.GeneratePerformanceCommand( | ||||
|                 biquad_detail_aspect.node_id, PerformanceState::Stop, | ||||
|                 biquad_detail_aspect.performance_entry_address); | ||||
|         } | ||||
|  | ||||
|         DetailAspect volume_ramp_detail_aspect(*this, PerformanceEntryType::Voice, | ||||
|                                                voice_info.node_id, PerformanceDetailType::Unk3); | ||||
|         command_buffer.GenerateVolumeRampCommand( | ||||
|             voice_info.node_id, voice_info, render_context.mix_buffer_count + channel, precision); | ||||
|         if (volume_ramp_detail_aspect.initialized) { | ||||
|             command_buffer.GeneratePerformanceCommand( | ||||
|                 volume_ramp_detail_aspect.node_id, PerformanceState::Stop, | ||||
|                 volume_ramp_detail_aspect.performance_entry_address); | ||||
|         } | ||||
|  | ||||
|         voice_info.prev_volume = voice_info.volume; | ||||
|  | ||||
|         if (voice_info.mix_id == UnusedMixId) { | ||||
|             if (voice_info.splitter_id != UnusedSplitterId) { | ||||
|                 auto i{channel}; | ||||
|                 auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, i)}; | ||||
|                 while (destination != nullptr) { | ||||
|                     if (destination->IsConfigured()) { | ||||
|                         const auto mix_id{destination->GetMixId()}; | ||||
|                         if (mix_id < mix_context.GetCount() && | ||||
|                             static_cast<s32>(mix_id) != UnusedSplitterId) { | ||||
|                             auto mix_info{mix_context.GetInfo(mix_id)}; | ||||
|                             GenerateVoiceMixCommand( | ||||
|                                 destination->GetMixVolume(), destination->GetMixVolumePrev(), | ||||
|                                 voice_state, mix_info->buffer_offset, mix_info->buffer_count, | ||||
|                                 render_context.mix_buffer_count + channel, voice_info.node_id); | ||||
|                             destination->MarkAsNeedToUpdateInternalState(); | ||||
|                         } | ||||
|                     } | ||||
|                     i += voice_info.channel_count; | ||||
|                     destination = splitter_context.GetDesintationData(voice_info.splitter_id, i); | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             DetailAspect volume_mix_detail_aspect(*this, PerformanceEntryType::Voice, | ||||
|                                                   voice_info.node_id, PerformanceDetailType::Unk3); | ||||
|             auto mix_info{mix_context.GetInfo(voice_info.mix_id)}; | ||||
|             GenerateVoiceMixCommand(channel_resource.mix_volumes, channel_resource.prev_mix_volumes, | ||||
|                                     voice_state, mix_info->buffer_offset, mix_info->buffer_count, | ||||
|                                     render_context.mix_buffer_count + channel, voice_info.node_id); | ||||
|             if (volume_mix_detail_aspect.initialized) { | ||||
|                 command_buffer.GeneratePerformanceCommand( | ||||
|                     volume_mix_detail_aspect.node_id, PerformanceState::Stop, | ||||
|                     volume_mix_detail_aspect.performance_entry_address); | ||||
|             } | ||||
|  | ||||
|             channel_resource.prev_mix_volumes = channel_resource.mix_volumes; | ||||
|         } | ||||
|         voice_info.biquad_initialized[0] = voice_info.biquads[0].enabled; | ||||
|         voice_info.biquad_initialized[1] = voice_info.biquads[1].enabled; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateVoiceCommands() { | ||||
|     const auto voice_count{voice_context.GetCount()}; | ||||
|  | ||||
|     for (u32 i = 0; i < voice_count; i++) { | ||||
|         auto sorted_info{voice_context.GetSortedInfo(i)}; | ||||
|  | ||||
|         if (sorted_info->ShouldSkip() || !sorted_info->UpdateForCommandGeneration(voice_context)) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         EntryAspect voice_entry_aspect(*this, PerformanceEntryType::Voice, sorted_info->node_id); | ||||
|  | ||||
|         GenerateVoiceCommand(*sorted_info); | ||||
|  | ||||
|         if (voice_entry_aspect.initialized) { | ||||
|             command_buffer.GeneratePerformanceCommand(voice_entry_aspect.node_id, | ||||
|                                                       PerformanceState::Stop, | ||||
|                                                       voice_entry_aspect.performance_entry_address); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     splitter_context.UpdateInternalState(); | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateBufferMixerCommand(const s16 buffer_offset, | ||||
|                                                   EffectInfoBase& effect_info, const s32 node_id) { | ||||
|     u8 precision{15}; | ||||
|     if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { | ||||
|         precision = 23; | ||||
|     } | ||||
|  | ||||
|     if (effect_info.IsEnabled()) { | ||||
|         const auto& parameter{ | ||||
|             *reinterpret_cast<BufferMixerInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||||
|         for (u32 i = 0; i < parameter.mix_count; i++) { | ||||
|             if (parameter.volumes[i] != 0.0f) { | ||||
|                 command_buffer.GenerateMixCommand(node_id, buffer_offset + parameter.inputs[i], | ||||
|                                                   buffer_offset + parameter.outputs[i], | ||||
|                                                   buffer_offset, parameter.volumes[i], precision); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateDelayCommand(const s16 buffer_offset, EffectInfoBase& effect_info, | ||||
|                                             const s32 node_id) { | ||||
|     command_buffer.GenerateDelayCommand(node_id, effect_info, buffer_offset); | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateReverbCommand(const s16 buffer_offset, EffectInfoBase& effect_info, | ||||
|                                              const s32 node_id, | ||||
|                                              const bool long_size_pre_delay_supported) { | ||||
|     command_buffer.GenerateReverbCommand(node_id, effect_info, buffer_offset, | ||||
|                                          long_size_pre_delay_supported); | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateI3dl2ReverbEffectCommand(const s16 buffer_offset, | ||||
|                                                         EffectInfoBase& effect_info, | ||||
|                                                         const s32 node_id) { | ||||
|     command_buffer.GenerateI3dl2ReverbCommand(node_id, effect_info, buffer_offset); | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateAuxCommand(const s16 buffer_offset, EffectInfoBase& effect_info, | ||||
|                                           const s32 node_id) { | ||||
|  | ||||
|     if (effect_info.IsEnabled()) { | ||||
|         effect_info.GetWorkbuffer(0); | ||||
|         effect_info.GetWorkbuffer(1); | ||||
|     } | ||||
|  | ||||
|     if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) { | ||||
|         const auto& parameter{ | ||||
|             *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||||
|         auto channel_index{parameter.mix_buffer_count - 1}; | ||||
|         u32 write_offset{0}; | ||||
|         for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) { | ||||
|             auto new_update_count{command_header.sample_count + write_offset}; | ||||
|             const auto update_count{channel_index > 0 ? 0 : new_update_count}; | ||||
|             command_buffer.GenerateAuxCommand(node_id, effect_info, parameter.inputs[i], | ||||
|                                               parameter.outputs[i], buffer_offset, update_count, | ||||
|                                               parameter.count_max, write_offset); | ||||
|             write_offset = new_update_count; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset, | ||||
|                                                          EffectInfoBase& effect_info, | ||||
|                                                          const s32 node_id) { | ||||
|     const auto& parameter{ | ||||
|         *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||||
|     if (effect_info.IsEnabled()) { | ||||
|         bool needs_init{false}; | ||||
|  | ||||
|         switch (parameter.state) { | ||||
|         case EffectInfoBase::ParameterState::Initialized: | ||||
|             needs_init = true; | ||||
|             break; | ||||
|         case EffectInfoBase::ParameterState::Updating: | ||||
|         case EffectInfoBase::ParameterState::Updated: | ||||
|             if (render_context.behavior->IsBiquadFilterEffectStateClearBugFixed()) { | ||||
|                 needs_init = false; | ||||
|             } else { | ||||
|                 needs_init = parameter.state == EffectInfoBase::ParameterState::Updating; | ||||
|             } | ||||
|             break; | ||||
|         default: | ||||
|             LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}", | ||||
|                       static_cast<u32>(parameter.state)); | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         for (s8 channel = 0; channel < parameter.channel_count; channel++) { | ||||
|             command_buffer.GenerateBiquadFilterCommand( | ||||
|                 node_id, effect_info, buffer_offset, channel, needs_init, | ||||
|                 render_context.behavior->UseBiquadFilterFloatProcessing()); | ||||
|         } | ||||
|     } else { | ||||
|         for (s8 channel = 0; channel < parameter.channel_count; channel++) { | ||||
|             command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset, | ||||
|                                                         channel); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateLightLimiterEffectCommand(const s16 buffer_offset, | ||||
|                                                          EffectInfoBase& effect_info, | ||||
|                                                          const s32 node_id, | ||||
|                                                          const u32 effect_index) { | ||||
|  | ||||
|     const auto& state{*reinterpret_cast<LightLimiterInfo::State*>(effect_info.GetStateBuffer())}; | ||||
|  | ||||
|     if (render_context.behavior->IsEffectInfoVersion2Supported()) { | ||||
|         const auto& parameter{ | ||||
|             *reinterpret_cast<LightLimiterInfo::ParameterVersion2*>(effect_info.GetParameter())}; | ||||
|         const auto& result_state{*reinterpret_cast<LightLimiterInfo::StatisticsInternal*>( | ||||
|             &effect_context.GetDspSharedResultState(effect_index))}; | ||||
|         command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, result_state, | ||||
|                                                    state, effect_info.IsEnabled(), | ||||
|                                                    effect_info.GetWorkbuffer(-1)); | ||||
|     } else { | ||||
|         const auto& parameter{ | ||||
|             *reinterpret_cast<LightLimiterInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||||
|         command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, state, | ||||
|                                                    effect_info.IsEnabled(), | ||||
|                                                    effect_info.GetWorkbuffer(-1)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateCaptureCommand(const s16 buffer_offset, EffectInfoBase& effect_info, | ||||
|                                               const s32 node_id) { | ||||
|     if (effect_info.IsEnabled()) { | ||||
|         effect_info.GetWorkbuffer(0); | ||||
|     } | ||||
|  | ||||
|     if (effect_info.GetSendBuffer()) { | ||||
|         const auto& parameter{ | ||||
|             *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())}; | ||||
|         auto channel_index{parameter.mix_buffer_count - 1}; | ||||
|         u32 write_offset{0}; | ||||
|         for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) { | ||||
|             auto new_update_count{command_header.sample_count + write_offset}; | ||||
|             const auto update_count{channel_index > 0 ? 0 : new_update_count}; | ||||
|             command_buffer.GenerateCaptureCommand(node_id, effect_info, parameter.inputs[i], | ||||
|                                                   parameter.outputs[i], buffer_offset, update_count, | ||||
|                                                   parameter.count_max, write_offset); | ||||
|             write_offset = new_update_count; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateCompressorCommand(const s16 buffer_offset, | ||||
|                                                  EffectInfoBase& effect_info, const s32 node_id) { | ||||
|     command_buffer.GenerateCompressorCommand(buffer_offset, effect_info, node_id); | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateEffectCommand(MixInfo& mix_info) { | ||||
|     const auto effect_count{effect_context.GetCount()}; | ||||
|     for (u32 i = 0; i < effect_count; i++) { | ||||
|         const auto effect_index{mix_info.effect_order_buffer[i]}; | ||||
|         if (effect_index == -1) { | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         auto& effect_info = effect_context.GetInfo(effect_index); | ||||
|         if (effect_info.ShouldSkip()) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         const auto entry_type{mix_info.mix_id == FinalMixId ? PerformanceEntryType::FinalMix | ||||
|                                                             : PerformanceEntryType::SubMix}; | ||||
|  | ||||
|         switch (effect_info.GetType()) { | ||||
|         case EffectInfoBase::Type::Mix: { | ||||
|             DetailAspect mix_detail_aspect(*this, entry_type, mix_info.node_id, | ||||
|                                            PerformanceDetailType::Unk5); | ||||
|             GenerateBufferMixerCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); | ||||
|             if (mix_detail_aspect.initialized) { | ||||
|                 command_buffer.GeneratePerformanceCommand( | ||||
|                     mix_detail_aspect.node_id, PerformanceState::Stop, | ||||
|                     mix_detail_aspect.performance_entry_address); | ||||
|             } | ||||
|         } break; | ||||
|  | ||||
|         case EffectInfoBase::Type::Aux: { | ||||
|             DetailAspect aux_detail_aspect(*this, entry_type, mix_info.node_id, | ||||
|                                            PerformanceDetailType::Unk7); | ||||
|             GenerateAuxCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); | ||||
|             if (aux_detail_aspect.initialized) { | ||||
|                 command_buffer.GeneratePerformanceCommand( | ||||
|                     aux_detail_aspect.node_id, PerformanceState::Stop, | ||||
|                     aux_detail_aspect.performance_entry_address); | ||||
|             } | ||||
|         } break; | ||||
|  | ||||
|         case EffectInfoBase::Type::Delay: { | ||||
|             DetailAspect delay_detail_aspect(*this, entry_type, mix_info.node_id, | ||||
|                                              PerformanceDetailType::Unk6); | ||||
|             GenerateDelayCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); | ||||
|             if (delay_detail_aspect.initialized) { | ||||
|                 command_buffer.GeneratePerformanceCommand( | ||||
|                     delay_detail_aspect.node_id, PerformanceState::Stop, | ||||
|                     delay_detail_aspect.performance_entry_address); | ||||
|             } | ||||
|         } break; | ||||
|  | ||||
|         case EffectInfoBase::Type::Reverb: { | ||||
|             DetailAspect reverb_detail_aspect(*this, entry_type, mix_info.node_id, | ||||
|                                               PerformanceDetailType::Unk8); | ||||
|             GenerateReverbCommand(mix_info.buffer_offset, effect_info, mix_info.node_id, | ||||
|                                   render_context.behavior->IsLongSizePreDelaySupported()); | ||||
|             if (reverb_detail_aspect.initialized) { | ||||
|                 command_buffer.GeneratePerformanceCommand( | ||||
|                     reverb_detail_aspect.node_id, PerformanceState::Stop, | ||||
|                     reverb_detail_aspect.performance_entry_address); | ||||
|             } | ||||
|         } break; | ||||
|  | ||||
|         case EffectInfoBase::Type::I3dl2Reverb: { | ||||
|             DetailAspect i3dl2_detail_aspect(*this, entry_type, mix_info.node_id, | ||||
|                                              PerformanceDetailType::Unk9); | ||||
|             GenerateI3dl2ReverbEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); | ||||
|             if (i3dl2_detail_aspect.initialized) { | ||||
|                 command_buffer.GeneratePerformanceCommand( | ||||
|                     i3dl2_detail_aspect.node_id, PerformanceState::Stop, | ||||
|                     i3dl2_detail_aspect.performance_entry_address); | ||||
|             } | ||||
|         } break; | ||||
|  | ||||
|         case EffectInfoBase::Type::BiquadFilter: { | ||||
|             DetailAspect biquad_detail_aspect(*this, entry_type, mix_info.node_id, | ||||
|                                               PerformanceDetailType::Unk4); | ||||
|             GenerateBiquadFilterEffectCommand(mix_info.buffer_offset, effect_info, | ||||
|                                               mix_info.node_id); | ||||
|             if (biquad_detail_aspect.initialized) { | ||||
|                 command_buffer.GeneratePerformanceCommand( | ||||
|                     biquad_detail_aspect.node_id, PerformanceState::Stop, | ||||
|                     biquad_detail_aspect.performance_entry_address); | ||||
|             } | ||||
|         } break; | ||||
|  | ||||
|         case EffectInfoBase::Type::LightLimiter: { | ||||
|             DetailAspect light_limiter_detail_aspect(*this, entry_type, mix_info.node_id, | ||||
|                                                      PerformanceDetailType::Unk11); | ||||
|             GenerateLightLimiterEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id, | ||||
|                                               effect_index); | ||||
|             if (light_limiter_detail_aspect.initialized) { | ||||
|                 command_buffer.GeneratePerformanceCommand( | ||||
|                     light_limiter_detail_aspect.node_id, PerformanceState::Stop, | ||||
|                     light_limiter_detail_aspect.performance_entry_address); | ||||
|             } | ||||
|         } break; | ||||
|  | ||||
|         case EffectInfoBase::Type::Capture: { | ||||
|             DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id, | ||||
|                                                PerformanceDetailType::Unk12); | ||||
|             GenerateCaptureCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); | ||||
|             if (capture_detail_aspect.initialized) { | ||||
|                 command_buffer.GeneratePerformanceCommand( | ||||
|                     capture_detail_aspect.node_id, PerformanceState::Stop, | ||||
|                     capture_detail_aspect.performance_entry_address); | ||||
|             } | ||||
|         } break; | ||||
|  | ||||
|         case EffectInfoBase::Type::Compressor: { | ||||
|             DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id, | ||||
|                                                PerformanceDetailType::Unk13); | ||||
|             GenerateCompressorCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); | ||||
|             if (capture_detail_aspect.initialized) { | ||||
|                 command_buffer.GeneratePerformanceCommand( | ||||
|                     capture_detail_aspect.node_id, PerformanceState::Stop, | ||||
|                     capture_detail_aspect.performance_entry_address); | ||||
|             } | ||||
|         } break; | ||||
|  | ||||
|         default: | ||||
|             LOG_ERROR(Service_Audio, "Invalid effect type {}", | ||||
|                       static_cast<u32>(effect_info.GetType())); | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         effect_info.UpdateForCommandGeneration(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateMixCommands(MixInfo& mix_info) { | ||||
|     u8 precision{15}; | ||||
|     if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { | ||||
|         precision = 23; | ||||
|     } | ||||
|  | ||||
|     if (!mix_info.HasAnyConnection()) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (mix_info.dst_mix_id == UnusedMixId) { | ||||
|         if (mix_info.dst_splitter_id != UnusedSplitterId) { | ||||
|             s16 dest_id{0}; | ||||
|             auto destination{ | ||||
|                 splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id)}; | ||||
|             while (destination != nullptr) { | ||||
|                 if (destination->IsConfigured()) { | ||||
|                     auto splitter_mix_id{destination->GetMixId()}; | ||||
|                     if (splitter_mix_id < mix_context.GetCount()) { | ||||
|                         auto splitter_mix_info{mix_context.GetInfo(splitter_mix_id)}; | ||||
|                         const s16 input_index{static_cast<s16>(mix_info.buffer_offset + | ||||
|                                                                (dest_id % mix_info.buffer_count))}; | ||||
|                         for (s16 i = 0; i < splitter_mix_info->buffer_count; i++) { | ||||
|                             auto volume{mix_info.volume * destination->GetMixVolume(i)}; | ||||
|                             if (volume != 0.0f) { | ||||
|                                 command_buffer.GenerateMixCommand( | ||||
|                                     mix_info.node_id, input_index, | ||||
|                                     splitter_mix_info->buffer_offset + i, mix_info.buffer_offset, | ||||
|                                     volume, precision); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 dest_id++; | ||||
|                 destination = | ||||
|                     splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id); | ||||
|             } | ||||
|         } | ||||
|     } else { | ||||
|         auto dest_mix_info{mix_context.GetInfo(mix_info.dst_mix_id)}; | ||||
|         for (s16 i = 0; i < mix_info.buffer_count; i++) { | ||||
|             for (s16 j = 0; j < dest_mix_info->buffer_count; j++) { | ||||
|                 auto volume{mix_info.volume * mix_info.mix_volumes[i][j]}; | ||||
|                 if (volume != 0.0f) { | ||||
|                     command_buffer.GenerateMixCommand(mix_info.node_id, mix_info.buffer_offset + i, | ||||
|                                                       dest_mix_info->buffer_offset + j, | ||||
|                                                       mix_info.buffer_offset, volume, precision); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateSubMixCommand(MixInfo& mix_info) { | ||||
|     command_buffer.GenerateDepopForMixBuffersCommand(mix_info.node_id, mix_info, | ||||
|                                                      render_context.depop_buffer); | ||||
|     GenerateEffectCommand(mix_info); | ||||
|  | ||||
|     DetailAspect mix_detail_aspect(*this, PerformanceEntryType::SubMix, mix_info.node_id, | ||||
|                                    PerformanceDetailType::Unk5); | ||||
|  | ||||
|     GenerateMixCommands(mix_info); | ||||
|  | ||||
|     if (mix_detail_aspect.initialized) { | ||||
|         command_buffer.GeneratePerformanceCommand(mix_detail_aspect.node_id, PerformanceState::Stop, | ||||
|                                                   mix_detail_aspect.performance_entry_address); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateSubMixCommands() { | ||||
|     const auto submix_count{mix_context.GetCount()}; | ||||
|     for (s32 i = 0; i < submix_count; i++) { | ||||
|         auto sorted_info{mix_context.GetSortedInfo(i)}; | ||||
|         if (!sorted_info->in_use || sorted_info->mix_id == FinalMixId) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         EntryAspect submix_entry_aspect(*this, PerformanceEntryType::SubMix, sorted_info->node_id); | ||||
|  | ||||
|         GenerateSubMixCommand(*sorted_info); | ||||
|  | ||||
|         if (submix_entry_aspect.initialized) { | ||||
|             command_buffer.GeneratePerformanceCommand( | ||||
|                 submix_entry_aspect.node_id, PerformanceState::Stop, | ||||
|                 submix_entry_aspect.performance_entry_address); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateFinalMixCommand() { | ||||
|     auto& final_mix_info{*mix_context.GetFinalMixInfo()}; | ||||
|  | ||||
|     command_buffer.GenerateDepopForMixBuffersCommand(final_mix_info.node_id, final_mix_info, | ||||
|                                                      render_context.depop_buffer); | ||||
|     GenerateEffectCommand(final_mix_info); | ||||
|  | ||||
|     u8 precision{15}; | ||||
|     if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { | ||||
|         precision = 23; | ||||
|     } | ||||
|  | ||||
|     for (s16 i = 0; i < final_mix_info.buffer_count; i++) { | ||||
|         DetailAspect volume_aspect(*this, PerformanceEntryType::FinalMix, final_mix_info.node_id, | ||||
|                                    PerformanceDetailType::Unk3); | ||||
|         command_buffer.GenerateVolumeCommand(final_mix_info.node_id, final_mix_info.buffer_offset, | ||||
|                                              i, final_mix_info.volume, precision); | ||||
|         if (volume_aspect.initialized) { | ||||
|             command_buffer.GeneratePerformanceCommand(volume_aspect.node_id, PerformanceState::Stop, | ||||
|                                                       volume_aspect.performance_entry_address); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateFinalMixCommands() { | ||||
|     auto final_mix_info{mix_context.GetFinalMixInfo()}; | ||||
|     EntryAspect final_mix_entry(*this, PerformanceEntryType::FinalMix, final_mix_info->node_id); | ||||
|     GenerateFinalMixCommand(); | ||||
|     if (final_mix_entry.initialized) { | ||||
|         command_buffer.GeneratePerformanceCommand(final_mix_entry.node_id, PerformanceState::Stop, | ||||
|                                                   final_mix_entry.performance_entry_address); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateSinkCommands() { | ||||
|     const auto sink_count{sink_context.GetCount()}; | ||||
|  | ||||
|     for (u32 i = 0; i < sink_count; i++) { | ||||
|         auto sink_info{sink_context.GetInfo(i)}; | ||||
|         if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::DeviceSink) { | ||||
|             auto state{reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info->GetState())}; | ||||
|             if (command_header.sample_rate != TargetSampleRate && | ||||
|                 state->upsampler_info == nullptr) { | ||||
|                 auto device_state{sink_info->GetDeviceState()}; | ||||
|                 device_state->upsampler_info = render_context.upsampler_manager->Allocate(); | ||||
|             } | ||||
|  | ||||
|             EntryAspect device_sink_entry(*this, PerformanceEntryType::Sink, | ||||
|                                           sink_info->GetNodeId()); | ||||
|             auto final_mix{mix_context.GetFinalMixInfo()}; | ||||
|             GenerateSinkCommand(final_mix->buffer_offset, *sink_info); | ||||
|  | ||||
|             if (device_sink_entry.initialized) { | ||||
|                 command_buffer.GeneratePerformanceCommand( | ||||
|                     device_sink_entry.node_id, PerformanceState::Stop, | ||||
|                     device_sink_entry.performance_entry_address); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     for (u32 i = 0; i < sink_count; i++) { | ||||
|         auto sink_info{sink_context.GetInfo(i)}; | ||||
|         if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::CircularBufferSink) { | ||||
|             EntryAspect circular_buffer_entry(*this, PerformanceEntryType::Sink, | ||||
|                                               sink_info->GetNodeId()); | ||||
|             auto final_mix{mix_context.GetFinalMixInfo()}; | ||||
|             GenerateSinkCommand(final_mix->buffer_offset, *sink_info); | ||||
|  | ||||
|             if (circular_buffer_entry.initialized) { | ||||
|                 command_buffer.GeneratePerformanceCommand( | ||||
|                     circular_buffer_entry.node_id, PerformanceState::Stop, | ||||
|                     circular_buffer_entry.performance_entry_address); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) { | ||||
|     if (sink_info.ShouldSkip()) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     switch (sink_info.GetType()) { | ||||
|     case SinkInfoBase::Type::DeviceSink: | ||||
|         GenerateDeviceSinkCommand(buffer_offset, sink_info); | ||||
|         break; | ||||
|  | ||||
|     case SinkInfoBase::Type::CircularBufferSink: | ||||
|         command_buffer.GenerateCircularBufferSinkCommand(sink_info.GetNodeId(), sink_info, | ||||
|                                                          buffer_offset); | ||||
|         break; | ||||
|  | ||||
|     default: | ||||
|         LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(sink_info.GetType())); | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     sink_info.UpdateForCommandGeneration(); | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GenerateDeviceSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) { | ||||
|     auto& parameter{ | ||||
|         *reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())}; | ||||
|     auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())}; | ||||
|  | ||||
|     if (render_context.channels == 2 && parameter.downmix_enabled) { | ||||
|         command_buffer.GenerateDownMix6chTo2chCommand(InvalidNodeId, parameter.inputs, | ||||
|                                                       buffer_offset, parameter.downmix_coeff); | ||||
|     } | ||||
|  | ||||
|     if (state.upsampler_info != nullptr) { | ||||
|         command_buffer.GenerateUpsampleCommand( | ||||
|             InvalidNodeId, buffer_offset, *state.upsampler_info, parameter.input_count, | ||||
|             parameter.inputs, command_header.buffer_count, command_header.sample_count, | ||||
|             command_header.sample_rate); | ||||
|     } | ||||
|  | ||||
|     command_buffer.GenerateDeviceSinkCommand(InvalidNodeId, buffer_offset, sink_info, | ||||
|                                              render_context.session_id, | ||||
|                                              command_header.samples_buffer); | ||||
| } | ||||
|  | ||||
| void CommandGenerator::GeneratePerformanceCommand( | ||||
|     s32 node_id, PerformanceState state, const PerformanceEntryAddresses& entry_addresses) { | ||||
|     command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses); | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										349
									
								
								src/audio_core/renderer/command/command_generator.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										349
									
								
								src/audio_core/renderer/command/command_generator.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,349 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <span> | ||||
|  | ||||
| #include "audio_core/renderer/command/commands.h" | ||||
| #include "audio_core/renderer/performance/performance_manager.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore { | ||||
| struct AudioRendererSystemContext; | ||||
|  | ||||
| namespace AudioRenderer { | ||||
| class CommandBuffer; | ||||
| struct CommandListHeader; | ||||
| class VoiceContext; | ||||
| class MixContext; | ||||
| class EffectContext; | ||||
| class SplitterContext; | ||||
| class SinkContext; | ||||
| class BehaviorInfo; | ||||
| class VoiceInfo; | ||||
| struct VoiceState; | ||||
| class MixInfo; | ||||
| class SinkInfoBase; | ||||
|  | ||||
| /** | ||||
|  * Generates all commands to build up a command list, which are sent to the AudioRender for | ||||
|  * processing. | ||||
|  */ | ||||
| class CommandGenerator { | ||||
| public: | ||||
|     explicit CommandGenerator(CommandBuffer& command_buffer, | ||||
|                               const CommandListHeader& command_list_header, | ||||
|                               const AudioRendererSystemContext& render_context, | ||||
|                               VoiceContext& voice_context, MixContext& mix_context, | ||||
|                               EffectContext& effect_context, SinkContext& sink_context, | ||||
|                               SplitterContext& splitter_context, | ||||
|                               PerformanceManager* performance_manager); | ||||
|  | ||||
|     /** | ||||
|      * Calculate the buffer size needed for commands. | ||||
|      * | ||||
|      * @param behavior - Used to check what features are enabled. | ||||
|      * @param params    - Input rendering parameters for numbers of voices/mixes/sinks etc. | ||||
|      */ | ||||
|     static u64 CalculateCommandBufferSize(const BehaviorInfo& behavior, | ||||
|                                           const AudioRendererParameterInternal& params) { | ||||
|         u64 size{0}; | ||||
|  | ||||
|         // Effects | ||||
|         size += params.effects * sizeof(EffectInfoBase); | ||||
|  | ||||
|         // Voices | ||||
|         u64 voice_size{0}; | ||||
|         if (behavior.IsWaveBufferVer2Supported()) { | ||||
|             voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion2Command), | ||||
|                                            sizeof(PcmInt16DataSourceVersion2Command)), | ||||
|                                   sizeof(PcmFloatDataSourceVersion2Command)); | ||||
|         } else { | ||||
|             voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion1Command), | ||||
|                                            sizeof(PcmInt16DataSourceVersion1Command)), | ||||
|                                   sizeof(PcmFloatDataSourceVersion1Command)); | ||||
|         } | ||||
|         voice_size += sizeof(BiquadFilterCommand) * MaxBiquadFilters; | ||||
|         voice_size += sizeof(VolumeRampCommand); | ||||
|         voice_size += sizeof(MixRampGroupedCommand); | ||||
|  | ||||
|         size += params.voices * (params.splitter_infos * sizeof(DepopPrepareCommand) + voice_size); | ||||
|  | ||||
|         // Sub mixes | ||||
|         size += sizeof(DepopForMixBuffersCommand) + | ||||
|                 (sizeof(MixCommand) * MaxMixBuffers) * MaxMixBuffers; | ||||
|  | ||||
|         // Final mix | ||||
|         size += sizeof(DepopForMixBuffersCommand) + sizeof(VolumeCommand) * MaxMixBuffers; | ||||
|  | ||||
|         // Splitters | ||||
|         size += params.splitter_destinations * sizeof(MixRampCommand) * MaxMixBuffers; | ||||
|  | ||||
|         // Sinks | ||||
|         size += | ||||
|             params.sinks * std::max(sizeof(DeviceSinkCommand), sizeof(CircularBufferSinkCommand)); | ||||
|  | ||||
|         // Performance | ||||
|         size += (params.effects + params.voices + params.sinks + params.sub_mixes + 1 + | ||||
|                  PerformanceManager::MaxDetailEntries) * | ||||
|                 sizeof(PerformanceCommand); | ||||
|         return size; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get the current command buffer used to generate commands. | ||||
|      * | ||||
|      * @return The command buffer. | ||||
|      */ | ||||
|     CommandBuffer& GetCommandBuffer() { | ||||
|         return command_buffer; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get the current performance manager, | ||||
|      * | ||||
|      * @return The performance manager. May be nullptr. | ||||
|      */ | ||||
|     PerformanceManager* GetPerformanceManager() { | ||||
|         return performance_manager; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Generate a data source command. | ||||
|      * These are the basis for all audio output. | ||||
|      * | ||||
|      * @param voice_info  - Generate the command from this voice. | ||||
|      * @param voice_state - State used by the AudioRenderer across calls. | ||||
|      * @param channel     - Channel index to generate the command into. | ||||
|      */ | ||||
|     void GenerateDataSourceCommand(VoiceInfo& voice_info, const VoiceState& voice_state, | ||||
|                                    s8 channel); | ||||
|  | ||||
|     /** | ||||
|      * Generate voice mixing commands. | ||||
|      * These are used to mix buffers together, to mix one input to many outputs, | ||||
|      * and also used as copy commands to move data around and prevent it being accidentally | ||||
|      * overwritten, e.g by another data source command into the same channel. | ||||
|      * | ||||
|      * @param mix_volumes      - Current volumes of the mix. | ||||
|      * @param prev_mix_volumes - Previous volumes of the mix. | ||||
|      * @param voice_state      - State used by the AudioRenderer across calls. | ||||
|      * @param output_index     - Output mix buffer index. | ||||
|      * @param buffer_count     - Number of active mix buffers. | ||||
|      * @param input_index      - Input mix buffer index. | ||||
|      * @param node_id          - Node id of the voice this command is generated for. | ||||
|      */ | ||||
|     void GenerateVoiceMixCommand(std::span<const f32> mix_volumes, | ||||
|                                  std::span<const f32> prev_mix_volumes, | ||||
|                                  const VoiceState& voice_state, s16 output_index, s16 buffer_count, | ||||
|                                  s16 input_index, s32 node_id); | ||||
|  | ||||
|     /** | ||||
|      * Generate a biquad filter command for a voice. | ||||
|      * | ||||
|      * @param voice_info   - Voice info this command is generated from. | ||||
|      * @param voice_state  - State used by the AudioRenderer across calls. | ||||
|      * @param buffer_count - Number of active mix buffers. | ||||
|      * @param channel      - Channel index of this command. | ||||
|      * @param node_id      - Node id of the voice this command is generated for. | ||||
|      */ | ||||
|     void GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, const VoiceState& voice_state, | ||||
|                                              s16 buffer_count, s8 channel, s32 node_id); | ||||
|  | ||||
|     /** | ||||
|      * Generate commands for a voice. | ||||
|      * Includes a data source, biquad filter, volume and mixing. | ||||
|      * | ||||
|      * @param voice_info - Voice info these commands are generated from. | ||||
|      */ | ||||
|     void GenerateVoiceCommand(VoiceInfo& voice_info); | ||||
|  | ||||
|     /** | ||||
|      * Generate commands for all voices. | ||||
|      */ | ||||
|     void GenerateVoiceCommands(); | ||||
|  | ||||
|     /** | ||||
|      * Generate a mixing command. | ||||
|      * | ||||
|      * @param buffer_offset    - Base mix buffer offset to use. | ||||
|      * @param effect_info_base - BufferMixer effect info. | ||||
|      * @param node_id          - Node id of the mix this command is generated for. | ||||
|      */ | ||||
|     void GenerateBufferMixerCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, | ||||
|                                     s32 node_id); | ||||
|  | ||||
|     /** | ||||
|      * Generate a delay effect command. | ||||
|      * | ||||
|      * @param buffer_offset    - Base mix buffer offset to use. | ||||
|      * @param effect_info_base - Delay effect info. | ||||
|      * @param node_id          - Node id of the mix this command is generated for. | ||||
|      */ | ||||
|     void GenerateDelayCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id); | ||||
|  | ||||
|     /** | ||||
|      * Generate a reverb effect command. | ||||
|      * | ||||
|      * @param buffer_offset                 - Base mix buffer offset to use. | ||||
|      * @param effect_info_base              - Reverb effect info. | ||||
|      * @param node_id                       - Node id of the mix this command is generated for. | ||||
|      * @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb starts. | ||||
|      */ | ||||
|     void GenerateReverbCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id, | ||||
|                                bool long_size_pre_delay_supported); | ||||
|  | ||||
|     /** | ||||
|      * Generate an I3DL2 reverb effect command. | ||||
|      * | ||||
|      * @param buffer_offset    - Base mix buffer offset to use. | ||||
|      * @param effect_info_base - I3DL2Reverb effect info. | ||||
|      * @param node_id          - Node id of the mix this command is generated for. | ||||
|      */ | ||||
|     void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, | ||||
|                                           s32 node_id); | ||||
|  | ||||
|     /** | ||||
|      * Generate an aux effect command. | ||||
|      * | ||||
|      * @param buffer_offset    - Base mix buffer offset to use. | ||||
|      * @param effect_info_base - Aux effect info. | ||||
|      * @param node_id          - Node id of the mix this command is generated for. | ||||
|      */ | ||||
|     void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); | ||||
|  | ||||
|     /** | ||||
|      * Generate a biquad filter effect command. | ||||
|      * | ||||
|      * @param buffer_offset    - Base mix buffer offset to use. | ||||
|      * @param effect_info_base - Aux effect info. | ||||
|      * @param node_id          - Node id of the mix this command is generated for. | ||||
|      */ | ||||
|     void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, | ||||
|                                            s32 node_id); | ||||
|  | ||||
|     /** | ||||
|      * Generate a light limiter effect command. | ||||
|      * | ||||
|      * @param buffer_offset    - Base mix buffer offset to use. | ||||
|      * @param effect_info_base - Limiter effect info. | ||||
|      * @param node_id          - Node id of the mix this command is generated for. | ||||
|      * @param effect_index     - Index for the statistics state. | ||||
|      */ | ||||
|     void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, | ||||
|                                            s32 node_id, u32 effect_index); | ||||
|  | ||||
|     /** | ||||
|      * Generate a capture effect command. | ||||
|      * Writes a mix buffer back to game memory. | ||||
|      * | ||||
|      * @param buffer_offset    - Base mix buffer offset to use. | ||||
|      * @param effect_info_base - Capture effect info. | ||||
|      * @param node_id          - Node id of the mix this command is generated for. | ||||
|      */ | ||||
|     void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); | ||||
|  | ||||
|     /** | ||||
|      * Generate a compressor effect command. | ||||
|      * | ||||
|      * @param buffer_offset    - Base mix buffer offset to use. | ||||
|      * @param effect_info_base - Compressor effect info. | ||||
|      * @param node_id          - Node id of the mix this command is generated for. | ||||
|      */ | ||||
|     void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info, | ||||
|                                    const s32 node_id); | ||||
|  | ||||
|     /** | ||||
|      * Generate all effect commands for a mix. | ||||
|      * | ||||
|      * @param mix_info - Mix to generate effects from. | ||||
|      */ | ||||
|     void GenerateEffectCommand(MixInfo& mix_info); | ||||
|  | ||||
|     /** | ||||
|      * Generate all mix commands. | ||||
|      * | ||||
|      * @param mix_info - Mix to generate effects from. | ||||
|      */ | ||||
|     void GenerateMixCommands(MixInfo& mix_info); | ||||
|  | ||||
|     /** | ||||
|      * Generate a submix command. | ||||
|      * Generates all effects and all mixing commands. | ||||
|      * | ||||
|      * @param mix_info - Mix to generate effects from. | ||||
|      */ | ||||
|     void GenerateSubMixCommand(MixInfo& mix_info); | ||||
|  | ||||
|     /** | ||||
|      * Generate all submix command. | ||||
|      */ | ||||
|     void GenerateSubMixCommands(); | ||||
|  | ||||
|     /** | ||||
|      * Generate the final mix. | ||||
|      */ | ||||
|     void GenerateFinalMixCommand(); | ||||
|  | ||||
|     /** | ||||
|      * Generate the final mix commands. | ||||
|      */ | ||||
|     void GenerateFinalMixCommands(); | ||||
|  | ||||
|     /** | ||||
|      * Generate all sink commands. | ||||
|      */ | ||||
|     void GenerateSinkCommands(); | ||||
|  | ||||
|     /** | ||||
|      * Generate a sink command. | ||||
|      * Sends samples out to the backend, or a game-supplied circular buffer. | ||||
|      * | ||||
|      * @param buffer_offset - Base mix buffer offset to use. | ||||
|      * @param sink_info     - Sink info to generate the commands from. | ||||
|      */ | ||||
|     void GenerateSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info); | ||||
|  | ||||
|     /** | ||||
|      * Generate a device sink command. | ||||
|      * Sends samples out to the backend. | ||||
|      * | ||||
|      * @param buffer_offset - Base mix buffer offset to use. | ||||
|      * @param sink_info     - Sink info to generate the commands from. | ||||
|      */ | ||||
|     void GenerateDeviceSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info); | ||||
|  | ||||
|     /** | ||||
|      * Generate a performance command. | ||||
|      * Used to report performance metrics of the AudioRenderer back to the game. | ||||
|      * | ||||
|      * @param buffer_offset - Base mix buffer offset to use. | ||||
|      * @param sink_info     - Sink info to generate the commands from. | ||||
|      */ | ||||
|     void GeneratePerformanceCommand(s32 node_id, PerformanceState state, | ||||
|                                     const PerformanceEntryAddresses& entry_addresses); | ||||
|  | ||||
| private: | ||||
|     /// Commands will be written by this buffer | ||||
|     CommandBuffer& command_buffer; | ||||
|     /// Header information for the commands generated | ||||
|     const CommandListHeader& command_header; | ||||
|     /// Various things to control generation | ||||
|     const AudioRendererSystemContext& render_context; | ||||
|     /// Used for generating voices | ||||
|     VoiceContext& voice_context; | ||||
|     /// Used for generating mixes | ||||
|     MixContext& mix_context; | ||||
|     /// Used for generating effects | ||||
|     EffectContext& effect_context; | ||||
|     /// Used for generating sinks | ||||
|     SinkContext& sink_context; | ||||
|     /// Used for generating submixes | ||||
|     SplitterContext& splitter_context; | ||||
|     /// Used for generating performance | ||||
|     PerformanceManager* performance_manager; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioRenderer | ||||
| } // namespace AudioCore | ||||
							
								
								
									
										22
									
								
								src/audio_core/renderer/command/command_list_header.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/audio_core/renderer/command/command_list_header.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <span> | ||||
|  | ||||
| #include "audio_core/common/common.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
|  | ||||
| struct CommandListHeader { | ||||
|     u64 buffer_size; | ||||
|     u32 command_count; | ||||
|     std::span<s32> samples_buffer; | ||||
|     s16 buffer_count; | ||||
|     u32 sample_count; | ||||
|     u32 sample_rate; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -0,0 +1,254 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "audio_core/renderer/command/commands.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
| /** | ||||
|  * Estimate the processing time required for all commands. | ||||
|  */ | ||||
| class ICommandProcessingTimeEstimator { | ||||
| public: | ||||
|     virtual ~ICommandProcessingTimeEstimator() = default; | ||||
|  | ||||
|     virtual u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const = 0; | ||||
|     virtual u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const = 0; | ||||
|     virtual u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const = 0; | ||||
|     virtual u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const = 0; | ||||
|     virtual u32 Estimate(const AdpcmDataSourceVersion1Command& command) const = 0; | ||||
|     virtual u32 Estimate(const AdpcmDataSourceVersion2Command& command) const = 0; | ||||
|     virtual u32 Estimate(const VolumeCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const VolumeRampCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const BiquadFilterCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const MixCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const MixRampCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const MixRampGroupedCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const DepopPrepareCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const DepopForMixBuffersCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const DelayCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const UpsampleCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const DownMix6chTo2chCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const AuxCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const DeviceSinkCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const CircularBufferSinkCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const ReverbCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const I3dl2ReverbCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const PerformanceCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const ClearMixBufferCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const CopyMixBufferCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const LightLimiterVersion1Command& command) const = 0; | ||||
|     virtual u32 Estimate(const LightLimiterVersion2Command& command) const = 0; | ||||
|     virtual u32 Estimate(const MultiTapBiquadFilterCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const CaptureCommand& command) const = 0; | ||||
|     virtual u32 Estimate(const CompressorCommand& command) const = 0; | ||||
| }; | ||||
|  | ||||
| class CommandProcessingTimeEstimatorVersion1 final : public ICommandProcessingTimeEstimator { | ||||
| public: | ||||
|     CommandProcessingTimeEstimatorVersion1(u32 sample_count_, u32 buffer_count_) | ||||
|         : sample_count{sample_count_}, buffer_count{buffer_count_} {} | ||||
|  | ||||
|     u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; | ||||
|     u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; | ||||
|     u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; | ||||
|     u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; | ||||
|     u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; | ||||
|     u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; | ||||
|     u32 Estimate(const VolumeCommand& command) const override; | ||||
|     u32 Estimate(const VolumeRampCommand& command) const override; | ||||
|     u32 Estimate(const BiquadFilterCommand& command) const override; | ||||
|     u32 Estimate(const MixCommand& command) const override; | ||||
|     u32 Estimate(const MixRampCommand& command) const override; | ||||
|     u32 Estimate(const MixRampGroupedCommand& command) const override; | ||||
|     u32 Estimate(const DepopPrepareCommand& command) const override; | ||||
|     u32 Estimate(const DepopForMixBuffersCommand& command) const override; | ||||
|     u32 Estimate(const DelayCommand& command) const override; | ||||
|     u32 Estimate(const UpsampleCommand& command) const override; | ||||
|     u32 Estimate(const DownMix6chTo2chCommand& command) const override; | ||||
|     u32 Estimate(const AuxCommand& command) const override; | ||||
|     u32 Estimate(const DeviceSinkCommand& command) const override; | ||||
|     u32 Estimate(const CircularBufferSinkCommand& command) const override; | ||||
|     u32 Estimate(const ReverbCommand& command) const override; | ||||
|     u32 Estimate(const I3dl2ReverbCommand& command) const override; | ||||
|     u32 Estimate(const PerformanceCommand& command) const override; | ||||
|     u32 Estimate(const ClearMixBufferCommand& command) const override; | ||||
|     u32 Estimate(const CopyMixBufferCommand& command) const override; | ||||
|     u32 Estimate(const LightLimiterVersion1Command& command) const override; | ||||
|     u32 Estimate(const LightLimiterVersion2Command& command) const override; | ||||
|     u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; | ||||
|     u32 Estimate(const CaptureCommand& command) const override; | ||||
|     u32 Estimate(const CompressorCommand& command) const override; | ||||
|  | ||||
| private: | ||||
|     u32 sample_count{}; | ||||
|     u32 buffer_count{}; | ||||
| }; | ||||
|  | ||||
| class CommandProcessingTimeEstimatorVersion2 final : public ICommandProcessingTimeEstimator { | ||||
| public: | ||||
|     CommandProcessingTimeEstimatorVersion2(u32 sample_count_, u32 buffer_count_) | ||||
|         : sample_count{sample_count_}, buffer_count{buffer_count_} {} | ||||
|  | ||||
|     u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; | ||||
|     u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; | ||||
|     u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; | ||||
|     u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; | ||||
|     u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; | ||||
|     u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; | ||||
|     u32 Estimate(const VolumeCommand& command) const override; | ||||
|     u32 Estimate(const VolumeRampCommand& command) const override; | ||||
|     u32 Estimate(const BiquadFilterCommand& command) const override; | ||||
|     u32 Estimate(const MixCommand& command) const override; | ||||
|     u32 Estimate(const MixRampCommand& command) const override; | ||||
|     u32 Estimate(const MixRampGroupedCommand& command) const override; | ||||
|     u32 Estimate(const DepopPrepareCommand& command) const override; | ||||
|     u32 Estimate(const DepopForMixBuffersCommand& command) const override; | ||||
|     u32 Estimate(const DelayCommand& command) const override; | ||||
|     u32 Estimate(const UpsampleCommand& command) const override; | ||||
|     u32 Estimate(const DownMix6chTo2chCommand& command) const override; | ||||
|     u32 Estimate(const AuxCommand& command) const override; | ||||
|     u32 Estimate(const DeviceSinkCommand& command) const override; | ||||
|     u32 Estimate(const CircularBufferSinkCommand& command) const override; | ||||
|     u32 Estimate(const ReverbCommand& command) const override; | ||||
|     u32 Estimate(const I3dl2ReverbCommand& command) const override; | ||||
|     u32 Estimate(const PerformanceCommand& command) const override; | ||||
|     u32 Estimate(const ClearMixBufferCommand& command) const override; | ||||
|     u32 Estimate(const CopyMixBufferCommand& command) const override; | ||||
|     u32 Estimate(const LightLimiterVersion1Command& command) const override; | ||||
|     u32 Estimate(const LightLimiterVersion2Command& command) const override; | ||||
|     u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; | ||||
|     u32 Estimate(const CaptureCommand& command) const override; | ||||
|     u32 Estimate(const CompressorCommand& command) const override; | ||||
|  | ||||
| private: | ||||
|     u32 sample_count{}; | ||||
|     u32 buffer_count{}; | ||||
| }; | ||||
|  | ||||
| class CommandProcessingTimeEstimatorVersion3 final : public ICommandProcessingTimeEstimator { | ||||
| public: | ||||
|     CommandProcessingTimeEstimatorVersion3(u32 sample_count_, u32 buffer_count_) | ||||
|         : sample_count{sample_count_}, buffer_count{buffer_count_} {} | ||||
|  | ||||
|     u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; | ||||
|     u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; | ||||
|     u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; | ||||
|     u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; | ||||
|     u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; | ||||
|     u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; | ||||
|     u32 Estimate(const VolumeCommand& command) const override; | ||||
|     u32 Estimate(const VolumeRampCommand& command) const override; | ||||
|     u32 Estimate(const BiquadFilterCommand& command) const override; | ||||
|     u32 Estimate(const MixCommand& command) const override; | ||||
|     u32 Estimate(const MixRampCommand& command) const override; | ||||
|     u32 Estimate(const MixRampGroupedCommand& command) const override; | ||||
|     u32 Estimate(const DepopPrepareCommand& command) const override; | ||||
|     u32 Estimate(const DepopForMixBuffersCommand& command) const override; | ||||
|     u32 Estimate(const DelayCommand& command) const override; | ||||
|     u32 Estimate(const UpsampleCommand& command) const override; | ||||
|     u32 Estimate(const DownMix6chTo2chCommand& command) const override; | ||||
|     u32 Estimate(const AuxCommand& command) const override; | ||||
|     u32 Estimate(const DeviceSinkCommand& command) const override; | ||||
|     u32 Estimate(const CircularBufferSinkCommand& command) const override; | ||||
|     u32 Estimate(const ReverbCommand& command) const override; | ||||
|     u32 Estimate(const I3dl2ReverbCommand& command) const override; | ||||
|     u32 Estimate(const PerformanceCommand& command) const override; | ||||
|     u32 Estimate(const ClearMixBufferCommand& command) const override; | ||||
|     u32 Estimate(const CopyMixBufferCommand& command) const override; | ||||
|     u32 Estimate(const LightLimiterVersion1Command& command) const override; | ||||
|     u32 Estimate(const LightLimiterVersion2Command& command) const override; | ||||
|     u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; | ||||
|     u32 Estimate(const CaptureCommand& command) const override; | ||||
|     u32 Estimate(const CompressorCommand& command) const override; | ||||
|  | ||||
| private: | ||||
|     u32 sample_count{}; | ||||
|     u32 buffer_count{}; | ||||
| }; | ||||
|  | ||||
| class CommandProcessingTimeEstimatorVersion4 final : public ICommandProcessingTimeEstimator { | ||||
| public: | ||||
|     CommandProcessingTimeEstimatorVersion4(u32 sample_count_, u32 buffer_count_) | ||||
|         : sample_count{sample_count_}, buffer_count{buffer_count_} {} | ||||
|  | ||||
|     u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; | ||||
|     u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; | ||||
|     u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; | ||||
|     u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; | ||||
|     u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; | ||||
|     u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; | ||||
|     u32 Estimate(const VolumeCommand& command) const override; | ||||
|     u32 Estimate(const VolumeRampCommand& command) const override; | ||||
|     u32 Estimate(const BiquadFilterCommand& command) const override; | ||||
|     u32 Estimate(const MixCommand& command) const override; | ||||
|     u32 Estimate(const MixRampCommand& command) const override; | ||||
|     u32 Estimate(const MixRampGroupedCommand& command) const override; | ||||
|     u32 Estimate(const DepopPrepareCommand& command) const override; | ||||
|     u32 Estimate(const DepopForMixBuffersCommand& command) const override; | ||||
|     u32 Estimate(const DelayCommand& command) const override; | ||||
|     u32 Estimate(const UpsampleCommand& command) const override; | ||||
|     u32 Estimate(const DownMix6chTo2chCommand& command) const override; | ||||
|     u32 Estimate(const AuxCommand& command) const override; | ||||
|     u32 Estimate(const DeviceSinkCommand& command) const override; | ||||
|     u32 Estimate(const CircularBufferSinkCommand& command) const override; | ||||
|     u32 Estimate(const ReverbCommand& command) const override; | ||||
|     u32 Estimate(const I3dl2ReverbCommand& command) const override; | ||||
|     u32 Estimate(const PerformanceCommand& command) const override; | ||||
|     u32 Estimate(const ClearMixBufferCommand& command) const override; | ||||
|     u32 Estimate(const CopyMixBufferCommand& command) const override; | ||||
|     u32 Estimate(const LightLimiterVersion1Command& command) const override; | ||||
|     u32 Estimate(const LightLimiterVersion2Command& command) const override; | ||||
|     u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; | ||||
|     u32 Estimate(const CaptureCommand& command) const override; | ||||
|     u32 Estimate(const CompressorCommand& command) const override; | ||||
|  | ||||
| private: | ||||
|     u32 sample_count{}; | ||||
|     u32 buffer_count{}; | ||||
| }; | ||||
|  | ||||
| class CommandProcessingTimeEstimatorVersion5 final : public ICommandProcessingTimeEstimator { | ||||
| public: | ||||
|     CommandProcessingTimeEstimatorVersion5(u32 sample_count_, u32 buffer_count_) | ||||
|         : sample_count{sample_count_}, buffer_count{buffer_count_} {} | ||||
|  | ||||
|     u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; | ||||
|     u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; | ||||
|     u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; | ||||
|     u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; | ||||
|     u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; | ||||
|     u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; | ||||
|     u32 Estimate(const VolumeCommand& command) const override; | ||||
|     u32 Estimate(const VolumeRampCommand& command) const override; | ||||
|     u32 Estimate(const BiquadFilterCommand& command) const override; | ||||
|     u32 Estimate(const MixCommand& command) const override; | ||||
|     u32 Estimate(const MixRampCommand& command) const override; | ||||
|     u32 Estimate(const MixRampGroupedCommand& command) const override; | ||||
|     u32 Estimate(const DepopPrepareCommand& command) const override; | ||||
|     u32 Estimate(const DepopForMixBuffersCommand& command) const override; | ||||
|     u32 Estimate(const DelayCommand& command) const override; | ||||
|     u32 Estimate(const UpsampleCommand& command) const override; | ||||
|     u32 Estimate(const DownMix6chTo2chCommand& command) const override; | ||||
|     u32 Estimate(const AuxCommand& command) const override; | ||||
|     u32 Estimate(const DeviceSinkCommand& command) const override; | ||||
|     u32 Estimate(const CircularBufferSinkCommand& command) const override; | ||||
|     u32 Estimate(const ReverbCommand& command) const override; | ||||
|     u32 Estimate(const I3dl2ReverbCommand& command) const override; | ||||
|     u32 Estimate(const PerformanceCommand& command) const override; | ||||
|     u32 Estimate(const ClearMixBufferCommand& command) const override; | ||||
|     u32 Estimate(const CopyMixBufferCommand& command) const override; | ||||
|     u32 Estimate(const LightLimiterVersion1Command& command) const override; | ||||
|     u32 Estimate(const LightLimiterVersion2Command& command) const override; | ||||
|     u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; | ||||
|     u32 Estimate(const CaptureCommand& command) const override; | ||||
|     u32 Estimate(const CompressorCommand& command) const override; | ||||
|  | ||||
| private: | ||||
|     u32 sample_count{}; | ||||
|     u32 buffer_count{}; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										32
									
								
								src/audio_core/renderer/command/commands.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/audio_core/renderer/command/commands.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "audio_core/renderer/command/data_source/adpcm.h" | ||||
| #include "audio_core/renderer/command/data_source/pcm_float.h" | ||||
| #include "audio_core/renderer/command/data_source/pcm_int16.h" | ||||
| #include "audio_core/renderer/command/effect/aux_.h" | ||||
| #include "audio_core/renderer/command/effect/biquad_filter.h" | ||||
| #include "audio_core/renderer/command/effect/capture.h" | ||||
| #include "audio_core/renderer/command/effect/compressor.h" | ||||
| #include "audio_core/renderer/command/effect/delay.h" | ||||
| #include "audio_core/renderer/command/effect/i3dl2_reverb.h" | ||||
| #include "audio_core/renderer/command/effect/light_limiter.h" | ||||
| #include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h" | ||||
| #include "audio_core/renderer/command/effect/reverb.h" | ||||
| #include "audio_core/renderer/command/icommand.h" | ||||
| #include "audio_core/renderer/command/mix/clear_mix.h" | ||||
| #include "audio_core/renderer/command/mix/copy_mix.h" | ||||
| #include "audio_core/renderer/command/mix/depop_for_mix_buffers.h" | ||||
| #include "audio_core/renderer/command/mix/depop_prepare.h" | ||||
| #include "audio_core/renderer/command/mix/mix.h" | ||||
| #include "audio_core/renderer/command/mix/mix_ramp.h" | ||||
| #include "audio_core/renderer/command/mix/mix_ramp_grouped.h" | ||||
| #include "audio_core/renderer/command/mix/volume.h" | ||||
| #include "audio_core/renderer/command/mix/volume_ramp.h" | ||||
| #include "audio_core/renderer/command/performance/performance.h" | ||||
| #include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h" | ||||
| #include "audio_core/renderer/command/resample/upsample.h" | ||||
| #include "audio_core/renderer/command/sink/circular_buffer.h" | ||||
| #include "audio_core/renderer/command/sink/device.h" | ||||
							
								
								
									
										84
									
								
								src/audio_core/renderer/command/data_source/adpcm.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/audio_core/renderer/command/data_source/adpcm.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <span> | ||||
|  | ||||
| #include "audio_core/renderer/adsp/command_list_processor.h" | ||||
| #include "audio_core/renderer/command/data_source/adpcm.h" | ||||
| #include "audio_core/renderer/command/data_source/decode.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
|  | ||||
| void AdpcmDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, | ||||
|                                           std::string& string) { | ||||
|     string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample " | ||||
|                           "rate {} target sample rate {} src quality {}\n", | ||||
|                           output_index, sample_rate, processor.target_sample_rate, src_quality); | ||||
| } | ||||
|  | ||||
| void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { | ||||
|     auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, | ||||
|                                                   processor.sample_count)}; | ||||
|  | ||||
|     DecodeFromWaveBuffersArgs args{ | ||||
|         .sample_format{SampleFormat::Adpcm}, | ||||
|         .output{out_buffer}, | ||||
|         .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, | ||||
|         .wave_buffers{wave_buffers}, | ||||
|         .channel{0}, | ||||
|         .channel_count{1}, | ||||
|         .src_quality{src_quality}, | ||||
|         .pitch{pitch}, | ||||
|         .source_sample_rate{sample_rate}, | ||||
|         .target_sample_rate{processor.target_sample_rate}, | ||||
|         .sample_count{processor.sample_count}, | ||||
|         .data_address{data_address}, | ||||
|         .data_size{data_size}, | ||||
|         .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, | ||||
|         .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, | ||||
|     }; | ||||
|  | ||||
|     DecodeFromWaveBuffers(*processor.memory, args); | ||||
| } | ||||
|  | ||||
| bool AdpcmDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| void AdpcmDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, | ||||
|                                           std::string& string) { | ||||
|     string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample " | ||||
|                           "rate {} target sample rate {} src quality {}\n", | ||||
|                           output_index, sample_rate, processor.target_sample_rate, src_quality); | ||||
| } | ||||
|  | ||||
| void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { | ||||
|     auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, | ||||
|                                                   processor.sample_count)}; | ||||
|  | ||||
|     DecodeFromWaveBuffersArgs args{ | ||||
|         .sample_format{SampleFormat::Adpcm}, | ||||
|         .output{out_buffer}, | ||||
|         .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, | ||||
|         .wave_buffers{wave_buffers}, | ||||
|         .channel{0}, | ||||
|         .channel_count{1}, | ||||
|         .src_quality{src_quality}, | ||||
|         .pitch{pitch}, | ||||
|         .source_sample_rate{sample_rate}, | ||||
|         .target_sample_rate{processor.target_sample_rate}, | ||||
|         .sample_count{processor.sample_count}, | ||||
|         .data_address{data_address}, | ||||
|         .data_size{data_size}, | ||||
|         .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, | ||||
|         .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, | ||||
|     }; | ||||
|  | ||||
|     DecodeFromWaveBuffers(*processor.memory, args); | ||||
| } | ||||
|  | ||||
| bool AdpcmDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										119
									
								
								src/audio_core/renderer/command/data_source/adpcm.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								src/audio_core/renderer/command/data_source/adpcm.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <string> | ||||
|  | ||||
| #include "audio_core/common/common.h" | ||||
| #include "audio_core/common/wave_buffer.h" | ||||
| #include "audio_core/renderer/command/icommand.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
| namespace ADSP { | ||||
| class CommandListProcessor; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers | ||||
|  * into the output_index mix buffer. | ||||
|  */ | ||||
| struct AdpcmDataSourceVersion1Command : ICommand { | ||||
|     /** | ||||
|      * Print this command's information to a string. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @param string    - The string to print into. | ||||
|      */ | ||||
|     void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||||
|  | ||||
|     /** | ||||
|      * Process this command. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      */ | ||||
|     void Process(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /** | ||||
|      * Verify this command's data is valid. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @return True if the command is valid, otherwise false. | ||||
|      */ | ||||
|     bool Verify(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /// Quality used for sample rate conversion | ||||
|     SrcQuality src_quality; | ||||
|     /// Mix buffer index for decoded samples | ||||
|     s16 output_index; | ||||
|     /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | ||||
|     u16 flags; | ||||
|     /// Wavebuffer sample rate | ||||
|     u32 sample_rate; | ||||
|     /// Pitch used for sample rate conversion | ||||
|     f32 pitch; | ||||
|     /// Wavebuffers containing the wavebuffer address, context address, looping information etc | ||||
|     std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; | ||||
|     /// Voice state, updated each call and written back to game | ||||
|     CpuAddr voice_state; | ||||
|     /// Coefficients data address | ||||
|     CpuAddr data_address; | ||||
|     /// Coefficients data size | ||||
|     u64 data_size; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * AudioRenderer command to decode ADPCM-encoded version 2 wavebuffers | ||||
|  * into the output_index mix buffer. | ||||
|  */ | ||||
| struct AdpcmDataSourceVersion2Command : ICommand { | ||||
|     /** | ||||
|      * Print this command's information to a string. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @param string    - The string to print into. | ||||
|      */ | ||||
|     void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||||
|  | ||||
|     /** | ||||
|      * Process this command. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      */ | ||||
|     void Process(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /** | ||||
|      * Verify this command's data is valid. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @return True if the command is valid, otherwise false. | ||||
|      */ | ||||
|     bool Verify(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /// Quality used for sample rate conversion | ||||
|     SrcQuality src_quality; | ||||
|     /// Mix buffer index for decoded samples | ||||
|     s16 output_index; | ||||
|     /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | ||||
|     u16 flags; | ||||
|     /// Wavebuffer sample rate | ||||
|     u32 sample_rate; | ||||
|     /// Pitch used for sample rate conversion | ||||
|     f32 pitch; | ||||
|     /// Target channel to read within the wavebuffer | ||||
|     s8 channel_index; | ||||
|     /// Number of channels within the wavebuffer | ||||
|     s8 channel_count; | ||||
|     /// Wavebuffers containing the wavebuffer address, context address, looping information etc | ||||
|     std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; | ||||
|     /// Voice state, updated each call and written back to game | ||||
|     CpuAddr voice_state; | ||||
|     /// Coefficients data address | ||||
|     CpuAddr data_address; | ||||
|     /// Coefficients data size | ||||
|     u64 data_size; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										428
									
								
								src/audio_core/renderer/command/data_source/decode.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										428
									
								
								src/audio_core/renderer/command/data_source/decode.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,428 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <array> | ||||
| #include <vector> | ||||
|  | ||||
| #include "audio_core/renderer/command/data_source/decode.h" | ||||
| #include "audio_core/renderer/command/resample/resample.h" | ||||
| #include "common/fixed_point.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/memory.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
|  | ||||
| constexpr u32 TempBufferSize = 0x3F00; | ||||
| constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4}; | ||||
|  | ||||
| /** | ||||
|  * Decode PCM data. Only s16 or f32 is supported. | ||||
|  * | ||||
|  * @tparam T         - Type to decode. Only s16 and f32 are supported. | ||||
|  * @param memory     - Core memory for reading samples. | ||||
|  * @param out_buffer - Output mix buffer to receive the samples. | ||||
|  * @param req        - Information for how to decode. | ||||
|  * @return Number of samples decoded. | ||||
|  */ | ||||
| template <typename T> | ||||
| static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, | ||||
|                      const DecodeArg& req) { | ||||
|     constexpr s32 min{std::numeric_limits<s16>::min()}; | ||||
|     constexpr s32 max{std::numeric_limits<s16>::max()}; | ||||
|  | ||||
|     if (req.buffer == 0 || req.buffer_size == 0) { | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     if (req.start_offset >= req.end_offset) { | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     auto samples_to_decode{ | ||||
|         std::min(req.samples_to_read, req.end_offset - req.start_offset - req.offset)}; | ||||
|     u32 channel_count{static_cast<u32>(req.channel_count)}; | ||||
|  | ||||
|     switch (req.channel_count) { | ||||
|     default: { | ||||
|         const VAddr source{req.buffer + | ||||
|                            (((req.start_offset + req.offset) * channel_count) * sizeof(T))}; | ||||
|         const u64 size{channel_count * samples_to_decode}; | ||||
|         const u64 size_bytes{size * sizeof(T)}; | ||||
|  | ||||
|         std::vector<T> samples(size); | ||||
|         memory.ReadBlockUnsafe(source, samples.data(), size_bytes); | ||||
|  | ||||
|         if constexpr (std::is_floating_point_v<T>) { | ||||
|             for (u32 i = 0; i < samples_to_decode; i++) { | ||||
|                 auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] * | ||||
|                                              std::numeric_limits<s16>::max())}; | ||||
|                 out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max)); | ||||
|             } | ||||
|         } else { | ||||
|             for (u32 i = 0; i < samples_to_decode; i++) { | ||||
|                 out_buffer[i] = samples[i * channel_count + req.target_channel]; | ||||
|             } | ||||
|         } | ||||
|     } break; | ||||
|  | ||||
|     case 1: | ||||
|         if (req.target_channel != 0) { | ||||
|             LOG_ERROR(Service_Audio, "Invalid target channel, expected 0, got {}", | ||||
|                       req.target_channel); | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))}; | ||||
|         std::vector<T> samples(samples_to_decode); | ||||
|         memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T)); | ||||
|  | ||||
|         if constexpr (std::is_floating_point_v<T>) { | ||||
|             for (u32 i = 0; i < samples_to_decode; i++) { | ||||
|                 auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] * | ||||
|                                              std::numeric_limits<s16>::max())}; | ||||
|                 out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max)); | ||||
|             } | ||||
|         } else { | ||||
|             std::memcpy(out_buffer.data(), samples.data(), samples_to_decode * sizeof(s16)); | ||||
|         } | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     return samples_to_decode; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Decode ADPCM data. | ||||
|  * | ||||
|  * @param memory     - Core memory for reading samples. | ||||
|  * @param out_buffer - Output mix buffer to receive the samples. | ||||
|  * @param req        - Information for how to decode. | ||||
|  * @return Number of samples decoded. | ||||
|  */ | ||||
| static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, | ||||
|                        const DecodeArg& req) { | ||||
|     constexpr u32 SamplesPerFrame{14}; | ||||
|     constexpr u32 NibblesPerFrame{16}; | ||||
|  | ||||
|     if (req.buffer == 0 || req.buffer_size == 0) { | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     if (req.end_offset < req.start_offset) { | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     auto end{(req.end_offset % SamplesPerFrame) + | ||||
|              NibblesPerFrame * (req.end_offset / SamplesPerFrame)}; | ||||
|     if (req.end_offset % SamplesPerFrame) { | ||||
|         end += 3; | ||||
|     } else { | ||||
|         end += 1; | ||||
|     } | ||||
|  | ||||
|     if (req.buffer_size < end / 2) { | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     auto samples_to_process{ | ||||
|         std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)}; | ||||
|  | ||||
|     auto samples_to_read{samples_to_process}; | ||||
|     auto start_pos{req.start_offset + req.offset}; | ||||
|     auto samples_remaining_in_frame{start_pos % SamplesPerFrame}; | ||||
|     auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame + | ||||
|                            samples_remaining_in_frame}; | ||||
|  | ||||
|     if (samples_remaining_in_frame) { | ||||
|         position_in_frame += 2; | ||||
|     } | ||||
|  | ||||
|     const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)}; | ||||
|     std::vector<u8> wavebuffer(size); | ||||
|     memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(), | ||||
|                            wavebuffer.size()); | ||||
|  | ||||
|     auto context{req.adpcm_context}; | ||||
|     auto header{context->header}; | ||||
|     u8 coeff_index{static_cast<u8>((header >> 4U) & 0xFU)}; | ||||
|     u8 scale{static_cast<u8>(header & 0xFU)}; | ||||
|     s32 coeff0{req.coefficients[coeff_index * 2 + 0]}; | ||||
|     s32 coeff1{req.coefficients[coeff_index * 2 + 1]}; | ||||
|  | ||||
|     auto yn0{context->yn0}; | ||||
|     auto yn1{context->yn1}; | ||||
|  | ||||
|     static constexpr std::array<s32, 16> Steps{ | ||||
|         0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1, | ||||
|     }; | ||||
|  | ||||
|     const auto decode_sample = [&](const s32 code) -> s16 { | ||||
|         const auto xn = code * (1 << scale); | ||||
|         const auto prediction = coeff0 * yn0 + coeff1 * yn1; | ||||
|         const auto sample = ((xn << 11) + 0x400 + prediction) >> 11; | ||||
|         const auto saturated = std::clamp<s32>(sample, -0x8000, 0x7FFF); | ||||
|         yn1 = yn0; | ||||
|         yn0 = static_cast<s16>(saturated); | ||||
|         return yn0; | ||||
|     }; | ||||
|  | ||||
|     u32 read_index{0}; | ||||
|     u32 write_index{0}; | ||||
|  | ||||
|     while (samples_to_read > 0) { | ||||
|         // Are we at a new frame? | ||||
|         if ((position_in_frame % NibblesPerFrame) == 0) { | ||||
|             header = wavebuffer[read_index++]; | ||||
|             coeff_index = (header >> 4) & 0xF; | ||||
|             scale = header & 0xF; | ||||
|             coeff0 = req.coefficients[coeff_index * 2 + 0]; | ||||
|             coeff1 = req.coefficients[coeff_index * 2 + 1]; | ||||
|             position_in_frame += 2; | ||||
|  | ||||
|             // Can we consume all of this frame's samples? | ||||
|             if (samples_to_read >= SamplesPerFrame) { | ||||
|                 // Can grab all samples until the next header | ||||
|                 for (u32 i = 0; i < SamplesPerFrame / 2; i++) { | ||||
|                     auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]}; | ||||
|                     auto code1{Steps[wavebuffer[read_index] & 0xF]}; | ||||
|                     read_index++; | ||||
|  | ||||
|                     out_buffer[write_index++] = decode_sample(code0); | ||||
|                     out_buffer[write_index++] = decode_sample(code1); | ||||
|                 } | ||||
|  | ||||
|                 position_in_frame += SamplesPerFrame; | ||||
|                 samples_to_read -= SamplesPerFrame; | ||||
|                 continue; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Decode a single sample | ||||
|         auto code{wavebuffer[read_index]}; | ||||
|         if (position_in_frame & 1) { | ||||
|             code &= 0xF; | ||||
|             read_index++; | ||||
|         } else { | ||||
|             code >>= 4; | ||||
|         } | ||||
|  | ||||
|         out_buffer[write_index++] = decode_sample(Steps[code]); | ||||
|  | ||||
|         position_in_frame++; | ||||
|         samples_to_read--; | ||||
|     } | ||||
|  | ||||
|     context->header = header; | ||||
|     context->yn0 = yn0; | ||||
|     context->yn1 = yn1; | ||||
|  | ||||
|     return samples_to_process; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Decode implementation. | ||||
|  * Decode wavebuffers according to the given args. | ||||
|  * | ||||
|  * @param memory - Core memory to read data from. | ||||
|  * @param args   - The wavebuffer data, and information for how to decode it. | ||||
|  */ | ||||
| void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) { | ||||
|     auto& voice_state{*args.voice_state}; | ||||
|     auto remaining_sample_count{args.sample_count}; | ||||
|     auto fraction{voice_state.fraction}; | ||||
|  | ||||
|     const auto sample_rate_ratio{ | ||||
|         (Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) * | ||||
|         args.pitch}; | ||||
|     const auto size_required{fraction + remaining_sample_count * sample_rate_ratio}; | ||||
|  | ||||
|     if (size_required < 0) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto pitch{PitchBySrcQuality[static_cast<u32>(args.src_quality)]}; | ||||
|     if (static_cast<u32>(pitch + size_required.to_int_floor()) > TempBufferSize) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto max_remaining_sample_count{ | ||||
|         ((Common::FixedPoint<17, 15>(TempBufferSize) - fraction) / sample_rate_ratio) | ||||
|             .to_uint_floor()}; | ||||
|     max_remaining_sample_count = std::min(max_remaining_sample_count, remaining_sample_count); | ||||
|  | ||||
|     auto wavebuffers_consumed{voice_state.wave_buffers_consumed}; | ||||
|     auto wavebuffer_index{voice_state.wave_buffer_index}; | ||||
|     auto played_sample_count{voice_state.played_sample_count}; | ||||
|  | ||||
|     bool is_buffer_starved{false}; | ||||
|     u32 offset{voice_state.offset}; | ||||
|  | ||||
|     auto output_buffer{args.output}; | ||||
|     std::vector<s16> temp_buffer(TempBufferSize, 0); | ||||
|  | ||||
|     while (remaining_sample_count > 0) { | ||||
|         const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)}; | ||||
|         const auto samples_to_read{ | ||||
|             (fraction + samples_to_write * sample_rate_ratio).to_uint_floor()}; | ||||
|  | ||||
|         u32 temp_buffer_pos{0}; | ||||
|  | ||||
|         if (!args.IsVoicePitchAndSrcSkippedSupported) { | ||||
|             for (u32 i = 0; i < pitch; i++) { | ||||
|                 temp_buffer[i] = voice_state.sample_history[i]; | ||||
|             } | ||||
|             temp_buffer_pos = pitch; | ||||
|         } | ||||
|  | ||||
|         u32 samples_read{0}; | ||||
|         while (samples_read < samples_to_read) { | ||||
|             if (wavebuffer_index >= MaxWaveBuffers) { | ||||
|                 LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index); | ||||
|                 wavebuffer_index = 0; | ||||
|                 voice_state.wave_buffer_valid.fill(false); | ||||
|                 wavebuffers_consumed = MaxWaveBuffers; | ||||
|             } | ||||
|  | ||||
|             if (!voice_state.wave_buffer_valid[wavebuffer_index]) { | ||||
|                 is_buffer_starved = true; | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             auto& wavebuffer{args.wave_buffers[wavebuffer_index]}; | ||||
|  | ||||
|             if (offset == 0 && args.sample_format == SampleFormat::Adpcm && | ||||
|                 wavebuffer.context != 0) { | ||||
|                 memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context, | ||||
|                                        wavebuffer.context_size); | ||||
|             } | ||||
|  | ||||
|             auto start_offset{wavebuffer.start_offset}; | ||||
|             auto end_offset{wavebuffer.end_offset}; | ||||
|  | ||||
|             if (wavebuffer.loop && voice_state.loop_count > 0 && | ||||
|                 wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 && | ||||
|                 wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) { | ||||
|                 start_offset = wavebuffer.loop_start_offset; | ||||
|                 end_offset = wavebuffer.loop_end_offset; | ||||
|             } | ||||
|  | ||||
|             DecodeArg decode_arg{.buffer{wavebuffer.buffer}, | ||||
|                                  .buffer_size{wavebuffer.buffer_size}, | ||||
|                                  .start_offset{start_offset}, | ||||
|                                  .end_offset{end_offset}, | ||||
|                                  .channel_count{args.channel_count}, | ||||
|                                  .coefficients{}, | ||||
|                                  .adpcm_context{nullptr}, | ||||
|                                  .target_channel{args.channel}, | ||||
|                                  .offset{offset}, | ||||
|                                  .samples_to_read{samples_to_read - samples_read}}; | ||||
|  | ||||
|             s32 samples_decoded{0}; | ||||
|  | ||||
|             switch (args.sample_format) { | ||||
|             case SampleFormat::PcmInt16: | ||||
|                 samples_decoded = DecodePcm<s16>( | ||||
|                     memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, | ||||
|                     decode_arg); | ||||
|                 break; | ||||
|  | ||||
|             case SampleFormat::PcmFloat: | ||||
|                 samples_decoded = DecodePcm<f32>( | ||||
|                     memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, | ||||
|                     decode_arg); | ||||
|                 break; | ||||
|  | ||||
|             case SampleFormat::Adpcm: { | ||||
|                 decode_arg.adpcm_context = &voice_state.adpcm_context; | ||||
|                 memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size); | ||||
|                 samples_decoded = DecodeAdpcm( | ||||
|                     memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, | ||||
|                     decode_arg); | ||||
|             } break; | ||||
|  | ||||
|             default: | ||||
|                 LOG_ERROR(Service_Audio, "Invalid sample format to decode {}", | ||||
|                           static_cast<u32>(args.sample_format)); | ||||
|                 samples_decoded = 0; | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             played_sample_count += samples_decoded; | ||||
|             samples_read += samples_decoded; | ||||
|             temp_buffer_pos += samples_decoded; | ||||
|             offset += samples_decoded; | ||||
|  | ||||
|             if (samples_decoded == 0 || offset >= end_offset - start_offset) { | ||||
|                 offset = 0; | ||||
|                 if (!wavebuffer.loop) { | ||||
|                     voice_state.wave_buffer_valid[wavebuffer_index] = false; | ||||
|                     voice_state.loop_count = 0; | ||||
|  | ||||
|                     if (wavebuffer.stream_ended) { | ||||
|                         played_sample_count = 0; | ||||
|                     } | ||||
|  | ||||
|                     wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; | ||||
|                     wavebuffers_consumed++; | ||||
|                 } else { | ||||
|                     voice_state.loop_count++; | ||||
|                     if (wavebuffer.loop_count > 0 && | ||||
|                         (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) { | ||||
|                         voice_state.wave_buffer_valid[wavebuffer_index] = false; | ||||
|                         voice_state.loop_count = 0; | ||||
|  | ||||
|                         if (wavebuffer.stream_ended) { | ||||
|                             played_sample_count = 0; | ||||
|                         } | ||||
|  | ||||
|                         wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; | ||||
|                         wavebuffers_consumed++; | ||||
|                     } | ||||
|  | ||||
|                     if (samples_decoded == 0) { | ||||
|                         is_buffer_starved = true; | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
|                     if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) { | ||||
|                         played_sample_count = 0; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (args.IsVoicePitchAndSrcSkippedSupported) { | ||||
|             if (samples_read > output_buffer.size()) { | ||||
|                 LOG_ERROR(Service_Audio, "Attempting to write past the end of output buffer!"); | ||||
|             } | ||||
|             for (u32 i = 0; i < samples_read; i++) { | ||||
|                 output_buffer[i] = temp_buffer[i]; | ||||
|             } | ||||
|         } else { | ||||
|             std::memset(&temp_buffer[temp_buffer_pos], 0, | ||||
|                         (samples_to_read - samples_read) * sizeof(s16)); | ||||
|  | ||||
|             Resample(output_buffer, temp_buffer, sample_rate_ratio, fraction, samples_to_write, | ||||
|                      args.src_quality); | ||||
|  | ||||
|             std::memcpy(voice_state.sample_history.data(), &temp_buffer[samples_to_read], | ||||
|                         pitch * sizeof(s16)); | ||||
|         } | ||||
|  | ||||
|         remaining_sample_count -= samples_to_write; | ||||
|         if (remaining_sample_count != 0 && is_buffer_starved) { | ||||
|             LOG_ERROR(Service_Audio, "Samples remaining but buffer is starving??"); | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         output_buffer = output_buffer.subspan(samples_to_write); | ||||
|     } | ||||
|  | ||||
|     voice_state.wave_buffers_consumed = wavebuffers_consumed; | ||||
|     voice_state.played_sample_count = played_sample_count; | ||||
|     voice_state.wave_buffer_index = wavebuffer_index; | ||||
|     voice_state.offset = offset; | ||||
|     voice_state.fraction = fraction; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										59
									
								
								src/audio_core/renderer/command/data_source/decode.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/audio_core/renderer/command/data_source/decode.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <span> | ||||
|  | ||||
| #include "audio_core/common/common.h" | ||||
| #include "audio_core/common/wave_buffer.h" | ||||
| #include "audio_core/renderer/voice/voice_state.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace Core::Memory { | ||||
| class Memory; | ||||
| } | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
|  | ||||
| struct DecodeFromWaveBuffersArgs { | ||||
|     SampleFormat sample_format; | ||||
|     std::span<s32> output; | ||||
|     VoiceState* voice_state; | ||||
|     std::span<WaveBufferVersion2> wave_buffers; | ||||
|     s8 channel; | ||||
|     s8 channel_count; | ||||
|     SrcQuality src_quality; | ||||
|     f32 pitch; | ||||
|     u32 source_sample_rate; | ||||
|     u32 target_sample_rate; | ||||
|     u32 sample_count; | ||||
|     CpuAddr data_address; | ||||
|     u64 data_size; | ||||
|     bool IsVoicePlayedSampleCountResetAtLoopPointSupported; | ||||
|     bool IsVoicePitchAndSrcSkippedSupported; | ||||
| }; | ||||
|  | ||||
| struct DecodeArg { | ||||
|     CpuAddr buffer; | ||||
|     u64 buffer_size; | ||||
|     u32 start_offset; | ||||
|     u32 end_offset; | ||||
|     s8 channel_count; | ||||
|     std::array<s16, 16> coefficients; | ||||
|     VoiceState::AdpcmContext* adpcm_context; | ||||
|     s8 target_channel; | ||||
|     u32 offset; | ||||
|     u32 samples_to_read; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Decode wavebuffers according to the given args. | ||||
|  * | ||||
|  * @param memory - Core memory to read data from. | ||||
|  * @param args - The wavebuffer data, and information for how to decode it. | ||||
|  */ | ||||
| void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args); | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										86
									
								
								src/audio_core/renderer/command/data_source/pcm_float.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/audio_core/renderer/command/data_source/pcm_float.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/renderer/adsp/command_list_processor.h" | ||||
| #include "audio_core/renderer/command/data_source/decode.h" | ||||
| #include "audio_core/renderer/command/data_source/pcm_float.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
|  | ||||
| void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, | ||||
|                                              std::string& string) { | ||||
|     string += | ||||
|         fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} " | ||||
|                     "channel count {} source sample rate {} target sample rate {} src quality {}\n", | ||||
|                     output_index, channel_index, channel_count, sample_rate, | ||||
|                     processor.target_sample_rate, src_quality); | ||||
| } | ||||
|  | ||||
| void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { | ||||
|     auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, | ||||
|                                                     processor.sample_count); | ||||
|  | ||||
|     DecodeFromWaveBuffersArgs args{ | ||||
|         .sample_format{SampleFormat::PcmFloat}, | ||||
|         .output{out_buffer}, | ||||
|         .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, | ||||
|         .wave_buffers{wave_buffers}, | ||||
|         .channel{channel_index}, | ||||
|         .channel_count{channel_count}, | ||||
|         .src_quality{src_quality}, | ||||
|         .pitch{pitch}, | ||||
|         .source_sample_rate{sample_rate}, | ||||
|         .target_sample_rate{processor.target_sample_rate}, | ||||
|         .sample_count{processor.sample_count}, | ||||
|         .data_address{0}, | ||||
|         .data_size{0}, | ||||
|         .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, | ||||
|         .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, | ||||
|     }; | ||||
|  | ||||
|     DecodeFromWaveBuffers(*processor.memory, args); | ||||
| } | ||||
|  | ||||
| bool PcmFloatDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, | ||||
|                                              std::string& string) { | ||||
|     string += | ||||
|         fmt::format("PcmFloatDataSourceVersion2Command\n\toutput_index {:02X} channel {} " | ||||
|                     "channel count {} source sample rate {} target sample rate {} src quality {}\n", | ||||
|                     output_index, channel_index, channel_count, sample_rate, | ||||
|                     processor.target_sample_rate, src_quality); | ||||
| } | ||||
|  | ||||
| void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { | ||||
|     auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, | ||||
|                                                     processor.sample_count); | ||||
|  | ||||
|     DecodeFromWaveBuffersArgs args{ | ||||
|         .sample_format{SampleFormat::PcmFloat}, | ||||
|         .output{out_buffer}, | ||||
|         .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, | ||||
|         .wave_buffers{wave_buffers}, | ||||
|         .channel{channel_index}, | ||||
|         .channel_count{channel_count}, | ||||
|         .src_quality{src_quality}, | ||||
|         .pitch{pitch}, | ||||
|         .source_sample_rate{sample_rate}, | ||||
|         .target_sample_rate{processor.target_sample_rate}, | ||||
|         .sample_count{processor.sample_count}, | ||||
|         .data_address{0}, | ||||
|         .data_size{0}, | ||||
|         .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, | ||||
|         .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, | ||||
|     }; | ||||
|  | ||||
|     DecodeFromWaveBuffers(*processor.memory, args); | ||||
| } | ||||
|  | ||||
| bool PcmFloatDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										113
									
								
								src/audio_core/renderer/command/data_source/pcm_float.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/audio_core/renderer/command/data_source/pcm_float.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| #include "audio_core/common/wave_buffer.h" | ||||
| #include "audio_core/renderer/command/icommand.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
| namespace ADSP { | ||||
| class CommandListProcessor; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * AudioRenderer command to decode PCM float-encoded version 1 wavebuffers | ||||
|  * into the output_index mix buffer. | ||||
|  */ | ||||
| struct PcmFloatDataSourceVersion1Command : ICommand { | ||||
|     /** | ||||
|      * Print this command's information to a string. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @param string    - The string to print into. | ||||
|      */ | ||||
|     void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||||
|  | ||||
|     /** | ||||
|      * Process this command. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      */ | ||||
|     void Process(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /** | ||||
|      * Verify this command's data is valid. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @return True if the command is valid, otherwise false. | ||||
|      */ | ||||
|     bool Verify(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /// Quality used for sample rate conversion | ||||
|     SrcQuality src_quality; | ||||
|     /// Mix buffer index for decoded samples | ||||
|     s16 output_index; | ||||
|     /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | ||||
|     u16 flags; | ||||
|     /// Wavebuffer sample rate | ||||
|     u32 sample_rate; | ||||
|     /// Pitch used for sample rate conversion | ||||
|     f32 pitch; | ||||
|     /// Target channel to read within the wavebuffer | ||||
|     s8 channel_index; | ||||
|     /// Number of channels within the wavebuffer | ||||
|     s8 channel_count; | ||||
|     /// Wavebuffers containing the wavebuffer address, context address, looping information etc | ||||
|     std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; | ||||
|     /// Voice state, updated each call and written back to game | ||||
|     CpuAddr voice_state; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * AudioRenderer command to decode PCM float-encoded version 2 wavebuffers | ||||
|  * into the output_index mix buffer. | ||||
|  */ | ||||
| struct PcmFloatDataSourceVersion2Command : ICommand { | ||||
|     /** | ||||
|      * Print this command's information to a string. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @param string    - The string to print into. | ||||
|      */ | ||||
|     void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||||
|  | ||||
|     /** | ||||
|      * Process this command. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      */ | ||||
|     void Process(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /** | ||||
|      * Verify this command's data is valid. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @return True if the command is valid, otherwise false. | ||||
|      */ | ||||
|     bool Verify(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /// Quality used for sample rate conversion | ||||
|     SrcQuality src_quality; | ||||
|     /// Mix buffer index for decoded samples | ||||
|     s16 output_index; | ||||
|     /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | ||||
|     u16 flags; | ||||
|     /// Wavebuffer sample rate | ||||
|     u32 sample_rate; | ||||
|     /// Pitch used for sample rate conversion | ||||
|     f32 pitch; | ||||
|     /// Target channel to read within the wavebuffer | ||||
|     s8 channel_index; | ||||
|     /// Number of channels within the wavebuffer | ||||
|     s8 channel_count; | ||||
|     /// Wavebuffers containing the wavebuffer address, context address, looping information etc | ||||
|     std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; | ||||
|     /// Voice state, updated each call and written back to game | ||||
|     CpuAddr voice_state; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										87
									
								
								src/audio_core/renderer/command/data_source/pcm_int16.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/audio_core/renderer/command/data_source/pcm_int16.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <span> | ||||
|  | ||||
| #include "audio_core/renderer/adsp/command_list_processor.h" | ||||
| #include "audio_core/renderer/command/data_source/decode.h" | ||||
| #include "audio_core/renderer/command/data_source/pcm_int16.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
|  | ||||
| void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, | ||||
|                                              std::string& string) { | ||||
|     string += | ||||
|         fmt::format("PcmInt16DataSourceVersion1Command\n\toutput_index {:02X} channel {} " | ||||
|                     "channel count {} source sample rate {} target sample rate {} src quality {}\n", | ||||
|                     output_index, channel_index, channel_count, sample_rate, | ||||
|                     processor.target_sample_rate, src_quality); | ||||
| } | ||||
|  | ||||
| void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { | ||||
|     auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, | ||||
|                                                     processor.sample_count); | ||||
|  | ||||
|     DecodeFromWaveBuffersArgs args{ | ||||
|         .sample_format{SampleFormat::PcmInt16}, | ||||
|         .output{out_buffer}, | ||||
|         .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, | ||||
|         .wave_buffers{wave_buffers}, | ||||
|         .channel{channel_index}, | ||||
|         .channel_count{channel_count}, | ||||
|         .src_quality{src_quality}, | ||||
|         .pitch{pitch}, | ||||
|         .source_sample_rate{sample_rate}, | ||||
|         .target_sample_rate{processor.target_sample_rate}, | ||||
|         .sample_count{processor.sample_count}, | ||||
|         .data_address{0}, | ||||
|         .data_size{0}, | ||||
|         .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, | ||||
|         .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, | ||||
|     }; | ||||
|  | ||||
|     DecodeFromWaveBuffers(*processor.memory, args); | ||||
| } | ||||
|  | ||||
| bool PcmInt16DataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, | ||||
|                                              std::string& string) { | ||||
|     string += | ||||
|         fmt::format("PcmInt16DataSourceVersion2Command\n\toutput_index {:02X} channel {} " | ||||
|                     "channel count {} source sample rate {} target sample rate {} src quality {}\n", | ||||
|                     output_index, channel_index, channel_count, sample_rate, | ||||
|                     processor.target_sample_rate, src_quality); | ||||
| } | ||||
|  | ||||
| void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { | ||||
|     auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, | ||||
|                                                     processor.sample_count); | ||||
|     DecodeFromWaveBuffersArgs args{ | ||||
|         .sample_format{SampleFormat::PcmInt16}, | ||||
|         .output{out_buffer}, | ||||
|         .voice_state{reinterpret_cast<VoiceState*>(voice_state)}, | ||||
|         .wave_buffers{wave_buffers}, | ||||
|         .channel{channel_index}, | ||||
|         .channel_count{channel_count}, | ||||
|         .src_quality{src_quality}, | ||||
|         .pitch{pitch}, | ||||
|         .source_sample_rate{sample_rate}, | ||||
|         .target_sample_rate{processor.target_sample_rate}, | ||||
|         .sample_count{processor.sample_count}, | ||||
|         .data_address{0}, | ||||
|         .data_size{0}, | ||||
|         .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, | ||||
|         .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, | ||||
|     }; | ||||
|  | ||||
|     DecodeFromWaveBuffers(*processor.memory, args); | ||||
| } | ||||
|  | ||||
| bool PcmInt16DataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										110
									
								
								src/audio_core/renderer/command/data_source/pcm_int16.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								src/audio_core/renderer/command/data_source/pcm_int16.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| #include "audio_core/common/wave_buffer.h" | ||||
| #include "audio_core/renderer/command/icommand.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
| namespace ADSP { | ||||
| class CommandListProcessor; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers | ||||
|  * into the output_index mix buffer. | ||||
|  */ | ||||
| struct PcmInt16DataSourceVersion1Command : ICommand { | ||||
|     /** | ||||
|      * Print this command's information to a string. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @param string    - The string to print into. | ||||
|      */ | ||||
|     void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||||
|  | ||||
|     /** | ||||
|      * Process this command. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      */ | ||||
|     void Process(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /** | ||||
|      * Verify this command's data is valid. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @return True if the command is valid, otherwise false. | ||||
|      */ | ||||
|     bool Verify(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /// Quality used for sample rate conversion | ||||
|     SrcQuality src_quality; | ||||
|     /// Mix buffer index for decoded samples | ||||
|     s16 output_index; | ||||
|     /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | ||||
|     u16 flags; | ||||
|     /// Wavebuffer sample rate | ||||
|     u32 sample_rate; | ||||
|     /// Pitch used for sample rate conversion | ||||
|     f32 pitch; | ||||
|     /// Target channel to read within the wavebuffer | ||||
|     s8 channel_index; | ||||
|     /// Number of channels within the wavebuffer | ||||
|     s8 channel_count; | ||||
|     /// Wavebuffers containing the wavebuffer address, context address, looping information etc | ||||
|     std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; | ||||
|     /// Voice state, updated each call and written back to game | ||||
|     CpuAddr voice_state; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * AudioRenderer command to decode PCM s16-encoded version 2 wavebuffers | ||||
|  * into the output_index mix buffer. | ||||
|  */ | ||||
| struct PcmInt16DataSourceVersion2Command : ICommand { | ||||
|     /** | ||||
|      * Print this command's information to a string. | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @param string    - The string to print into. | ||||
|      */ | ||||
|     void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||||
|  | ||||
|     /** | ||||
|      * Process this command. | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      */ | ||||
|     void Process(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /** | ||||
|      * Verify this command's data is valid. | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @return True if the command is valid, otherwise false. | ||||
|      */ | ||||
|     bool Verify(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /// Quality used for sample rate conversion | ||||
|     SrcQuality src_quality; | ||||
|     /// Mix buffer index for decoded samples | ||||
|     s16 output_index; | ||||
|     /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) | ||||
|     u16 flags; | ||||
|     /// Wavebuffer sample rate | ||||
|     u32 sample_rate; | ||||
|     /// Pitch used for sample rate conversion | ||||
|     f32 pitch; | ||||
|     /// Target channel to read within the wavebuffer | ||||
|     s8 channel_index; | ||||
|     /// Number of channels within the wavebuffer | ||||
|     s8 channel_count; | ||||
|     /// Wavebuffers containing the wavebuffer address, context address, looping information etc | ||||
|     std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; | ||||
|     /// Voice state, updated each call and written back to game | ||||
|     CpuAddr voice_state; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										207
									
								
								src/audio_core/renderer/command/effect/aux_.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								src/audio_core/renderer/command/effect/aux_.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,207 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/renderer/adsp/command_list_processor.h" | ||||
| #include "audio_core/renderer/command/effect/aux_.h" | ||||
| #include "audio_core/renderer/effect/aux_.h" | ||||
| #include "core/memory.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
| /** | ||||
|  * Reset an AuxBuffer. | ||||
|  * | ||||
|  * @param memory   - Core memory for writing. | ||||
|  * @param aux_info - Memory address pointing to the AuxInfo to reset. | ||||
|  */ | ||||
| static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) { | ||||
|     if (aux_info == 0) { | ||||
|         LOG_ERROR(Service_Audio, "Aux info is 0!"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto info{reinterpret_cast<AuxInfo::AuxInfoDsp*>(memory.GetPointer(aux_info))}; | ||||
|     info->read_offset = 0; | ||||
|     info->write_offset = 0; | ||||
|     info->total_sample_count = 0; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Write the given input mix buffer to the memory at send_buffer, and update send_info_ if | ||||
|  * update_count is set, to notify the game that an update happened. | ||||
|  * | ||||
|  * @param memory       - Core memory for writing. | ||||
|  * @param send_info_   - Meta information for where to write the mix buffer. | ||||
|  * @param sample_count - Unused. | ||||
|  * @param send_buffer  - Memory address to write the mix buffer to. | ||||
|  * @param count_max    - Maximum number of samples in the receiving buffer. | ||||
|  * @param input        - Input mix buffer to write. | ||||
|  * @param write_count_ - Number of samples to write. | ||||
|  * @param write_offset - Current offset to begin writing the receiving buffer at. | ||||
|  * @param update_count - If non-zero, send_info_ will be updated. | ||||
|  * @return Number of samples written. | ||||
|  */ | ||||
| static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_, | ||||
|                              [[maybe_unused]] u32 sample_count, const CpuAddr send_buffer, | ||||
|                              const u32 count_max, std::span<const s32> input, | ||||
|                              const u32 write_count_, const u32 write_offset, | ||||
|                              const u32 update_count) { | ||||
|     if (write_count_ > count_max) { | ||||
|         LOG_ERROR(Service_Audio, | ||||
|                   "write_count must be smaller than count_max! write_count {}, count_max {}", | ||||
|                   write_count_, count_max); | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     if (input.empty()) { | ||||
|         LOG_ERROR(Service_Audio, "input buffer is empty!"); | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     if (send_buffer == 0) { | ||||
|         LOG_ERROR(Service_Audio, "send_buffer is 0!"); | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     if (count_max == 0) { | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     AuxInfo::AuxInfoDsp send_info{}; | ||||
|     memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp)); | ||||
|  | ||||
|     u32 target_write_offset{send_info.write_offset + write_offset}; | ||||
|     if (target_write_offset > count_max || write_count_ == 0) { | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     u32 write_count{write_count_}; | ||||
|     u32 write_pos{0}; | ||||
|     while (write_count > 0) { | ||||
|         u32 to_write{std::min(count_max - target_write_offset, write_count)}; | ||||
|  | ||||
|         if (to_write > 0) { | ||||
|             memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32), | ||||
|                                     &input[write_pos], to_write * sizeof(s32)); | ||||
|         } | ||||
|  | ||||
|         target_write_offset = (target_write_offset + to_write) % count_max; | ||||
|         write_count -= to_write; | ||||
|         write_pos += to_write; | ||||
|     } | ||||
|  | ||||
|     if (update_count) { | ||||
|         send_info.write_offset = (send_info.write_offset + update_count) % count_max; | ||||
|     } | ||||
|  | ||||
|     memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp)); | ||||
|  | ||||
|     return write_count_; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Read the given memory at return_buffer into the output mix buffer, and update return_info_ if | ||||
|  * update_count is set, to notify the game that an update happened. | ||||
|  * | ||||
|  * @param memory        - Core memory for writing. | ||||
|  * @param return_info_  - Meta information for where to read the mix buffer. | ||||
|  * @param return_buffer - Memory address to read the samples from. | ||||
|  * @param count_max     - Maximum number of samples in the receiving buffer. | ||||
|  * @param output        - Output mix buffer which will receive the samples. | ||||
|  * @param count_        - Number of samples to read. | ||||
|  * @param read_offset   - Current offset to begin reading the return_buffer at. | ||||
|  * @param update_count  - If non-zero, send_info_ will be updated. | ||||
|  * @return Number of samples read. | ||||
|  */ | ||||
| static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr return_info_, | ||||
|                             const CpuAddr return_buffer, const u32 count_max, std::span<s32> output, | ||||
|                             const u32 count_, const u32 read_offset, const u32 update_count) { | ||||
|     if (count_max == 0) { | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     if (count_ > count_max) { | ||||
|         LOG_ERROR(Service_Audio, "count must be smaller than count_max! count {}, count_max {}", | ||||
|                   count_, count_max); | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     if (output.empty()) { | ||||
|         LOG_ERROR(Service_Audio, "output buffer is empty!"); | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     if (return_buffer == 0) { | ||||
|         LOG_ERROR(Service_Audio, "return_buffer is 0!"); | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     AuxInfo::AuxInfoDsp return_info{}; | ||||
|     memory.ReadBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp)); | ||||
|  | ||||
|     u32 target_read_offset{return_info.read_offset + read_offset}; | ||||
|     if (target_read_offset > count_max) { | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     u32 read_count{count_}; | ||||
|     u32 read_pos{0}; | ||||
|     while (read_count > 0) { | ||||
|         u32 to_read{std::min(count_max - target_read_offset, read_count)}; | ||||
|  | ||||
|         if (to_read > 0) { | ||||
|             memory.ReadBlockUnsafe(return_buffer + target_read_offset * sizeof(s32), | ||||
|                                    &output[read_pos], to_read * sizeof(s32)); | ||||
|         } | ||||
|  | ||||
|         target_read_offset = (target_read_offset + to_read) % count_max; | ||||
|         read_count -= to_read; | ||||
|         read_pos += to_read; | ||||
|     } | ||||
|  | ||||
|     if (update_count) { | ||||
|         return_info.read_offset = (return_info.read_offset + update_count) % count_max; | ||||
|     } | ||||
|  | ||||
|     memory.WriteBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp)); | ||||
|  | ||||
|     return count_; | ||||
| } | ||||
|  | ||||
| void AuxCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||||
|                       std::string& string) { | ||||
|     string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled, | ||||
|                           input, output); | ||||
| } | ||||
|  | ||||
| void AuxCommand::Process(const ADSP::CommandListProcessor& processor) { | ||||
|     auto input_buffer{ | ||||
|         processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; | ||||
|     auto output_buffer{ | ||||
|         processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; | ||||
|  | ||||
|     if (effect_enabled) { | ||||
|         WriteAuxBufferDsp(*processor.memory, send_buffer_info, processor.sample_count, send_buffer, | ||||
|                           count_max, input_buffer, processor.sample_count, write_offset, | ||||
|                           update_count); | ||||
|  | ||||
|         auto read{ReadAuxBufferDsp(*processor.memory, return_buffer_info, return_buffer, count_max, | ||||
|                                    output_buffer, processor.sample_count, write_offset, | ||||
|                                    update_count)}; | ||||
|  | ||||
|         if (read != processor.sample_count) { | ||||
|             std::memset(&output_buffer[read], 0, processor.sample_count - read); | ||||
|         } | ||||
|     } else { | ||||
|         ResetAuxBufferDsp(*processor.memory, send_buffer_info); | ||||
|         ResetAuxBufferDsp(*processor.memory, return_buffer_info); | ||||
|         if (input != output) { | ||||
|             std::memcpy(output_buffer.data(), input_buffer.data(), output_buffer.size_bytes()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool AuxCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										66
									
								
								src/audio_core/renderer/command/effect/aux_.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/audio_core/renderer/command/effect/aux_.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| #include "audio_core/renderer/command/icommand.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
| namespace ADSP { | ||||
| class CommandListProcessor; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game | ||||
|  * memory, and reading into the output buffer from game memory. | ||||
|  */ | ||||
| struct AuxCommand : ICommand { | ||||
|     /** | ||||
|      * Print this command's information to a string. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @param string    - The string to print into. | ||||
|      */ | ||||
|     void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||||
|  | ||||
|     /** | ||||
|      * Process this command. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      */ | ||||
|     void Process(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /** | ||||
|      * Verify this command's data is valid. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @return True if the command is valid, otherwise false. | ||||
|      */ | ||||
|     bool Verify(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /// Input mix buffer index | ||||
|     s16 input; | ||||
|     /// Output mix buffer index | ||||
|     s16 output; | ||||
|     /// Meta info for writing | ||||
|     CpuAddr send_buffer_info; | ||||
|     /// Meta info for reading | ||||
|     CpuAddr return_buffer_info; | ||||
|     /// Game memory write buffer | ||||
|     CpuAddr send_buffer; | ||||
|     /// Game memory read buffer | ||||
|     CpuAddr return_buffer; | ||||
|     /// Max samples to read/write | ||||
|     u32 count_max; | ||||
|     /// Current read/write offset | ||||
|     u32 write_offset; | ||||
|     /// Number of samples to update per call | ||||
|     u32 update_count; | ||||
|     /// is this effect enabled? | ||||
|     bool effect_enabled; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										118
									
								
								src/audio_core/renderer/command/effect/biquad_filter.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/audio_core/renderer/command/effect/biquad_filter.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/renderer/adsp/command_list_processor.h" | ||||
| #include "audio_core/renderer/command/effect/biquad_filter.h" | ||||
| #include "audio_core/renderer/voice/voice_state.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
| /** | ||||
|  * Biquad filter float implementation. | ||||
|  * | ||||
|  * @param output       - Output container for filtered samples. | ||||
|  * @param input        - Input container for samples to be filtered. | ||||
|  * @param b            - Feedforward coefficients. | ||||
|  * @param a            - Feedback coefficients. | ||||
|  * @param state        - State to track previous samples between calls. | ||||
|  * @param sample_count - Number of samples to process. | ||||
|  */ | ||||
| void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input, | ||||
|                             std::array<s16, 3>& b_, std::array<s16, 2>& a_, | ||||
|                             VoiceState::BiquadFilterState& state, const u32 sample_count) { | ||||
|     constexpr s64 min{std::numeric_limits<s32>::min()}; | ||||
|     constexpr s64 max{std::numeric_limits<s32>::max()}; | ||||
|     std::array<f64, 3> b{Common::FixedPoint<50, 14>::from_base(b_[0]).to_double(), | ||||
|                          Common::FixedPoint<50, 14>::from_base(b_[1]).to_double(), | ||||
|                          Common::FixedPoint<50, 14>::from_base(b_[2]).to_double()}; | ||||
|     std::array<f64, 2> a{Common::FixedPoint<50, 14>::from_base(a_[0]).to_double(), | ||||
|                          Common::FixedPoint<50, 14>::from_base(a_[1]).to_double()}; | ||||
|     std::array<f64, 4> s{state.s0.to_double(), state.s1.to_double(), state.s2.to_double(), | ||||
|                          state.s3.to_double()}; | ||||
|  | ||||
|     for (u32 i = 0; i < sample_count; i++) { | ||||
|         f64 in_sample{static_cast<f64>(input[i])}; | ||||
|         auto sample{in_sample * b[0] + s[0] * b[1] + s[1] * b[2] + s[2] * a[0] + s[3] * a[1]}; | ||||
|  | ||||
|         output[i] = static_cast<s32>(std::clamp(static_cast<s64>(sample), min, max)); | ||||
|  | ||||
|         s[1] = s[0]; | ||||
|         s[0] = in_sample; | ||||
|         s[3] = s[2]; | ||||
|         s[2] = sample; | ||||
|     } | ||||
|  | ||||
|     state.s0 = s[0]; | ||||
|     state.s1 = s[1]; | ||||
|     state.s2 = s[2]; | ||||
|     state.s3 = s[3]; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Biquad filter s32 implementation. | ||||
|  * | ||||
|  * @param output       - Output container for filtered samples. | ||||
|  * @param input        - Input container for samples to be filtered. | ||||
|  * @param b            - Feedforward coefficients. | ||||
|  * @param a            - Feedback coefficients. | ||||
|  * @param state        - State to track previous samples between calls. | ||||
|  * @param sample_count - Number of samples to process. | ||||
|  */ | ||||
| static void ApplyBiquadFilterInt(std::span<s32> output, std::span<const s32> input, | ||||
|                                  std::array<s16, 3>& b_, std::array<s16, 2>& a_, | ||||
|                                  VoiceState::BiquadFilterState& state, const u32 sample_count) { | ||||
|     constexpr s64 min{std::numeric_limits<s32>::min()}; | ||||
|     constexpr s64 max{std::numeric_limits<s32>::max()}; | ||||
|     std::array<Common::FixedPoint<50, 14>, 3> b{ | ||||
|         Common::FixedPoint<50, 14>::from_base(b_[0]), | ||||
|         Common::FixedPoint<50, 14>::from_base(b_[1]), | ||||
|         Common::FixedPoint<50, 14>::from_base(b_[2]), | ||||
|     }; | ||||
|     std::array<Common::FixedPoint<50, 14>, 3> a{ | ||||
|         Common::FixedPoint<50, 14>::from_base(a_[0]), | ||||
|         Common::FixedPoint<50, 14>::from_base(a_[1]), | ||||
|     }; | ||||
|  | ||||
|     for (u32 i = 0; i < sample_count; i++) { | ||||
|         s64 in_sample{input[i]}; | ||||
|         auto sample{in_sample * b[0] + state.s0}; | ||||
|         const auto out_sample{std::clamp(sample.to_long(), min, max)}; | ||||
|  | ||||
|         output[i] = static_cast<s32>(out_sample); | ||||
|  | ||||
|         state.s0 = state.s1 + b[1] * in_sample + a[0] * out_sample; | ||||
|         state.s1 = 0 + b[2] * in_sample + a[1] * out_sample; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void BiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||||
|                                std::string& string) { | ||||
|     string += fmt::format( | ||||
|         "BiquadFilterCommand\n\tinput {:02X} output {:02X} needs_init {} use_float_processing {}\n", | ||||
|         input, output, needs_init, use_float_processing); | ||||
| } | ||||
|  | ||||
| void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) { | ||||
|     auto state_{reinterpret_cast<VoiceState::BiquadFilterState*>(state)}; | ||||
|     if (needs_init) { | ||||
|         std::memset(state_, 0, sizeof(VoiceState::BiquadFilterState)); | ||||
|     } | ||||
|  | ||||
|     auto input_buffer{ | ||||
|         processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; | ||||
|     auto output_buffer{ | ||||
|         processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; | ||||
|  | ||||
|     if (use_float_processing) { | ||||
|         ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_, | ||||
|                                processor.sample_count); | ||||
|     } else { | ||||
|         ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_, | ||||
|                              processor.sample_count); | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool BiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										74
									
								
								src/audio_core/renderer/command/effect/biquad_filter.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/audio_core/renderer/command/effect/biquad_filter.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| #include "audio_core/renderer/command/icommand.h" | ||||
| #include "audio_core/renderer/voice/voice_info.h" | ||||
| #include "audio_core/renderer/voice/voice_state.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
| namespace ADSP { | ||||
| class CommandListProcessor; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * AudioRenderer command for applying a biquad filter to the input mix buffer, saving the results to | ||||
|  * the output mix buffer. | ||||
|  */ | ||||
| struct BiquadFilterCommand : ICommand { | ||||
|     /** | ||||
|      * Print this command's information to a string. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @param string    - The string to print into. | ||||
|      */ | ||||
|     void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||||
|  | ||||
|     /** | ||||
|      * Process this command. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      */ | ||||
|     void Process(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /** | ||||
|      * Verify this command's data is valid. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @return True if the command is valid, otherwise false. | ||||
|      */ | ||||
|     bool Verify(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /// Input mix buffer index | ||||
|     s16 input; | ||||
|     /// Output mix buffer index | ||||
|     s16 output; | ||||
|     /// Input parameters for biquad | ||||
|     VoiceInfo::BiquadFilterParameter biquad; | ||||
|     /// Biquad state, updated each call | ||||
|     CpuAddr state; | ||||
|     /// If true, reset the state | ||||
|     bool needs_init; | ||||
|     /// If true, use float processing rather than int | ||||
|     bool use_float_processing; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Biquad filter float implementation. | ||||
|  * | ||||
|  * @param output       - Output container for filtered samples. | ||||
|  * @param input        - Input container for samples to be filtered. | ||||
|  * @param b            - Feedforward coefficients. | ||||
|  * @param a            - Feedback coefficients. | ||||
|  * @param state        - State to track previous samples. | ||||
|  * @param sample_count - Number of samples to process. | ||||
|  */ | ||||
| void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input, | ||||
|                             std::array<s16, 3>& b, std::array<s16, 2>& a, | ||||
|                             VoiceState::BiquadFilterState& state, const u32 sample_count); | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										142
									
								
								src/audio_core/renderer/command/effect/capture.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								src/audio_core/renderer/command/effect/capture.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,142 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/renderer/adsp/command_list_processor.h" | ||||
| #include "audio_core/renderer/command/effect/capture.h" | ||||
| #include "audio_core/renderer/effect/aux_.h" | ||||
| #include "core/memory.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
| /** | ||||
|  * Reset an AuxBuffer. | ||||
|  * | ||||
|  * @param memory   - Core memory for writing. | ||||
|  * @param aux_info - Memory address pointing to the AuxInfo to reset. | ||||
|  */ | ||||
| static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) { | ||||
|     if (aux_info == 0) { | ||||
|         LOG_ERROR(Service_Audio, "Aux info is 0!"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, read_offset)), 0); | ||||
|     memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, write_offset)), 0); | ||||
|     memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, total_sample_count)), 0); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Write the given input mix buffer to the memory at send_buffer, and update send_info_ if | ||||
|  * update_count is set, to notify the game that an update happened. | ||||
|  * | ||||
|  * @param memory       - Core memory for writing. | ||||
|  * @param send_info_   - Header information for where to write the mix buffer. | ||||
|  * @param send_buffer  - Memory address to write the mix buffer to. | ||||
|  * @param count_max    - Maximum number of samples in the receiving buffer. | ||||
|  * @param input        - Input mix buffer to write. | ||||
|  * @param write_count_ - Number of samples to write. | ||||
|  * @param write_offset - Current offset to begin writing the receiving buffer at. | ||||
|  * @param update_count - If non-zero, send_info_ will be updated. | ||||
|  * @return Number of samples written. | ||||
|  */ | ||||
| static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_, | ||||
|                              const CpuAddr send_buffer, u32 count_max, std::span<const s32> input, | ||||
|                              const u32 write_count_, const u32 write_offset, | ||||
|                              const u32 update_count) { | ||||
|     if (write_count_ > count_max) { | ||||
|         LOG_ERROR(Service_Audio, | ||||
|                   "write_count must be smaller than count_max! write_count {}, count_max {}", | ||||
|                   write_count_, count_max); | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     if (send_info_ == 0) { | ||||
|         LOG_ERROR(Service_Audio, "send_info is 0!"); | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     if (input.empty()) { | ||||
|         LOG_ERROR(Service_Audio, "input buffer is empty!"); | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     if (send_buffer == 0) { | ||||
|         LOG_ERROR(Service_Audio, "send_buffer is 0!"); | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     if (count_max == 0) { | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     AuxInfo::AuxBufferInfo send_info{}; | ||||
|     memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo)); | ||||
|  | ||||
|     u32 target_write_offset{send_info.dsp_info.write_offset + write_offset}; | ||||
|     if (target_write_offset > count_max || write_count_ == 0) { | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     u32 write_count{write_count_}; | ||||
|     u32 write_pos{0}; | ||||
|     while (write_count > 0) { | ||||
|         u32 to_write{std::min(count_max - target_write_offset, write_count)}; | ||||
|  | ||||
|         if (to_write > 0) { | ||||
|             memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32), | ||||
|                                     &input[write_pos], to_write * sizeof(s32)); | ||||
|         } | ||||
|  | ||||
|         target_write_offset = (target_write_offset + to_write) % count_max; | ||||
|         write_count -= to_write; | ||||
|         write_pos += to_write; | ||||
|     } | ||||
|  | ||||
|     if (update_count) { | ||||
|         const auto count_diff{send_info.dsp_info.total_sample_count - | ||||
|                               send_info.cpu_info.total_sample_count}; | ||||
|         if (count_diff >= count_max) { | ||||
|             auto dsp_lost_count{send_info.dsp_info.lost_sample_count + update_count}; | ||||
|             if (dsp_lost_count - send_info.cpu_info.lost_sample_count < | ||||
|                 send_info.dsp_info.lost_sample_count - send_info.cpu_info.lost_sample_count) { | ||||
|                 dsp_lost_count = send_info.cpu_info.lost_sample_count - 1; | ||||
|             } | ||||
|             send_info.dsp_info.lost_sample_count = dsp_lost_count; | ||||
|         } | ||||
|  | ||||
|         send_info.dsp_info.write_offset = | ||||
|             (send_info.dsp_info.write_offset + update_count + count_max) % count_max; | ||||
|  | ||||
|         auto new_sample_count{send_info.dsp_info.total_sample_count + update_count}; | ||||
|         if (new_sample_count - send_info.cpu_info.total_sample_count < count_diff) { | ||||
|             new_sample_count = send_info.cpu_info.total_sample_count - 1; | ||||
|         } | ||||
|         send_info.dsp_info.total_sample_count = new_sample_count; | ||||
|     } | ||||
|  | ||||
|     memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo)); | ||||
|  | ||||
|     return write_count_; | ||||
| } | ||||
|  | ||||
| void CaptureCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||||
|                           std::string& string) { | ||||
|     string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled, | ||||
|                           input, output); | ||||
| } | ||||
|  | ||||
| void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) { | ||||
|     if (effect_enabled) { | ||||
|         auto input_buffer{ | ||||
|             processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; | ||||
|         WriteAuxBufferDsp(*processor.memory, send_buffer_info, send_buffer, count_max, input_buffer, | ||||
|                           processor.sample_count, write_offset, update_count); | ||||
|     } else { | ||||
|         ResetAuxBufferDsp(*processor.memory, send_buffer_info); | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool CaptureCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										62
									
								
								src/audio_core/renderer/command/effect/capture.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/audio_core/renderer/command/effect/capture.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| #include "audio_core/renderer/command/icommand.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
| namespace ADSP { | ||||
| class CommandListProcessor; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory | ||||
|  * address. | ||||
|  */ | ||||
| struct CaptureCommand : ICommand { | ||||
|     /** | ||||
|      * Print this command's information to a string. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @param string    - The string to print into. | ||||
|      */ | ||||
|     void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||||
|  | ||||
|     /** | ||||
|      * Process this command. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      */ | ||||
|     void Process(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /** | ||||
|      * Verify this command's data is valid. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @return True if the command is valid, otherwise false. | ||||
|      */ | ||||
|     bool Verify(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /// Input mix buffer index | ||||
|     s16 input; | ||||
|     /// Output mix buffer index | ||||
|     s16 output; | ||||
|     /// Meta info for writing | ||||
|     CpuAddr send_buffer_info; | ||||
|     /// Game memory write buffer | ||||
|     CpuAddr send_buffer; | ||||
|     /// Max samples to read/write | ||||
|     u32 count_max; | ||||
|     /// Current read/write offset | ||||
|     u32 write_offset; | ||||
|     /// Number of samples to update per call | ||||
|     u32 update_count; | ||||
|     /// is this effect enabled? | ||||
|     bool effect_enabled; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										156
									
								
								src/audio_core/renderer/command/effect/compressor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								src/audio_core/renderer/command/effect/compressor.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <cmath> | ||||
| #include <span> | ||||
| #include <vector> | ||||
|  | ||||
| #include "audio_core/renderer/adsp/command_list_processor.h" | ||||
| #include "audio_core/renderer/command/effect/compressor.h" | ||||
| #include "audio_core/renderer/effect/compressor.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
|  | ||||
| static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params, | ||||
|                                          CompressorInfo::State& state) { | ||||
|     const auto ratio{1.0f / params.compressor_ratio}; | ||||
|     auto makeup_gain{0.0f}; | ||||
|     if (params.makeup_gain_enabled) { | ||||
|         makeup_gain = (params.threshold * 0.5f) * (ratio - 1.0f) - 3.0f; | ||||
|     } | ||||
|     state.makeup_gain = makeup_gain; | ||||
|     state.unk_18 = params.unk_28; | ||||
|  | ||||
|     const auto a{(params.out_gain + makeup_gain) / 20.0f * 3.3219f}; | ||||
|     const auto b{(a - std::trunc(a)) * 0.69315f}; | ||||
|     const auto c{std::pow(2.0f, b)}; | ||||
|  | ||||
|     state.unk_0C = (1.0f - ratio) / 6.0f; | ||||
|     state.unk_14 = params.threshold + 1.5f; | ||||
|     state.unk_10 = params.threshold - 1.5f; | ||||
|     state.unk_20 = c; | ||||
| } | ||||
|  | ||||
| static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params, | ||||
|                                        CompressorInfo::State& state) { | ||||
|     std::memset(&state, 0, sizeof(CompressorInfo::State)); | ||||
|  | ||||
|     state.unk_00 = 0; | ||||
|     state.unk_04 = 1.0f; | ||||
|     state.unk_08 = 1.0f; | ||||
|  | ||||
|     SetCompressorEffectParameter(params, state); | ||||
| } | ||||
|  | ||||
| static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params, | ||||
|                                   CompressorInfo::State& state, bool enabled, | ||||
|                                   std::vector<std::span<const s32>> input_buffers, | ||||
|                                   std::vector<std::span<s32>> output_buffers, u32 sample_count) { | ||||
|     if (enabled) { | ||||
|         auto state_00{state.unk_00}; | ||||
|         auto state_04{state.unk_04}; | ||||
|         auto state_08{state.unk_08}; | ||||
|         auto state_18{state.unk_18}; | ||||
|  | ||||
|         for (u32 i = 0; i < sample_count; i++) { | ||||
|             auto a{0.0f}; | ||||
|             for (s16 channel = 0; channel < params.channel_count; channel++) { | ||||
|                 const auto input_sample{Common::FixedPoint<49, 15>(input_buffers[channel][i])}; | ||||
|                 a += (input_sample * input_sample).to_float(); | ||||
|             } | ||||
|  | ||||
|             state_00 += params.unk_24 * ((a / params.channel_count) - state.unk_00); | ||||
|  | ||||
|             auto b{-100.0f}; | ||||
|             auto c{0.0f}; | ||||
|             if (state_00 >= 1.0e-10) { | ||||
|                 b = std::log10(state_00) * 10.0f; | ||||
|                 c = 1.0f; | ||||
|             } | ||||
|  | ||||
|             if (b >= state.unk_10) { | ||||
|                 const auto d{b >= state.unk_14 | ||||
|                                  ? ((1.0f / params.compressor_ratio) - 1.0f) * | ||||
|                                        (b - params.threshold) | ||||
|                                  : (b - state.unk_10) * (b - state.unk_10) * -state.unk_0C}; | ||||
|                 const auto e{d / 20.0f * 3.3219f}; | ||||
|                 const auto f{(e - std::trunc(e)) * 0.69315f}; | ||||
|                 c = std::pow(2.0f, f); | ||||
|             } | ||||
|  | ||||
|             state_18 = params.unk_28; | ||||
|             auto tmp{c}; | ||||
|             if ((state_04 - c) <= 0.08f) { | ||||
|                 state_18 = params.unk_2C; | ||||
|                 if (((state_04 - c) >= -0.08f) && (std::abs(state_08 - c) >= 0.001f)) { | ||||
|                     tmp = state_04; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             state_04 = tmp; | ||||
|             state_08 += (c - state_08) * state_18; | ||||
|  | ||||
|             for (s16 channel = 0; channel < params.channel_count; channel++) { | ||||
|                 output_buffers[channel][i] = static_cast<s32>( | ||||
|                     static_cast<f32>(input_buffers[channel][i]) * state_08 * state.unk_20); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         state.unk_00 = state_00; | ||||
|         state.unk_04 = state_04; | ||||
|         state.unk_08 = state_08; | ||||
|         state.unk_18 = state_18; | ||||
|     } else { | ||||
|         for (s16 channel = 0; channel < params.channel_count; channel++) { | ||||
|             if (params.inputs[channel] != params.outputs[channel]) { | ||||
|                 std::memcpy((char*)output_buffers[channel].data(), | ||||
|                             (char*)input_buffers[channel].data(), | ||||
|                             output_buffers[channel].size_bytes()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||||
|                              std::string& string) { | ||||
|     string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled); | ||||
|     for (s16 i = 0; i < parameter.channel_count; i++) { | ||||
|         string += fmt::format("{:02X}, ", inputs[i]); | ||||
|     } | ||||
|     string += "\n\toutputs: "; | ||||
|     for (s16 i = 0; i < parameter.channel_count; i++) { | ||||
|         string += fmt::format("{:02X}, ", outputs[i]); | ||||
|     } | ||||
|     string += "\n"; | ||||
| } | ||||
|  | ||||
| void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) { | ||||
|     std::vector<std::span<const s32>> input_buffers(parameter.channel_count); | ||||
|     std::vector<std::span<s32>> output_buffers(parameter.channel_count); | ||||
|  | ||||
|     for (s16 i = 0; i < parameter.channel_count; i++) { | ||||
|         input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, | ||||
|                                                          processor.sample_count); | ||||
|         output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, | ||||
|                                                           processor.sample_count); | ||||
|     } | ||||
|  | ||||
|     auto state_{reinterpret_cast<CompressorInfo::State*>(state)}; | ||||
|  | ||||
|     if (effect_enabled) { | ||||
|         if (parameter.state == CompressorInfo::ParameterState::Updating) { | ||||
|             SetCompressorEffectParameter(parameter, *state_); | ||||
|         } else if (parameter.state == CompressorInfo::ParameterState::Initialized) { | ||||
|             InitializeCompressorEffect(parameter, *state_); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     ApplyCompressorEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, | ||||
|                           processor.sample_count); | ||||
| } | ||||
|  | ||||
| bool CompressorCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										60
									
								
								src/audio_core/renderer/command/effect/compressor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/audio_core/renderer/command/effect/compressor.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <string> | ||||
|  | ||||
| #include "audio_core/renderer/command/icommand.h" | ||||
| #include "audio_core/renderer/effect/compressor.h" | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
| namespace ADSP { | ||||
| class CommandListProcessor; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * AudioRenderer command for limiting volume between a high and low threshold. | ||||
|  * Version 1. | ||||
|  */ | ||||
| struct CompressorCommand : ICommand { | ||||
|     /** | ||||
|      * Print this command's information to a string. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @param string    - The string to print into. | ||||
|      */ | ||||
|     void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; | ||||
|  | ||||
|     /** | ||||
|      * Process this command. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      */ | ||||
|     void Process(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /** | ||||
|      * Verify this command's data is valid. | ||||
|      * | ||||
|      * @param processor - The CommandListProcessor processing this command. | ||||
|      * @return True if the command is valid, otherwise false. | ||||
|      */ | ||||
|     bool Verify(const ADSP::CommandListProcessor& processor) override; | ||||
|  | ||||
|     /// Input mix buffer offsets for each channel | ||||
|     std::array<s16, MaxChannels> inputs; | ||||
|     /// Output mix buffer offsets for each channel | ||||
|     std::array<s16, MaxChannels> outputs; | ||||
|     /// Input parameters | ||||
|     CompressorInfo::ParameterVersion2 parameter; | ||||
|     /// State, updated each call | ||||
|     CpuAddr state; | ||||
|     /// Game-supplied workbuffer (Unused) | ||||
|     CpuAddr workbuffer; | ||||
|     /// Is this effect enabled? | ||||
|     bool effect_enabled; | ||||
| }; | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
							
								
								
									
										238
									
								
								src/audio_core/renderer/command/effect/delay.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								src/audio_core/renderer/command/effect/delay.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,238 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include "audio_core/renderer/adsp/command_list_processor.h" | ||||
| #include "audio_core/renderer/command/effect/delay.h" | ||||
|  | ||||
| namespace AudioCore::AudioRenderer { | ||||
| /** | ||||
|  * Update the DelayInfo state according to the given parameters. | ||||
|  * | ||||
|  * @param params - Input parameters to update the state. | ||||
|  * @param state  - State to be updated. | ||||
|  */ | ||||
| static void SetDelayEffectParameter(const DelayInfo::ParameterVersion1& params, | ||||
|                                     DelayInfo::State& state) { | ||||
|     auto channel_spread{params.channel_spread}; | ||||
|     state.feedback_gain = params.feedback_gain * 0.97998046875f; | ||||
|     state.delay_feedback_gain = state.feedback_gain * (1.0f - channel_spread); | ||||
|     if (params.channel_count == 4 || params.channel_count == 6) { | ||||
|         channel_spread >>= 1; | ||||
|     } | ||||
|     state.delay_feedback_cross_gain = channel_spread * state.feedback_gain; | ||||
|     state.lowpass_feedback_gain = params.lowpass_amount * 0.949951171875f; | ||||
|     state.lowpass_gain = 1.0f - state.lowpass_feedback_gain; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Initialize a new DelayInfo state according to the given parameters. | ||||
|  * | ||||
|  * @param params     - Input parameters to update the state. | ||||
|  * @param state      - State to be updated. | ||||
|  * @param workbuffer - Game-supplied memory for the state. (Unused) | ||||
|  */ | ||||
| static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params, | ||||
|                                   DelayInfo::State& state, | ||||
|                                   [[maybe_unused]] const CpuAddr workbuffer) { | ||||
|     state = {}; | ||||
|  | ||||
|     for (u32 channel = 0; channel < params.channel_count; channel++) { | ||||
|         Common::FixedPoint<32, 32> sample_count_max{0.064f}; | ||||
|         sample_count_max *= params.sample_rate.to_int_floor() * params.delay_time_max; | ||||
|  | ||||
|         Common::FixedPoint<18, 14> delay_time{params.delay_time}; | ||||
|         delay_time *= params.sample_rate / 1000; | ||||
|         Common::FixedPoint<32, 32> sample_count{delay_time}; | ||||
|  | ||||
|         if (sample_count > sample_count_max) { | ||||
|             sample_count = sample_count_max; | ||||
|         } | ||||
|  | ||||
|         state.delay_lines[channel].sample_count_max = sample_count_max.to_int_floor(); | ||||
|         state.delay_lines[channel].sample_count = sample_count.to_int_floor(); | ||||
|         state.delay_lines[channel].buffer.resize(state.delay_lines[channel].sample_count, 0); | ||||
|         if (state.delay_lines[channel].buffer.size() == 0) { | ||||
|             state.delay_lines[channel].buffer.push_back(0); | ||||
|         } | ||||
|         state.delay_lines[channel].buffer_pos = 0; | ||||
|         state.delay_lines[channel].decay_rate = 1.0f; | ||||
|     } | ||||
|  | ||||
|     SetDelayEffectParameter(params, state); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Delay effect impl, according to the parameters and current state, on the input mix buffers, | ||||
|  * saving the results to the output mix buffers. | ||||
|  * | ||||
|  * @tparam NumChannels - Number of channels to process. 1-6. | ||||
|  * @param params       - Input parameters to use. | ||||
|  * @param state        - State to use, must be initialized (see InitializeDelayEffect). | ||||
|  * @param inputs       - Input mix buffers to performan the delay on. | ||||
|  * @param outputs      - Output mix buffers to receive the delayed samples. | ||||
|  * @param sample_count - Number of samples to process. | ||||
|  */ | ||||
| template <size_t NumChannels> | ||||
| static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state, | ||||
|                        std::vector<std::span<const s32>>& inputs, | ||||
|                        std::vector<std::span<s32>>& outputs, const u32 sample_count) { | ||||
|     for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { | ||||
|         std::array<Common::FixedPoint<50, 14>, NumChannels> input_samples{}; | ||||
|         for (u32 channel = 0; channel < NumChannels; channel++) { | ||||
|             input_samples[channel] = inputs[channel][sample_index] * 64; | ||||
|         } | ||||
|  | ||||
|         std::array<Common::FixedPoint<50, 14>, NumChannels> delay_samples{}; | ||||
|         for (u32 channel = 0; channel < NumChannels; channel++) { | ||||
|             delay_samples[channel] = state.delay_lines[channel].Read(); | ||||
|         } | ||||
|  | ||||
|         // clang-format off | ||||
|         std::array<std::array<Common::FixedPoint<18, 14>, NumChannels>, NumChannels> matrix{}; | ||||
|         if constexpr (NumChannels == 1) { | ||||
|             matrix = {{ | ||||
|                 {state.feedback_gain}, | ||||
|             }}; | ||||
|         } else if constexpr (NumChannels == 2) { | ||||
|             matrix = {{ | ||||
|                 {state.delay_feedback_gain, state.delay_feedback_cross_gain}, | ||||
|                 {state.delay_feedback_cross_gain, state.delay_feedback_gain}, | ||||
|             }}; | ||||
|         } else if constexpr (NumChannels == 4) { | ||||
|             matrix = {{ | ||||
|                 {state.delay_feedback_gain, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, 0.0f}, | ||||
|                 {state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain}, | ||||
|                 {state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain}, | ||||
|                 {0.0f, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain}, | ||||
|             }}; | ||||
|         } else if constexpr (NumChannels == 6) { | ||||
|             matrix = {{ | ||||
|                 {state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f}, | ||||
|                 {0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain}, | ||||
|                 {state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, 0.0f, 0.0f}, | ||||
|                 {0.0f, 0.0f, 0.0f, params.feedback_gain, 0.0f, 0.0f}, | ||||
|                 {state.delay_feedback_cross_gain, 0.0f, 0.0f, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain}, | ||||
|                 {0.0f, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain, state.delay_feedback_gain}, | ||||
|             }}; | ||||
|         } | ||||
|         // clang-format on | ||||
|  | ||||
|         std::array<Common::FixedPoint<50, 14>, NumChannels> gained_samples{}; | ||||
|         for (u32 channel = 0; channel < NumChannels; channel++) { | ||||
|             Common::FixedPoint<50, 14> delay{}; | ||||
|             for (u32 j = 0; j < NumChannels; j++) { | ||||
|                 delay += delay_samples[j] * matrix[j][channel]; | ||||
|             } | ||||
|             gained_samples[channel] = input_samples[channel] * params.in_gain + delay; | ||||
|         } | ||||
|  | ||||
|         for (u32 channel = 0; channel < NumChannels; channel++) { | ||||
|             state.lowpass_z[channel] = gained_samples[channel] * state.lowpass_gain + | ||||
|                                        state.lowpass_z[channel] * state.lowpass_feedback_gain; | ||||
|             state.delay_lines[channel].Write(state.lowpass_z[channel]); | ||||
|         } | ||||
|  | ||||
|         for (u32 channel = 0; channel < NumChannels; channel++) { | ||||
|             outputs[channel][sample_index] = (input_samples[channel] * params.dry_gain + | ||||
|                                               delay_samples[channel] * params.wet_gain) | ||||
|                                                  .to_int_floor() / | ||||
|                                              64; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Apply a delay effect if enabled, according to the parameters and current state, on the input mix | ||||
|  * buffers, saving the results to the output mix buffers. | ||||
|  * | ||||
|  * @param params       - Input parameters to use. | ||||
|  * @param state        - State to use, must be initialized (see InitializeDelayEffect). | ||||
|  * @param enabled      - If enabled, delay will be applied, otherwise input is copied to output. | ||||
|  * @param inputs       - Input mix buffers to performan the delay on. | ||||
|  * @param outputs      - Output mix buffers to receive the delayed samples. | ||||
|  * @param sample_count - Number of samples to process. | ||||
|  */ | ||||
| static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state, | ||||
|                              const bool enabled, std::vector<std::span<const s32>>& inputs, | ||||
|                              std::vector<std::span<s32>>& outputs, const u32 sample_count) { | ||||
|  | ||||
|     if (!IsChannelCountValid(params.channel_count)) { | ||||
|         LOG_ERROR(Service_Audio, "Invalid delay channels {}", params.channel_count); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (enabled) { | ||||
|         switch (params.channel_count) { | ||||
|         case 1: | ||||
|             ApplyDelay<1>(params, state, inputs, outputs, sample_count); | ||||
|             break; | ||||
|         case 2: | ||||
|             ApplyDelay<2>(params, state, inputs, outputs, sample_count); | ||||
|             break; | ||||
|         case 4: | ||||
|             ApplyDelay<4>(params, state, inputs, outputs, sample_count); | ||||
|             break; | ||||
|         case 6: | ||||
|             ApplyDelay<6>(params, state, inputs, outputs, sample_count); | ||||
|             break; | ||||
|         default: | ||||
|             for (u32 channel = 0; channel < params.channel_count; channel++) { | ||||
|                 if (inputs[channel].data() != outputs[channel].data()) { | ||||
|                     std::memcpy(outputs[channel].data(), inputs[channel].data(), | ||||
|                                 sample_count * sizeof(s32)); | ||||
|                 } | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|     } else { | ||||
|         for (u32 channel = 0; channel < params.channel_count; channel++) { | ||||
|             if (inputs[channel].data() != outputs[channel].data()) { | ||||
|                 std::memcpy(outputs[channel].data(), inputs[channel].data(), | ||||
|                             sample_count * sizeof(s32)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||||
|                         std::string& string) { | ||||
|     string += fmt::format("DelayCommand\n\tenabled {} \n\tinputs: ", effect_enabled); | ||||
|     for (u32 i = 0; i < MaxChannels; i++) { | ||||
|         string += fmt::format("{:02X}, ", inputs[i]); | ||||
|     } | ||||
|     string += "\n\toutputs: "; | ||||
|     for (u32 i = 0; i < MaxChannels; i++) { | ||||
|         string += fmt::format("{:02X}, ", outputs[i]); | ||||
|     } | ||||
|     string += "\n"; | ||||
| } | ||||
|  | ||||
| void DelayCommand::Process(const ADSP::CommandListProcessor& processor) { | ||||
|     std::vector<std::span<const s32>> input_buffers(parameter.channel_count); | ||||
|     std::vector<std::span<s32>> output_buffers(parameter.channel_count); | ||||
|  | ||||
|     for (s16 i = 0; i < parameter.channel_count; i++) { | ||||
|         input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, | ||||
|                                                          processor.sample_count); | ||||
|         output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, | ||||
|                                                           processor.sample_count); | ||||
|     } | ||||
|  | ||||
|     auto state_{reinterpret_cast<DelayInfo::State*>(state)}; | ||||
|  | ||||
|     if (effect_enabled) { | ||||
|         if (parameter.state == DelayInfo::ParameterState::Updating) { | ||||
|             SetDelayEffectParameter(parameter, *state_); | ||||
|         } else if (parameter.state == DelayInfo::ParameterState::Initialized) { | ||||
|             InitializeDelayEffect(parameter, *state_, workbuffer); | ||||
|         } | ||||
|     } | ||||
|     ApplyDelayEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, | ||||
|                      processor.sample_count); | ||||
| } | ||||
|  | ||||
| bool DelayCommand::Verify(const ADSP::CommandListProcessor& processor) { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| } // namespace AudioCore::AudioRenderer | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 Kelebek1
					Kelebek1